git.delta.rocks / jrsonnet / refs/commits / f89d04014f0f

difftreelog

source

src/db/db.rs2.6 KiBsourcehistory
1//! Small .toml based readable data store23use 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	_lockfile: lockfile::Lockfile,19}2021pub trait DbData: DeserializeOwned + Serialize + Default {22	const DB_NAME: &'static str;2324	fn open(db: &Db) -> Result<DbFile<Self>> {25		db.db::<Self>()26	}27}2829#[derive(Clone)]30pub struct Db(Arc<Mutex<DbInternal>>);31impl Db {32	pub fn new(root: impl AsRef<Path>) -> Result<Self> {33		let root: &Path = root.as_ref();34		std::fs::create_dir_all(&root).context("db root")?;35		let mut lockfile = root.to_owned();36		lockfile.push(".lock");37		let lockfile = lockfile::Lockfile::create(lockfile).context("db lock")?;38		Ok(Db(Arc::new(Mutex::new(DbInternal {39			root: root.to_owned(),40			locked_paths: HashSet::new(),41			_lockfile: lockfile,42		}))))43	}4445	pub fn db<T: DbData>(&self) -> Result<DbFile<T>> {46		let name = T::DB_NAME;47		assert!(!name.contains("/") && !name.contains("\\"));48		let mut db = self.0.lock().unwrap();49		let mut data_path = db.root.clone();50		data_path.push(format!("{}.toml", name));5152		if !db.locked_paths.insert(data_path.clone()) {53			anyhow::bail!("file is already open");54		}5556		let data = if data_path.exists() {57			let raw_data = std::fs::read(&data_path).context("reading file")?;58			toml::from_slice(&raw_data).context("parsing file")?59		} else {60			T::default()61		};6263		Ok(DbFile {64			db: self.clone(),65			root: db.root.clone(),66			path: data_path,67			data,68			dirty: Cell::new(false),69		})70	}71}7273pub struct DbFile<T: DbData> {74	db: Db,75	root: PathBuf,76	path: PathBuf,77	data: T,78	dirty: Cell<bool>,79}8081impl<T: DbData> Deref for DbFile<T> {82	type Target = T;8384	fn deref(&self) -> &Self::Target {85		&self.data86	}87}8889impl<T: DbData> DerefMut for DbFile<T> {90	fn deref_mut(&mut self) -> &mut Self::Target {91		self.dirty.set(true);92		&mut self.data93	}94}9596impl<T: DbData> DbFile<T> {97	pub fn write(&self) -> Result<()> {98		if !self.dirty.get() {99			return Ok(());100		}101		let mut temp = tempfile::Builder::new()102			.prefix("~")103			.suffix(".toml")104			.tempfile_in(&self.root)?;105		let mut out = String::new();106		let mut serializer = toml::Serializer::new(&mut out);107		serializer.pretty_array(true).pretty_string(true);108		self.data.serialize(&mut serializer)?;109		temp.write_all(&out.as_bytes())?;110		temp.persist(&self.path)?;111		self.dirty.set(false);112		Ok(())113	}114}115116impl<T: DbData> Drop for DbFile<T> {117	fn drop(&mut self) {118		let mut db = self.db.0.lock().unwrap();119		self.write().unwrap();120		db.locked_paths.remove(&self.path);121	}122}