use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::path::PathBuf;
use tokio::fs;
use crate::domains::convert::SerializablePrefValue;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct SettingState {
pub domain: String,
pub key: String,
pub original_value: Option<SerializablePrefValue>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct LoadedSnapshot {
pub settings: Vec<SettingState>,
pub exec_run_count: i32,
pub version: String,
pub digest: String,
#[serde(skip)]
path: PathBuf,
}
impl LoadedSnapshot {
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub async fn delete(&self) -> Result<()> {
fs::remove_file(&self.path)
.await
.with_context(|| format!("Could not delete snapshot file {:?}.", &self.path))
}
pub async fn save(&self) -> Result<()> {
if let Some(dir) = self.path.parent() {
fs::create_dir_all(dir).await?;
}
let json = serde_json::to_string_pretty(self)?;
fs::write(&self.path, json).await?;
Ok(())
}
}
pub struct Snapshot {
path: PathBuf,
}
impl Snapshot {
#[must_use]
pub const fn new(path: PathBuf) -> Self {
Self { path }
}
#[must_use]
pub fn path(&self) -> &Path {
self.path.as_path()
}
#[must_use]
pub fn is_loadable(&self) -> bool {
!self.path.as_os_str().is_empty() && self.path.try_exists().unwrap_or(false)
}
#[must_use]
pub fn new_empty(&self) -> LoadedSnapshot {
LoadedSnapshot {
settings: vec![],
exec_run_count: 0,
version: env!("CARGO_PKG_VERSION").to_string(),
digest: String::new(),
path: self.path.clone(),
}
}
pub async fn load(&self) -> Result<LoadedSnapshot> {
if self.is_loadable() {
let txt = fs::read_to_string(&self.path).await?;
let snap_result: Result<LoadedSnapshot, _> = serde_json::from_str(&txt);
match snap_result {
Ok(mut snap) => {
snap.path = self.path.to_owned();
Ok(snap)
}
Err(e) => {
#[derive(Deserialize)]
struct SettingsOnly {
settings: Vec<SettingState>,
}
let settings_only_result: Result<SettingsOnly, _> = serde_json::from_str(&txt);
match settings_only_result {
Ok(settings_only) => {
let mut snap = self.new_empty();
snap.settings = settings_only.settings;
snap.path = self.path.to_owned();
Ok(snap)
}
Err(_) => {
bail!("Failed to deserialize snapshot: {e}")
}
}
}
}
} else {
bail!("Invalid path, cannot load.")
}
}
}