229 lines
7.0 KiB
Rust
229 lines
7.0 KiB
Rust
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\VpnProxy")
|
|
}
|
|
|
|
#[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<PathBuf>) -> 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<PathBuf>) -> Self {
|
|
Self::with_activity_limit(root, DEFAULT_ACTIVITY_LIMIT)
|
|
}
|
|
|
|
pub fn with_activity_limit(root: impl Into<PathBuf>, activity_limit: usize) -> Self {
|
|
Self {
|
|
paths: StoragePaths::new(root),
|
|
activity_limit,
|
|
}
|
|
}
|
|
|
|
pub fn paths(&self) -> &StoragePaths {
|
|
&self.paths
|
|
}
|
|
|
|
pub fn ensure_dirs(&self) -> io::Result<()> {
|
|
fs::create_dir_all(&self.paths.config_dir)?;
|
|
fs::create_dir_all(&self.paths.state_dir)?;
|
|
fs::create_dir_all(&self.paths.generated_dir)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read_profiles(&self) -> io::Result<Vec<Profile>> {
|
|
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<Vec<Target>> {
|
|
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<Vec<ComponentStatus>> {
|
|
self.read_json_or_default(&self.paths.components_file)
|
|
}
|
|
|
|
pub fn write_components(&self, components: &[ComponentStatus]) -> io::Result<()> {
|
|
self.write_json(&self.paths.components_file, components)
|
|
}
|
|
|
|
pub fn read_local_singbox_config(&self) -> io::Result<LocalSingBoxConfig> {
|
|
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<Option<SubscriptionCache>> {
|
|
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<Vec<ActivityEntry>> {
|
|
let entries = self.read_json_or_default(&self.paths.activity_file)?;
|
|
Ok(cap_activity(entries, self.activity_limit))
|
|
}
|
|
|
|
pub fn write_activity(&self, entries: &[ActivityEntry]) -> io::Result<()> {
|
|
let entries = cap_activity(entries.to_vec(), self.activity_limit);
|
|
self.write_json(&self.paths.activity_file, &entries)
|
|
}
|
|
|
|
pub fn append_activity(&self, entry: ActivityEntry) -> io::Result<Vec<ActivityEntry>> {
|
|
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<T>(&self, path: &Path) -> io::Result<T>
|
|
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<T>(&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<T>(&self, path: &Path) -> io::Result<Option<T>>
|
|
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)
|
|
}
|
|
}
|
|
}
|