tauri_store/store/
mod.rs

1mod id;
2mod marshaler;
3mod options;
4mod resource;
5mod save;
6mod state;
7mod watch;
8
9use crate::collection::CollectionMarker;
10use crate::error::{Error, Result};
11use crate::event::{
12  emit, ConfigPayload, EventSource, StatePayload, STORE_CONFIG_CHANGE_EVENT,
13  STORE_STATE_CHANGE_EVENT,
14};
15use crate::manager::ManagerExt;
16use crate::StoreCollection;
17use options::set_options;
18use save::{debounce, throttle, SaveHandle};
19use serde::de::DeserializeOwned;
20use serde_json::Value;
21use std::collections::HashMap;
22use std::fs::File;
23use std::io::ErrorKind;
24use std::io::Write;
25use std::marker::PhantomData;
26use std::path::PathBuf;
27use std::sync::{Arc, OnceLock};
28use std::{fmt, fs};
29use tauri::async_runtime::spawn_blocking;
30use tauri::{AppHandle, ResourceId, Runtime};
31use watch::Watcher;
32
33pub use id::StoreId;
34pub use marshaler::{JsonMarshaler, Marshaler, MarshalingError, PrettyJsonMarshaler};
35pub use options::StoreOptions;
36pub(crate) use resource::StoreResource;
37pub use save::SaveStrategy;
38pub use state::StoreState;
39pub use watch::WatcherId;
40
41#[cfg(feature = "marshaler-toml")]
42pub use marshaler::{PrettyTomlMarshaler, TomlMarshaler};
43
44type ResourceTuple<R, C> = (ResourceId, Arc<StoreResource<R, C>>);
45
46/// A key-value store that can persist its state to disk.
47pub struct Store<R, C>
48where
49  R: Runtime,
50  C: CollectionMarker,
51{
52  app: AppHandle<R>,
53  pub(crate) id: StoreId,
54  state: StoreState,
55  pub(crate) save_on_exit: bool,
56  save_on_change: bool,
57  save_strategy: Option<SaveStrategy>,
58  debounce_save_handle: OnceLock<SaveHandle<R>>,
59  throttle_save_handle: OnceLock<SaveHandle<R>>,
60  watchers: HashMap<WatcherId, Watcher<R>>,
61  phantom: PhantomData<C>,
62}
63
64impl<R, C> Store<R, C>
65where
66  R: Runtime,
67  C: CollectionMarker,
68{
69  pub(crate) fn load(app: &AppHandle<R>, id: impl AsRef<str>) -> Result<ResourceTuple<R, C>> {
70    let id = StoreId::from(id.as_ref());
71    let collection = app.store_collection_with_marker::<C>();
72    let marshaler = collection.marshaler_table.get(&id);
73    let path = make_path::<R, C>(&collection, &id, marshaler.extension());
74    let state = match fs::read(&path) {
75      Ok(bytes) => marshaler
76        .deserialize(&bytes)
77        .map_err(Error::FailedToDeserialize)?,
78      Err(err) if err.kind() == ErrorKind::NotFound => StoreState::default(),
79      Err(err) => return Err(Error::Io(err)),
80    };
81
82    let mut store = Self {
83      app: app.clone(),
84      id,
85      state,
86      save_on_change: false,
87      save_on_exit: true,
88      save_strategy: None,
89      debounce_save_handle: OnceLock::new(),
90      throttle_save_handle: OnceLock::new(),
91      watchers: HashMap::new(),
92      phantom: PhantomData,
93    };
94
95    store.run_pending_migrations()?;
96
97    Ok(StoreResource::create(app, store))
98  }
99
100  fn run_pending_migrations(&mut self) -> Result<()> {
101    self
102      .app
103      .store_collection_with_marker::<C>()
104      .migrator
105      .lock()
106      .expect("migrator is poisoned")
107      .migrate::<R, C>(&self.app, &self.id, &mut self.state)
108  }
109
110  /// The id of the store.
111  #[inline]
112  pub fn id(&self) -> StoreId {
113    self.id.clone()
114  }
115
116  /// Path to the store file.
117  pub fn path(&self) -> PathBuf {
118    let collection = self.app.store_collection_with_marker::<C>();
119    let marshaler = collection.marshaler_table.get(&self.id);
120    make_path::<R, C>(&collection, &self.id, marshaler.extension())
121  }
122
123  /// Gets a handle to the application instance.
124  pub fn app_handle(&self) -> &AppHandle<R> {
125    &self.app
126  }
127
128  /// Gets a reference to the raw store state.
129  #[inline]
130  pub fn raw_state(&self) -> &StoreState {
131    &self.state
132  }
133
134  /// Tries to parse the store state as an instance of type `T`.
135  pub fn state<T>(&self) -> Result<T>
136  where
137    T: DeserializeOwned,
138  {
139    let value = Value::from(&self.state);
140    Ok(serde_json::from_value(value)?)
141  }
142
143  /// Tries to parse the store state as an instance of type `T`.
144  ///
145  /// If it cannot be parsed, returns the provided default value.
146  pub fn state_or<T>(&self, default: T) -> T
147  where
148    T: DeserializeOwned,
149  {
150    self.state().unwrap_or(default)
151  }
152
153  /// Tries to parse the store state as an instance of type `T`.
154  ///
155  /// If it cannot be parsed, returns the default value of `T`.
156  pub fn state_or_default<T>(&self) -> T
157  where
158    T: DeserializeOwned + Default,
159  {
160    self.state().unwrap_or_default()
161  }
162
163  /// Tries to parse the store state as an instance of type `T`.
164  ///
165  /// If it cannot be parsed, returns the result of the provided closure.
166  pub fn state_or_else<T>(&self, f: impl FnOnce() -> T) -> T
167  where
168    T: DeserializeOwned,
169  {
170    self.state().unwrap_or_else(|_| f())
171  }
172
173  /// Gets a reference to a raw value from the store.
174  pub fn get_raw(&self, key: impl AsRef<str>) -> Option<&Value> {
175    self.state.get_raw(key)
176  }
177
178  /// Gets a reference to a raw value from the store.
179  ///
180  /// # Safety
181  ///
182  /// This is *undefined behavior* if the key doesn't exist in the store.
183  pub unsafe fn get_raw_unchecked(&self, key: impl AsRef<str>) -> &Value {
184    unsafe { self.state.get_raw_unchecked(key) }
185  }
186
187  /// Gets a value from the store and tries to parse it as an instance of type `T`.
188  pub fn get<T>(&self, key: impl AsRef<str>) -> Result<T>
189  where
190    T: DeserializeOwned,
191  {
192    self.state.get(key)
193  }
194
195  /// Gets a value from the store and tries to parse it as an instance of type `T`.
196  ///
197  /// If the key does not exist, returns the provided default value.
198  pub fn get_or<T>(&self, key: impl AsRef<str>, default: T) -> T
199  where
200    T: DeserializeOwned,
201  {
202    self.state.get_or(key, default)
203  }
204
205  /// Gets a value from the store and tries to parse it as an instance of type `T`.
206  ///
207  /// If the key does not exist, returns the default value of `T`.
208  pub fn get_or_default<T>(&self, key: impl AsRef<str>) -> T
209  where
210    T: DeserializeOwned + Default,
211  {
212    self.state.get_or_default(key)
213  }
214
215  /// Gets a value from the store and tries to parse it as an instance of type `T`.
216  ///
217  /// If the key does not exist, returns the result of the provided closure.
218  pub fn get_or_else<T>(&self, key: impl AsRef<str>, f: impl FnOnce() -> T) -> T
219  where
220    T: DeserializeOwned,
221  {
222    self.state.get_or_else(key, f)
223  }
224
225  /// Gets a value from the store and parses it as an instance of type `T`.
226  ///
227  /// # Safety
228  ///
229  /// This is *undefined behavior* if the key doesn't exist in the store
230  /// **OR** if the value cannot be represented as a valid `T`.
231  pub unsafe fn get_unchecked<T>(&self, key: impl AsRef<str>) -> T
232  where
233    T: DeserializeOwned,
234  {
235    unsafe { self.state.get_unchecked(key) }
236  }
237
238  /// Sets a key-value pair in the store.
239  pub fn set(&mut self, key: impl AsRef<str>, value: impl Into<Value>) -> Result<()> {
240    self.state.set(key, value);
241    self.on_state_change(None::<&str>)
242  }
243
244  /// Patches the store state, optionally having a window as the source.
245  #[doc(hidden)]
246  pub fn patch_with_source<S, E>(&mut self, state: S, source: E) -> Result<()>
247  where
248    S: Into<StoreState>,
249    E: Into<EventSource>,
250  {
251    self.state.patch(state);
252    self.on_state_change(source)
253  }
254
255  /// Patches the store state.
256  pub fn patch<S>(&mut self, state: S) -> Result<()>
257  where
258    S: Into<StoreState>,
259  {
260    self.patch_with_source(state, None::<&str>)
261  }
262
263  /// Whether the store has a key.
264  pub fn has(&self, key: impl AsRef<str>) -> bool {
265    self.state.has(key)
266  }
267
268  /// Creates an iterator over the store keys.
269  pub fn keys(&self) -> impl Iterator<Item = &String> {
270    self.state.keys()
271  }
272
273  /// Creates an iterator over the store values.
274  pub fn values(&self) -> impl Iterator<Item = &Value> {
275    self.state.values()
276  }
277
278  /// Creates an iterator over the store entries.
279  pub fn entries(&self) -> impl Iterator<Item = (&String, &Value)> {
280    self.state.entries()
281  }
282
283  /// Returns the amount of items in the store.
284  #[inline]
285  pub fn len(&self) -> usize {
286    self.state.len()
287  }
288
289  /// Whether the store is empty.
290  #[inline]
291  pub fn is_empty(&self) -> bool {
292    self.state.is_empty()
293  }
294
295  /// Save the store state to the disk.
296  pub fn save(&self) -> Result<()> {
297    match self.save_strategy() {
298      SaveStrategy::Immediate => self.save_now()?,
299      SaveStrategy::Debounce(duration) => {
300        self
301          .debounce_save_handle
302          .get_or_init(|| debounce::<R, C>(self.id.clone(), duration))
303          .call(&self.app);
304      }
305      SaveStrategy::Throttle(duration) => {
306        self
307          .throttle_save_handle
308          .get_or_init(|| throttle::<R, C>(self.id.clone(), duration))
309          .call(&self.app);
310      }
311    }
312
313    Ok(())
314  }
315
316  /// Save the store immediately, ignoring the save strategy.
317  pub fn save_now(&self) -> Result<()> {
318    let collection = self.app.store_collection_with_marker::<C>();
319    if collection.save_denylist.contains(&self.id) {
320      return Ok(());
321    }
322
323    let marshaler = collection.marshaler_table.get(&self.id);
324    let bytes = marshaler
325      .serialize(&self.state)
326      .map_err(Error::FailedToSerialize)?;
327
328    let path = self.path();
329    if let Some(parent) = path.parent() {
330      fs::create_dir_all(parent)?;
331    }
332
333    let mut file = File::create(path)?;
334    file.write_all(&bytes)?;
335    file.flush()?;
336
337    if cfg!(feature = "file-sync-all") {
338      file.sync_all()?;
339    }
340
341    Ok(())
342  }
343
344  /// Whether to save the store on exit.
345  /// This is enabled by default.
346  #[inline]
347  pub fn save_on_exit(&mut self, enabled: bool) {
348    self.save_on_exit = enabled;
349  }
350
351  /// Whether to save the store on state change.
352  #[inline]
353  pub fn save_on_change(&mut self, enabled: bool) {
354    self.save_on_change = enabled;
355  }
356
357  /// Current save strategy used by this store.
358  pub fn save_strategy(&self) -> SaveStrategy {
359    self.save_strategy.unwrap_or_else(|| {
360      self
361        .app
362        .store_collection_with_marker::<C>()
363        .default_save_strategy
364    })
365  }
366
367  /// Sets the save strategy for this store.
368  /// Calling this will abort any pending save operation.
369  pub fn set_save_strategy(&mut self, strategy: SaveStrategy) {
370    if strategy.is_debounce() {
371      self
372        .debounce_save_handle
373        .take()
374        .inspect(SaveHandle::abort);
375    } else if strategy.is_throttle() {
376      self
377        .throttle_save_handle
378        .take()
379        .inspect(SaveHandle::abort);
380    }
381
382    self.save_strategy = Some(strategy);
383  }
384
385  /// Watches the store for changes.
386  pub fn watch<F>(&mut self, f: F) -> WatcherId
387  where
388    F: Fn(AppHandle<R>) -> Result<()> + Send + Sync + 'static,
389  {
390    let (id, listener) = Watcher::new(f);
391    self.watchers.insert(id, listener);
392    id
393  }
394
395  /// Removes a listener from this store.
396  pub fn unwatch(&mut self, id: impl Into<WatcherId>) -> bool {
397    self.watchers.remove(&id.into()).is_some()
398  }
399
400  /// Sets the store options.
401  pub fn set_options(&mut self, options: StoreOptions) -> Result<()> {
402    self.set_options_with_source(options, None::<&str>)
403  }
404
405  /// Sets the store options, optionally having a window as the source.
406  #[doc(hidden)]
407  pub fn set_options_with_source<E>(&mut self, options: StoreOptions, source: E) -> Result<()>
408  where
409    E: Into<EventSource>,
410  {
411    set_options(self, options);
412    self.on_config_change(source)
413  }
414
415  fn on_state_change(&self, source: impl Into<EventSource>) -> Result<()> {
416    self.emit_state_change(source)?;
417    self.call_watchers();
418
419    if self.save_on_change {
420      self.save()?;
421    }
422
423    Ok(())
424  }
425
426  fn emit_state_change(&self, source: impl Into<EventSource>) -> Result<()> {
427    let source: EventSource = source.into();
428
429    // If we also skip the store when the source is the backend,
430    // the window where the store resides would never know about the change.
431    if !source.is_backend()
432      && self
433        .app
434        .store_collection_with_marker::<C>()
435        .sync_denylist
436        .contains(&self.id)
437    {
438      return Ok(());
439    }
440
441    emit(
442      &self.app,
443      STORE_STATE_CHANGE_EVENT,
444      &StatePayload::from(self),
445      source,
446    )
447  }
448
449  fn on_config_change(&self, source: impl Into<EventSource>) -> Result<()> {
450    self.emit_config_change(source)
451  }
452
453  fn emit_config_change(&self, source: impl Into<EventSource>) -> Result<()> {
454    emit(
455      &self.app,
456      STORE_CONFIG_CHANGE_EVENT,
457      &ConfigPayload::from(self),
458      source,
459    )
460  }
461
462  /// Calls all watchers currently attached to the store.
463  fn call_watchers(&self) {
464    if self.watchers.is_empty() {
465      return;
466    }
467
468    for watcher in self.watchers.values() {
469      let app = self.app.clone();
470      let watcher = watcher.clone();
471      spawn_blocking(move || watcher.call(app));
472    }
473  }
474
475  pub(crate) fn abort_pending_save(&self) {
476    self
477      .debounce_save_handle
478      .get()
479      .map(SaveHandle::abort);
480
481    self
482      .throttle_save_handle
483      .get()
484      .map(SaveHandle::abort);
485  }
486
487  pub(crate) fn destroy(&mut self) -> Result<()> {
488    self.abort_pending_save();
489    self.state.clear();
490    fs::remove_file(self.path())?;
491    Ok(())
492  }
493}
494
495impl<R, C> fmt::Debug for Store<R, C>
496where
497  R: Runtime,
498  C: CollectionMarker,
499{
500  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501    f.debug_struct("Store")
502      .field("id", &self.id)
503      .field("state", &self.state)
504      .field("watchers", &self.watchers.len())
505      .field("save_on_exit", &self.save_on_exit)
506      .field("save_on_change", &self.save_on_change)
507      .field("save_strategy", &self.save_strategy)
508      .finish_non_exhaustive()
509  }
510}
511
512fn make_path<R, C>(collection: &StoreCollection<R, C>, id: &StoreId, extension: &str) -> PathBuf
513where
514  R: Runtime,
515  C: CollectionMarker,
516{
517  debug_assert!(
518    !extension.eq_ignore_ascii_case("tauristore"),
519    "illegal store extension: {extension}"
520  );
521
522  let filename = if cfg!(debug_assertions) && collection.debug_stores {
523    format!("{id}.dev.{extension}")
524  } else {
525    format!("{id}.{extension}")
526  };
527
528  collection.path_of(id).join(filename)
529}