rust-galmon-web/src/models/helper.rs

274 lines
5.8 KiB
Rust

use failure::Error;
use std::cell::RefCell;
use std::fmt;
use std::rc::{Rc, Weak};
use crate::api;
use crate::utils::callbacks;
// -------------------- callbacks --------------------
/// Callback registration.
///
/// Callback will be unregistered when this is dropped.
#[derive(Default, Debug)]
pub struct CallbackRegistration(callbacks::CallbackRegistration<()>);
#[derive(Debug)]
pub(super) struct Callbacks(callbacks::Callbacks<()>);
impl Callbacks {
pub(super) fn new() -> Self {
Self(callbacks::Callbacks::new())
}
pub(super) fn register(&self, callback: yew::Callback<()>) -> CallbackRegistration {
CallbackRegistration(self.0.register(callback))
}
pub(super) fn emit(&self) {
self.0.emit(());
}
}
impl Drop for Callbacks {
fn drop(&mut self) {
// final emit to let all know we're gone
self.emit();
}
}
// -------------------- model data wrapper --------------------
#[derive(Debug)]
pub(super) struct ModelBase {
callbacks: Callbacks,
service: Rc<api::APIService<()>>,
}
impl Clone for ModelBase {
fn clone(&self) -> Self {
Self {
callbacks: Callbacks::new(), // always fresh callbacks
service: self.service.clone(),
}
}
}
impl From<()> for ModelBase {
fn from(_: ()) -> Self {
Self {
callbacks: Callbacks::new(),
service: Rc::new(api::APIService::new_no_message()),
}
}
}
impl<T: Model> From<&ModelHandle<T>> for ModelBase {
fn from(model: &ModelHandle<T>) -> Self {
model.inner.base.clone()
}
}
#[derive(Debug)]
struct Inner<F, T> {
base: ModelBase,
result: RefCell<Option<Rc<Result<F, String>>>>,
fetch: RefCell<Option<api::FetchTask>>,
custom: T,
}
mod hidden {
pub trait Model: Sized + std::fmt::Debug {
type FetchData: std::fmt::Debug;
const NAME: &'static str;
fn refresh(sr: &super::ModelHandle<Self>) -> Option<super::api::FetchTask>;
}
}
pub(super) use self::hidden::Model;
pub struct ModelHandle<T: Model> {
inner: Rc<Inner<T::FetchData, T>>,
}
impl<T: Model> ModelHandle<T> {
pub(super) fn new<B>(s: T, b: B) -> Self
where
B: Into<ModelBase>,
{
let inner = Rc::new(Inner {
base: b.into(),
result: Default::default(),
fetch: Default::default(),
custom: s,
});
Self {
inner,
}
}
pub(super) fn service(&self) -> &api::APIService<()> {
&self.inner.base.service
}
pub(super) fn data(&self) -> &T {
&self.inner.custom
}
#[allow(unused)]
pub(super) fn merge_received<M, L, I>(&self, result: Result<I, Error>, load: L, merge: M)
where
T::FetchData: Clone,
M: FnOnce(&mut T::FetchData, I),
L: FnOnce(I) -> T::FetchData,
{
let mut data = self.inner.result.borrow_mut();
self.inner.fetch.replace(None);
// result: RefCell<Option<Rc<Result<F, String>>>>,
match result {
Ok(v) => {
if let Some(data) = &mut *data {
let data = Rc::make_mut(data);
if let Ok(data) = data {
// merge with existing data
merge(data, v);
} else {
// overwrite error
*data = Ok(load(v));
}
} else {
// first reply
*data = Some(Rc::new(Ok(load(v))));
}
},
Err(e) => {
if data.is_none() {
// first reply, remember error
*data = Some(Rc::new(Err(format!("{}", e))));
}
},
}
drop(data); // release lock before notify
self.inner.base.callbacks.emit();
}
pub(super) fn set_received(&self, result: Result<T::FetchData, Error>) {
let mut data = self.inner.result.borrow_mut();
self.inner.fetch.replace(None);
// result: RefCell<Option<Rc<Result<F, String>>>>,
match result {
Ok(v) => {
*data = Some(Rc::new(Ok(v)));
},
Err(e) => {
if data.is_none() {
// first reply, remember error
*data = Some(Rc::new(Err(format!("{}", e))));
}
},
}
drop(data); // release lock before notify
self.inner.base.callbacks.emit();
}
#[allow(unused)]
pub(super) fn downgrade(&self) -> ModelWeakHandle<T> {
ModelWeakHandle {
inner: Rc::downgrade(&self.inner),
}
}
/// Refresh data
pub fn refresh(&self) {
let mut fetch = self.inner.fetch.borrow_mut();
if fetch.is_some() { return; } // pending request
*fetch = T::refresh(self);
}
/// Whether a request is pending (doesn't mean no previous data is available)
pub fn is_pending(&self) -> bool {
let fetch = self.inner.fetch.borrow();
fetch.is_some()
}
/// Get data.
///
/// If all fetches so far returned in an error returns the first error;
/// if no data was received yet (and no error was returned) an initial
/// refresh will be triggered.
pub fn get(&self) -> Option<Rc<Result<T::FetchData, String>>> {
let result = self.inner.result.borrow();
if result.is_none() {
self.refresh();
}
result.clone()
}
/// Register a callback to be called when data is refreshed.
pub fn register(&self, callback: yew::Callback<()>) -> CallbackRegistration {
self.inner.base.callbacks.register(callback)
}
}
impl<T: Model> Clone for ModelHandle<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: Model> From<&'_ ModelHandle<T>> for ModelHandle<T> {
fn from(r: &'_ ModelHandle<T>) -> Self {
Self {
inner: r.inner.clone(),
}
}
}
impl<T: Model> PartialEq for ModelHandle<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.inner, &other.inner)
}
}
impl<T: Model> Eq for ModelHandle<T> { }
impl<T: Model> fmt::Debug for ModelHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("ModelHandle<")?;
f.write_str(T::NAME)?;
f.write_str("(")?;
self.inner.fmt(f)?;
f.write_str(")")?;
Ok(())
}
}
#[allow(unused)]
pub struct ModelWeakHandle<T: Model> {
inner: Weak<Inner<T::FetchData, T>>,
}
impl<T: Model> ModelWeakHandle<T> {
#[allow(unused)]
pub(super) fn upgrade(&self) -> Option<ModelHandle<T>> {
Some(ModelHandle {
inner: self.inner.upgrade()?,
})
}
}
impl<T: Model> fmt::Debug for ModelWeakHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("ModelWeakHandle<")?;
f.write_str(T::NAME)?;
f.write_str(">")
}
}