From 50d828d0d7e5429ffc47ee28079818656d502963 Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 19 Jul 2026 08:44:24 +0800 Subject: [PATCH] fix(profile): handle missing chain files without breaking rollback --- Changelog.md | 1 + src-tauri/src/cmd/profile.rs | 12 ++++++++ src-tauri/src/cmd/save_profile.rs | 50 +++++++++++++++++++++---------- src-tauri/src/config/prfitem.rs | 4 ++- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8a1f0cf51..47c4b6f48 100644 --- a/Changelog.md +++ b/Changelog.md @@ -42,6 +42,7 @@ - 修复内核启动慢导致首页上下跳动的问题 - 修复 VLESS xhttp 链接解析及 IPv6 地址格式问题 - 修复通过普通导入或深链导入订阅后,自动更新任务未及时生效的问题 +- 修复链式配置文件首次编辑失败及校验回滚后遗留空文件的问题,并优化文件读取错误提示
✨ 新增功能 diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 5bd5c16b6..9d75f4e0e 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -404,6 +404,18 @@ pub async fn read_profile_file(index: String) -> CmdResult { ..Default::default() } }; + + if let Some(file) = item.file.as_ref() { + let path = dirs::app_profiles_dir().stringify_err()?.join(file.as_str()); + match tokio::fs::try_exists(&path).await { + Ok(true) => {} + Ok(false) => return Ok(String::new()), + Err(err) => { + return Err(format!("failed to check profile file \"{}\": {err}", path.display()).into()); + } + } + } + let data = item.read_file().await.stringify_err()?; Ok(data) } diff --git a/src-tauri/src/cmd/save_profile.rs b/src-tauri/src/cmd/save_profile.rs index b7d48ed7a..a615992ff 100644 --- a/src-tauri/src/cmd/save_profile.rs +++ b/src-tauri/src/cmd/save_profile.rs @@ -40,19 +40,29 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR (path, is_merge, is_script, affects_runtime) }; - // 读取原始内容(在释放profiles_guard后进行) - let original_content = PrfItem { - file: Some(rel_path.clone()), - ..Default::default() - } - .read_file() - .await - .stringify_err()?; - let profiles_dir = dirs::app_profiles_dir().stringify_err()?; let file_path = profiles_dir.join(rel_path.as_str()); let file_path_str = file_path.to_string_lossy().to_string(); + // 读取原始内容(在释放profiles_guard后进行) + let original_existed = fs::try_exists(&file_path).await.map_err(|err| { + String::from(format!( + "failed to check profile file \"{}\": {err}", + file_path.display() + )) + })?; + let original_content = if original_existed { + PrfItem { + file: Some(rel_path.clone()), + ..Default::default() + } + .read_file() + .await + .stringify_err()? + } else { + String::new() + }; + // 保存新的配置文件 fs::write(&file_path, &file_data).await.stringify_err()?; @@ -68,6 +78,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR &file_path_str, &file_path, &original_content, + original_existed, is_merge_file, is_script_file, affects_runtime, @@ -83,8 +94,16 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR Ok(changes_applied) } -async fn restore_original(file_path: &std::path::Path, original_content: &str) -> Result<(), String> { - fs::write(file_path, original_content).await.stringify_err() +async fn restore_original( + file_path: &std::path::Path, + original_content: &str, + original_existed: bool, +) -> Result<(), String> { + if original_existed { + fs::write(file_path, original_content).await.stringify_err() + } else { + fs::remove_file(file_path).await.stringify_err() + } } fn profile_affects_runtime(profiles: &IProfiles, index: &str) -> bool { @@ -112,6 +131,7 @@ async fn handle_saved_profile_file( file_path_str: &str, file_path: &std::path::Path, original_content: &str, + original_existed: bool, is_merge_file: bool, is_script_file: bool, affects_runtime: bool, @@ -138,13 +158,13 @@ async fn handle_saved_profile_file( } Ok(outcome) => { logging!(warn, Type::Config, "[cmd配置save] 文件验证失败: {}", outcome); - restore_original(file_path, original_content).await?; + restore_original(file_path, original_content, original_existed).await?; handle_validation_notice(&outcome, target, file_type); return Ok(outcome); } Err(e) => { logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e); - restore_original(file_path, original_content).await?; + restore_original(file_path, original_content, original_existed).await?; return Err(e.to_string().into()); } } @@ -165,13 +185,13 @@ async fn handle_saved_profile_file( } Ok(outcome) => { logging!(warn, Type::Config, "[cmd配置save] 运行时配置应用失败: {}", outcome); - restore_original(file_path, original_content).await?; + restore_original(file_path, original_content, original_existed).await?; handle_validation_notice(&outcome, ValidationNoticeTarget::Runtime, "运行时配置"); Ok(outcome) } Err(err) => { logging!(error, Type::Config, "[cmd配置save] 运行时配置应用错误: {}", err); - restore_original(file_path, original_content).await?; + restore_original(file_path, original_content, original_existed).await?; Err(err.to_string().into()) } } diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 0b17f0697..88ff36da9 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -533,7 +533,9 @@ impl PrfItem { .as_ref() .ok_or_else(|| anyhow::anyhow!("could not find the file"))?; let path = dirs::app_profiles_dir()?.join(file.as_str()); - let content = fs::read_to_string(path).await.context("failed to read the file")?; + let content = fs::read_to_string(&path) + .await + .with_context(|| format!("failed to read the file \"{}\"", path.display()))?; Ok(content.into()) }