vedvaring 0.2.2

easy persistence
Documentation
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::fmt::Display;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{fs, sync::Arc};

pub trait DefaultWithId: FsTrait {
    fn default_with_id(id: Self::Key) -> Self;
}

#[derive(Debug)]
pub struct StoredProvider<Raw: FsTrait, T: StoredTrait<Raw, S>, S = ()> {
    inner: Arc<RwLock<HashMap<Raw::Key, Stored<S, Raw, T>>>>,
    state: Arc<S>,
}

impl<Raw: FsTrait, T: StoredTrait<Raw, S>, S> StoredProvider<Raw, T, S> {
    pub fn new(state: impl Into<Arc<S>>) -> Self {
        let state = state.into();

        let path = Raw::items_path();
        let mut out: HashMap<Raw::Key, Stored<S, Raw, T>> = Default::default();

        for entry in fs::read_dir(&path).unwrap() {
            let path = entry.unwrap().path();
            let s: String = fs::read_to_string(&path).unwrap();
            let s: Raw = serde_json::from_str(&s).unwrap();
            let key = s.item_id();
            let s = Stored::new(s, state.clone());
            out.insert(key, s);
        }

        Self {
            inner: Arc::new(RwLock::new(out)),
            state: state.into(),
        }
    }

    pub fn load_all(&self) -> Vec<Stored<S, Raw, T>> {
        self.inner.read().unwrap().values().cloned().collect()
    }

    pub fn load(&self, id: Raw::Key) -> Option<Stored<S, Raw, T>> {
        let guard = self.inner.read().unwrap();
        let val = guard.get(&id);
        if val.is_some() {
            return val.cloned();
        }
        drop(guard);

        let val = Stored::load(id.clone(), self.state.clone())?;
        let mut guard = self.inner.write().unwrap();
        guard.insert(id, val.clone());
        Some(val)
    }
}

#[derive(Debug)]
pub struct Stored<S, Raw: FsTrait, T: StoredTrait<Raw, S>> {
    inner: Arc<RwLock<T>>,
    _phantom: PhantomData<Raw>,
    _ph2: PhantomData<S>,
}

impl<S, Raw: FsTrait, T: StoredTrait<Raw, S>> Clone for Stored<S, Raw, T> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
            _phantom: PhantomData,
            _ph2: PhantomData,
        }
    }
}

impl<S, Raw: FsTrait, T: StoredTrait<Raw, S>> Stored<S, Raw, T> {
    pub fn new(raw: Raw, state: impl Into<Arc<S>>) -> Self {
        let state: Arc<S> = state.into();
        let item = T::into_item(&raw, &state);
        Self {
            inner: Arc::new(RwLock::new(item)),
            _phantom: PhantomData,
            _ph2: PhantomData,
        }
    }

    pub fn load(id: Raw::Key, state: impl Into<Arc<S>>) -> Option<Self> {
        let state: Arc<S> = state.into();
        let raw = Raw::load(id)?;
        let item = T::into_item(&raw, &state);
        Some(Self {
            _phantom: PhantomData,
            inner: Arc::new(RwLock::new(item)),
            _ph2: PhantomData,
        })
    }

    pub fn read(&self) -> RwLockReadGuard<T> {
        self.inner.try_read().unwrap()
    }

    pub fn write(&self) -> StoredWriteGuard<T, Raw, S> {
        StoredWriteGuard {
            guard: self.inner.try_write().unwrap(),
            _ph1: PhantomData,
            _ph2: PhantomData,
        }
    }
}

pub struct StoredWriteGuard<'a, T: StoredTrait<Raw, S>, Raw: FsTrait, S> {
    guard: RwLockWriteGuard<'a, T>,
    _ph1: PhantomData<Raw>,
    _ph2: PhantomData<S>,
}

impl<'a, T: StoredTrait<Raw, S>, Raw: FsTrait, S> Drop for StoredWriteGuard<'a, T, Raw, S> {
    fn drop(&mut self) {
        self.guard.into_raw().save().unwrap();
    }
}

/// raw is the thing that actually get saved
/// but in app code you might wanna turn that raw data into
/// actual types like an id could become an object and such
/// so you use the into_item to transform it
/// depending on the item, you might need some state to do that transform
/// like a provider to turn ids into object
pub trait StoredTrait<Raw: FsTrait, S> {
    fn into_item(raw: &Raw, state: &S) -> Self;
    fn into_raw(&self) -> Raw;
}

