Refactor ProxyWarden routing and settings flow

This commit is contained in:
2026-07-09 11:51:16 +03:00
parent db0c1dede9
commit 1bb795a532
18 changed files with 1018 additions and 210 deletions
+67 -33
View File
@@ -2,6 +2,7 @@ use crate::activity::{append_activity, cap_activity, DEFAULT_ACTIVITY_LIMIT};
use crate::models::{
ActivityEntry, ComponentStatus, LocalSingBoxConfig, Profile, SubscriptionCache, Target,
};
use crate::safe_fs;
use serde::{de::DeserializeOwned, Serialize};
use std::fs;
use std::io::{self, ErrorKind};
@@ -144,10 +145,9 @@ impl JsonStorage {
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()),
},
Ok(contents) => {
parse_json(path, &contents).or_else(|error| recover_corrupt_json(path, error))
}
Err(error) if error.kind() == ErrorKind::NotFound => Ok(T::default()),
Err(error) => Err(error),
}
@@ -167,7 +167,9 @@ impl JsonStorage {
T: DeserializeOwned,
{
match fs::read_to_string(path) {
Ok(contents) => Ok(serde_json::from_str(&contents).ok()),
Ok(contents) => parse_json(path, &contents)
.map(Some)
.or_else(|error| recover_corrupt_json(path, error).map(Some)),
Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
Err(error) => Err(error),
}
@@ -181,40 +183,72 @@ impl Default for JsonStorage {
}
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}"))
safe_fs::backup_path(path)
}
fn write_atomic(path: &Path, contents: &[u8]) -> io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
safe_fs::write_with_backup(path, contents)
}
fn parse_json<T>(path: &Path, contents: &str) -> io::Result<T>
where
T: DeserializeOwned,
{
serde_json::from_str(contents).map_err(|error| {
io::Error::new(
ErrorKind::InvalidData,
format!("Invalid JSON in '{}': {error}", path.display()),
)
})
}
fn recover_corrupt_json<T>(path: &Path, parse_error: io::Error) -> io::Result<T>
where
T: DeserializeOwned,
{
let corrupt_path = safe_fs::corrupt_path(path);
move_corrupt_file(path, &corrupt_path)?;
let backup_path = backup_path(path);
if backup_path.exists() {
let backup_contents = fs::read_to_string(&backup_path)?;
match parse_json(&backup_path, &backup_contents) {
Ok(value) => {
fs::copy(&backup_path, path)?;
Ok(value)
}
Err(backup_error) => Err(io::Error::new(
ErrorKind::InvalidData,
format!(
"Invalid JSON in '{}'; corrupt file moved to '{}'; backup '{}' could not be restored: {backup_error}; original error: {parse_error}",
path.display(),
corrupt_path.display(),
backup_path.display()
),
)),
}
} else {
Err(io::Error::new(
ErrorKind::InvalidData,
format!(
"Invalid JSON in '{}'; corrupt file moved to '{}'; no valid backup available: {parse_error}",
path.display(),
corrupt_path.display()
),
))
}
}
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) {
fn move_corrupt_file(path: &Path, corrupt_path: &Path) -> io::Result<()> {
match fs::rename(path, corrupt_path) {
Ok(()) => Ok(()),
Err(error) => {
let _ = fs::remove_file(&temp_path);
Err(error)
Err(rename_error) => {
fs::copy(path, corrupt_path)?;
fs::remove_file(path)?;
if !corrupt_path.exists() {
return Err(rename_error);
}
Ok(())
}
}
}