tauri_plugin_store/
store.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use crate::{ChangePayload, StoreState};
6use serde_json::Value as JsonValue;
7use std::{
8    collections::HashMap,
9    fs,
10    path::{Path, PathBuf},
11    sync::{Arc, Mutex},
12    time::Duration,
13};
14use tauri::{path::BaseDirectory, AppHandle, Emitter, Manager, Resource, ResourceId, Runtime};
15use tokio::{
16    select,
17    sync::mpsc::{unbounded_channel, UnboundedSender},
18    time::sleep,
19};
20
21pub type SerializeFn =
22    fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
23pub type DeserializeFn =
24    fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>>;
25
26pub fn resolve_store_path<R: Runtime>(
27    app: &AppHandle<R>,
28    path: impl AsRef<Path>,
29) -> crate::Result<PathBuf> {
30    Ok(dunce::simplified(&app.path().resolve(path, BaseDirectory::AppData)?).to_path_buf())
31}
32
33/// Builds a [`Store`]
34pub struct StoreBuilder<R: Runtime> {
35    app: AppHandle<R>,
36    path: PathBuf,
37    defaults: Option<HashMap<String, JsonValue>>,
38    serialize_fn: SerializeFn,
39    deserialize_fn: DeserializeFn,
40    auto_save: Option<Duration>,
41    create_new: bool,
42    override_defaults: bool,
43}
44
45impl<R: Runtime> StoreBuilder<R> {
46    /// Creates a new [`StoreBuilder`].
47    ///
48    /// # Examples
49    /// ```
50    /// tauri::Builder::default()
51    ///   .plugin(tauri_plugin_store::Builder::default().build())
52    ///   .setup(|app| {
53    ///     let builder = tauri_plugin_store::StoreBuilder::new(app, "store.bin");
54    ///     Ok(())
55    ///   });
56    /// ```
57    pub fn new<M: Manager<R>, P: AsRef<Path>>(manager: &M, path: P) -> Self {
58        let app = manager.app_handle().clone();
59        let state = app.state::<StoreState>();
60        let serialize_fn = state.default_serialize;
61        let deserialize_fn = state.default_deserialize;
62        Self {
63            app,
64            path: path.as_ref().to_path_buf(),
65            defaults: None,
66            serialize_fn,
67            deserialize_fn,
68            auto_save: Some(Duration::from_millis(100)),
69            create_new: false,
70            override_defaults: false,
71        }
72    }
73
74    /// Inserts a default key-value pair.
75    ///
76    /// # Examples
77    /// ```
78    /// tauri::Builder::default()
79    ///   .plugin(tauri_plugin_store::Builder::default().build())
80    ///   .setup(|app| {
81    ///     let mut defaults = std::collections::HashMap::new();
82    ///     defaults.insert("foo".to_string(), "bar".into());
83    ///
84    ///     let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin")
85    ///       .defaults(defaults)
86    ///       .build()?;
87    ///     Ok(())
88    ///   });
89    /// ```
90    pub fn defaults(mut self, defaults: HashMap<String, JsonValue>) -> Self {
91        self.defaults = Some(defaults);
92        self
93    }
94
95    /// Inserts multiple default key-value pairs.
96    ///
97    /// # Examples
98    /// ```
99    /// tauri::Builder::default()
100    ///   .plugin(tauri_plugin_store::Builder::default().build())
101    ///   .setup(|app| {
102    ///     let store = tauri_plugin_store::StoreBuilder::new(app, "store.bin")
103    ///       .default("foo".to_string(), "bar")
104    ///       .build()?;
105    ///     Ok(())
106    ///   });
107    /// ```
108    pub fn default(mut self, key: impl Into<String>, value: impl Into<JsonValue>) -> Self {
109        let key = key.into();
110        let value = value.into();
111        self.defaults
112            .get_or_insert(HashMap::new())
113            .insert(key, value);
114        self
115    }
116
117    /// Defines a custom serialization function.
118    ///
119    /// # Examples
120    /// ```
121    /// tauri::Builder::default()
122    ///   .plugin(tauri_plugin_store::Builder::default().build())
123    ///   .setup(|app| {
124    ///     let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
125    ///       .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into))
126    ///       .build()?;
127    ///     Ok(())
128    ///   });
129    /// ```
130    pub fn serialize(mut self, serialize: SerializeFn) -> Self {
131        self.serialize_fn = serialize;
132        self
133    }
134
135    /// Defines a custom deserialization function
136    ///
137    /// # Examples
138    /// ```
139    /// tauri::Builder::default()
140    ///   .plugin(tauri_plugin_store::Builder::default().build())
141    ///   .setup(|app| {
142    ///     let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
143    ///       .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into))
144    ///       .build()?;
145    ///     Ok(())
146    ///   });
147    /// ```
148    pub fn deserialize(mut self, deserialize: DeserializeFn) -> Self {
149        self.deserialize_fn = deserialize;
150        self
151    }
152
153    /// Auto save on modified with a debounce duration
154    ///
155    /// # Examples
156    /// ```
157    /// tauri::Builder::default()
158    ///    .plugin(tauri_plugin_store::Builder::default().build())
159    ///   .setup(|app| {
160    ///     let store = tauri_plugin_store::StoreBuilder::new(app, "store.json")
161    ///         .auto_save(std::time::Duration::from_millis(100))
162    ///         .build()?;
163    ///     Ok(())
164    ///   });
165    /// ```
166    pub fn auto_save(mut self, debounce_duration: Duration) -> Self {
167        self.auto_save = Some(debounce_duration);
168        self
169    }
170
171    /// Disable auto save on modified with a debounce duration.
172    pub fn disable_auto_save(mut self) -> Self {
173        self.auto_save = None;
174        self
175    }
176
177    /// Force create a new store with default values even if it already exists.
178    pub fn create_new(mut self) -> Self {
179        self.create_new = true;
180        self
181    }
182
183    /// Override the store values when creating the store, ignoring defaults.
184    pub fn override_defaults(mut self) -> Self {
185        self.override_defaults = true;
186        self
187    }
188
189    pub(crate) fn build_inner(mut self) -> crate::Result<(Arc<Store<R>>, ResourceId)> {
190        let stores = self.app.state::<StoreState>().stores.clone();
191        let mut stores = stores.lock().unwrap();
192
193        self.path = resolve_store_path(&self.app, self.path)?;
194
195        if self.create_new {
196            if let Some(rid) = stores.remove(&self.path) {
197                let _ = self.app.resources_table().take::<Store<R>>(rid);
198            }
199        } else if let Some(rid) = stores.get(&self.path) {
200            // The resource id we stored can be invalid due to
201            // the resource table getting modified by an external source
202            // (e.g. `App::cleanup_before_exit` > `manager.resources_table.clear()`)
203            return Ok((self.app.resources_table().get(*rid)?, *rid));
204        }
205
206        // if stores.contains_key(&self.path) {
207        //     return Err(crate::Error::AlreadyExists(self.path));
208        // }
209
210        let mut store_inner = StoreInner::new(
211            self.app.clone(),
212            self.path.clone(),
213            self.defaults.take(),
214            self.serialize_fn,
215            self.deserialize_fn,
216        );
217
218        if !self.create_new {
219            if self.override_defaults {
220                let _ = store_inner.load_ignore_defaults();
221            } else {
222                let _ = store_inner.load();
223            }
224        }
225
226        let store = Store {
227            auto_save: self.auto_save,
228            auto_save_debounce_sender: Arc::new(Mutex::new(None)),
229            store: Arc::new(Mutex::new(store_inner)),
230        };
231
232        let store = Arc::new(store);
233        let rid = self.app.resources_table().add_arc(store.clone());
234        stores.insert(self.path, rid);
235
236        Ok((store, rid))
237    }
238
239    /// Load the existing store with the same path or creates a new [`Store`].
240    ///
241    /// If a store with the same path has already been loaded its instance is returned.
242    ///
243    /// # Examples
244    /// ```
245    /// tauri::Builder::default()
246    ///   .plugin(tauri_plugin_store::Builder::default().build())
247    ///   .setup(|app| {
248    ///     let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build();
249    ///     Ok(())
250    ///   });
251    /// ```
252    pub fn build(self) -> crate::Result<Arc<Store<R>>> {
253        let (store, _) = self.build_inner()?;
254        Ok(store)
255    }
256}
257
258enum AutoSaveMessage {
259    Reset,
260    Cancel,
261}
262
263#[derive(Clone)]
264struct StoreInner<R: Runtime> {
265    app: AppHandle<R>,
266    path: PathBuf,
267    cache: HashMap<String, JsonValue>,
268    defaults: Option<HashMap<String, JsonValue>>,
269    serialize_fn: SerializeFn,
270    deserialize_fn: DeserializeFn,
271}
272
273impl<R: Runtime> StoreInner<R> {
274    fn new(
275        app: AppHandle<R>,
276        path: PathBuf,
277        defaults: Option<HashMap<String, JsonValue>>,
278        serialize_fn: SerializeFn,
279        deserialize_fn: DeserializeFn,
280    ) -> Self {
281        Self {
282            app,
283            path,
284            cache: defaults.clone().unwrap_or_default(),
285            defaults,
286            serialize_fn,
287            deserialize_fn,
288        }
289    }
290
291    /// Saves the store to disk at the store's `path`.
292    pub fn save(&self) -> crate::Result<()> {
293        fs::create_dir_all(self.path.parent().expect("invalid store path"))?;
294
295        let bytes = (self.serialize_fn)(&self.cache).map_err(crate::Error::Serialize)?;
296        fs::write(&self.path, bytes)?;
297
298        Ok(())
299    }
300
301    /// Update the store from the on-disk state
302    ///
303    /// Note: This method loads the data and merges it with the current store
304    pub fn load(&mut self) -> crate::Result<()> {
305        let bytes = fs::read(&self.path)?;
306
307        self.cache
308            .extend((self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?);
309
310        Ok(())
311    }
312
313    /// Load the store from the on-disk state, ignoring defaults
314    pub fn load_ignore_defaults(&mut self) -> crate::Result<()> {
315        let bytes = fs::read(&self.path)?;
316        self.cache = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
317        Ok(())
318    }
319
320    /// Inserts a key-value pair into the store.
321    pub fn set(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
322        let key = key.into();
323        let value = value.into();
324        self.cache.insert(key.clone(), value.clone());
325        let _ = self.emit_change_event(&key, Some(&value));
326    }
327
328    /// Returns a reference to the value corresponding to the key.
329    pub fn get(&self, key: impl AsRef<str>) -> Option<&JsonValue> {
330        self.cache.get(key.as_ref())
331    }
332
333    /// Returns `true` if the given `key` exists in the store.
334    pub fn has(&self, key: impl AsRef<str>) -> bool {
335        self.cache.contains_key(key.as_ref())
336    }
337
338    /// Removes a key-value pair from the store.
339    pub fn delete(&mut self, key: impl AsRef<str>) -> bool {
340        let flag = self.cache.remove(key.as_ref()).is_some();
341        if flag {
342            let _ = self.emit_change_event(key.as_ref(), None);
343        }
344        flag
345    }
346
347    /// Clears the store, removing all key-value pairs.
348    ///
349    /// Note: To clear the storage and reset it to its `default` value, use [`reset`](Self::reset) instead.
350    pub fn clear(&mut self) {
351        let keys: Vec<String> = self.cache.keys().cloned().collect();
352        self.cache.clear();
353        for key in &keys {
354            let _ = self.emit_change_event(key, None);
355        }
356    }
357
358    /// Resets the store to its `default` value.
359    ///
360    /// If no default value has been set, this method behaves identical to [`clear`](Self::clear).
361    pub fn reset(&mut self) {
362        if let Some(defaults) = &self.defaults {
363            for (key, value) in &self.cache {
364                if defaults.get(key) != Some(value) {
365                    let _ = self.emit_change_event(key, defaults.get(key));
366                }
367            }
368            for (key, value) in defaults {
369                if !self.cache.contains_key(key) {
370                    let _ = self.emit_change_event(key, Some(value));
371                }
372            }
373            self.cache.clone_from(defaults);
374        } else {
375            self.clear()
376        }
377    }
378
379    /// An iterator visiting all keys in arbitrary order.
380    pub fn keys(&self) -> impl Iterator<Item = &String> {
381        self.cache.keys()
382    }
383
384    /// An iterator visiting all values in arbitrary order.
385    pub fn values(&self) -> impl Iterator<Item = &JsonValue> {
386        self.cache.values()
387    }
388
389    /// An iterator visiting all key-value pairs in arbitrary order.
390    pub fn entries(&self) -> impl Iterator<Item = (&String, &JsonValue)> {
391        self.cache.iter()
392    }
393
394    /// Returns the number of elements in the store.
395    pub fn len(&self) -> usize {
396        self.cache.len()
397    }
398
399    /// Returns true if the store contains no elements.
400    pub fn is_empty(&self) -> bool {
401        self.cache.is_empty()
402    }
403
404    fn emit_change_event(&self, key: &str, value: Option<&JsonValue>) -> crate::Result<()> {
405        let state = self.app.state::<StoreState>();
406        let stores = state.stores.lock().unwrap();
407        let exists = value.is_some();
408        self.app.emit(
409            "store://change",
410            ChangePayload {
411                path: &self.path,
412                resource_id: stores.get(&self.path).copied(),
413                key,
414                value,
415                exists,
416            },
417        )?;
418        Ok(())
419    }
420}
421
422impl<R: Runtime> std::fmt::Debug for StoreInner<R> {
423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424        f.debug_struct("Store")
425            .field("path", &self.path)
426            .field("cache", &self.cache)
427            .finish()
428    }
429}
430
431pub struct Store<R: Runtime> {
432    auto_save: Option<Duration>,
433    auto_save_debounce_sender: Arc<Mutex<Option<UnboundedSender<AutoSaveMessage>>>>,
434    store: Arc<Mutex<StoreInner<R>>>,
435}
436
437impl<R: Runtime> Resource for Store<R> {
438    fn close(self: Arc<Self>) {
439        let store = self.store.lock().unwrap();
440        let state = store.app.state::<StoreState>();
441        let mut stores = state.stores.lock().unwrap();
442        stores.remove(&store.path);
443    }
444}
445
446impl<R: Runtime> Store<R> {
447    // /// Do something with the inner store,
448    // /// useful for batching some work if you need higher performance
449    // pub fn with_store<T>(&self, f: impl FnOnce(&mut StoreInner<R>) -> T) -> T {
450    //     let mut store = self.store.lock().unwrap();
451    //     f(&mut store)
452    // }
453
454    /// Inserts a key-value pair into the store.
455    pub fn set(&self, key: impl Into<String>, value: impl Into<JsonValue>) {
456        self.store.lock().unwrap().set(key.into(), value.into());
457        let _ = self.trigger_auto_save();
458    }
459
460    /// Returns the value for the given `key` or `None` if the key does not exist.
461    pub fn get(&self, key: impl AsRef<str>) -> Option<JsonValue> {
462        self.store.lock().unwrap().get(key).cloned()
463    }
464
465    /// Returns `true` if the given `key` exists in the store.
466    pub fn has(&self, key: impl AsRef<str>) -> bool {
467        self.store.lock().unwrap().has(key)
468    }
469
470    /// Removes a key-value pair from the store.
471    pub fn delete(&self, key: impl AsRef<str>) -> bool {
472        let deleted = self.store.lock().unwrap().delete(key);
473        if deleted {
474            let _ = self.trigger_auto_save();
475        }
476        deleted
477    }
478
479    /// Clears the store, removing all key-value pairs.
480    ///
481    /// Note: To clear the storage and reset it to its `default` value, use [`reset`](Self::reset) instead.
482    pub fn clear(&self) {
483        self.store.lock().unwrap().clear();
484        let _ = self.trigger_auto_save();
485    }
486
487    /// Resets the store to its `default` value.
488    ///
489    /// If no default value has been set, this method behaves identical to [`clear`](Self::clear).
490    pub fn reset(&self) {
491        self.store.lock().unwrap().reset();
492        let _ = self.trigger_auto_save();
493    }
494
495    /// Returns a list of all keys in the store.
496    pub fn keys(&self) -> Vec<String> {
497        self.store.lock().unwrap().keys().cloned().collect()
498    }
499
500    /// Returns a list of all values in the store.
501    pub fn values(&self) -> Vec<JsonValue> {
502        self.store.lock().unwrap().values().cloned().collect()
503    }
504
505    /// Returns a list of all key-value pairs in the store.
506    pub fn entries(&self) -> Vec<(String, JsonValue)> {
507        self.store
508            .lock()
509            .unwrap()
510            .entries()
511            .map(|(k, v)| (k.to_owned(), v.to_owned()))
512            .collect()
513    }
514
515    /// Returns the number of elements in the store.
516    pub fn length(&self) -> usize {
517        self.store.lock().unwrap().len()
518    }
519
520    /// Returns true if the store contains no elements.
521    pub fn is_empty(&self) -> bool {
522        self.store.lock().unwrap().is_empty()
523    }
524
525    /// Update the store from the on-disk state
526    ///
527    /// Note:
528    ///   - This method loads the data and merges it with the current store,
529    ///     this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
530    ///     to fully match the store with the on-disk state,
531    ///     use [`reload_override_defaults`](Self::reload_override_defaults) instead
532    ///   - This method does not emit change events
533    pub fn reload(&self) -> crate::Result<()> {
534        self.store.lock().unwrap().load()
535    }
536
537    /// Load the store from the on-disk state, ignoring defaults
538    ///
539    /// Note: This method does not emit change events
540    pub fn reload_ignore_defaults(&self) -> crate::Result<()> {
541        self.store.lock().unwrap().load_ignore_defaults()
542    }
543
544    /// Saves the store to disk at the store's `path`.
545    pub fn save(&self) -> crate::Result<()> {
546        if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
547            let _ = sender.send(AutoSaveMessage::Cancel);
548        }
549        self.store.lock().unwrap().save()
550    }
551
552    /// Removes the store from the resource table
553    pub fn close_resource(&self) {
554        let store = self.store.lock().unwrap();
555        let app = store.app.clone();
556        let state = app.state::<StoreState>();
557        let stores = state.stores.lock().unwrap();
558        if let Some(rid) = stores.get(&store.path).copied() {
559            drop(store);
560            drop(stores);
561            let _ = app.resources_table().close(rid);
562        }
563    }
564
565    fn trigger_auto_save(&self) -> crate::Result<()> {
566        let Some(auto_save_delay) = self.auto_save else {
567            return Ok(());
568        };
569        if auto_save_delay.is_zero() {
570            return self.save();
571        }
572        let mut auto_save_debounce_sender = self.auto_save_debounce_sender.lock().unwrap();
573        if let Some(ref sender) = *auto_save_debounce_sender {
574            let _ = sender.send(AutoSaveMessage::Reset);
575            return Ok(());
576        }
577        let (sender, mut receiver) = unbounded_channel();
578        auto_save_debounce_sender.replace(sender);
579        drop(auto_save_debounce_sender);
580        let store = self.store.clone();
581        let auto_save_debounce_sender = self.auto_save_debounce_sender.clone();
582        tauri::async_runtime::spawn(async move {
583            loop {
584                select! {
585                    should_cancel = receiver.recv() => {
586                        if matches!(should_cancel, Some(AutoSaveMessage::Cancel) | None) {
587                            return;
588                        }
589                    }
590                    _ = sleep(auto_save_delay) => {
591                        auto_save_debounce_sender.lock().unwrap().take();
592                        let _ = store.lock().unwrap().save();
593                        return;
594                    }
595                };
596            }
597        });
598        Ok(())
599    }
600
601    fn apply_pending_auto_save(&self) {
602        // Cancel and save if auto save is pending
603        if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
604            let _ = sender.send(AutoSaveMessage::Cancel);
605            let _ = self.save();
606        };
607    }
608}
609
610impl<R: Runtime> Drop for Store<R> {
611    fn drop(&mut self) {
612        self.apply_pending_auto_save();
613    }
614}