#[derive(Debug)]
pub struct Saved<T: FsTrait> {
    inner: Arc<RwLock<T>>,
}

impl<T: Display + FsTrait> Display for Saved<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = self.inner.try_read().unwrap().to_string();
        write!(f, "{s}")
    }
}

impl<T: FsTrait + DefaultWithId> Saved<T> {
    pub fn load_or_create(id: T::Key) -> Self {
        let x = match Self::load(id.clone()) {
            Some(item) => item,
            None => Saved::new(T::default_with_id(id)),
        };

        x
    }
}

impl<T: FsTrait> Saved<T> {
    pub fn new(item: T) -> Self {
        item.save().unwrap();
        Self {
            inner: Arc::new(RwLock::new(item)),
        }
    }

    pub fn load_all() -> Vec<Self> {
        T::load_all()
            .into_iter()
            .map(|item| Self {
                inner: Arc::new(RwLock::new(item)),
            })
            .collect()
    }

    pub fn load(id: T::Key) -> Option<Self> {
        let item = T::load(id)?;
        Some(Self {
            inner: Arc::new(RwLock::new(item)),
        })
    }

    pub fn read(&self) -> RwLockReadGuard<T> {
        self.inner.try_read().unwrap()
    }

    pub fn write(&self) -> MyWriteGuard<T> {
        MyWriteGuard(self.inner.try_write().unwrap())
    }
}

impl<T: FsTrait> Clone for Saved<T> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
        }
    }
}

/// Wrapper for writeguard which saves the item to disk when the writeguard goes out of scope.
pub struct MyWriteGuard<'a, T: FsTrait>(RwLockWriteGuard<'a, T>);

impl<'a, T: FsTrait> Drop for MyWriteGuard<'a, T> {
    fn drop(&mut self) {
        self.save().unwrap();
    }
}

impl<'a, T: FsTrait> Deref for MyWriteGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<'a, T: FsTrait> DerefMut for MyWriteGuard<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[derive(Debug)]
pub enum SaveError {
    Serialize(serde_json::Error),
    Io(std::io::Error),
}

use std::hash::Hash;

pub trait FsTrait
where
    Self: DeserializeOwned + Serialize + Sized,
{
    type Key: Display + Clone + Hash + PartialEq + Eq;

    fn item_id(&self) -> Self::Key;

    fn crate_name() -> String {
        std::env::current_exe()
            .unwrap()
            .file_stem()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string()
            .to_lowercase()
    }

    fn root() -> PathBuf {
        dirs::data_local_dir().unwrap().join(Self::crate_name())
    }

    fn items_path() -> PathBuf {
        let name = std::any::type_name::<Self>();
        let path = Self::root().join(name);
        fs::create_dir_all(&path).unwrap();
        path
    }

    fn item_path(&self) -> PathBuf {
        Self::items_path().join(self.item_id().to_string())
    }

    fn load_all() -> Vec<Self> {
        let path = Self::items_path();
        let mut out: Vec<Self> = vec![];

        for entry in fs::read_dir(&path).unwrap() {
            let path = entry.unwrap().path();
            let s: String = fs::read_to_string(&path).unwrap();
            let s: Self = serde_json::from_str(&s).unwrap();
            out.push(s);
        }

        out
    }

    fn load(id: Self::Key) -> Option<Self> {
        let path = Self::items_path().join(id.to_string());
        if !path.exists() {
            return None;
        }

        let s: String = fs::read_to_string(&path).unwrap();
        let t: Self = serde_json::from_str(&s).unwrap();
        Some(t)
    }

    fn delete(id: Self::Key) {
        let path = Self::items_path().join(id.to_string());
        fs::remove_file(&path).unwrap();
    }

    fn save(&self) -> Result<(), SaveError> {
        use std::io::Write;

        let path = self.item_path();
        let s = serde_json::to_string_pretty(self).map_err(SaveError::Serialize)?;
        let mut f = fs::File::create(&path).map_err(SaveError::Io)?;
        f.write_all(s.as_bytes()).map_err(SaveError::Io)?;
        Ok(())
    }
}