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