use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
io::Result,
path::{Path, PathBuf},
sync::Arc,
};
mod file_store;
mod json_store;
mod memory_store;
use file_store::FileStore;
use json_store::JsonStore;
use memory_store::MemoryStore;
pub use file_store::Config;
#[derive(Debug, Clone)]
pub struct Store(StoreType);
#[derive(Debug, Clone)]
enum StoreType {
File(Arc<RwLock<FileStore>>, PathBuf),
Memory(MemoryStore),
}
pub const IN_MEMORY: &str = "::memory::";
impl Store {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::new_with_cfg(path, Config::default())
}
pub fn new_with_cfg<P: AsRef<Path>>(path: P, cfg: Config) -> Result<Self> {
if path.as_ref() == Path::new(IN_MEMORY) {
Ok(Self(StoreType::Memory(MemoryStore::default())))
} else {
let s = FileStore::new_with_cfg(path, cfg)?;
let p = s.path().to_path_buf();
Ok(Self(StoreType::File(Arc::new(RwLock::new(s)), p)))
}
}
#[must_use]
pub fn path(&self) -> &Path {
match &self.0 {
StoreType::File(_, p) => p,
StoreType::Memory(_) => Path::new(IN_MEMORY),
}
}
pub fn save<T>(&self, obj: &T) -> Result<String>
where
for<'de> T: Serialize + Deserialize<'de>,
{
match &self.0 {
StoreType::File(f, _) => f.write().save(obj),
StoreType::Memory(m) => m.save(obj),
}
}
pub fn save_with_id<T>(&self, obj: &T, id: &str) -> Result<String>
where
for<'de> T: Serialize + Deserialize<'de>,
{
match &self.0 {
StoreType::File(f, _) => f.write().save_with_id(obj, id),
StoreType::Memory(m) => m.save_with_id(obj, id),
}
}
pub fn get<T>(&self, id: &str) -> Result<T>
where
for<'de> T: Deserialize<'de>,
{
match &self.0 {
StoreType::File(f, _) => f.read().get(id),
StoreType::Memory(m) => m.get(id),
}
}
pub fn all<T>(&self) -> Result<BTreeMap<String, T>>
where
for<'de> T: Deserialize<'de>,
{
match &self.0 {
StoreType::File(f, _) => f.read().all(),
StoreType::Memory(m) => m.all(),
}
}
pub fn delete(&self, id: &str) -> Result<()> {
match &self.0 {
StoreType::File(f, _) => f.write().delete(id),
StoreType::Memory(m) => m.delete(id),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_derive::{Deserialize, Serialize};
use std::thread;
use tempfile::tempdir;
#[derive(Serialize, Deserialize)]
struct Data {
x: i32,
}
fn multi_threaded_write(store: Store) {
let mut threads: Vec<thread::JoinHandle<()>> = vec![];
for i in 0..20 {
let db = store.clone();
let x = Data { x: i };
threads.push(thread::spawn(move || {
db.save_with_id(&x, &i.to_string()).unwrap();
}));
}
for t in threads {
t.join().unwrap();
}
let all = store.all::<Data>().unwrap();
assert_eq!(all.len(), 20);
for (id, data) in all {
assert_eq!(data.x.to_string(), id);
}
}
#[test]
fn multi_threaded_write_with_single_file() {
let dir = tempdir().expect("Could not create temporary directory");
let file = dir.path().join("db.json");
let mut cfg = Config::default();
cfg.single = true;
let store = Store::new_with_cfg(file, cfg).unwrap();
multi_threaded_write(store);
}
#[test]
fn multi_threaded_write_with_dir() {
#[derive(Serialize, Deserialize)]
struct Data {
x: i32,
}
let dir = tempdir().expect("Could not create temporary directory");
let mut cfg = Config::default();
cfg.single = false;
let store = Store::new_with_cfg(dir.path(), cfg).unwrap();
multi_threaded_write(store);
}
#[test]
fn multi_threaded_write_in_memory() {
#[derive(Serialize, Deserialize)]
struct Data {
x: i32,
}
let store = Store::new(IN_MEMORY).unwrap();
multi_threaded_write(store);
}
}