use crate::activity::{append_activity, cap_activity, DEFAULT_ACTIVITY_LIMIT}; use crate::models::{ ActivityEntry, ComponentStatus, LocalSingBoxConfig, Profile, SubscriptionCache, Target, }; use serde::{de::DeserializeOwned, Serialize}; use std::fs; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; pub fn default_config_root() -> PathBuf { PathBuf::from(r"C:\ProgramData\ProxyWarden") } #[derive(Debug, Clone, PartialEq, Eq)] pub struct StoragePaths { pub root: PathBuf, pub config_dir: PathBuf, pub state_dir: PathBuf, pub generated_dir: PathBuf, pub profiles_file: PathBuf, pub targets_file: PathBuf, pub components_file: PathBuf, pub local_singbox_file: PathBuf, pub singbox_subscription_cache_file: PathBuf, pub activity_file: PathBuf, } impl StoragePaths { pub fn new(root: impl Into) -> Self { let root = root.into(); let config_dir = root.join("config"); let state_dir = root.join("state"); let generated_dir = root.join("generated"); Self { root, profiles_file: config_dir.join("profiles.json"), targets_file: config_dir.join("targets.json"), components_file: config_dir.join("components.json"), local_singbox_file: config_dir.join("local-singbox.json"), singbox_subscription_cache_file: state_dir.join("singbox-subscription-cache.json"), activity_file: state_dir.join("activity.json"), config_dir, state_dir, generated_dir, } } } impl Default for StoragePaths { fn default() -> Self { Self::new(default_config_root()) } } #[derive(Debug, Clone)] pub struct JsonStorage { paths: StoragePaths, activity_limit: usize, } impl JsonStorage { pub fn new(root: impl Into) -> Self { Self::with_activity_limit(root, DEFAULT_ACTIVITY_LIMIT) } pub fn with_activity_limit(root: impl Into, activity_limit: usize) -> Self { Self { paths: StoragePaths::new(root), activity_limit, } } pub fn paths(&self) -> &StoragePaths { &self.paths } pub fn read_profiles(&self) -> io::Result> { self.read_json_or_default(&self.paths.profiles_file) } pub fn write_profiles(&self, profiles: &[Profile]) -> io::Result<()> { self.write_json(&self.paths.profiles_file, profiles) } pub fn read_targets(&self) -> io::Result> { self.read_json_or_default(&self.paths.targets_file) } pub fn write_targets(&self, targets: &[Target]) -> io::Result<()> { self.write_json(&self.paths.targets_file, targets) } pub fn read_components(&self) -> io::Result> { self.read_json_or_default(&self.paths.components_file) } pub fn read_local_singbox_config(&self) -> io::Result { self.read_json_or_default(&self.paths.local_singbox_file) } pub fn write_local_singbox_config(&self, config: &LocalSingBoxConfig) -> io::Result<()> { self.write_json(&self.paths.local_singbox_file, config) } pub fn read_singbox_subscription_cache(&self) -> io::Result> { self.read_optional_json(&self.paths.singbox_subscription_cache_file) } pub fn write_singbox_subscription_cache(&self, cache: &SubscriptionCache) -> io::Result<()> { self.write_json(&self.paths.singbox_subscription_cache_file, cache) } pub fn remove_singbox_subscription_cache(&self) -> io::Result<()> { match fs::remove_file(&self.paths.singbox_subscription_cache_file) { Ok(()) => Ok(()), Err(error) if error.kind() == ErrorKind::NotFound => Ok(()), Err(error) => Err(error), } } pub fn read_activity(&self) -> io::Result> { let entries = self.read_json_or_default(&self.paths.activity_file)?; Ok(cap_activity(entries, self.activity_limit)) } pub fn append_activity(&self, entry: ActivityEntry) -> io::Result> { let entries = self.read_activity()?; let entries = append_activity(entries, entry, self.activity_limit); self.write_json(&self.paths.activity_file, &entries)?; Ok(entries) } fn read_json_or_default(&self, path: &Path) -> io::Result where T: DeserializeOwned + Default, { match fs::read_to_string(path) { Ok(contents) => match serde_json::from_str(&contents) { Ok(value) => Ok(value), Err(_) => Ok(T::default()), }, Err(error) if error.kind() == ErrorKind::NotFound => Ok(T::default()), Err(error) => Err(error), } } fn write_json(&self, path: &Path, value: &T) -> io::Result<()> where T: Serialize + ?Sized, { let contents = serde_json::to_vec_pretty(value) .map_err(|error| io::Error::new(ErrorKind::InvalidData, error))?; write_atomic(path, &contents) } fn read_optional_json(&self, path: &Path) -> io::Result> where T: DeserializeOwned, { match fs::read_to_string(path) { Ok(contents) => Ok(serde_json::from_str(&contents).ok()), Err(error) if error.kind() == ErrorKind::NotFound => Ok(None), Err(error) => Err(error), } } } impl Default for JsonStorage { fn default() -> Self { Self::new(default_config_root()) } } pub fn backup_path(path: &Path) -> PathBuf { sibling_with_suffix(path, "bak") } fn temp_path(path: &Path) -> PathBuf { sibling_with_suffix(path, "tmp") } fn sibling_with_suffix(path: &Path, suffix: &str) -> PathBuf { let file_name = path .file_name() .and_then(|value| value.to_str()) .unwrap_or("storage.json"); path.with_file_name(format!("{file_name}.{suffix}")) } fn write_atomic(path: &Path, contents: &[u8]) -> io::Result<()> { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } let temp_path = temp_path(path); fs::write(&temp_path, contents)?; if path.exists() { fs::copy(path, backup_path(path))?; fs::remove_file(path)?; } match fs::rename(&temp_path, path) { Ok(()) => Ok(()), Err(error) => { let _ = fs::remove_file(&temp_path); Err(error) } } }