274 lines
5.8 KiB
Rust
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(">")
|
|
}
|
|
}
|