Skip to main content

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