mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 11:42:08 +08:00
* refactor(core): type notice statuses behind the frozen wire contract * refactor(utils): unified retry and strategy-list helpers * refactor(feat): declarative effect registry for verge and clash patches * refactor(core): derive refresh announcements from transaction commit * refactor(core): fold tray, hotkey, autostart updates into effect ensures * refactor(utils): migrate remaining bounded retry loops * docs: add src-tauri architecture guardrails * refactor(core): coalesce burst refresh announcements * refactor(core): complete bounded service retry migration * ci(backend): add architecture ban gates * test(feat): freeze representative effect registry mappings * docs(backend): record dead wire contract entries * chore(core): document watcher exit conditions * fix(backend): preserve applied state across later failures * fix(config): retain runtime after an applied core replacement * refactor(backend): flatten refresh publication and retry policies * docs(backend): focus guardrails on architecture ownership * refactor(effects): use one execution order * fix(config): publish retained runtime and reconcile proxy settings
92 lines
3.0 KiB
Rust
92 lines
3.0 KiB
Rust
use crate::utils::dirs;
|
|
use anyhow::{Context as _, Result, bail};
|
|
use std::{io::ErrorKind, path::PathBuf};
|
|
|
|
pub(crate) struct FileSnapshot {
|
|
pub(crate) path: PathBuf,
|
|
content: Option<Vec<u8>>,
|
|
}
|
|
|
|
async fn capture_files(paths: &[PathBuf]) -> Result<Vec<FileSnapshot>> {
|
|
let mut snapshots = Vec::with_capacity(paths.len());
|
|
for path in paths {
|
|
let content = match tokio::fs::read(path).await {
|
|
Ok(content) => Some(content),
|
|
Err(error) if error.kind() == ErrorKind::NotFound => None,
|
|
Err(error) => {
|
|
return Err(error).with_context(|| format!("failed to snapshot {}", path.display()));
|
|
}
|
|
};
|
|
snapshots.push(FileSnapshot {
|
|
path: path.clone(),
|
|
content,
|
|
});
|
|
}
|
|
Ok(snapshots)
|
|
}
|
|
|
|
pub(crate) async fn capture_config_files() -> Result<Vec<FileSnapshot>> {
|
|
let runtime_path = dirs::app_home_dir()?.join(crate::constants::files::RUNTIME_CONFIG);
|
|
capture_files(&[dirs::clash_path()?, dirs::verge_path()?, runtime_path]).await
|
|
}
|
|
|
|
pub(crate) async fn restore_files(snapshots: &[FileSnapshot]) -> Result<()> {
|
|
let mut errors = Vec::new();
|
|
for snapshot in snapshots {
|
|
let result = match &snapshot.content {
|
|
Some(content) => tokio::fs::write(&snapshot.path, content).await,
|
|
None => match tokio::fs::remove_file(&snapshot.path).await {
|
|
Ok(()) => Ok(()),
|
|
Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
|
|
Err(error) => Err(error),
|
|
},
|
|
};
|
|
if let Err(error) = result {
|
|
errors.push(format!("{}: {}", snapshot.path.display(), error));
|
|
}
|
|
}
|
|
|
|
if errors.is_empty() {
|
|
Ok(())
|
|
} else {
|
|
bail!("{}", errors.join("; "))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{capture_files, restore_files};
|
|
|
|
#[tokio::test]
|
|
async fn restores_every_configuration_layer_after_partial_write() -> anyhow::Result<()> {
|
|
let root = std::env::temp_dir().join(format!(
|
|
"config-rollback-{}-{}",
|
|
std::process::id(),
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)?
|
|
.as_nanos()
|
|
));
|
|
tokio::fs::create_dir_all(&root).await?;
|
|
let paths = [
|
|
root.join("config.yaml"),
|
|
root.join("verge.yaml"),
|
|
root.join("clash-verge.yaml"),
|
|
];
|
|
for (index, path) in paths.iter().enumerate() {
|
|
tokio::fs::write(path, format!("original-{index}")).await?;
|
|
}
|
|
|
|
let snapshots = capture_files(&paths).await?;
|
|
tokio::fs::write(&paths[0], "changed").await?;
|
|
tokio::fs::write(&paths[1], "changed").await?;
|
|
tokio::fs::remove_file(&paths[2]).await?;
|
|
restore_files(&snapshots).await?;
|
|
|
|
for (index, path) in paths.iter().enumerate() {
|
|
assert_eq!(tokio::fs::read_to_string(path).await?, format!("original-{index}"));
|
|
}
|
|
tokio::fs::remove_dir_all(root).await?;
|
|
Ok(())
|
|
}
|
|
}
|