#![allow(clippy::unwrap_used)]
use core::fmt::Debug;
use std::{fs::File, io, os::unix::fs::FileExt, path::Path, sync::Arc};
use parking_lot::Mutex;
use serde::{Serialize, de::DeserializeOwned};
use serde_json::{from_reader, to_vec_pretty, to_writer_pretty};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PersistError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
}
type Result<T> = std::result::Result<T, PersistError>;
#[derive(Debug)]
struct Inner<T: DeserializeOwned + Serialize + Default> {
data: Mutex<T>,
file: File,
}
impl<T> Inner<T>
where
T: DeserializeOwned + Serialize + Default,
{
fn open(path: impl AsRef<Path>) -> Self {
let str_path = path.as_ref().to_str().unwrap_or_default().to_string();
let mut file = File::options()
.write(true)
.read(true)
.create(true)
.truncate(false)
.open(path.as_ref())
.expect(&str_path);
if file.metadata().expect(&str_path).len() == 0 {
let data = T::default();
to_writer_pretty(&mut file, &data).expect(&str_path);
let data = Mutex::new(data);
Self { data, file }
} else {
let data = from_reader(&mut file).expect(&str_path);
let data = Mutex::new(data);
Self { data, file }
}
}
fn replace(&self, data: T) -> Result<()> {
let mut inner = self.data.lock();
*inner = data;
self.flush(&inner)?;
Ok(())
}
fn update(&self, f: impl FnOnce(&mut T)) -> Result<()> {
let mut inner = self.data.lock();
f(&mut inner);
self.flush(&inner)?;
Ok(())
}
fn save(&self) -> Result<()> {
let data = self.data.lock();
self.flush(&data)?;
Ok(())
}
fn flush(&self, data: &T) -> Result<()> {
self.file.sync_all()?;
let bytes = to_vec_pretty(data)?;
self.file.set_len(0)?;
self.file.write_all_at(&bytes, 0)?;
self.file.sync_all()?;
Ok(())
}
}
impl<T> Drop for Inner<T>
where
T: DeserializeOwned + Serialize + Default,
{
fn drop(&mut self) {
if let Err(e) = self.save() {
tracing::error!("Failed to save data: {}", e);
}
}
}
#[derive(Debug, Clone)]
pub struct Persist<T: DeserializeOwned + Serialize + Default> {
inner: Arc<Inner<T>>,
path: String,
}
impl<T> Persist<T>
where
T: DeserializeOwned + Serialize + Default,
{
pub fn open(path: impl AsRef<Path>) -> Self {
let str_path = path.as_ref().to_str().unwrap_or_default().to_string();
let inner = Inner::open(path);
Self {
inner: Arc::new(inner),
path: str_path,
}
}
pub fn replace(&self, data: T) {
self.inner.replace(data).expect(&self.path);
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
self.inner.update(f).expect(&self.path);
}
#[tracing::instrument(skip(self))]
pub fn save(&self) {
self.inner.save().expect(&self.path);
}
pub fn cloned(&self) -> T
where
T: Clone,
{
self.inner.data.lock().clone()
}
}