use std::{ fs::{self, File}, io::Write, path::PathBuf, }; use directories_next::ProjectDirs; #[derive(Clone)] pub struct DataDir { pub path: PathBuf, } impl DataDir { pub fn new(dir: Option<&str>) -> Self { let dirs = ProjectDirs::from("", "", "openworm").unwrap(); let mut path = dirs.data_local_dir().to_path_buf(); if let Some(dir) = dir { path = path.join(dir); } Self { path } } } pub trait DataRsc: serde::Serialize + Default { fn name() -> &'static str; fn path() -> String { Self::name().to_string() + ".ron" } fn parse_version(text: &str, version: u32) -> Result; fn version() -> u32; } pub struct DataGuard { path: PathBuf, val: T, } impl DataDir { pub fn load(&self) -> DataGuard { let path = self.path.join(T::path()); let invalid = |info: &str| { println!("warning: invalid config @ {path:?}: {info}"); DataGuard { path: path.clone(), val: T::default(), } }; match fs::read_to_string(&path) { Ok(text) => { let mut lines = text.lines(); let Some(first) = lines.next() else { return invalid("empty file"); }; let version_str: String = first .chars() .skip_while(|c| *c != 'v') .skip(1) .take_while(|c| !c.is_whitespace()) .collect(); let Ok(version): Result = version_str.parse() else { return invalid("invalid version"); }; let text: String = lines.collect(); match T::parse_version(&text, version) { Ok(val) => DataGuard { path, val }, Err(e) => invalid(&e), } } Err(_) => DataGuard { path, val: T::default(), }, } } } impl std::ops::Deref for DataGuard { type Target = T; fn deref(&self) -> &Self::Target { &self.val } } impl std::ops::DerefMut for DataGuard { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.val } } impl Drop for DataGuard { fn drop(&mut self) { let dir = self.path.parent().unwrap(); fs::create_dir_all(dir).unwrap(); let mut file = File::create(dir.join(T::path())).unwrap(); let ron = ron::ser::to_string_pretty(&self.val, ron::ser::PrettyConfig::new()).unwrap(); let data = format!("// v{}\n{}\n", T::version(), ron); if let Err(e) = file.write_all(data.as_bytes()) { println!("Failed to write config @ {:?}: {e}", self.path); } } }