use std::{ path::PathBuf, sync::{ Arc, atomic::{AtomicBool, Ordering}, }, }; use etcetera::{AppStrategy, AppStrategyArgs, app_strategy::Xdg}; use smol::unblock; pub struct StorageManager { xdg: Xdg, config: Option>, data: Option>, saving_config: Arc, saving_data: Arc, saving_config_failed: Arc, saving_data_failed: Arc, } impl StorageManager { pub fn load(app: AppStrategyArgs) -> std::io::Result { let xdg = etcetera::choose_app_strategy(app).map_err(std::io::Error::other)?; let config = std::fs::read(xdg.in_config_dir("config")) .or_else(|_| std::fs::read(xdg.in_config_dir("config.tmp"))) .map(|config| config.into_boxed_slice()) .ok(); let data = std::fs::read(xdg.in_data_dir("save")) .or_else(|_| std::fs::read(xdg.in_data_dir("save.tmp"))) .map(|data| data.into_boxed_slice()) .ok(); Ok(Self { xdg, config, data, saving_config: Arc::new(AtomicBool::new(false)), saving_data: Arc::new(AtomicBool::new(false)), saving_config_failed: Arc::new(AtomicBool::new(false)), saving_data_failed: Arc::new(AtomicBool::new(false)), }) } pub fn config(&self) -> Option<&[u8]> { self.config.as_deref() } pub fn data(&self) -> Option<&[u8]> { self.data.as_deref() } pub fn is_saving_config(&self) -> bool { self.saving_config.load(Ordering::Acquire) } pub fn is_saving_data(&self) -> bool { self.saving_data.load(Ordering::Acquire) } pub fn saving_config_failed(&self) -> bool { self.saving_config_failed.load(Ordering::Acquire) } pub fn saving_data_failed(&self) -> bool { self.saving_data_failed.load(Ordering::Acquire) } fn background_save( &mut self, file: PathBuf, data: Box<[u8]>, state: Arc, failed: Arc, ) { let tmp_file = file.with_added_extension("tmp"); self.data = Some(data.clone()); unblock(move || { if let Err(e) = { std::fs::write(&tmp_file, &data)?; let saved_data = std::fs::read(&tmp_file)?; if *saved_data != *data { return Err(std::io::Error::other("Failed to verify file integrity")); } std::fs::rename(tmp_file, &file)?; state.store(false, Ordering::Release); Ok(()) } { log::error!("{e}"); failed.store(true, Ordering::Release); Err(e) } else { Ok(()) } }) .detach(); } pub fn background_save_config(&mut self, data: Box<[u8]>) { self.saving_config_failed.store(false, Ordering::Release); self.saving_config.store(true, Ordering::Release); self.background_save( self.xdg.in_data_dir("config"), data, self.saving_config.clone(), self.saving_config_failed.clone(), ) } pub fn background_save_data(&mut self, data: Box<[u8]>) { self.saving_data_failed.store(false, Ordering::Release); self.saving_data.store(true, Ordering::Release); self.background_save( self.xdg.in_data_dir("save"), data, self.saving_data.clone(), self.saving_config_failed.clone(), ) } }