acidjson/
lib.rs

1use serde::{de::DeserializeOwned, Serialize};
2use std::{
3    io::Write,
4    sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
5};
6use std::{
7    ops::{Deref, DerefMut},
8    path::{Path, PathBuf},
9    sync::Arc,
10};
11use thiserror::Error;
12
13#[derive(Error, Debug)]
14pub enum AcidJsonError {
15    #[error("I/O error: {0}")]
16    IoError(#[from] std::io::Error),
17    #[error("JSON parsing error: {0}")]
18    JsonError(#[from] serde_json::Error),
19}
20
21/// A "smart pointer" to a JSON file on disk. Can be used in a RwLock-like fashion for thread-safe, ACID-guaranteed updates to the underlying file. Is "Arc-like" can can be cheaply cloned to create more references to the same file.
22#[derive(Clone, Debug)]
23pub struct AcidJson<T: Serialize + DeserializeOwned + Sync> {
24    cached: Arc<RwLock<T>>,
25    fname: PathBuf,
26}
27
28impl<T: Serialize + DeserializeOwned + Sync> AcidJson<T> {
29    /// Opens an AcidJson.
30    pub fn open(fname: &Path) -> Result<Self, AcidJsonError> {
31        let file_contents = std::fs::read(fname)?;
32        let parsed: T = serde_json::from_slice(&file_contents)?;
33        Ok(Self {
34            cached: RwLock::new(parsed).into(),
35            fname: fname.to_owned(),
36        })
37    }
38
39    /// Opens an AcidJson, with a default value if the file does not yet exist.
40    pub fn open_or_else(fname: &Path, gen_def: impl FnOnce() -> T) -> Result<Self, AcidJsonError> {
41        if !fname.exists() {
42            std::fs::write(fname, serde_json::to_vec(&gen_def())?)?;
43        }
44        let file_contents = std::fs::read(fname)?;
45        let parsed: T = serde_json::from_slice(&file_contents)?;
46        Ok(Self {
47            cached: RwLock::new(parsed).into(),
48            fname: fname.to_owned(),
49        })
50    }
51
52    /// Read-locks the AcidJson.
53    pub fn read(&self) -> AcidJsonReadGuard<T> {
54        let inner = self.cached.read().unwrap();
55        AcidJsonReadGuard { inner }
56    }
57
58    /// Write-locks the AcidJson.
59    pub fn write(&self) -> AcidJsonWriteGuard<T> {
60        let inner = self.cached.write().unwrap();
61        let init_serialized = serde_json::to_vec(inner.deref()).expect("cannot serialize");
62        AcidJsonWriteGuard {
63            inner,
64            fname: self.fname.clone(),
65            init_serialized,
66        }
67    }
68}
69
70/// A read guard for an acidjson.
71pub struct AcidJsonReadGuard<'a, T: Serialize + DeserializeOwned + Sync> {
72    inner: RwLockReadGuard<'a, T>,
73}
74
75impl<'a, T: Serialize + DeserializeOwned + Sync> Deref for AcidJsonReadGuard<'a, T> {
76    type Target = T;
77
78    fn deref(&self) -> &Self::Target {
79        &self.inner
80    }
81}
82
83/// A write guard for an acidjson.
84pub struct AcidJsonWriteGuard<'a, T: Serialize + DeserializeOwned + Sync> {
85    inner: RwLockWriteGuard<'a, T>,
86    fname: PathBuf,
87    init_serialized: Vec<u8>,
88}
89
90impl<'a, T: Serialize + DeserializeOwned + Sync> Deref for AcidJsonWriteGuard<'a, T> {
91    type Target = T;
92
93    fn deref(&self) -> &Self::Target {
94        self.inner.deref()
95    }
96}
97
98impl<'a, T: Serialize + DeserializeOwned + Sync> DerefMut for AcidJsonWriteGuard<'a, T> {
99    fn deref_mut(&mut self) -> &mut Self::Target {
100        self.inner.deref_mut()
101    }
102}
103
104impl<'a, T: Serialize + DeserializeOwned + Sync> Drop for AcidJsonWriteGuard<'a, T> {
105    fn drop(&mut self) {
106        let serialized = serde_json::to_vec(self.inner.deref()).expect("cannot serialize");
107        if serialized != self.init_serialized {
108            atomicwrites::AtomicFile::new(
109                &self.fname,
110                atomicwrites::OverwriteBehavior::AllowOverwrite,
111            )
112            .write(|f| f.write_all(&serialized))
113            .expect("could not write acidjson");
114            log::debug!(
115                "wrote {} bytes to {}",
116                serialized.len(),
117                self.fname.as_os_str().to_string_lossy()
118            );
119        } else {
120            log::debug!("not writing because nothing changed")
121        }
122    }
123}