summaryrefslogtreecommitdiff
path: root/src/storage.rs
blob: c099481e2371062a479a80ecc8187b370a17f76f (plain)
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(),
		)
	}
}