From d05b1c313010eb655079bd782e77d5c4a75bd9e6 Mon Sep 17 00:00:00 2001 From: wonfen Date: Mon, 9 Jun 2025 13:47:38 +0800 Subject: [PATCH] feat: clean up redundant profiles files --- .husky/pre-commit | 2 +- src-tauri/src/cmd/profile.rs | 4 + src-tauri/src/config/profiles.rs | 203 ++++++++++++++++++++++++++++++- src-tauri/src/utils/resolve.rs | 9 ++ 4 files changed, 216 insertions(+), 2 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 4f437094..a4ab405f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -20,7 +20,7 @@ if git diff --cached --name-only | grep -q '^src-tauri/'; then cd .. fi -git add . +#git add . # 允许提交 exit 0 diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 45956376..f1ecab6a 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -93,6 +93,10 @@ pub async fn update_profile(index: String, option: Option) -> CmdResu #[tauri::command] pub async fn delete_profile(index: String) -> CmdResult { let should_update = wrap_err!({ Config::profiles().data().delete_item(index) })?; + + // 删除后自动清理冗余文件 + let _ = Config::profiles().latest().auto_cleanup(); + if should_update { wrap_err!(CoreManager::global().update_config().await)?; handle::Handle::refresh_clash(); diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index 1dd78dfd..46c3056d 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -3,7 +3,7 @@ use crate::utils::{dirs, help}; use anyhow::{bail, Context, Result}; use serde::{Deserialize, Serialize}; use serde_yaml::Mapping; -use std::{fs, io::Write}; +use std::{collections::HashSet, fs, io::Write}; /// Define the `profiles.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -15,6 +15,14 @@ pub struct IProfiles { pub items: Option>, } +/// 清理结果 +#[derive(Debug, Clone)] +pub struct CleanupResult { + pub total_files: usize, + pub deleted_files: Vec, + pub failed_deletions: Vec, +} + macro_rules! patch { ($lv: expr, $rv: expr, $key: tt) => { if ($rv.$key).is_some() { @@ -485,4 +493,197 @@ impl IProfiles { .collect() }) } + + /// 以 app 中的 profile 列表为准,删除不再需要的文件 + pub fn cleanup_orphaned_files(&self) -> Result { + let profiles_dir = dirs::app_profiles_dir()?; + + if !profiles_dir.exists() { + return Ok(CleanupResult { + total_files: 0, + deleted_files: vec![], + failed_deletions: vec![], + }); + } + + // 获取所有 active profile 的文件名集合 + let active_files = self.get_all_active_files(); + + // 添加全局扩展配置文件到保护列表 + let protected_files = self.get_protected_global_files(); + + // 扫描 profiles 目录下的所有文件 + let mut total_files = 0; + let mut deleted_files = vec![]; + let mut failed_deletions = vec![]; + + for entry in std::fs::read_dir(&profiles_dir)? { + let entry = entry?; + let path = entry.path(); + + if !path.is_file() { + continue; + } + + total_files += 1; + + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { + if Self::is_profile_file(file_name) { + // 检查是否为全局扩展文件 + if protected_files.contains(file_name) { + log::debug!(target: "app", "保护全局扩展配置文件: {}", file_name); + continue; + } + + // 检查是否为活跃文件 + if !active_files.contains(file_name) { + match std::fs::remove_file(&path) { + Ok(_) => { + deleted_files.push(file_name.to_string()); + log::info!(target: "app", "已清理冗余文件: {}", file_name); + } + Err(e) => { + failed_deletions.push(format!("{}: {}", file_name, e)); + log::warn!(target: "app", "清理文件失败: {} - {}", file_name, e); + } + } + } + } + } + } + + let result = CleanupResult { + total_files, + deleted_files, + failed_deletions, + }; + + log::info!( + target: "app", + "Profile 文件清理完成: 总文件数={}, 删除文件数={}, 失败数={}", + result.total_files, + result.deleted_files.len(), + result.failed_deletions.len() + ); + + Ok(result) + } + + /// 不删除全局扩展配置 + fn get_protected_global_files(&self) -> HashSet { + let mut protected_files = HashSet::new(); + + protected_files.insert("Merge.yaml".to_string()); + protected_files.insert("Script.js".to_string()); + + protected_files + } + + /// 获取所有 active profile 关联的文件名 + fn get_all_active_files(&self) -> HashSet { + let mut active_files = HashSet::new(); + + if let Some(items) = &self.items { + for item in items { + // 收集所有类型 profile 的文件 + if let Some(file) = &item.file { + active_files.insert(file.clone()); + } + + // 对于主 profile 类型(remote/local),还需要收集其关联的扩展文件 + if let Some(itype) = &item.itype { + if itype == "remote" || itype == "local" { + if let Some(option) = &item.option { + // 收集关联的扩展文件 + if let Some(merge_uid) = &option.merge { + if let Ok(merge_item) = self.get_item(merge_uid) { + if let Some(file) = &merge_item.file { + active_files.insert(file.clone()); + } + } + } + + if let Some(script_uid) = &option.script { + if let Ok(script_item) = self.get_item(script_uid) { + if let Some(file) = &script_item.file { + active_files.insert(file.clone()); + } + } + } + + if let Some(rules_uid) = &option.rules { + if let Ok(rules_item) = self.get_item(rules_uid) { + if let Some(file) = &rules_item.file { + active_files.insert(file.clone()); + } + } + } + + if let Some(proxies_uid) = &option.proxies { + if let Ok(proxies_item) = self.get_item(proxies_uid) { + if let Some(file) = &proxies_item.file { + active_files.insert(file.clone()); + } + } + } + + if let Some(groups_uid) = &option.groups { + if let Ok(groups_item) = self.get_item(groups_uid) { + if let Some(file) = &groups_item.file { + active_files.insert(file.clone()); + } + } + } + } + } + } + } + } + + active_files + } + + /// 检查文件名是否符合 profile 文件的命名规则 + fn is_profile_file(filename: &str) -> bool { + // 匹配各种 profile 文件格式 + // R12345678.yaml (remote) + // L12345678.yaml (local) + // m12345678.yaml (merge) + // s12345678.js (script) + // r12345678.yaml (rules) + // p12345678.yaml (proxies) + // g12345678.yaml (groups) + + let patterns = [ + r"^[RL][a-zA-Z0-9]+\.yaml$", // Remote/Local profiles + r"^m[a-zA-Z0-9]+\.yaml$", // Merge files + r"^s[a-zA-Z0-9]+\.js$", // Script files + r"^[rpg][a-zA-Z0-9]+\.yaml$", // Rules/Proxies/Groups files + ]; + + patterns.iter().any(|pattern| { + regex::Regex::new(pattern) + .map(|re| re.is_match(filename)) + .unwrap_or(false) + }) + } + + pub fn auto_cleanup(&self) -> Result<()> { + match self.cleanup_orphaned_files() { + Ok(result) => { + if !result.deleted_files.is_empty() { + log::info!( + target: "app", + "自动清理完成,删除了 {} 个冗余文件", + result.deleted_files.len() + ); + } + Ok(()) + } + Err(e) => { + log::warn!(target: "app", "自动清理失败: {}", e); + Ok(()) + } + } + } } diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 57558566..862e0479 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -169,6 +169,15 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) { logging!(trace, Type::Config, true, "初始化配置..."); logging_error!(Type::Config, true, Config::init_config().await); + // 启动时清理冗余的 Profile 文件 + logging!(info, Type::Setup, true, "清理冗余的Profile文件..."); + let profiles = Config::profiles(); + if let Err(e) = profiles.latest().auto_cleanup() { + logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e); + } else { + logging!(info, Type::Setup, true, "启动时Profile文件清理完成"); + } + logging!(trace, Type::Core, true, "启动核心管理器..."); logging_error!(Type::Core, true, CoreManager::global().init().await);