use crossbeam_channel::{Receiver, unbounded};
use serde::{Serialize, de::DeserializeOwned};
use std::thread;
use crate::{ChangeEvent, SettingsError, SettingsHandle, StoredValue};
type FieldGetter<T, V> = Box<dyn for<'a> Fn(&'a T) -> &'a V + Send + Sync + 'static>;
type FieldMutator<T, V> = Box<dyn Fn(&mut T, V) + Send + Sync + 'static>;
pub struct ReadOnlyField<T, V> {
handle: SettingsHandle<T>,
getter: FieldGetter<T, V>,
}
impl<T, V> ReadOnlyField<T, V>
where
T: Clone + Send + Sync + 'static,
V: Clone,
{
pub fn new(handle: SettingsHandle<T>, getter: FieldGetter<T, V>) -> Self {
Self { handle, getter }
}
pub fn get(&self) -> V {
let snapshot = self.handle.snapshot();
(self.getter)(snapshot.as_ref()).clone()
}
}
pub struct PersistedField<T, V> {
handle: SettingsHandle<T>,
key: &'static str,
getter: FieldGetter<T, V>,
mutator: FieldMutator<T, V>,
}
impl<T, V> PersistedField<T, V>
where
T: Clone + Send + Sync + 'static,
V: Clone,
{
pub fn new(
handle: SettingsHandle<T>,
key: &'static str,
getter: FieldGetter<T, V>,
mutator: FieldMutator<T, V>,
) -> Self {
Self {
handle,
key,
getter,
mutator,
}
}
pub fn get(&self) -> V {
let snapshot = self.handle.snapshot();
(self.getter)(snapshot.as_ref()).clone()
}
}
impl<T, V> PersistedField<T, V>
where
T: Clone + Send + Sync + 'static,
V: Clone + Serialize,
{
pub fn set(&self, value: V) -> Result<(), SettingsError> {
let old_value = Some(StoredValue::encode(&self.get())?);
let stored = StoredValue::encode(&value)?;
let mutator = &self.mutator;
self.handle
.write_field(self.key, old_value, stored, |next| {
mutator(next, value);
})
}
}
impl<T, V> PersistedField<T, V>
where
T: Clone + Send + Sync + 'static,
V: DeserializeOwned + Send + 'static,
{
pub fn on_change(&self) -> Receiver<V> {
let source = self.handle.on_change();
let (tx, rx) = unbounded();
let key = self.key.to_string();
thread::spawn(move || {
while let Ok(event) = source.recv() {
let ChangeEvent::Set {
key: event_key,
new_value,
..
} = event
else {
continue;
};
if event_key != key {
continue;
}
let Ok(decoded) = new_value.decode::<V>() else {
continue;
};
if tx.send(decoded).is_err() {
return;
}
}
});
rx
}
}