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<Box<[u8]>>,
data: Option<Box<[u8]>>,
saving_config: Arc<AtomicBool>,
saving_data: Arc<AtomicBool>,
saving_config_failed: Arc<AtomicBool>,
saving_data_failed: Arc<AtomicBool>,
}
impl StorageManager {
pub fn load(app: AppStrategyArgs) -> std::io::Result<Self> {
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<AtomicBool>,
failed: Arc<AtomicBool>,
) {
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(),
)
}
}
|