use super::CmdResult; use crate::{ cmd::StringifyErr as _, cmd::validate::{ValidationNoticeTarget, handle_validation_notice}, config::{Config, IProfiles, PrfItem}, core::{ CoreManager, handle, validate::{CoreConfigValidator, ValidationOutcome}, }, module::auto_backup::{AutoBackupManager, AutoBackupTrigger}, utils::dirs, }; use clash_verge_logging::{Type, logging, logging_error}; use smartstring::alias::String; use tokio::fs; /// 保存profiles的配置 #[tauri::command] pub async fn save_profile_file(index: String, file_data: Option) -> CmdResult { let file_data = match file_data { Some(d) => d, None => return Ok(ValidationOutcome::Valid), }; let backup_trigger = match index.as_str() { "Merge" => Some(AutoBackupTrigger::GlobalMerge), "Script" => Some(AutoBackupTrigger::GlobalScript), _ => None, }; // 在异步操作前获取必要元数据并释放锁 let (rel_path, is_merge_file, is_script_file, affects_runtime) = { let profiles = Config::profiles().await; let profiles_guard = profiles.latest_arc(); let item = profiles_guard.get_item(&index).stringify_err()?; let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge"); let path = item.file.clone().ok_or("file field is null")?; let is_script = item.itype.as_ref().is_some_and(|t| t == "script") || path.ends_with(".js"); let affects_runtime = profile_affects_runtime(&profiles_guard, &index); (path, is_merge, is_script, affects_runtime) }; 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()?; let changes_applied = handle_saved_profile_file( &file_path_str, &file_path, &original_content, original_existed, is_merge_file, is_script_file, affects_runtime, ) .await?; if changes_applied.is_valid() && let Some(trigger) = backup_trigger { AutoBackupManager::trigger_backup(trigger); } Ok(changes_applied) } async fn restore_original( file_path: &std::path::Path, original_content: &str, original_existed: bool, ) -> CmdResult<()> { 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 { let Some(current_uid) = profiles.current.as_ref() else { return false; }; if current_uid == index { return true; } let Ok(item) = profiles.get_item(current_uid) else { return false; }; [ item.current_merge().map_or("Merge", String::as_str), item.current_script().map_or("Script", String::as_str), item.current_rules().map_or("Rules", String::as_str), item.current_proxies().map_or("Proxies", String::as_str), item.current_groups().map_or("Groups", String::as_str), ] .contains(&index) } 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, ) -> CmdResult { let (target, file_type) = if is_script_file { (ValidationNoticeTarget::Script, "脚本文件") } else if is_merge_file { (ValidationNoticeTarget::Merge, "合并配置文件") } else { (ValidationNoticeTarget::Runtime, "YAML配置文件") }; match CoreConfigValidator::validate_config_file_outcome(file_path_str, Some(is_merge_file)).await { Ok(outcome) if outcome.is_valid() => {} Ok(outcome) => { logging!(warn, Type::Config, "[cmd配置save] 文件验证失败: {}", outcome); 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, original_existed).await?; return Err(e.to_string().into()); } } if !affects_runtime { return Ok(ValidationOutcome::Valid); } logging!( info, Type::Config, "[cmd配置save] 保存项影响当前运行时配置,开始统一应用" ); match CoreManager::global().update_config_forced().await { Ok(outcome) if outcome.is_valid() => { logging_error!(Type::Config, Config::sync_dns_override().await); handle::Handle::refresh_clash(); Ok(ValidationOutcome::Valid) } Ok(outcome) => { logging!(warn, Type::Config, "[cmd配置save] 运行时配置应用失败: {}", outcome); 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, original_existed).await?; Err(err.to_string().into()) } } }