use std::borrow::Cow; use serde::{Serialize, Deserialize}; use serde::de::DeserializeOwned; use std::path::{Path, PathBuf}; use std::fs::File; use std::io::{self, Read, Write}; use serde_json::{Map, Value, Error as SerdeError}; use std::fmt::{self, Display, Formatter}; /// Polymorphic value store, optionally backed by a file /// /// Anything serializable and unserializable can be put into the store. /// /// Please be mindful of the fact that values are serialized and unserialized to/from serde_json::Value /// when they move between the store and the outside world. This incurs some performance penalty, /// but makes it possible to store any Serializable type at all. #[derive(Debug, Serialize, Deserialize)] pub struct Store { path: Option, autosave: bool, items: Map, } impl Default for Store { fn default() -> Self { Self { path: None, autosave: false, items: Map::new() } } } /// Errors occuring in the store's methods #[derive(Debug)] pub enum StoreError { IOError(io::Error), SerdeError(SerdeError), Other(Cow<'static, str>), } pub type Result = std::result::Result; impl From for StoreError { fn from(e: io::Error) -> Self { Self::IOError(e) } } impl From for StoreError { fn from(e: SerdeError) -> Self { Self::SerdeError(e) } } impl Display for StoreError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { StoreError::IOError(e) => { write!(f, "IOError: {}", e) } StoreError::SerdeError(e) => { write!(f, "SerdeError: {}", e) } StoreError::Other(msg) => { write!(f, "{}", msg) } } } } impl Store { /// Create a new instance without initial load or a file path assigned. /// Path can always be set using `set_path()` pub fn new() -> Self { Default::default() } /// Create a new instance of the store. /// If a path is given, it will try to load the content from a file. pub fn from_file>(path: P) -> Self { let mut store = Store { path: Some(path.as_ref().into()), autosave: false, items: Map::new(), }; let _ = store.load().map_err(|e| { error!("Error loading store: {}", e); }); store } /// Set auto-save option - save on each mutation. pub fn set_autosave(&mut self, autosave: bool) { self.autosave = autosave; } /// Assign file path for save and load pub fn set_path>(&mut self, path: P) { self.path = Some(path.as_ref().into()); } /// Delete the assigned file path pub fn unset_path(&mut self) { self.path = None; } /// Load from a user-specified file (this ignores the path set with `set_path()` /// or `from_file()` pub fn load_from>(&mut self, path: P) -> Result<()> { self.items = Self::load_map_from(path)?; Ok(()) } /// Load a map from a file - returns the raw inner map. fn load_map_from>(path: P) -> Result> { let mut file = File::open(path)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; if let serde_json::Value::Object(v) = serde_json::from_str(&buf)? { Ok(v) } else { Err(StoreError::Other("Invalid data file format".into())) } } /// Load the store's content from the file selected using `set_path()` or `from_file()` pub fn load(&mut self) -> Result<()> { match &self.path { Some(path) => { self.items = Self::load_map_from(path)?; Ok(()) } None => { Err(StoreError::Other("No path set".into())) } } } /// Save the map to a custom file path. pub fn save_to>(&self, path: P) -> Result<()> { let as_str = serde_json::to_string(&self.items).unwrap(); let mut file = File::create(path)?; file.write(as_str.as_bytes())?; Ok(()) } /// Save the map to a file selected using `set_path()` or `from_file()`. /// /// Saving can be automated using `set_autosave()`. pub fn save(&self) -> Result<()> { self.save_to(self.path.as_ref().unwrap()) } /// Get an item using a string key. /// If no item was stored with this key, then None is returned. pub fn get(&self, key: &str) -> Option { self.items.get(key) .map(ToOwned::to_owned) .map(serde_json::from_value) .transpose() .unwrap_or(None) } /// Get an item, or a default value. pub fn get_or(&self, key: &str, def : T) -> T { self.get(key).unwrap_or(def) } /// Get an item, or a default value using the Default trait pub fn get_or_default(&self, key: &str) -> T { self.get(key).unwrap_or_default() } /// Get an item, or a default value computed from a callback. /// /// Use this if it's expensive to create the default value. pub fn get_or_else T>(&self, key: &str, f : O) -> T { self.get(key).unwrap_or_else(f) } /// Get an item using a string key, removing it from the store. /// If no item was stored with this key, then None is returned. pub fn take(&mut self, key: &str) -> Option { let value = self.get(&key); self.items.remove(key); if self.autosave { let _ = self.save(); } value } /// Remove an item matching a key. /// Returns true if any item was removed. /// /// This is similar to take(), but the old item is not unpacked and it's not an error /// if the old item is e.g. malformed. pub fn remove(&mut self, key: &str) -> bool { let old = self.items.remove(key); if self.autosave { let _ = self.save(); } old.is_some() } /// Assign an item to a string key. /// If the key was previously occupied, the old value is dropped. pub fn put(&mut self, key: ID, value: T) where ID: Into, T: Serialize + DeserializeOwned { self.items.insert(key.into(), serde_json::to_value(value).unwrap()); if self.autosave { let _ = self.save(); } } /// Assign an item to a string key. /// If the key was previously occupied, the old value is returned. pub fn replace(&mut self, key: ID, value: T) -> Option where ID: Into, T: Serialize + DeserializeOwned { let rv = self.items .insert(key.into(), serde_json::to_value(value).unwrap()) .map(serde_json::from_value) .transpose() .unwrap_or(None); if self.autosave { let _ = self.save(); } rv } }