123use anyhow::{Context, Result};4use serde::{de::DeserializeOwned, Serialize};5use std::{6 cell::Cell,7 collections::HashSet,8 io::Write,9 ops::{Deref, DerefMut},10 path::Path,11 path::PathBuf,12 sync::{Arc, Mutex},13};1415struct DbInternal {16 root: PathBuf,17 locked_paths: HashSet<PathBuf>,18}1920pub trait DbData: DeserializeOwned + Serialize + Default {21 const DB_NAME: &'static str;2223 fn open(db: &Db) -> Result<DbFile<Self>> {24 db.db::<Self>()25 }26}2728#[derive(Clone)]29pub struct Db(Arc<Mutex<DbInternal>>);30impl Db {31 pub fn new(root: impl AsRef<Path>) -> Result<Self> {32 let root: &Path = root.as_ref();33 std::fs::create_dir_all(&root).context("db root")?;34 Ok(Db(Arc::new(Mutex::new(DbInternal {35 root: root.to_owned(),36 locked_paths: HashSet::new(),37 }))))38 }3940 pub fn db<T: DbData>(&self) -> Result<DbFile<T>> {41 let name = T::DB_NAME;42 assert!(!name.contains("/") && !name.contains("\\"));43 let mut db = self.0.lock().unwrap();44 let mut data_path = db.root.clone();45 data_path.push(format!("{}.toml", name));4647 if !db.locked_paths.insert(data_path.clone()) {48 anyhow::bail!("file is already open");49 }5051 let data = if data_path.exists() {52 let raw_data = std::fs::read(&data_path).context("reading file")?;53 toml::from_slice(&raw_data).context("parsing file")?54 } else {55 T::default()56 };5758 Ok(DbFile {59 db: self.clone(),60 root: db.root.clone(),61 path: data_path,62 data,63 dirty: Cell::new(false),64 })65 }66}6768pub struct DbFile<T: DbData> {69 db: Db,70 root: PathBuf,71 path: PathBuf,72 data: T,73 dirty: Cell<bool>,74}7576impl<T: DbData> Deref for DbFile<T> {77 type Target = T;7879 fn deref(&self) -> &Self::Target {80 &self.data81 }82}8384impl<T: DbData> DerefMut for DbFile<T> {85 fn deref_mut(&mut self) -> &mut Self::Target {86 self.dirty.set(true);87 &mut self.data88 }89}9091impl<T: DbData> DbFile<T> {92 pub fn write(&self) -> Result<()> {93 if !self.dirty.get() {94 return Ok(());95 }96 let mut temp = tempfile::Builder::new()97 .prefix("~")98 .suffix(".toml")99 .tempfile_in(&self.root)?;100 let mut out = String::new();101 let mut serializer = toml::Serializer::new(&mut out);102 serializer.pretty_array(true).pretty_string(true);103 self.data.serialize(&mut serializer)?;104 temp.write_all(&out.as_bytes())?;105 temp.persist(&self.path)?;106 self.dirty.set(false);107 Ok(())108 }109}110111impl<T: DbData> Drop for DbFile<T> {112 fn drop(&mut self) {113 let mut db = self.db.0.lock().unwrap();114 self.write().unwrap();115 db.locked_paths.remove(&self.path);116 }117}