use std::io; use std::path::PathBuf; use std::sync::Arc; use serde::{de::DeserializeOwned, Serialize}; use tokio::{ fs::{self, File}, io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, }; #[derive(Clone)] pub struct Persistence { state: Arc, } pub struct PersistenceFile { content: S, file: Option, } enum PersistenceState { NoPersistence, Persistence { directory: PathBuf }, } impl Persistence { pub async fn new() -> Persistence { Persistence { state: Arc::new(PersistenceState::NoPersistence), } } pub async fn new_persistence(directory: PathBuf) -> io::Result { let _ = fs::read_dir(&directory).await?; Ok(Persistence { state: Arc::new(PersistenceState::Persistence { directory }), }) } pub async fn open( &self, name: String, ) -> Result, io::Error> { Ok(match &*self.state { PersistenceState::NoPersistence => PersistenceFile { content: S::default(), file: None, }, PersistenceState::Persistence { directory } => { let file_path = directory.join(&name); let mut file = fs::OpenOptions::new() .create(true) .read(true) .write(true) .open(file_path) .await?; let mut content = String::new(); file.read_to_string(&mut content).await?; let content = ron::from_str(&content).unwrap_or_else(|_| { info!("Failed to load persistence {name}, using default"); S::default() }); PersistenceFile { content, file: Some(file), } } }) } } impl PersistenceFile where S: Serialize, { pub fn get(&self) -> &S { &self.content } pub async fn set(&mut self, s: S) -> anyhow::Result<()> { self.content = s; if let Some(file) = &mut self.file { let serialized = ron::ser::to_string_pretty(&self.content, Default::default())?; file.rewind().await?; file.set_len(0).await?; file.write_all(serialized.as_bytes()).await?; } Ok(()) } pub async fn update(&mut self, f: impl FnOnce(&mut S)) -> anyhow::Result<()> where S: Clone + PartialEq, { let mut new = self.content.clone(); f(&mut new); if new != self.content { self.set(new).await?; } Ok(()) } }