mod autosave;
mod builder;
mod handle;
mod marker;
mod table;
use crate::error::Result;
use crate::event::{emit, STORE_UNLOAD_EVENT};
use crate::migration::Migrator;
use crate::store::{SaveStrategy, Store, StoreId, StoreResource, StoreState, WatcherId};
use autosave::Autosave;
use dashmap::{DashMap, DashSet};
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::fmt;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use table::{MarshalerTable, PathTable};
use tauri::{AppHandle, Resource, ResourceId, Runtime};
pub use builder::StoreCollectionBuilder;
pub use handle::Handle;
pub use marker::{CollectionMarker, DefaultMarker};
pub type OnLoadFn<R, C> = dyn Fn(&Store<R, C>) -> Result<()> + Send + Sync;
pub struct StoreCollection<R, C>
where
R: Runtime,
C: CollectionMarker,
{
pub(crate) handle: Handle<R>,
pub(crate) name: Box<str>,
pub(crate) stores: DashMap<StoreId, ResourceId>,
pub(crate) path_table: PathTable,
pub(crate) marshaler_table: MarshalerTable,
pub(crate) on_load: Option<Box<OnLoadFn<R, C>>>,
pub(crate) autosave: Mutex<Autosave>,
pub(crate) default_save_strategy: SaveStrategy,
pub(crate) save_denylist: DashSet<StoreId>,
pub(crate) sync_denylist: DashSet<StoreId>,
pub(crate) migrator: Mutex<Migrator>,
pub(crate) debug_stores: bool,
phantom: PhantomData<C>,
}
impl<R, C> StoreCollection<R, C>
where
R: Runtime,
C: CollectionMarker,
{
pub fn builder() -> StoreCollectionBuilder<R, C> {
StoreCollectionBuilder::<R, C>::new()
}
pub(crate) fn get_resource(&self, id: impl AsRef<str>) -> Result<Arc<StoreResource<R, C>>> {
let id = StoreId::from(id.as_ref());
let rid = match self.rid(&id) {
Some(rid) => rid,
None => self.load_store(id)?,
};
StoreResource::get(self.handle.app(), rid)
}
fn load_store(&self, id: StoreId) -> Result<ResourceId> {
let (rid, resource) = Store::load(self.handle.app(), &id)?;
if let Some(on_load) = &self.on_load {
resource.locked(|store| on_load(store))?;
}
self.stores.insert(id, rid);
Ok(rid)
}
fn rid(&self, store_id: &StoreId) -> Option<ResourceId> {
self.stores.get(store_id).map(|it| *it.value())
}
fn rids(&self) -> Vec<ResourceId> {
self.stores.iter().map(|it| *it.value()).collect()
}
pub fn app_handle(&self) -> &AppHandle<R> {
self.handle.app()
}
pub fn ids(&self) -> Vec<StoreId> {
self
.stores
.iter()
.map(|it| it.key().clone())
.collect()
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn path(&self) -> &Path {
&self.path_table.default
}
pub fn path_of(&self, store_id: impl AsRef<str>) -> &Path {
let store_id = StoreId::from(store_id.as_ref());
self.path_table.get(&store_id)
}
pub fn with_store<F, T>(&self, store_id: impl AsRef<str>, f: F) -> Result<T>
where
F: FnOnce(&mut Store<R, C>) -> T,
{
Ok(self.get_resource(store_id)?.locked(f))
}
pub fn raw_state(&self, store_id: impl AsRef<str>) -> Result<StoreState> {
self
.get_resource(store_id)?
.locked(|store| Ok(store.raw_state().clone()))
}
pub fn state<T>(&self, store_id: impl AsRef<str>) -> Result<T>
where
T: DeserializeOwned,
{
self
.get_resource(store_id)?
.locked(|store| store.state())
}
pub fn state_or<T>(&self, store_id: impl AsRef<str>, default: T) -> Result<T>
where
T: DeserializeOwned,
{
self
.get_resource(store_id)?
.locked(move |store| Ok(store.state_or(default)))
}
pub fn state_or_default<T>(&self, store_id: impl AsRef<str>) -> Result<T>
where
T: DeserializeOwned + Default,
{
self
.get_resource(store_id)?
.locked(|store| Ok(store.state_or_default()))
}
pub fn state_or_else<T>(&self, store_id: impl AsRef<str>, f: impl FnOnce() -> T) -> Result<T>
where
T: DeserializeOwned,
{
self
.get_resource(store_id)?
.locked(|store| Ok(store.state_or_else(f)))
}
pub fn get_raw(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Option<Value> {
self
.get_resource(store_id)
.ok()?
.locked(|store| store.get_raw(key).cloned())
}
pub unsafe fn get_raw_unchecked(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Value {
unsafe { self.get_raw(store_id, key).unwrap_unchecked() }
}
pub fn get<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Result<T>
where
T: DeserializeOwned,
{
self
.get_resource(store_id)?
.locked(|store| store.get(key))
}
pub fn get_or<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>, default: T) -> T
where
T: DeserializeOwned,
{
self.get(store_id, key).unwrap_or(default)
}
pub fn get_or_default<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
where
T: Default + DeserializeOwned,
{
self.get(store_id, key).unwrap_or_default()
}
pub fn get_or_else<T>(
&self,
store_id: impl AsRef<str>,
key: impl AsRef<str>,
f: impl FnOnce() -> T,
) -> T
where
T: DeserializeOwned,
{
self.get(store_id, key).unwrap_or_else(|_| f())
}
pub unsafe fn get_unchecked<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
where
T: DeserializeOwned,
{
unsafe { self.get(store_id, key).unwrap_unchecked() }
}
pub fn set<K, V>(&self, store_id: impl AsRef<str>, key: K, value: V) -> Result<()>
where
K: AsRef<str>,
V: Into<Value>,
{
self
.get_resource(store_id)?
.locked(|store| store.set(key, value))
}
pub fn patch<S>(&self, store_id: impl AsRef<str>, state: S) -> Result<()>
where
S: Into<StoreState>,
{
self
.get_resource(store_id)?
.locked(|store| store.patch(state))
}
pub fn save(&self, store_id: impl AsRef<str>) -> Result<()> {
self
.get_resource(store_id)?
.locked(|store| store.save())
}
pub fn save_now(&self, store_id: impl AsRef<str>) -> Result<()> {
self.get_resource(store_id)?.locked(|store| {
store.abort_pending_save();
store.save_now()
})
}
pub fn save_some(&self, ids: &[impl AsRef<str>]) -> Result<()> {
ids.iter().try_for_each(|id| self.save(id))
}
pub fn save_some_now(&self, ids: &[impl AsRef<str>]) -> Result<()> {
ids.iter().try_for_each(|id| self.save_now(id))
}
pub fn save_all(&self) -> Result<()> {
let app = self.handle.app();
self
.rids()
.into_iter()
.try_for_each(|rid| StoreResource::<R, C>::save(app, rid))
}
pub fn save_all_now(&self) -> Result<()> {
let app = self.handle.app();
self
.rids()
.into_iter()
.try_for_each(|rid| StoreResource::<R, C>::save_now(app, rid))
}
#[inline]
pub fn default_save_strategy(&self) -> SaveStrategy {
self.default_save_strategy
}
pub fn set_autosave(&self, duration: Duration) {
if let Ok(mut autosave) = self.autosave.lock() {
autosave.set_duration(duration);
autosave.start::<R, C>(self.handle.app());
}
}
pub fn clear_autosave(&self) {
if let Ok(mut autosave) = self.autosave.lock() {
autosave.stop();
}
}
pub fn watch<F>(&self, store_id: impl AsRef<str>, f: F) -> Result<WatcherId>
where
F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
{
self
.get_resource(store_id)?
.locked(|store| Ok(store.watch(f)))
}
pub fn unwatch(
&self,
store_id: impl AsRef<str>,
watcher_id: impl Into<WatcherId>,
) -> Result<bool> {
self
.get_resource(store_id)?
.locked(|store| Ok(store.unwatch(watcher_id)))
}
pub fn allow_save(&self, id: impl AsRef<str>) {
let id = StoreId::from(id.as_ref());
self.save_denylist.remove(&id);
}
pub fn deny_save(&self, id: impl AsRef<str>) {
let id = StoreId::from(id.as_ref());
self.save_denylist.insert(id);
}
pub fn allow_sync(&self, id: impl AsRef<str>) {
let id = StoreId::from(id.as_ref());
self.sync_denylist.remove(&id);
}
pub fn deny_sync(&self, id: impl AsRef<str>) {
let id = StoreId::from(id.as_ref());
self.sync_denylist.insert(id);
}
#[doc(hidden)]
pub fn destroy(&self, id: impl AsRef<str>) -> Result<()> {
let id = StoreId::from(id.as_ref());
self.unload_and(&id, Store::destroy)
}
#[doc(hidden)]
pub fn unload_store(&self, id: &StoreId) -> Result<()> {
self.unload_and(id, |store| store.save_now())
}
fn unload_and<F>(&self, id: &StoreId, f: F) -> Result<()>
where
F: FnOnce(&mut Store<R, C>) -> Result<()>,
{
let app = self.handle.app();
if let Some((_, rid)) = self.stores.remove(id) {
let resource = StoreResource::<R, C>::take(app, rid)?;
resource.locked(f)?;
emit(app, STORE_UNLOAD_EVENT, id, None::<&str>)?;
}
Ok(())
}
#[doc(hidden)]
pub fn on_exit(&self) -> Result<()> {
self.clear_autosave();
let app = self.handle.app();
for rid in self.rids() {
if let Ok(resource) = StoreResource::<R, C>::take(app, rid) {
resource.locked(|store| {
store.abort_pending_save();
if store.save_on_exit {
let _ = store.save_now();
}
});
}
}
Ok(())
}
}
impl<R, C> Resource for StoreCollection<R, C>
where
R: Runtime,
C: CollectionMarker,
{
}
impl<R, C> fmt::Debug for StoreCollection<R, C>
where
R: Runtime,
C: CollectionMarker,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StoreCollection")
.field("name", &self.name)
.finish_non_exhaustive()
}
}