diff --git a/examples/config.rs b/examples/config.rs index a0fe5a5..511154f 100644 --- a/examples/config.rs +++ b/examples/config.rs @@ -23,7 +23,7 @@ type DB = FileDatabase; lazy_static! { static ref CONFIG: DB = { - let db = FileDatabase::from_path("/tmp/config.ron", Config::default()) + let db = FileDatabase::load_from_path_or_default("/tmp/config.ron") .expect("Create database from path"); db.load().expect("Config to load"); db diff --git a/examples/full.rs b/examples/full.rs index 020452e..320b2f7 100644 --- a/examples/full.rs +++ b/examples/full.rs @@ -20,7 +20,7 @@ struct Person { fn do_main() -> Result<(), failure::Error> { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new())?; + let db = FileDatabase::, Ron>::load_from_path_or_default("test.ron")?; println!("Writing to Database"); db.write(|db| { diff --git a/examples/switching.rs b/examples/switching.rs index 1dba2f2..db02f8c 100644 --- a/examples/switching.rs +++ b/examples/switching.rs @@ -20,7 +20,7 @@ struct Person { fn do_main() -> Result<(), failure::Error> { use std::collections::HashMap; - let db = FileDatabase::, Ron>::from_path("test.ron", HashMap::new())?; + let db = FileDatabase::, Ron>::load_from_path_or_default("test.ron")?; println!("Writing to Database"); db.write(|db| { diff --git a/src/backend/mod.rs b/src/backend/mod.rs index fd13c1a..889c1b1 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -12,7 +12,7 @@ use failure::ResultExt; -use crate::error; +use crate::error::{self, RustbreakErrorKind as ErrorKind}; /// The Backend Trait. /// @@ -59,7 +59,7 @@ pub use mmap::MmapStorage; mod path; pub use path::PathBackend; -/// A backend using a file +/// A backend using a file. #[derive(Debug)] pub struct FileBackend(std::fs::File); @@ -70,10 +70,10 @@ impl Backend for FileBackend { let mut buffer = vec![]; self.0 .seek(SeekFrom::Start(0)) - .context(error::RustbreakErrorKind::Backend)?; + .context(ErrorKind::Backend)?; self.0 .read_to_end(&mut buffer) - .context(error::RustbreakErrorKind::Backend)?; + .context(ErrorKind::Backend)?; Ok(buffer) } @@ -82,16 +82,10 @@ impl Backend for FileBackend { self.0 .seek(SeekFrom::Start(0)) - .context(error::RustbreakErrorKind::Backend)?; - self.0 - .set_len(0) - .context(error::RustbreakErrorKind::Backend)?; - self.0 - .write_all(data) - .context(error::RustbreakErrorKind::Backend)?; - self.0 - .sync_all() - .context(error::RustbreakErrorKind::Backend)?; + .context(ErrorKind::Backend)?; + self.0.set_len(0).context(ErrorKind::Backend)?; + self.0.write_all(data).context(ErrorKind::Backend)?; + self.0.sync_all().context(ErrorKind::Backend)?; Ok(()) } } @@ -108,7 +102,7 @@ impl FileBackend { .write(true) .create(true) .open(path) - .context(error::RustbreakErrorKind::Backend)?, + .context(ErrorKind::Backend)?, )) } @@ -125,6 +119,58 @@ impl FileBackend { } } +impl FileBackend { + /// Opens a new [`FileBackend`] for a given path. + /// Errors when the file doesn't yet exist. + pub fn from_path_or_fail>(path: P) -> error::Result { + use std::fs::OpenOptions; + + Ok(Self( + OpenOptions::new() + .read(true) + .write(true) + .open(path) + .context(ErrorKind::Backend)?, + )) + } + + /// Opens a new [`FileBackend`] for a given path. + /// Creates a file if it doesn't yet exist. + /// + /// Returns the [`FileBackend`] and whether the file already existed. + pub fn from_path_or_create>(path: P) -> error::Result<(Self, bool)> { + use std::fs::OpenOptions; + + let exists = path.as_ref().is_file(); + Ok(( + Self( + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path) + .context(ErrorKind::Backend)?, + ), + exists, + )) + } + + /// Opens a new [`FileBackend`] for a given path. + /// Creates a file if it doesn't yet exist, and calls `closure` with it. + pub fn from_path_or_create_and(path: P, closure: C) -> error::Result + where + C: FnOnce(&mut std::fs::File), + P: AsRef, + { + Self::from_path_or_create(path).map(|(mut b, exists)| { + if exists { + closure(&mut b.0) + } + b + }) + } +} + /// An in memory backend. /// /// It is backed by a byte vector (`Vec`). diff --git a/src/backend/path.rs b/src/backend/path.rs index 2ea787f..5fa7296 100644 --- a/src/backend/path.rs +++ b/src/backend/path.rs @@ -24,12 +24,44 @@ pub struct PathBackend { impl PathBackend { /// Opens a new [`PathBackend`] for a given path. - pub fn open(path: PathBuf) -> error::Result { + /// Errors when the file doesn't yet exist. + pub fn from_path_or_fail(path: PathBuf) -> error::Result { + OpenOptions::new() + .open(path.as_path()) + .context(ErrorKind::Backend)?; + Ok(Self { path }) + } + + /// Opens a new [`PathBackend`] for a given path. + /// Creates a file if it doesn't yet exist. + /// + /// Returns the [`PathBackend`] and whether the file already existed. + pub fn from_path_or_create(path: PathBuf) -> error::Result<(Self, bool)> { + let exists = path.as_path().is_file(); OpenOptions::new() .write(true) .create(true) .open(path.as_path()) .context(ErrorKind::Backend)?; + Ok((Self { path }, exists)) + } + + /// Opens a new [`PathBackend`] for a given path. + /// Creates a file if it doesn't yet exist, and calls `closure` with it. + pub fn from_path_or_create_and(path: PathBuf, closure: C) -> error::Result + where + C: FnOnce(&mut std::fs::File), + { + let exists = path.as_path().is_file(); + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path.as_path()) + .context(ErrorKind::Backend)?; + if !exists { + closure(&mut file) + } Ok(Self { path }) } } @@ -75,8 +107,9 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_path_backend_existing() { let file = NamedTempFile::new().expect("could not create temporary file"); - let mut backend = - PathBackend::open(file.path().to_owned()).expect("could not create backend"); + let (mut backend, existed) = PathBackend::from_path_or_create(file.path().to_owned()) + .expect("could not create backend"); + assert!(existed); let data = [4, 5, 1, 6, 8, 1]; backend.put_data(&data).expect("could not put data"); @@ -89,7 +122,9 @@ mod tests { let dir = tempfile::tempdir().expect("could not create temporary directory"); let mut file_path = dir.path().to_owned(); file_path.push("rustbreak_path_db.db"); - let mut backend = PathBackend::open(file_path).expect("could not create backend"); + let (mut backend, existed) = + PathBackend::from_path_or_create(file_path).expect("could not create backend"); + assert!(!existed); let data = [4, 5, 1, 6, 8, 1]; backend.put_data(&data).expect("could not put data"); diff --git a/src/lib.rs b/src/lib.rs index 4ea0354..ccaa9c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,6 +210,7 @@ pub use crate::error::RustbreakError; use std::fmt::Debug; use std::ops::Deref; +use std::path::PathBuf; use std::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; use failure::ResultExt; @@ -220,6 +221,8 @@ use serde::Serialize; use crate::backend::MmapStorage; use crate::backend::{Backend, FileBackend, MemoryBackend, PathBackend}; +use crate::error::RustbreakErrorKind as ErrorKind; + /// The Central Database to Rustbreak. /// /// It has 3 Type Generics: @@ -302,10 +305,7 @@ where where T: FnOnce(&mut Data) -> R, { - let mut lock = self - .data - .write() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut lock = self.data.write().map_err(|_| ErrorKind::Poison)?; Ok(task(&mut lock)) } @@ -388,15 +388,12 @@ where where T: FnOnce(&mut Data) + std::panic::UnwindSafe, { - let mut lock = self - .data - .write() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut lock = self.data.write().map_err(|_| ErrorKind::Poison)?; let mut data = lock.clone(); std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { task(&mut data); })) - .map_err(|_| error::RustbreakErrorKind::WritePanic)?; + .map_err(|_| ErrorKind::WritePanic)?; *lock = data; Ok(()) } @@ -422,10 +419,7 @@ where where T: FnOnce(&Data) -> R, { - let mut lock = self - .data - .read() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut lock = self.data.read().map_err(|_| ErrorKind::Poison)?; Ok(task(&mut lock)) } @@ -467,9 +461,7 @@ where /// # } /// ``` pub fn borrow_data<'a>(&'a self) -> error::Result> { - self.data - .read() - .map_err(|_| error::RustbreakErrorKind::Poison.into()) + self.data.read().map_err(|_| ErrorKind::Poison.into()) } /// Write lock the database and get access to the underlying struct. @@ -522,34 +514,26 @@ where /// # } /// ``` pub fn borrow_data_mut<'a>(&'a self) -> error::Result> { - self.data - .write() - .map_err(|_| error::RustbreakErrorKind::Poison.into()) + self.data.write().map_err(|_| ErrorKind::Poison.into()) + } + + /// Load data from backend and return this data. + fn load_from_backend(backend: &mut Back, deser: &DeSer) -> error::Result { + let new_data = deser + .deserialize(&backend.get_data().context(ErrorKind::Backend)?[..]) + .context(ErrorKind::Deserialization)?; + + Ok(new_data) } /// Like [`Self::load`] but returns the write lock to data it used. fn load_get_data_lock(&self) -> error::Result> { - let mut backend_lock = self - .backend - .lock() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut backend_lock = self.backend.lock().map_err(|_| ErrorKind::Poison)?; - //let fresh_data = Self::inner_load(&mut backend_lock, - // &self.deser).context(error::RustbreakErrorKind::Backend)?; - let fresh_data = self - .deser - .deserialize( - &backend_lock - .get_data() - .context(error::RustbreakErrorKind::Backend)?[..], - ) - .context(error::RustbreakErrorKind::Deserialization)?; + let fresh_data = Self::load_from_backend(&mut backend_lock, &self.deser)?; drop(backend_lock); - let mut data_write_lock = self - .data - .write() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut data_write_lock = self.data.write().map_err(|_| ErrorKind::Poison)?; *data_write_lock = fresh_data; Ok(data_write_lock) } @@ -560,31 +544,21 @@ where } /// Like [`Self::save`] but with explicit read (or write) lock to data. - fn save_data_locked>(&self, lock: L) -> error::Result<()> -//where L::Target = Data - { + fn save_data_locked>(&self, lock: L) -> error::Result<()> { let ser = self .deser .serialize(lock.deref()) - .context(error::RustbreakErrorKind::Serialization)?; + .context(ErrorKind::Serialization)?; drop(lock); - let mut backend = self - .backend - .lock() - .map_err(|_| error::RustbreakErrorKind::Poison)?; - backend - .put_data(&ser) - .context(error::RustbreakErrorKind::Backend)?; + let mut backend = self.backend.lock().map_err(|_| ErrorKind::Poison)?; + backend.put_data(&ser).context(ErrorKind::Backend)?; Ok(()) } /// Flush the data structure to the backend. pub fn save(&self) -> error::Result<()> { - let data = self - .data - .read() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let data = self.data.read().map_err(|_| ErrorKind::Poison)?; self.save_data_locked(data) } @@ -596,9 +570,7 @@ where let data = if load { self.load_get_data_lock()? } else { - self.data - .write() - .map_err(|_| error::RustbreakErrorKind::Poison)? + self.data.write().map_err(|_| ErrorKind::Poison)? }; Ok(data.clone()) } @@ -607,10 +579,7 @@ where /// /// To save the data afterwards, call with `save` true. pub fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> { - let mut data = self - .data - .write() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let mut data = self.data.write().map_err(|_| ErrorKind::Poison)?; *data = new_data; if save { self.save_data_locked(data) @@ -631,12 +600,8 @@ where /// Break a database into its individual parts. pub fn into_inner(self) -> error::Result<(Data, Back, DeSer)> { Ok(( - self.data - .into_inner() - .map_err(|_| error::RustbreakErrorKind::Poison)?, - self.backend - .into_inner() - .map_err(|_| error::RustbreakErrorKind::Poison)?, + self.data.into_inner().map_err(|_| ErrorKind::Poison)?, + self.backend.into_inner().map_err(|_| ErrorKind::Poison)?, self.deser, )) } @@ -685,10 +650,7 @@ where /// # } /// ``` pub fn try_clone(&self) -> error::Result> { - let lock = self - .data - .read() - .map_err(|_| error::RustbreakErrorKind::Poison)?; + let lock = self.data.read().map_err(|_| ErrorKind::Poison)?; Ok(Database { data: RwLock::new(lock.clone()), @@ -706,18 +668,107 @@ where Data: Serialize + DeserializeOwned + Clone + Send, DeSer: DeSerializer + Send + Sync + Clone, { - /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path). - pub fn from_path(path: S, data: Data) -> error::Result + /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. + pub fn load_from_path(path: S) -> error::Result where S: AsRef, { - let backend = FileBackend::open(path).context(error::RustbreakErrorKind::Backend)?; + let mut backend = FileBackend::from_path_or_fail(path)?; + let deser = DeSer::default(); + let data = Self::load_from_backend(&mut backend, &deser)?; - Ok(Self { + let db = Self { data: RwLock::new(data), backend: Mutex::new(backend), - deser: DeSer::default(), - }) + deser, + }; + Ok(db) + } + + /// Load [`FileDatabase`] at `path` or initialise with `data`. + /// + /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. If the file does not exist, initialise with + /// `data`. + pub fn load_from_path_or(path: S, data: Data) -> error::Result + where + S: AsRef, + { + let (mut backend, exists) = FileBackend::from_path_or_create(path)?; + let deser = DeSer::default(); + if !exists { + let ser = deser.serialize(&data).context(ErrorKind::Serialization)?; + backend.put_data(&ser)?; + } + + let db = Self { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser, + }; + + if exists { + db.load()?; + } + + Ok(db) + } + + /// Load [`FileDatabase`] at `path` or initialise with `closure`. + /// + /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. If the file does not exist, `closure` is + /// called and the database is initialised with it's return value. + pub fn load_from_path_or_else(path: S, closure: C) -> error::Result + where + S: AsRef, + C: FnOnce() -> Data, + { + let (mut backend, exists) = FileBackend::from_path_or_create(path)?; + let deser = DeSer::default(); + let data = if exists { + Self::load_from_backend(&mut backend, &deser)? + } else { + let data = closure(); + + let ser = deser.serialize(&data).context(ErrorKind::Serialization)?; + backend.put_data(&ser)?; + + data + }; + + let db = Self { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser, + }; + Ok(db) + } + + /// Create [`FileDatabase`] at `path`. Initialise with `data` if the file + /// doesn't exist. + /// + /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path). + /// Contents are not loaded. If the file does not exist, it is + /// initialised with `data`. Frontend is always initialised with `data`. + pub fn create_at_path(path: S, data: Data) -> error::Result + where + S: AsRef, + { + let (mut backend, exists) = FileBackend::from_path_or_create(path)?; + let deser = DeSer::default(); + if !exists { + let ser = deser.serialize(&data).context(ErrorKind::Serialization)?; + backend.put_data(&ser)?; + } + + let db = Self { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser, + }; + Ok(db) } /// Create new [`FileDatabase`] from a file. @@ -732,6 +783,24 @@ where } } +impl Database +where + Data: Serialize + DeserializeOwned + Clone + Send + Default, + DeSer: DeSerializer + Send + Sync + Clone, +{ + /// Load [`FileDatabase`] at `path` or initialise with `Data::default()`. + /// + /// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. If the file does not exist, initialise with + /// `Data::default`. + pub fn load_from_path_or_default(path: S) -> error::Result + where + S: AsRef, + { + Self::load_from_path_or(path, Data::default()) + } +} + /// A database backed by a file, using atomic saves. pub type PathDatabase = Database; @@ -740,21 +809,112 @@ where Data: Serialize + DeserializeOwned + Clone + Send, DeSer: DeSerializer + Send + Sync + Clone, { - /// Create new [`PathDatabase`] from a [`Path`](std::path::Path). - pub fn from_path(path: S, data: Data) -> error::Result - where - S: ToOwned, - std::path::PathBuf: std::borrow::Borrow, - { - #[allow(clippy::redundant_clone)] // false positive - let backend = - PathBackend::open(path.to_owned()).context(error::RustbreakErrorKind::Backend)?; + /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. + pub fn load_from_path(path: PathBuf) -> error::Result { + let mut backend = PathBackend::from_path_or_fail(path)?; + let deser = DeSer::default(); + let data = Self::load_from_backend(&mut backend, &deser)?; - Ok(Self { + let db = Self { data: RwLock::new(data), backend: Mutex::new(backend), - deser: DeSer::default(), - }) + deser, + }; + Ok(db) + } + + /// Load [`PathDatabase`] at `path` or initialise with `data`. + /// + /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. If the file does not exist, initialise with + /// `data`. + pub fn load_from_path_or(path: PathBuf, data: Data) -> error::Result { + let (mut backend, exists) = PathBackend::from_path_or_create(path)?; + let deser = DeSer::default(); + if !exists { + let ser = deser.serialize(&data).context(ErrorKind::Serialization)?; + backend.put_data(&ser)?; + } + + let db = Self { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser, + }; + + if exists { + db.load()?; + } + + Ok(db) + } + + /// Load [`PathDatabase`] at `path` or initialise with `closure`. + /// + /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. If the file does not exist, `closure` is + /// called and the database is initialised with it's return value. + pub fn load_from_path_or_else(path: PathBuf, closure: C) -> error::Result + where + C: FnOnce() -> Data, + { + let (mut backend, exists) = PathBackend::from_path_or_create(path)?; + let deser = DeSer::default(); + let data = if exists { + Self::load_from_backend(&mut backend, &deser)? + } else { + let data = closure(); + + let ser = deser.serialize(&data).context(ErrorKind::Serialization)?; + backend.put_data(&ser)?; + + data + }; + + let db = Self { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser, + }; + Ok(db) + } + + /// Create [`PathDatabase`] at `path`. Initialise with `data` if the file + /// doesn't exist. + /// + /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path). + /// Contents are not loaded. If the file does not exist, it is + /// initialised with `data`. Frontend is always initialised with `data`. + pub fn create_at_path(path: PathBuf, data: Data) -> error::Result { + let (mut backend, exists) = PathBackend::from_path_or_create(path)?; + let deser = DeSer::default(); + if !exists { + let ser = deser.serialize(&data).context(ErrorKind::Serialization)?; + backend.put_data(&ser)?; + } + + let db = Self { + data: RwLock::new(data), + backend: Mutex::new(backend), + deser, + }; + Ok(db) + } +} + +impl Database +where + Data: Serialize + DeserializeOwned + Clone + Send + Default, + DeSer: DeSerializer + Send + Sync + Clone, +{ + /// Load [`PathDatabase`] at `path` or initialise with `Data::default()`. + /// + /// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path), + /// and load the contents. If the file does not exist, initialise with + /// `Data::default`. + pub fn load_from_path_or_default(path: PathBuf) -> error::Result { + Self::load_from_path_or(path, Data::default()) } } @@ -982,7 +1142,7 @@ mod tests { panic!("Panic should be catched") }) .expect_err("Did not error on panic in safe write!"); - assert_eq!(crate::error::RustbreakErrorKind::WritePanic, err.kind()); + assert_eq!(ErrorKind::WritePanic, err.kind()); assert_eq!( "Hello World", diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 979aca9..a092c22 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -108,7 +108,7 @@ fn create_filedb + Debug>() -> FileDatabase { fn create_filedb_from_path + Debug>() -> FileDatabase { let file = tempfile::NamedTempFile::new().expect("could not create temporary file"); - FileDatabase::from_path(file.path(), Data::default()).expect("could not create database") + FileDatabase::create_at_path(file.path(), Data::default()).expect("could not create database") } fn create_memdb + Debug>() -> MemoryDatabase { @@ -125,7 +125,7 @@ fn create_mmapdb_with_size + Debug>(size: usize) -> MmapDa fn create_pathdb + Debug>() -> PathDatabase { let file = tempfile::NamedTempFile::new().expect("could not create temporary file"); - PathDatabase::from_path(file.path().to_owned(), Data::default()) + PathDatabase::create_at_path(file.path().to_owned(), Data::default()) .expect("could not create database") }