tauri_store/
collection.rs

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