armour-core 0.1.5

Core types for armour ecosystem
Documentation
#![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,
        }
    }

    /// save the data to the file
    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()
    }
}