tauri_store/collection/
mod.rs

1mod autosave;
2mod builder;
3mod handle;
4mod marker;
5mod table;
6
7use crate::error::Result;
8use crate::event::{emit, STORE_UNLOAD_EVENT};
9use crate::migration::Migrator;
10use crate::store::{SaveStrategy, Store, StoreId, StoreResource, StoreState, WatcherId};
11use autosave::Autosave;
12use dashmap::{DashMap, DashSet};
13use serde::de::DeserializeOwned;
14use serde_json::Value;
15use std::fmt;
16use std::marker::PhantomData;
17use std::path::Path;
18use std::sync::{Arc, Mutex};
19use std::time::Duration;
20use table::{MarshalerTable, PathTable};
21use tauri::{AppHandle, Resource, ResourceId, Runtime};
22
23pub use builder::StoreCollectionBuilder;
24pub use handle::Handle;
25pub use marker::{CollectionMarker, DefaultMarker};
26
27/// Closure to be called when a store is loaded.
28pub type OnLoadFn<R, C> = dyn Fn(&Store<R, C>) -> Result<()> + Send + Sync;
29
30/// A collection of stores.
31/// This is the core component for store plugins.
32pub struct StoreCollection<R, C>
33where
34  R: Runtime,
35  C: CollectionMarker,
36{
37  pub(crate) handle: Handle<R>,
38  pub(crate) name: Box<str>,
39  pub(crate) stores: DashMap<StoreId, ResourceId>,
40  pub(crate) path_table: PathTable,
41  pub(crate) marshaler_table: MarshalerTable,
42  pub(crate) on_load: Option<Box<OnLoadFn<R, C>>>,
43  pub(crate) autosave: Mutex<Autosave>,
44  pub(crate) default_save_strategy: SaveStrategy,
45  pub(crate) save_denylist: DashSet<StoreId>,
46  pub(crate) sync_denylist: DashSet<StoreId>,
47  pub(crate) migrator: Mutex<Migrator>,
48  pub(crate) debug_stores: bool,
49  phantom: PhantomData<C>,
50}
51
52impl<R, C> StoreCollection<R, C>
53where
54  R: Runtime,
55  C: CollectionMarker,
56{
57  /// Builds a new store collection.
58  pub fn builder() -> StoreCollectionBuilder<R, C> {
59    StoreCollectionBuilder::<R, C>::new()
60  }
61
62  pub(crate) fn get_resource(&self, id: impl AsRef<str>) -> Result<Arc<StoreResource<R, C>>> {
63    let id = StoreId::from(id.as_ref());
64    let rid = match self.rid(&id) {
65      Some(rid) => rid,
66      None => self.load_store(id)?,
67    };
68
69    StoreResource::get(self.handle.app(), rid)
70  }
71
72  fn load_store(&self, id: StoreId) -> Result<ResourceId> {
73    let (rid, resource) = Store::load(self.handle.app(), &id)?;
74    if let Some(on_load) = &self.on_load {
75      resource.locked(|store| on_load(store))?;
76    }
77
78    self.stores.insert(id, rid);
79    Ok(rid)
80  }
81
82  /// Gets the resource id for a store.
83  fn rid(&self, store_id: &StoreId) -> Option<ResourceId> {
84    self.stores.get(store_id).map(|it| *it.value())
85  }
86
87  /// Gets the resource ids for all the stores.
88  fn rids(&self) -> Vec<ResourceId> {
89    self.stores.iter().map(|it| *it.value()).collect()
90  }
91
92  /// Gets a handle to the application instance.
93  pub fn app_handle(&self) -> &AppHandle<R> {
94    self.handle.app()
95  }
96
97  /// Lists all the store ids.
98  pub fn ids(&self) -> Vec<StoreId> {
99    self
100      .stores
101      .iter()
102      .map(|it| it.key().clone())
103      .collect()
104  }
105
106  /// Store collection name.
107  #[inline]
108  pub fn name(&self) -> &str {
109    &self.name
110  }
111
112  /// Default directory where the stores are saved.
113  #[inline]
114  pub fn path(&self) -> &Path {
115    &self.path_table.default
116  }
117
118  /// Directory where a specific store is saved.
119  pub fn path_of(&self, store_id: impl AsRef<str>) -> &Path {
120    let store_id = StoreId::from(store_id.as_ref());
121    self.path_table.get(&store_id)
122  }
123
124  /// Calls a closure with a mutable reference to the store with the given id.
125  pub fn with_store<F, T>(&self, store_id: impl AsRef<str>, f: F) -> Result<T>
126  where
127    F: FnOnce(&mut Store<R, C>) -> T,
128  {
129    Ok(self.get_resource(store_id)?.locked(f))
130  }
131
132  /// Gets a clone of the raw store state.
133  pub fn raw_state(&self, store_id: impl AsRef<str>) -> Result<StoreState> {
134    self
135      .get_resource(store_id)?
136      .locked(|store| Ok(store.raw_state().clone()))
137  }
138
139  /// Gets the store state, then tries to parse it as an instance of type `T`.
140  pub fn state<T>(&self, store_id: impl AsRef<str>) -> Result<T>
141  where
142    T: DeserializeOwned,
143  {
144    self
145      .get_resource(store_id)?
146      .locked(|store| store.state())
147  }
148
149  /// Gets the store state, then tries to parse it as an instance of type `T`.
150  ///
151  /// If it cannot be parsed, returns the provided default value.
152  pub fn state_or<T>(&self, store_id: impl AsRef<str>, default: T) -> Result<T>
153  where
154    T: DeserializeOwned,
155  {
156    self
157      .get_resource(store_id)?
158      .locked(move |store| Ok(store.state_or(default)))
159  }
160
161  /// Gets the store state, then tries to parse it as an instance of type `T`.
162  ///
163  /// If it cannot be parsed, returns the default value of `T`.
164  pub fn state_or_default<T>(&self, store_id: impl AsRef<str>) -> Result<T>
165  where
166    T: DeserializeOwned + Default,
167  {
168    self
169      .get_resource(store_id)?
170      .locked(|store| Ok(store.state_or_default()))
171  }
172
173  /// Gets the store state, then tries to parse it as an instance of type `T`.
174  ///
175  /// If it cannot be parsed, returns the result of the provided closure.
176  pub fn state_or_else<T>(&self, store_id: impl AsRef<str>, f: impl FnOnce() -> T) -> Result<T>
177  where
178    T: DeserializeOwned,
179  {
180    self
181      .get_resource(store_id)?
182      .locked(|store| Ok(store.state_or_else(f)))
183  }
184
185  /// Gets a raw value from a store.
186  pub fn get_raw(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Option<Value> {
187    self
188      .get_resource(store_id)
189      .ok()?
190      .locked(|store| store.get_raw(key).cloned())
191  }
192
193  /// Gets a raw value from a store.
194  ///
195  /// # Safety
196  ///
197  /// This is *undefined behavior* if the key doesn't exist in the store.
198  pub unsafe fn get_raw_unchecked(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Value {
199    unsafe { self.get_raw(store_id, key).unwrap_unchecked() }
200  }
201
202  /// Gets a value from a store and tries to parse it as an instance of type `T`.
203  pub fn get<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> Result<T>
204  where
205    T: DeserializeOwned,
206  {
207    self
208      .get_resource(store_id)?
209      .locked(|store| store.get(key))
210  }
211
212  /// Gets a value from a store and tries to parse it as an instance of type `T`.
213  ///
214  /// If the key does not exist, returns the provided default value.
215  pub fn get_or<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>, default: T) -> T
216  where
217    T: DeserializeOwned,
218  {
219    self.get(store_id, key).unwrap_or(default)
220  }
221
222  /// Gets a value from a store and tries to parse it as an instance of type `T`.
223  ///
224  /// If the key does not exist, returns the default value of `T`.
225  pub fn get_or_default<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
226  where
227    T: Default + DeserializeOwned,
228  {
229    self.get(store_id, key).unwrap_or_default()
230  }
231
232  /// Gets a value from a store and tries to parse it as an instance of type `T`.
233  ///
234  /// If the key does not exist, returns the result of the provided closure.
235  pub fn get_or_else<T>(
236    &self,
237    store_id: impl AsRef<str>,
238    key: impl AsRef<str>,
239    f: impl FnOnce() -> T,
240  ) -> T
241  where
242    T: DeserializeOwned,
243  {
244    self.get(store_id, key).unwrap_or_else(|_| f())
245  }
246
247  /// Gets a value from a store and parses it as an instance of type `T`.
248  ///
249  /// # Safety
250  ///
251  /// This is *undefined behavior* if the key doesn't exist in the store
252  /// **OR** if the value cannot be represented as a valid `T`.
253  pub unsafe fn get_unchecked<T>(&self, store_id: impl AsRef<str>, key: impl AsRef<str>) -> T
254  where
255    T: DeserializeOwned,
256  {
257    unsafe { self.get(store_id, key).unwrap_unchecked() }
258  }
259
260  /// Sets a key-value pair in a store.
261  pub fn set<K, V>(&self, store_id: impl AsRef<str>, key: K, value: V) -> Result<()>
262  where
263    K: AsRef<str>,
264    V: Into<Value>,
265  {
266    self
267      .get_resource(store_id)?
268      .locked(|store| store.set(key, value))
269  }
270
271  /// Patches a store state.
272  pub fn patch<S>(&self, store_id: impl AsRef<str>, state: S) -> Result<()>
273  where
274    S: Into<StoreState>,
275  {
276    self
277      .get_resource(store_id)?
278      .locked(|store| store.patch(state))
279  }
280
281  /// Saves a store to the disk.
282  pub fn save(&self, store_id: impl AsRef<str>) -> Result<()> {
283    self
284      .get_resource(store_id)?
285      .locked(|store| store.save())
286  }
287
288  /// Saves a store to the disk immediately, ignoring the save strategy.
289  pub fn save_now(&self, store_id: impl AsRef<str>) -> Result<()> {
290    self.get_resource(store_id)?.locked(|store| {
291      store.abort_pending_save();
292      store.save_now()
293    })
294  }
295
296  /// Saves some stores to the disk.
297  pub fn save_some(&self, ids: &[impl AsRef<str>]) -> Result<()> {
298    ids.iter().try_for_each(|id| self.save(id))
299  }
300
301  /// Saves some stores to the disk immediately, ignoring the save strategy.
302  pub fn save_some_now(&self, ids: &[impl AsRef<str>]) -> Result<()> {
303    ids.iter().try_for_each(|id| self.save_now(id))
304  }
305
306  /// Saves all the stores to the disk.
307  pub fn save_all(&self) -> Result<()> {
308    // I suppose going through the rids is better than through the store ids.
309    // This way, we don't need to hold references into the dashmap nor clone its keys.
310    // The downside (?) is that we need to use the StoreResource directly.
311    let app = self.handle.app();
312    self
313      .rids()
314      .into_iter()
315      .try_for_each(|rid| StoreResource::<R, C>::save(app, rid))
316  }
317
318  /// Saves all the stores to the disk immediately, ignoring the save strategy.
319  pub fn save_all_now(&self) -> Result<()> {
320    let app = self.handle.app();
321    self
322      .rids()
323      .into_iter()
324      .try_for_each(|rid| StoreResource::<R, C>::save_now(app, rid))
325  }
326
327  /// Default save strategy for the stores.
328  /// This can be overridden on a per-store basis.
329  #[inline]
330  pub fn default_save_strategy(&self) -> SaveStrategy {
331    self.default_save_strategy
332  }
333
334  /// Saves the stores periodically.
335  pub fn set_autosave(&self, duration: Duration) {
336    if let Ok(mut autosave) = self.autosave.lock() {
337      autosave.set_duration(duration);
338      autosave.start::<R, C>(self.handle.app());
339    }
340  }
341
342  /// Stops the autosave.
343  pub fn clear_autosave(&self) {
344    if let Ok(mut autosave) = self.autosave.lock() {
345      autosave.stop();
346    }
347  }
348
349  /// Watches a store for changes.
350  pub fn watch<F>(&self, store_id: impl AsRef<str>, f: F) -> Result<WatcherId>
351  where
352    F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
353  {
354    self
355      .get_resource(store_id)?
356      .locked(|store| Ok(store.watch(f)))
357  }
358
359  /// Removes a watcher from a store.
360  pub fn unwatch(
361    &self,
362    store_id: impl AsRef<str>,
363    watcher_id: impl Into<WatcherId>,
364  ) -> Result<bool> {
365    self
366      .get_resource(store_id)?
367      .locked(|store| Ok(store.unwatch(watcher_id)))
368  }
369
370  /// Removes a store from the save denylist.
371  pub fn allow_save(&self, id: impl AsRef<str>) {
372    let id = StoreId::from(id.as_ref());
373    self.save_denylist.remove(&id);
374  }
375
376  /// Adds a store to the save denylist.
377  pub fn deny_save(&self, id: impl AsRef<str>) {
378    let id = StoreId::from(id.as_ref());
379    self.save_denylist.insert(id);
380  }
381
382  /// Removes a store from the sync denylist.
383  pub fn allow_sync(&self, id: impl AsRef<str>) {
384    let id = StoreId::from(id.as_ref());
385    self.sync_denylist.remove(&id);
386  }
387
388  /// Adds a store to the deny denylist.
389  pub fn deny_sync(&self, id: impl AsRef<str>) {
390    let id = StoreId::from(id.as_ref());
391    self.sync_denylist.insert(id);
392  }
393
394  /// Destroys a store, cleans up its state, and deletes its file.
395  #[doc(hidden)]
396  pub fn destroy(&self, id: impl AsRef<str>) -> Result<()> {
397    let id = StoreId::from(id.as_ref());
398    self.unload_and(&id, Store::destroy)
399  }
400
401  /// Removes the store from the collection.
402  #[doc(hidden)]
403  pub fn unload_store(&self, id: &StoreId) -> Result<()> {
404    // The store needs to be saved immediately here.
405    // Otherwise, the plugin might try to load it again if `StoreCollection::get_resource` is called.
406    // This scenario will happen whenever the save strategy is not `Immediate`.
407    self.unload_and(id, |store| store.save_now())
408  }
409
410  fn unload_and<F>(&self, id: &StoreId, f: F) -> Result<()>
411  where
412    F: FnOnce(&mut Store<R, C>) -> Result<()>,
413  {
414    let app = self.handle.app();
415    if let Some((_, rid)) = self.stores.remove(id) {
416      let resource = StoreResource::<R, C>::take(app, rid)?;
417      resource.locked(f)?;
418      emit(app, STORE_UNLOAD_EVENT, id, None::<&str>)?;
419    }
420
421    Ok(())
422  }
423
424  /// Runs any necessary tasks before the application exits.
425  #[doc(hidden)]
426  pub fn on_exit(&self) -> Result<()> {
427    self.clear_autosave();
428
429    let app = self.handle.app();
430    for rid in self.rids() {
431      if let Ok(resource) = StoreResource::<R, C>::take(app, rid) {
432        resource.locked(|store| {
433          store.abort_pending_save();
434          if store.save_on_exit {
435            let _ = store.save_now();
436          }
437        });
438      }
439    }
440
441    Ok(())
442  }
443}
444
445impl<R, C> Resource for StoreCollection<R, C>
446where
447  R: Runtime,
448  C: CollectionMarker,
449{
450}
451
452impl<R, C> fmt::Debug for StoreCollection<R, C>
453where
454  R: Runtime,
455  C: CollectionMarker,
456{
457  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458    f.debug_struct("StoreCollection")
459      .field("name", &self.name)
460      .finish_non_exhaustive()
461  }
462}