// Copyright 2021 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use crate::Error; use serde_json::Value as JsonValue; use std::{ collections::HashMap, fs::{create_dir_all, read, File}, io::Write, path::PathBuf, }; use tauri::{AppHandle, Manager, Runtime}; type SerializeFn = fn(&HashMap) -> Result, Box>; type DeserializeFn = fn(&[u8]) -> Result, Box>; fn default_serialize( cache: &HashMap, ) -> Result, Box> { Ok(serde_json::to_vec(&cache)?) } fn default_deserialize( bytes: &[u8], ) -> Result, Box> { serde_json::from_slice(bytes).map_err(Into::into) } /// Builds a [`Store`] pub struct StoreBuilder { path: PathBuf, defaults: Option>, cache: HashMap, serialize: SerializeFn, deserialize: DeserializeFn, } impl StoreBuilder { /// Creates a new [`StoreBuilder`]. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// /// let builder = StoreBuilder::new("store.bin".parse()?); /// /// # Ok(()) /// # } /// ``` pub fn new(path: PathBuf) -> Self { Self { path, defaults: None, cache: Default::default(), serialize: default_serialize, deserialize: default_deserialize, } } /// Inserts a default key-value pair. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// use std::collections::HashMap; /// /// let mut defaults = HashMap::new(); /// /// defaults.insert("foo".to_string(), "bar".into()); /// /// let builder = StoreBuilder::new("store.bin".parse()?) /// .defaults(defaults); /// /// # Ok(()) /// # } pub fn defaults(mut self, defaults: HashMap) -> Self { self.cache = defaults.clone(); self.defaults = Some(defaults); self } /// Inserts multiple key-value pairs. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// /// let builder = StoreBuilder::new("store.bin".parse()?) /// .default("foo".to_string(), "bar".into()); /// /// # Ok(()) /// # } pub fn default(mut self, key: String, value: JsonValue) -> Self { self.cache.insert(key.clone(), value.clone()); self.defaults .get_or_insert(HashMap::new()) .insert(key, value); self } /// Defines a custom serialization function. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// /// let builder = StoreBuilder::new("store.json".parse()?) /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)); /// /// # Ok(()) /// # } pub fn serialize(mut self, serialize: SerializeFn) -> Self { self.serialize = serialize; self } /// Defines a custom deserialization function /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// /// let builder = StoreBuilder::new("store.json".parse()?) /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)); /// /// # Ok(()) /// # } pub fn deserialize(mut self, deserialize: DeserializeFn) -> Self { self.deserialize = deserialize; self } /// Builds the [`Store`]. /// /// # Examples /// ``` /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// /// let store = StoreBuilder::new("store.bin".parse()?).build(); /// /// # Ok(()) /// # } pub fn build(self) -> Store { Store { path: self.path, defaults: self.defaults, cache: self.cache, serialize: self.serialize, deserialize: self.deserialize, } } } #[derive(Clone)] pub struct Store { pub(crate) path: PathBuf, pub(crate) defaults: Option>, pub(crate) cache: HashMap, serialize: SerializeFn, deserialize: DeserializeFn, } impl Store { /// Update the store from the on-disk state pub fn load(&mut self, app: &AppHandle) -> Result<(), Error> { let app_dir = app .path() .app_data_dir() .expect("failed to resolve app dir"); let store_path = app_dir.join(&self.path); let bytes = read(store_path)?; self.cache = (self.deserialize)(&bytes).map_err(Error::Deserialize)?; Ok(()) } /// Saves the store to disk pub fn save(&self, app: &AppHandle) -> Result<(), Error> { let app_dir = app .path() .app_data_dir() .expect("failed to resolve app dir"); let store_path = app_dir.join(&self.path); create_dir_all(store_path.parent().expect("invalid store path"))?; let bytes = (self.serialize)(&self.cache).map_err(Error::Serialize)?; let mut f = File::create(&store_path)?; f.write_all(&bytes)?; Ok(()) } } impl std::fmt::Debug for Store { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Store") .field("path", &self.path) .field("defaults", &self.defaults) .field("cache", &self.cache) .finish() } }