From c736796380902376b643d2f03582779d61120ad4 Mon Sep 17 00:00:00 2001 From: Tunglies Date: Mon, 27 Oct 2025 20:55:51 +0800 Subject: [PATCH] feat(clippy): cognitive-complexity rule (#5215) * feat(config): enhance configuration initialization and validation process * refactor(profile): streamline profile update logic and enhance error handling * refactor(config): simplify profile item checks and streamline update flag processing * refactor(disney_plus): add cognitive complexity allowance for check_disney_plus function * refactor(enhance): restructure configuration and profile item handling for improved clarity and maintainability * refactor(tray): add cognitive complexity allowance for create_tray_menu function * refactor(config): add cognitive complexity allowance for patch_config function * refactor(profiles): simplify item removal logic by introducing take_item_file_by_uid helper function * refactor(profile): add new validation logic for profile configuration syntax * refactor(profiles): improve formatting and readability of take_item_file_by_uid function * refactor(cargo): change cognitive complexity level from warn to deny * refactor(cargo): ensure cognitive complexity is denied in Cargo.toml * refactor(i18n): clean up imports and improve code readability refactor(proxy): simplify system proxy toggle logic refactor(service): remove unnecessary `as_str()` conversion in error handling refactor(tray): modularize tray menu creation for better maintainability * refactor(tray): update menu item text handling to use references for improved performance --- src-tauri/.clippy.toml | 3 +- src-tauri/Cargo.toml | 1 + .../cmd/media_unlock_checker/disney_plus.rs | 1 + src-tauri/src/cmd/profile.rs | 513 +++++++++-------- src-tauri/src/cmd/service.rs | 2 +- src-tauri/src/config/config.rs | 50 +- src-tauri/src/config/profiles.rs | 87 +-- src-tauri/src/config/verge.rs | 1 + src-tauri/src/core/tray/mod.rs | 410 ++++++++------ src-tauri/src/enhance/mod.rs | 534 +++++++++++------- src-tauri/src/feat/config.rs | 275 ++++----- src-tauri/src/feat/profile.rs | 221 ++++---- src-tauri/src/feat/proxy.rs | 15 +- src-tauri/src/utils/i18n.rs | 1 + 14 files changed, 1130 insertions(+), 984 deletions(-) diff --git a/src-tauri/.clippy.toml b/src-tauri/.clippy.toml index 0e7ffd07..a1db40ca 100644 --- a/src-tauri/.clippy.toml +++ b/src-tauri/.clippy.toml @@ -1 +1,2 @@ -avoid-breaking-exported-api = true \ No newline at end of file +avoid-breaking-exported-api = true +cognitive-complexity-threshold = 25 \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4ac7cbd7..04302917 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -232,3 +232,4 @@ needless_raw_string_hashes = "deny" # Too many in existing code #restriction = { level = "allow", priority = -1 } or_fun_call = "deny" +cognitive_complexity = "deny" diff --git a/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs b/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs index d13133f2..dc18978e 100644 --- a/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs +++ b/src-tauri/src/cmd/media_unlock_checker/disney_plus.rs @@ -6,6 +6,7 @@ use crate::{logging, utils::logging::Type}; use super::UnlockItem; use super::utils::{country_code_to_emoji, get_local_date_string}; +#[allow(clippy::cognitive_complexity)] pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem { let device_api_url = "https://disney.api.edge.bamgrid.com/devices"; let auth_header = diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 766cea73..15177936 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -216,6 +216,257 @@ pub async fn delete_profile(index: String) -> CmdResult { Ok(()) } +/// 验证新配置文件的语法 +async fn validate_new_profile(new_profile: &String) -> Result<(), ()> { + logging!(info, Type::Cmd, "正在切换到新配置: {}", new_profile); + + // 获取目标配置文件路径 + let config_file_result = { + let profiles_config = Config::profiles().await; + let profiles_data = profiles_config.latest_ref(); + match profiles_data.get_item(new_profile) { + Ok(item) => { + if let Some(file) = &item.file { + let path = dirs::app_profiles_dir().map(|dir| dir.join(file.as_str())); + path.ok() + } else { + None + } + } + Err(e) => { + logging!(error, Type::Cmd, "获取目标配置信息失败: {}", e); + None + } + } + }; + + // 如果获取到文件路径,检查YAML语法 + if let Some(file_path) = config_file_result { + if !file_path.exists() { + logging!( + error, + Type::Cmd, + "目标配置文件不存在: {}", + file_path.display() + ); + handle::Handle::notice_message( + "config_validate::file_not_found", + format!("{}", file_path.display()), + ); + return Err(()); + } + + // 超时保护 + let file_read_result = tokio::time::timeout( + Duration::from_secs(5), + tokio::fs::read_to_string(&file_path), + ) + .await; + + match file_read_result { + Ok(Ok(content)) => { + let yaml_parse_result = AsyncHandler::spawn_blocking(move || { + serde_yaml_ng::from_str::(&content) + }) + .await; + + match yaml_parse_result { + Ok(Ok(_)) => { + logging!(info, Type::Cmd, "目标配置文件语法正确"); + Ok(()) + } + Ok(Err(err)) => { + let error_msg = format!(" {err}"); + logging!( + error, + Type::Cmd, + "目标配置文件存在YAML语法错误:{}", + error_msg + ); + handle::Handle::notice_message( + "config_validate::yaml_syntax_error", + error_msg.clone(), + ); + Err(()) + } + Err(join_err) => { + let error_msg = format!("YAML解析任务失败: {join_err}"); + logging!(error, Type::Cmd, "{}", error_msg); + handle::Handle::notice_message( + "config_validate::yaml_parse_error", + error_msg.clone(), + ); + Err(()) + } + } + } + Ok(Err(err)) => { + let error_msg = format!("无法读取目标配置文件: {err}"); + logging!(error, Type::Cmd, "{}", error_msg); + handle::Handle::notice_message( + "config_validate::file_read_error", + error_msg.clone(), + ); + Err(()) + } + Err(_) => { + let error_msg = "读取配置文件超时(5秒)".to_string(); + logging!(error, Type::Cmd, "{}", error_msg); + handle::Handle::notice_message( + "config_validate::file_read_timeout", + error_msg.clone(), + ); + Err(()) + } + } + } else { + Ok(()) + } +} + +/// 执行配置更新并处理结果 +async fn restore_previous_profile(prev_profile: String) -> CmdResult<()> { + logging!(info, Type::Cmd, "尝试恢复到之前的配置: {}", prev_profile); + let restore_profiles = IProfiles { + current: Some(prev_profile), + items: None, + }; + Config::profiles() + .await + .draft_mut() + .patch_config(restore_profiles) + .stringify_err()?; + Config::profiles().await.apply(); + crate::process::AsyncHandler::spawn(|| async move { + if let Err(e) = profiles_save_file_safe().await { + log::warn!(target: "app", "异步保存恢复配置文件失败: {e}"); + } + }); + logging!(info, Type::Cmd, "成功恢复到之前的配置"); + Ok(()) +} + +async fn handle_success(current_sequence: u64, current_value: Option) -> CmdResult { + let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); + if current_sequence < latest_sequence { + logging!( + info, + Type::Cmd, + "内核操作后发现更新的请求 (序列号: {} < {}),忽略当前结果", + current_sequence, + latest_sequence + ); + Config::profiles().await.discard(); + return Ok(false); + } + + logging!( + info, + Type::Cmd, + "配置更新成功,序列号: {}", + current_sequence + ); + Config::profiles().await.apply(); + handle::Handle::refresh_clash(); + + if let Err(e) = Tray::global().update_tooltip().await { + log::warn!(target: "app", "异步更新托盘提示失败: {e}"); + } + + if let Err(e) = Tray::global().update_menu().await { + log::warn!(target: "app", "异步更新托盘菜单失败: {e}"); + } + + if let Err(e) = profiles_save_file_safe().await { + log::warn!(target: "app", "异步保存配置文件失败: {e}"); + } + + if let Some(current) = ¤t_value { + logging!( + info, + Type::Cmd, + "向前端发送配置变更事件: {}, 序列号: {}", + current, + current_sequence + ); + handle::Handle::notify_profile_changed(current.clone()); + } + + CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); + Ok(true) +} + +async fn handle_validation_failure( + error_msg: String, + current_profile: Option, +) -> CmdResult { + logging!(warn, Type::Cmd, "配置验证失败: {}", error_msg); + Config::profiles().await.discard(); + if let Some(prev_profile) = current_profile { + restore_previous_profile(prev_profile).await?; + } + handle::Handle::notice_message("config_validate::error", error_msg); + CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); + Ok(false) +} + +async fn handle_update_error(e: E, current_sequence: u64) -> CmdResult { + logging!( + warn, + Type::Cmd, + "更新过程发生错误: {}, 序列号: {}", + e, + current_sequence + ); + Config::profiles().await.discard(); + handle::Handle::notice_message("config_validate::boot_error", e.to_string()); + CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); + Ok(false) +} + +async fn handle_timeout(current_profile: Option, current_sequence: u64) -> CmdResult { + let timeout_msg = "配置更新超时(30秒),可能是配置验证或核心通信阻塞"; + logging!( + error, + Type::Cmd, + "{}, 序列号: {}", + timeout_msg, + current_sequence + ); + Config::profiles().await.discard(); + if let Some(prev_profile) = current_profile { + restore_previous_profile(prev_profile).await?; + } + handle::Handle::notice_message("config_validate::timeout", timeout_msg); + CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); + Ok(false) +} + +async fn perform_config_update( + current_sequence: u64, + current_value: Option, + current_profile: Option, +) -> CmdResult { + logging!( + info, + Type::Cmd, + "开始内核配置更新,序列号: {}", + current_sequence + ); + let update_result = tokio::time::timeout( + Duration::from_secs(30), + CoreManager::global().update_config(), + ) + .await; + + match update_result { + Ok(Ok((true, _))) => handle_success(current_sequence, current_value).await, + Ok(Ok((false, error_msg))) => handle_validation_failure(error_msg, current_profile).await, + Ok(Err(e)) => handle_update_error(e, current_sequence).await, + Err(_) => handle_timeout(current_profile, current_sequence).await, + } +} + /// 修改profiles的配置 #[tauri::command] pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { @@ -256,108 +507,10 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { // 如果要切换配置,先检查目标配置文件是否有语法错误 if let Some(new_profile) = profiles.current.as_ref() && current_profile.as_ref() != Some(new_profile) + && validate_new_profile(new_profile).await.is_err() { - logging!(info, Type::Cmd, "正在切换到新配置: {}", new_profile); - - // 获取目标配置文件路径 - let config_file_result = { - let profiles_config = Config::profiles().await; - let profiles_data = profiles_config.latest_ref(); - match profiles_data.get_item(new_profile) { - Ok(item) => { - if let Some(file) = &item.file { - let path = dirs::app_profiles_dir().map(|dir| dir.join(file.as_str())); - path.ok() - } else { - None - } - } - Err(e) => { - logging!(error, Type::Cmd, "获取目标配置信息失败: {}", e); - None - } - } - }; - - // 如果获取到文件路径,检查YAML语法 - if let Some(file_path) = config_file_result { - if !file_path.exists() { - logging!( - error, - Type::Cmd, - "目标配置文件不存在: {}", - file_path.display() - ); - handle::Handle::notice_message( - "config_validate::file_not_found", - format!("{}", file_path.display()), - ); - return Ok(false); - } - - // 超时保护 - let file_read_result = tokio::time::timeout( - Duration::from_secs(5), - tokio::fs::read_to_string(&file_path), - ) - .await; - - match file_read_result { - Ok(Ok(content)) => { - let yaml_parse_result = AsyncHandler::spawn_blocking(move || { - serde_yaml_ng::from_str::(&content) - }) - .await; - - match yaml_parse_result { - Ok(Ok(_)) => { - logging!(info, Type::Cmd, "目标配置文件语法正确"); - } - Ok(Err(err)) => { - let error_msg = format!(" {err}"); - logging!( - error, - Type::Cmd, - "目标配置文件存在YAML语法错误:{}", - error_msg - ); - handle::Handle::notice_message( - "config_validate::yaml_syntax_error", - error_msg.clone(), - ); - return Ok(false); - } - Err(join_err) => { - let error_msg = format!("YAML解析任务失败: {join_err}"); - logging!(error, Type::Cmd, "{}", error_msg); - handle::Handle::notice_message( - "config_validate::yaml_parse_error", - error_msg.clone(), - ); - return Ok(false); - } - } - } - Ok(Err(err)) => { - let error_msg = format!("无法读取目标配置文件: {err}"); - logging!(error, Type::Cmd, "{}", error_msg); - handle::Handle::notice_message( - "config_validate::file_read_error", - error_msg.clone(), - ); - return Ok(false); - } - Err(_) => { - let error_msg = "读取配置文件超时(5秒)".to_string(); - logging!(error, Type::Cmd, "{}", error_msg); - handle::Handle::notice_message( - "config_validate::file_read_timeout", - error_msg.clone(), - ); - return Ok(false); - } - } - } + CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); + return Ok(false); } // 检查请求有效性 @@ -399,163 +552,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { return Ok(false); } - // 为配置更新添加超时保护 - logging!( - info, - Type::Cmd, - "开始内核配置更新,序列号: {}", - current_sequence - ); - let update_result = tokio::time::timeout( - Duration::from_secs(30), // 30秒超时 - CoreManager::global().update_config(), - ) - .await; - - // 更新配置并进行验证 - match update_result { - Ok(Ok((true, _))) => { - // 内核操作完成后再次检查请求有效性 - let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); - if current_sequence < latest_sequence { - logging!( - info, - Type::Cmd, - "内核操作后发现更新的请求 (序列号: {} < {}),忽略当前结果", - current_sequence, - latest_sequence - ); - Config::profiles().await.discard(); - return Ok(false); - } - - logging!( - info, - Type::Cmd, - "配置更新成功,序列号: {}", - current_sequence - ); - Config::profiles().await.apply(); - handle::Handle::refresh_clash(); - - // 强制刷新代理缓存,确保profile切换后立即获取最新节点数据 - // crate::process::AsyncHandler::spawn(|| async move { - // if let Err(e) = super::proxy::force_refresh_proxies().await { - // log::warn!(target: "app", "强制刷新代理缓存失败: {e}"); - // } - // }); - - if let Err(e) = Tray::global().update_tooltip().await { - log::warn!(target: "app", "异步更新托盘提示失败: {e}"); - } - - if let Err(e) = Tray::global().update_menu().await { - log::warn!(target: "app", "异步更新托盘菜单失败: {e}"); - } - - // 保存配置文件 - if let Err(e) = profiles_save_file_safe().await { - log::warn!(target: "app", "异步保存配置文件失败: {e}"); - } - - // 立即通知前端配置变更 - if let Some(current) = ¤t_value { - logging!( - info, - Type::Cmd, - "向前端发送配置变更事件: {}, 序列号: {}", - current, - current_sequence - ); - handle::Handle::notify_profile_changed(current.clone()); - } - - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); - Ok(true) - } - Ok(Ok((false, error_msg))) => { - logging!(warn, Type::Cmd, "配置验证失败: {}", error_msg); - Config::profiles().await.discard(); - // 如果验证失败,恢复到之前的配置 - if let Some(prev_profile) = current_profile { - logging!(info, Type::Cmd, "尝试恢复到之前的配置: {}", prev_profile); - let restore_profiles = IProfiles { - current: Some(prev_profile), - items: None, - }; - // 静默恢复,不触发验证 - Config::profiles() - .await - .draft_mut() - .patch_config(restore_profiles) - .stringify_err()?; - Config::profiles().await.apply(); - - crate::process::AsyncHandler::spawn(|| async move { - if let Err(e) = profiles_save_file_safe().await { - log::warn!(target: "app", "异步保存恢复配置文件失败: {e}"); - } - }); - - logging!(info, Type::Cmd, "成功恢复到之前的配置"); - } - - // 发送验证错误通知 - handle::Handle::notice_message("config_validate::error", error_msg.to_string()); - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); - Ok(false) - } - Ok(Err(e)) => { - logging!( - warn, - Type::Cmd, - "更新过程发生错误: {}, 序列号: {}", - e, - current_sequence - ); - Config::profiles().await.discard(); - handle::Handle::notice_message("config_validate::boot_error", e.to_string()); - - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); - Ok(false) - } - Err(_) => { - // 超时处理 - let timeout_msg = "配置更新超时(30秒),可能是配置验证或核心通信阻塞"; - logging!( - error, - Type::Cmd, - "{}, 序列号: {}", - timeout_msg, - current_sequence - ); - Config::profiles().await.discard(); - - if let Some(prev_profile) = current_profile { - logging!( - info, - Type::Cmd, - "超时后尝试恢复到之前的配置: {}, 序列号: {}", - prev_profile, - current_sequence - ); - let restore_profiles = IProfiles { - current: Some(prev_profile), - items: None, - }; - Config::profiles() - .await - .draft_mut() - .patch_config(restore_profiles) - .stringify_err()?; - Config::profiles().await.apply(); - } - - handle::Handle::notice_message("config_validate::timeout", timeout_msg); - CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst); - Ok(false) - } - } + perform_config_update(current_sequence, current_value, current_profile).await } /// 根据profile name修改profiles diff --git a/src-tauri/src/cmd/service.rs b/src-tauri/src/cmd/service.rs index 5231ea90..c2f557de 100644 --- a/src-tauri/src/cmd/service.rs +++ b/src-tauri/src/cmd/service.rs @@ -12,7 +12,7 @@ async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> .await { let emsg = format!("{} Service failed: {}", op_type, e); - return Err(t(emsg.as_str()).await.into()); + return Err(t(emsg.as_str()).await); } Ok(()) } diff --git a/src-tauri/src/config/config.rs b/src-tauri/src/config/config.rs index ced80e1d..dfbfed7c 100644 --- a/src-tauri/src/config/config.rs +++ b/src-tauri/src/config/config.rs @@ -53,24 +53,33 @@ impl Config { /// 初始化订阅 pub async fn init_config() -> Result<()> { - if Self::profiles() - .await - .latest_ref() - .get_item(&"Merge".into()) - .is_err() - { + Self::ensure_default_profile_items().await?; + + let validation_result = Self::generate_and_validate().await?; + + if let Some((msg_type, msg_content)) = validation_result { + sleep(timing::STARTUP_ERROR_DELAY).await; + handle::Handle::notice_message(msg_type, msg_content); + } + + Ok(()) + } + + // Ensure "Merge" and "Script" profile items exist, adding them if missing. + async fn ensure_default_profile_items() -> Result<()> { + let profiles = Self::profiles().await; + if profiles.latest_ref().get_item(&"Merge".into()).is_err() { let merge_item = PrfItem::from_merge(Some("Merge".into()))?; profiles_append_item_safe(merge_item.clone()).await?; } - if Self::profiles() - .await - .latest_ref() - .get_item(&"Script".into()) - .is_err() - { + if profiles.latest_ref().get_item(&"Script".into()).is_err() { let script_item = PrfItem::from_script(Some("Script".into()))?; profiles_append_item_safe(script_item.clone()).await?; } + Ok(()) + } + + async fn generate_and_validate() -> Result> { // 生成运行时配置 if let Err(err) = Self::generate().await { logging!(error, Type::Config, "生成运行时配置失败: {}", err); @@ -81,7 +90,7 @@ impl Config { // 生成运行时配置文件并验证 let config_result = Self::generate_file(ConfigType::Run).await; - let validation_result = if config_result.is_ok() { + if config_result.is_ok() { // 验证配置文件 logging!(info, Type::Config, "开始验证配置"); @@ -97,12 +106,12 @@ impl Config { CoreManager::global() .use_default_config("config_validate::boot_error", &error_msg) .await?; - Some(("config_validate::boot_error", error_msg)) + Ok(Some(("config_validate::boot_error", error_msg))) } else { logging!(info, Type::Config, "配置验证成功"); // 前端没有必要知道验证成功的消息,也没有事件驱动 // Some(("config_validate::success", String::new())) - None + Ok(None) } } Err(err) => { @@ -110,7 +119,7 @@ impl Config { CoreManager::global() .use_default_config("config_validate::process_terminated", "") .await?; - Some(("config_validate::process_terminated", String::new())) + Ok(Some(("config_validate::process_terminated", String::new()))) } } } else { @@ -118,15 +127,8 @@ impl Config { CoreManager::global() .use_default_config("config_validate::error", "") .await?; - Some(("config_validate::error", String::new())) - }; - - if let Some((msg_type, msg_content)) = validation_result { - sleep(timing::STARTUP_ERROR_DELAY).await; - handle::Handle::notice_message(msg_type, msg_content); + Ok(Some(("config_validate::error", String::new()))) } - - Ok(()) } pub async fn generate_file(typ: ConfigType) -> Result { diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index 776ee21a..1601fb66 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -37,6 +37,18 @@ macro_rules! patch { } impl IProfiles { + // Helper to find and remove an item by uid from the items vec, returning its file name (if any). + fn take_item_file_by_uid( + items: &mut Vec, + target_uid: Option, + ) -> Option { + for (i, _) in items.iter().enumerate() { + if items[i].uid == target_uid { + return items.remove(i).file; + } + } + None + } pub async fn new() -> Self { match dirs::profiles_path() { Ok(path) => match help::read_yaml::(&path).await { @@ -277,98 +289,41 @@ impl IProfiles { let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone()); let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone()); let mut items = self.items.take().unwrap_or_default(); - let mut index = None; - let mut merge_index = None; - let mut script_index = None; - let mut rules_index = None; - let mut proxies_index = None; - let mut groups_index = None; - // get the index - for (i, _) in items.iter().enumerate() { - if items[i].uid == Some(uid.clone()) { - index = Some(i); - break; - } - } - if let Some(index) = index - && let Some(file) = items.remove(index).file - { + // remove the main item (if exists) and delete its file + if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.clone())) { let _ = dirs::app_profiles_dir()? .join(file.as_str()) .remove_if_exists() .await; } - // get the merge index - for (i, _) in items.iter().enumerate() { - if items[i].uid == merge_uid { - merge_index = Some(i); - break; - } - } - if let Some(index) = merge_index - && let Some(file) = items.remove(index).file - { + + // remove related extension items (merge, script, rules, proxies, groups) + if let Some(file) = Self::take_item_file_by_uid(&mut items, merge_uid.clone()) { let _ = dirs::app_profiles_dir()? .join(file.as_str()) .remove_if_exists() .await; } - // get the script index - for (i, _) in items.iter().enumerate() { - if items[i].uid == script_uid { - script_index = Some(i); - break; - } - } - if let Some(index) = script_index - && let Some(file) = items.remove(index).file - { + if let Some(file) = Self::take_item_file_by_uid(&mut items, script_uid.clone()) { let _ = dirs::app_profiles_dir()? .join(file.as_str()) .remove_if_exists() .await; } - // get the rules index - for (i, _) in items.iter().enumerate() { - if items[i].uid == rules_uid { - rules_index = Some(i); - break; - } - } - if let Some(index) = rules_index - && let Some(file) = items.remove(index).file - { + if let Some(file) = Self::take_item_file_by_uid(&mut items, rules_uid.clone()) { let _ = dirs::app_profiles_dir()? .join(file.as_str()) .remove_if_exists() .await; } - // get the proxies index - for (i, _) in items.iter().enumerate() { - if items[i].uid == proxies_uid { - proxies_index = Some(i); - break; - } - } - if let Some(index) = proxies_index - && let Some(file) = items.remove(index).file - { + if let Some(file) = Self::take_item_file_by_uid(&mut items, proxies_uid.clone()) { let _ = dirs::app_profiles_dir()? .join(file.as_str()) .remove_if_exists() .await; } - // get the groups index - for (i, _) in items.iter().enumerate() { - if items[i].uid == groups_uid { - groups_index = Some(i); - break; - } - } - if let Some(index) = groups_index - && let Some(file) = items.remove(index).file - { + if let Some(file) = Self::take_item_file_by_uid(&mut items, groups_uid.clone()) { let _ = dirs::app_profiles_dir()? .join(file.as_str()) .remove_if_exists() diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index 8533231e..3207b57c 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -438,6 +438,7 @@ impl IVerge { /// patch verge config /// only save to file + #[allow(clippy::cognitive_complexity)] pub fn patch_config(&mut self, patch: IVerge) { macro_rules! patch { ($key: tt) => { diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 2295c811..84f934a1 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -1,8 +1,10 @@ use once_cell::sync::OnceCell; use tauri::Emitter; use tauri::tray::TrayIconBuilder; +use tauri_plugin_mihomo::models::Proxies; #[cfg(target_os = "macos")] pub mod speed_rate; +use crate::config::PrfSelected; use crate::module::lightweight; use crate::process::AsyncHandler; use crate::utils::window_manager::WindowManager; @@ -34,6 +36,56 @@ use tauri::{ // TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑) +type ProxyMenuItem = (Option>, Vec>>); + +struct MenuTexts { + dashboard: String, + rule_mode: String, + global_mode: String, + direct_mode: String, + profiles: String, + proxies: String, + system_proxy: String, + tun_mode: String, + close_all_connections: String, + lightweight_mode: String, + copy_env: String, + conf_dir: String, + core_dir: String, + logs_dir: String, + open_dir: String, + restart_clash: String, + restart_app: String, + verge_version: String, + more: String, + exit: String, +} + +async fn fetch_menu_texts() -> MenuTexts { + MenuTexts { + dashboard: t("Dashboard").await, + rule_mode: t("Rule Mode").await, + global_mode: t("Global Mode").await, + direct_mode: t("Direct Mode").await, + profiles: t("Profiles").await, + proxies: t("Proxies").await, + system_proxy: t("System Proxy").await, + tun_mode: t("TUN Mode").await, + close_all_connections: t("Close All Connections").await, + lightweight_mode: t("LightWeight Mode").await, + copy_env: t("Copy Env").await, + conf_dir: t("Conf Dir").await, + core_dir: t("Core Dir").await, + logs_dir: t("Logs Dir").await, + open_dir: t("Open Dir").await, + restart_clash: t("Restart Clash Core").await, + restart_app: t("Restart App").await, + verge_version: t("Verge Version").await, + more: t("More").await, + exit: t("Exit").await, + } +} + #[derive(Clone)] struct TrayState {} @@ -606,70 +658,8 @@ impl Tray { } } -async fn create_tray_menu( - app_handle: &AppHandle, - mode: Option<&str>, - system_proxy_enabled: bool, - tun_mode_enabled: bool, - profile_uid_and_name: Vec<(String, String)>, - is_lightweight_mode: bool, -) -> Result> { - let mode = mode.unwrap_or(""); - - // 获取当前配置文件的选中代理组信息 - let current_profile_selected = { - let profiles_config = Config::profiles().await; - let profiles_ref = profiles_config.latest_ref(); - profiles_ref - .get_current() - .and_then(|uid| profiles_ref.get_item(&uid).ok()) - .and_then(|profile| profile.selected.clone()) - .unwrap_or_default() - }; - - let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await; - - let runtime_proxy_groups_order = cmd::get_runtime_config() - .await - .map_err(|e| { - logging!( - error, - Type::Cmd, - "Failed to fetch runtime proxy groups for tray menu: {e}" - ); - }) - .ok() - .flatten() - .map(|config| { - config - .get("proxy-groups") - .and_then(|groups| groups.as_sequence()) - .map(|groups| { - groups - .iter() - .filter_map(|group| group.get("name")) - .filter_map(|name| name.as_str()) - .map(|name| name.into()) - .collect::>() - }) - .unwrap_or_default() - }); - - let proxy_group_order_map = runtime_proxy_groups_order.as_ref().map(|group_names| { - group_names - .iter() - .enumerate() - .map(|(index, name)| (name.clone(), index)) - .collect::>() - }); - - let verge_settings = Config::verge().await.latest_ref().clone(); - let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false); - - let version = env!("CARGO_PKG_VERSION"); - - let hotkeys = verge_settings - .hotkeys +fn create_hotkeys(hotkeys: &Option>) -> HashMap { + hotkeys .as_ref() .map(|h| { h.iter() @@ -689,35 +679,45 @@ async fn create_tray_menu( }) .collect::>() }) - .unwrap_or_default(); + .unwrap_or_default() +} - let profile_menu_items: Vec> = { - let futures = profile_uid_and_name - .iter() - .map(|(profile_uid, profile_name)| { - let app_handle = app_handle.clone(); - let profile_uid = profile_uid.clone(); - let profile_name = profile_name.clone(); - async move { - let is_current_profile = Config::profiles() - .await - .data_mut() - .is_current_profile_index(profile_uid.clone()); - CheckMenuItem::with_id( - &app_handle, - format!("profiles_{profile_uid}"), - t(&profile_name).await, - true, - is_current_profile, - None::<&str>, - ) - } - }); - let results = join_all(futures).await; - results.into_iter().collect::, _>>()? - }; +async fn create_profile_menu_item( + app_handle: &AppHandle, + profile_uid_and_name: Vec<(String, String)>, +) -> Result>> { + let futures = profile_uid_and_name + .iter() + .map(|(profile_uid, profile_name)| { + let app_handle = app_handle.clone(); + let profile_uid = profile_uid.clone(); + let profile_name = profile_name.clone(); + async move { + let is_current_profile = Config::profiles() + .await + .latest_ref() + .is_current_profile_index(profile_uid.clone()); + CheckMenuItem::with_id( + &app_handle, + format!("profiles_{profile_uid}"), + t(&profile_name).await, + true, + is_current_profile, + None::<&str>, + ) + } + }); + let results = join_all(futures).await; + Ok(results.into_iter().collect::, _>>()?) +} - // 代理组子菜单 +fn create_subcreate_proxy_menu_item( + app_handle: &AppHandle, + proxy_mode: &str, + current_profile_selected: &[PrfSelected], + proxy_group_order_map: Option>, + proxy_nodes_data: Result, +) -> Result>> { let proxy_submenus: Vec> = { let mut submenus: Vec<(String, usize, Submenu)> = Vec::new(); @@ -725,7 +725,7 @@ async fn create_tray_menu( if let Ok(proxy_nodes_data) = proxy_nodes_data { for (group_name, group_data) in proxy_nodes_data.proxies.iter() { // Filter groups based on mode - let should_show = match mode { + let should_show = match proxy_mode { "global" => group_name == "GLOBAL", _ => group_name != "GLOBAL", } && @@ -781,7 +781,7 @@ async fn create_tray_menu( } // Determine if group is active - let is_group_active = match mode { + let is_group_active = match proxy_mode { "global" => group_name == "GLOBAL" && !now_proxy.is_empty(), "direct" => false, _ => { @@ -837,28 +837,117 @@ async fn create_tray_menu( .map(|(_, _, submenu)| submenu) .collect() }; + Ok(proxy_submenus) +} + +fn create_proxy_menu_item( + app_handle: &AppHandle, + show_proxy_groups_inline: bool, + proxy_submenus: Vec>, + proxies_text: &String, +) -> Result { + // 创建代理主菜单 + let (proxies_submenu, inline_proxy_items) = if show_proxy_groups_inline { + ( + None, + proxy_submenus + .into_iter() + .map(|submenu| Box::new(submenu) as Box>) + .collect(), + ) + } else if !proxy_submenus.is_empty() { + let proxy_submenu_refs: Vec<&dyn IsMenuItem> = proxy_submenus + .iter() + .map(|submenu| submenu as &dyn IsMenuItem) + .collect(); + + ( + Some(Submenu::with_id_and_items( + app_handle, + "proxies", + proxies_text, + true, + &proxy_submenu_refs, + )?), + Vec::new(), + ) + } else { + (None, Vec::new()) + }; + Ok((proxies_submenu, inline_proxy_items)) +} + +async fn create_tray_menu( + app_handle: &AppHandle, + mode: Option<&str>, + system_proxy_enabled: bool, + tun_mode_enabled: bool, + profile_uid_and_name: Vec<(String, String)>, + is_lightweight_mode: bool, +) -> Result> { + let current_proxy_mode = mode.unwrap_or(""); + + // 获取当前配置文件的选中代理组信息 + let current_profile_selected = { + let profiles_config = Config::profiles().await; + let profiles_ref = profiles_config.latest_ref(); + profiles_ref + .get_current() + .and_then(|uid| profiles_ref.get_item(&uid).ok()) + .and_then(|profile| profile.selected.clone()) + .unwrap_or_default() + }; + + let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await; + + let runtime_proxy_groups_order = cmd::get_runtime_config() + .await + .map_err(|e| { + logging!( + error, + Type::Cmd, + "Failed to fetch runtime proxy groups for tray menu: {e}" + ); + }) + .ok() + .flatten() + .map(|config| { + config + .get("proxy-groups") + .and_then(|groups| groups.as_sequence()) + .map(|groups| { + groups + .iter() + .filter_map(|group| group.get("name")) + .filter_map(|name| name.as_str()) + .map(|name| name.into()) + .collect::>() + }) + .unwrap_or_default() + }); + + let proxy_group_order_map: Option< + HashMap, usize>, + > = runtime_proxy_groups_order.as_ref().map(|group_names| { + group_names + .iter() + .enumerate() + .map(|(index, name)| (name.clone(), index)) + .collect::>() + }); + + let verge_settings = Config::verge().await.latest_ref().clone(); + let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false); + + let version = env!("CARGO_PKG_VERSION"); + + let hotkeys = create_hotkeys(&verge_settings.hotkeys); + + let profile_menu_items: Vec> = + create_profile_menu_item(app_handle, profile_uid_and_name).await?; // Pre-fetch all localized strings - let dashboard_text = t("Dashboard").await; - let rule_mode_text = t("Rule Mode").await; - let global_mode_text = t("Global Mode").await; - let direct_mode_text = t("Direct Mode").await; - let profiles_text = t("Profiles").await; - let proxies_text = t("Proxies").await; - let system_proxy_text = t("System Proxy").await; - let tun_mode_text = t("TUN Mode").await; - let close_all_connections_text = t("Close All Connections").await; - let lightweight_mode_text = t("LightWeight Mode").await; - let copy_env_text = t("Copy Env").await; - let conf_dir_text = t("Conf Dir").await; - let core_dir_text = t("Core Dir").await; - let logs_dir_text = t("Logs Dir").await; - let open_dir_text = t("Open Dir").await; - let restart_clash_text = t("Restart Clash Core").await; - let restart_app_text = t("Restart App").await; - let verge_version_text = t("Verge Version").await; - let more_text = t("More").await; - let exit_text = t("Exit").await; + let texts = &fetch_menu_texts().await; // Convert to references only when needed let profile_menu_items_refs: Vec<&dyn IsMenuItem> = profile_menu_items @@ -869,7 +958,7 @@ async fn create_tray_menu( let open_window = &MenuItem::with_id( app_handle, "open_window", - dashboard_text, + &texts.dashboard, true, hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()), )?; @@ -877,72 +966,57 @@ async fn create_tray_menu( let rule_mode = &CheckMenuItem::with_id( app_handle, "rule_mode", - rule_mode_text, + &texts.rule_mode, true, - mode == "rule", + current_proxy_mode == "rule", hotkeys.get("clash_mode_rule").map(|s| s.as_str()), )?; let global_mode = &CheckMenuItem::with_id( app_handle, "global_mode", - global_mode_text, + &texts.global_mode, true, - mode == "global", + current_proxy_mode == "global", hotkeys.get("clash_mode_global").map(|s| s.as_str()), )?; let direct_mode = &CheckMenuItem::with_id( app_handle, "direct_mode", - direct_mode_text, + &texts.direct_mode, true, - mode == "direct", + current_proxy_mode == "direct", hotkeys.get("clash_mode_direct").map(|s| s.as_str()), )?; let profiles = &Submenu::with_id_and_items( app_handle, "profiles", - profiles_text, + &texts.profiles, true, &profile_menu_items_refs, )?; - // 创建代理主菜单 - let (proxies_submenu, inline_proxy_items): (Option>, Vec<&dyn IsMenuItem>) = - if show_proxy_groups_inline { - ( - None, - proxy_submenus - .iter() - .map(|submenu| submenu as &dyn IsMenuItem) - .collect(), - ) - } else if !proxy_submenus.is_empty() { - let proxy_submenu_refs: Vec<&dyn IsMenuItem> = proxy_submenus - .iter() - .map(|submenu| submenu as &dyn IsMenuItem) - .collect(); + let proxy_sub_menus = create_subcreate_proxy_menu_item( + app_handle, + current_proxy_mode, + ¤t_profile_selected, + proxy_group_order_map, + proxy_nodes_data.map_err(anyhow::Error::from), + )?; - ( - Some(Submenu::with_id_and_items( - app_handle, - "proxies", - proxies_text, - true, - &proxy_submenu_refs, - )?), - Vec::new(), - ) - } else { - (None, Vec::new()) - }; + let (proxies_menu, inline_proxy_items) = create_proxy_menu_item( + app_handle, + show_proxy_groups_inline, + proxy_sub_menus, + &texts.proxies, + )?; let system_proxy = &CheckMenuItem::with_id( app_handle, "system_proxy", - system_proxy_text, + &texts.system_proxy, true, system_proxy_enabled, hotkeys.get("toggle_system_proxy").map(|s| s.as_str()), @@ -951,7 +1025,7 @@ async fn create_tray_menu( let tun_mode = &CheckMenuItem::with_id( app_handle, "tun_mode", - tun_mode_text, + &texts.tun_mode, true, tun_mode_enabled, hotkeys.get("toggle_tun_mode").map(|s| s.as_str()), @@ -960,7 +1034,7 @@ async fn create_tray_menu( let close_all_connections = &MenuItem::with_id( app_handle, "close_all_connections", - close_all_connections_text, + &texts.close_all_connections, true, None::<&str>, )?; @@ -968,18 +1042,18 @@ async fn create_tray_menu( let lighteweight_mode = &CheckMenuItem::with_id( app_handle, "entry_lightweight_mode", - lightweight_mode_text, + &texts.lightweight_mode, true, is_lightweight_mode, hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()), )?; - let copy_env = &MenuItem::with_id(app_handle, "copy_env", copy_env_text, true, None::<&str>)?; + let copy_env = &MenuItem::with_id(app_handle, "copy_env", &texts.copy_env, true, None::<&str>)?; let open_app_dir = &MenuItem::with_id( app_handle, "open_app_dir", - conf_dir_text, + &texts.conf_dir, true, None::<&str>, )?; @@ -987,7 +1061,7 @@ async fn create_tray_menu( let open_core_dir = &MenuItem::with_id( app_handle, "open_core_dir", - core_dir_text, + &texts.core_dir, true, None::<&str>, )?; @@ -995,7 +1069,7 @@ async fn create_tray_menu( let open_logs_dir = &MenuItem::with_id( app_handle, "open_logs_dir", - logs_dir_text, + &texts.logs_dir, true, None::<&str>, )?; @@ -1003,7 +1077,7 @@ async fn create_tray_menu( let open_dir = &Submenu::with_id_and_items( app_handle, "open_dir", - open_dir_text, + &texts.open_dir, true, &[open_app_dir, open_core_dir, open_logs_dir], )?; @@ -1011,7 +1085,7 @@ async fn create_tray_menu( let restart_clash = &MenuItem::with_id( app_handle, "restart_clash", - restart_clash_text, + &texts.restart_clash, true, None::<&str>, )?; @@ -1019,7 +1093,7 @@ async fn create_tray_menu( let restart_app = &MenuItem::with_id( app_handle, "restart_app", - restart_app_text, + &texts.restart_app, true, None::<&str>, )?; @@ -1027,7 +1101,7 @@ async fn create_tray_menu( let app_version = &MenuItem::with_id( app_handle, "app_version", - format!("{} {version}", verge_version_text), + format!("{} {version}", &texts.verge_version), true, None::<&str>, )?; @@ -1035,7 +1109,7 @@ async fn create_tray_menu( let more = &Submenu::with_id_and_items( app_handle, "more", - more_text, + &texts.more, true, &[ close_all_connections, @@ -1045,7 +1119,13 @@ async fn create_tray_menu( ], )?; - let quit = &MenuItem::with_id(app_handle, "quit", exit_text, true, Some("CmdOrControl+Q"))?; + let quit = &MenuItem::with_id( + app_handle, + "quit", + &texts.exit, + true, + Some("CmdOrControl+Q"), + )?; let separator = &PredefinedMenuItem::separator(app_handle)?; @@ -1063,9 +1143,9 @@ async fn create_tray_menu( // 如果有代理节点,添加代理节点菜单 if show_proxy_groups_inline { if !inline_proxy_items.is_empty() { - menu_items.extend_from_slice(&inline_proxy_items); + menu_items.extend(inline_proxy_items.iter().map(|item| item.as_ref())); } - } else if let Some(ref proxies_menu) = proxies_submenu { + } else if let Some(ref proxies_menu) = proxies_menu { menu_items.push(proxies_menu); } diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index 704f199f..fc8089c0 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -12,11 +12,35 @@ use smartstring::alias::String; use std::collections::{HashMap, HashSet}; type ResultLog = Vec<(String, String)>; +#[derive(Debug)] +struct ConfigValues { + clash_config: Mapping, + clash_core: Option, + enable_tun: bool, + enable_builtin: bool, + socks_enabled: bool, + http_enabled: bool, + enable_dns_settings: bool, + #[cfg(not(target_os = "windows"))] + redir_enabled: bool, + #[cfg(target_os = "linux")] + tproxy_enabled: bool, +} -/// Enhance mode -/// 返回最终订阅、该订阅包含的键、和script执行的结果 -pub async fn enhance() -> (Mapping, Vec, HashMap) { - // config.yaml 的订阅 +#[derive(Debug)] +struct ProfileItems { + config: Mapping, + merge_item: ChainItem, + script_item: ChainItem, + rules_item: ChainItem, + proxies_item: ChainItem, + groups_item: ChainItem, + global_merge: ChainItem, + global_script: ChainItem, + profile_name: String, +} + +async fn get_config_values() -> ConfigValues { let clash_config = { Config::clash().await.latest_ref().0.clone() }; let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = { @@ -31,12 +55,14 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { verge.enable_dns_settings.unwrap_or(false), ) }; + #[cfg(not(target_os = "windows"))] let redir_enabled = { let verge = Config::verge().await; let verge = verge.latest_ref(); verge.verge_redir_enabled.unwrap_or(false) }; + #[cfg(target_os = "linux")] let tproxy_enabled = { let verge = Config::verge().await; @@ -44,9 +70,189 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { verge.verge_tproxy_enabled.unwrap_or(false) }; + ConfigValues { + clash_config, + clash_core, + enable_tun, + enable_builtin, + socks_enabled, + http_enabled, + enable_dns_settings, + #[cfg(not(target_os = "windows"))] + redir_enabled, + #[cfg(target_os = "linux")] + tproxy_enabled, + } +} + +async fn collect_profile_items() -> ProfileItems { // 从profiles里拿东西 - 先收集需要的数据,然后释放锁 let ( - mut config, + current, + merge_uid, + script_uid, + rules_uid, + proxies_uid, + groups_uid, + _current_profile_uid, + name, + ) = { + let current = { + let profiles = Config::profiles().await; + let profiles_clone = profiles.latest_ref().clone(); + profiles_clone.current_mapping().await.unwrap_or_default() + }; + + let profiles = Config::profiles().await; + let profiles_ref = profiles.latest_ref(); + + let merge_uid = profiles_ref.current_merge().unwrap_or_default(); + let script_uid = profiles_ref.current_script().unwrap_or_default(); + let rules_uid = profiles_ref.current_rules().unwrap_or_default(); + let proxies_uid = profiles_ref.current_proxies().unwrap_or_default(); + let groups_uid = profiles_ref.current_groups().unwrap_or_default(); + let current_profile_uid = profiles_ref.get_current().unwrap_or_default(); + + let name = profiles_ref + .get_item(¤t_profile_uid) + .ok() + .and_then(|item| item.name.clone()) + .unwrap_or_default(); + + ( + current, + merge_uid, + script_uid, + rules_uid, + proxies_uid, + groups_uid, + current_profile_uid, + name, + ) + }; + + // 现在获取具体的items,此时profiles锁已经释放 + let merge_item = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&merge_uid).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Merge(Mapping::new()), + }); + + let script_item = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&script_uid).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), + }); + + let rules_item = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&rules_uid).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Rules(SeqMap::default()), + }); + + let proxies_item = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&proxies_uid).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Proxies(SeqMap::default()), + }); + + let groups_item = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&groups_uid).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Groups(SeqMap::default()), + }); + + let global_merge = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&"Merge".into()).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "Merge".into(), + data: ChainType::Merge(Mapping::new()), + }); + + let global_script = { + let item = { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + profiles.get_item(&"Script".into()).ok().cloned() + }; + if let Some(item) = item { + >::from_async(&item).await + } else { + None + } + } + .unwrap_or_else(|| ChainItem { + uid: "Script".into(), + data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), + }); + + ProfileItems { + config: current, merge_item, script_item, rules_item, @@ -54,192 +260,19 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { groups_item, global_merge, global_script, - profile_name, - ) = { - // 收集所有需要的数据,然后释放profiles锁 - let ( - current, - merge_uid, - script_uid, - rules_uid, - proxies_uid, - groups_uid, - _current_profile_uid, - name, - ) = { - // 分离async调用和数据获取,避免借用检查问题 - let current = { - let profiles = Config::profiles().await; - let profiles_clone = profiles.latest_ref().clone(); - profiles_clone.current_mapping().await.unwrap_or_default() - }; + profile_name: name, + } +} - // 重新获取锁进行其他操作 - let profiles = Config::profiles().await; - let profiles_ref = profiles.latest_ref(); +fn process_global_items( + mut config: Mapping, + global_merge: ChainItem, + global_script: ChainItem, + profile_name: String, +) -> (Mapping, Vec, HashMap) { + let mut result_map = HashMap::new(); + let mut exists_keys = use_keys(&config); - let merge_uid = profiles_ref.current_merge().unwrap_or_default(); - let script_uid = profiles_ref.current_script().unwrap_or_default(); - let rules_uid = profiles_ref.current_rules().unwrap_or_default(); - let proxies_uid = profiles_ref.current_proxies().unwrap_or_default(); - let groups_uid = profiles_ref.current_groups().unwrap_or_default(); - let current_profile_uid = profiles_ref.get_current().unwrap_or_default(); - - let name = profiles_ref - .get_item(¤t_profile_uid) - .ok() - .and_then(|item| item.name.clone()) - .unwrap_or_default(); - - ( - current, - merge_uid, - script_uid, - rules_uid, - proxies_uid, - groups_uid, - current_profile_uid, - name, - ) - }; - - // 现在获取具体的items,此时profiles锁已经释放 - let merge = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&merge_uid).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "".into(), - data: ChainType::Merge(Mapping::new()), - }); - - let script = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&script_uid).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "".into(), - data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), - }); - - let rules = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&rules_uid).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "".into(), - data: ChainType::Rules(SeqMap::default()), - }); - - let proxies = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&proxies_uid).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "".into(), - data: ChainType::Proxies(SeqMap::default()), - }); - - let groups = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&groups_uid).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "".into(), - data: ChainType::Groups(SeqMap::default()), - }); - - let global_merge = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&"Merge".into()).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "Merge".into(), - data: ChainType::Merge(Mapping::new()), - }); - - let global_script = { - let item = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - profiles.get_item(&"Script".into()).ok().cloned() - }; - if let Some(item) = item { - >::from_async(&item).await - } else { - None - } - } - .unwrap_or_else(|| ChainItem { - uid: "Script".into(), - data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), - }); - - ( - current, - merge, - script, - rules, - proxies, - groups, - global_merge, - global_script, - name, - ) - }; - - let mut result_map = HashMap::new(); // 保存脚本日志 - let mut exists_keys = use_keys(&config); // 保存出现过的keys - - // 全局Merge和Script if let ChainType::Merge(merge) = global_merge.data { exists_keys.extend(use_keys(&merge)); config = use_merge(merge, config.to_owned()); @@ -247,7 +280,6 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { if let ChainType::Script(script) = global_script.data { let mut logs = vec![]; - match use_script(script, config.to_owned(), profile_name.to_owned()) { Ok((res_config, res_logs)) => { exists_keys.extend(use_keys(&res_config)); @@ -256,11 +288,24 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { } Err(err) => logs.push(("exception".into(), err.to_string().into())), } - result_map.insert(global_script.uid, logs); } - // 订阅关联的Merge、Script、Rules、Proxies、Groups + (config, exists_keys, result_map) +} + +#[allow(clippy::too_many_arguments)] +fn process_profile_items( + mut config: Mapping, + mut exists_keys: Vec, + mut result_map: HashMap, + rules_item: ChainItem, + proxies_item: ChainItem, + groups_item: ChainItem, + merge_item: ChainItem, + script_item: ChainItem, + profile_name: String, +) -> (Mapping, Vec, HashMap) { if let ChainType::Rules(rules) = rules_item.data { config = use_seq(rules, config.to_owned(), "rules"); } @@ -280,7 +325,6 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { if let ChainType::Script(script) = script_item.data { let mut logs = vec![]; - match use_script(script, config.to_owned(), profile_name) { Ok((res_config, res_logs)) => { exists_keys.extend(use_keys(&res_config)); @@ -289,11 +333,20 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { } Err(err) => logs.push(("exception".into(), err.to_string().into())), } - result_map.insert(script_item.uid, logs); } - // 合并默认的config + (config, exists_keys, result_map) +} + +async fn merge_default_config( + mut config: Mapping, + clash_config: Mapping, + socks_enabled: bool, + http_enabled: bool, + #[cfg(not(target_os = "windows"))] redir_enabled: bool, + #[cfg(target_os = "linux")] tproxy_enabled: bool, +) -> Mapping { for (key, value) in clash_config.into_iter() { if key.as_str() == Some("tun") { let mut tun = config.get_mut("tun").map_or_else(Mapping::new, |val| { @@ -353,7 +406,14 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { } } - // 内建脚本最后跑 + config +} + +fn apply_builtin_scripts( + mut config: Mapping, + clash_core: Option, + enable_builtin: bool, +) -> Mapping { if enable_builtin { ChainItem::builtin() .into_iter() @@ -374,10 +434,10 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { }); } - config = use_tun(config, enable_tun); - config = use_sort(config); + config +} - // 应用独立的DNS配置(如果启用) +fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping { if enable_dns_settings { use crate::utils::dirs; use std::fs; @@ -389,7 +449,6 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { && let Ok(dns_yaml) = fs::read_to_string(&dns_path) && let Ok(dns_config) = serde_yaml_ng::from_str::(&dns_yaml) { - // 处理hosts配置 if let Some(hosts_value) = dns_config.get("hosts") && hosts_value.is_mapping() { @@ -410,9 +469,82 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { } } + config +} + +/// Enhance mode +/// 返回最终订阅、该订阅包含的键、和script执行的结果 +pub async fn enhance() -> (Mapping, Vec, HashMap) { + // gather config values + let cfg_vals = get_config_values().await; + let ConfigValues { + clash_config, + clash_core, + enable_tun, + enable_builtin, + socks_enabled, + http_enabled, + enable_dns_settings, + #[cfg(not(target_os = "windows"))] + redir_enabled, + #[cfg(target_os = "linux")] + tproxy_enabled, + } = cfg_vals; + + // collect profile items + let profile = collect_profile_items().await; + let config = profile.config; + let merge_item = profile.merge_item; + let script_item = profile.script_item; + let rules_item = profile.rules_item; + let proxies_item = profile.proxies_item; + let groups_item = profile.groups_item; + let global_merge = profile.global_merge; + let global_script = profile.global_script; + let profile_name = profile.profile_name; + + // process globals + let (config, exists_keys, result_map) = + process_global_items(config, global_merge, global_script, profile_name.clone()); + + // process profile-specific items + let (config, exists_keys, result_map) = process_profile_items( + config, + exists_keys, + result_map, + rules_item, + proxies_item, + groups_item, + merge_item, + script_item, + profile_name, + ); + + // merge default clash config + let config = merge_default_config( + config, + clash_config, + socks_enabled, + http_enabled, + #[cfg(not(target_os = "windows"))] + redir_enabled, + #[cfg(target_os = "linux")] + tproxy_enabled, + ) + .await; + + // builtin scripts + let mut config = apply_builtin_scripts(config, clash_core, enable_builtin); + + config = use_tun(config, enable_tun); + config = use_sort(config); + + // dns settings + config = apply_dns_settings(config, enable_dns_settings); + let mut exists_set = HashSet::new(); exists_set.extend(exists_keys); - exists_keys = exists_set.into_iter().collect(); + let exists_keys: Vec = exists_set.into_iter().collect(); (config, exists_keys, result_map) } diff --git a/src-tauri/src/feat/config.rs b/src-tauri/src/feat/config.rs index 506f5fa6..797bd601 100644 --- a/src-tauri/src/feat/config.rs +++ b/src-tauri/src/feat/config.rs @@ -63,23 +63,19 @@ enum UpdateFlags { LighteWeight = 1 << 10, } -/// Patch Verge configuration -pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { - Config::verge() - .await - .draft_mut() - .patch_config(patch.clone()); +fn determine_update_flags(patch: &IVerge) -> i32 { + let mut update_flags: i32 = UpdateFlags::None as i32; let tun_mode = patch.enable_tun_mode; let auto_launch = patch.enable_auto_launch; let system_proxy = patch.enable_system_proxy; let pac = patch.proxy_auto_config; - let pac_content = patch.pac_file_content; - let proxy_bypass = patch.system_proxy_bypass; - let language = patch.language; + let pac_content = &patch.pac_file_content; + let proxy_bypass = &patch.system_proxy_bypass; + let language = &patch.language; let mixed_port = patch.verge_mixed_port; #[cfg(target_os = "macos")] - let tray_icon = patch.tray_icon; + let tray_icon = &patch.tray_icon; #[cfg(not(target_os = "macos"))] let tray_icon: Option = None; let common_tray_icon = patch.common_tray_icon; @@ -100,145 +96,152 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { let enable_tray_speed = patch.enable_tray_speed; let enable_tray_icon = patch.enable_tray_icon; let enable_global_hotkey = patch.enable_global_hotkey; - let tray_event = patch.tray_event; + let tray_event = &patch.tray_event; let home_cards = patch.home_cards.clone(); let enable_auto_light_weight = patch.enable_auto_light_weight_mode; let enable_external_controller = patch.enable_external_controller; - let res: std::result::Result<(), anyhow::Error> = { - // Initialize with no flags set - let mut update_flags: i32 = UpdateFlags::None as i32; - if tun_mode.is_some() { - update_flags |= UpdateFlags::ClashConfig as i32; - update_flags |= UpdateFlags::SystrayMenu as i32; - update_flags |= UpdateFlags::SystrayTooltip as i32; - update_flags |= UpdateFlags::SystrayIcon as i32; - } - if enable_global_hotkey.is_some() || home_cards.is_some() { - update_flags |= UpdateFlags::VergeConfig as i32; - } - #[cfg(not(target_os = "windows"))] - if redir_enabled.is_some() || redir_port.is_some() { - update_flags |= UpdateFlags::RestartCore as i32; - } - #[cfg(target_os = "linux")] - if tproxy_enabled.is_some() || tproxy_port.is_some() { - update_flags |= UpdateFlags::RestartCore as i32; - } - if socks_enabled.is_some() - || http_enabled.is_some() - || socks_port.is_some() - || http_port.is_some() - || mixed_port.is_some() - { - update_flags |= UpdateFlags::RestartCore as i32; - } - if auto_launch.is_some() { - update_flags |= UpdateFlags::Launch as i32; - } + if tun_mode.is_some() { + update_flags |= UpdateFlags::ClashConfig as i32; + update_flags |= UpdateFlags::SystrayMenu as i32; + update_flags |= UpdateFlags::SystrayTooltip as i32; + update_flags |= UpdateFlags::SystrayIcon as i32; + } + if enable_global_hotkey.is_some() || home_cards.is_some() { + update_flags |= UpdateFlags::VergeConfig as i32; + } + #[cfg(not(target_os = "windows"))] + if redir_enabled.is_some() || redir_port.is_some() { + update_flags |= UpdateFlags::RestartCore as i32; + } + #[cfg(target_os = "linux")] + if tproxy_enabled.is_some() || tproxy_port.is_some() { + update_flags |= UpdateFlags::RestartCore as i32; + } + if socks_enabled.is_some() + || http_enabled.is_some() + || socks_port.is_some() + || http_port.is_some() + || mixed_port.is_some() + { + update_flags |= UpdateFlags::RestartCore as i32; + } + if auto_launch.is_some() { + update_flags |= UpdateFlags::Launch as i32; + } - if system_proxy.is_some() { - update_flags |= UpdateFlags::SysProxy as i32; - update_flags |= UpdateFlags::SystrayMenu as i32; - update_flags |= UpdateFlags::SystrayTooltip as i32; - update_flags |= UpdateFlags::SystrayIcon as i32; - } + if system_proxy.is_some() { + update_flags |= UpdateFlags::SysProxy as i32; + update_flags |= UpdateFlags::SystrayMenu as i32; + update_flags |= UpdateFlags::SystrayTooltip as i32; + update_flags |= UpdateFlags::SystrayIcon as i32; + } - if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() { - update_flags |= UpdateFlags::SysProxy as i32; - } + if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() { + update_flags |= UpdateFlags::SysProxy as i32; + } - if language.is_some() { - update_flags |= UpdateFlags::SystrayMenu as i32; - } - if common_tray_icon.is_some() - || sysproxy_tray_icon.is_some() - || tun_tray_icon.is_some() - || tray_icon.is_some() - || enable_tray_speed.is_some() - || enable_tray_icon.is_some() - { - update_flags |= UpdateFlags::SystrayIcon as i32; - } + if language.is_some() { + update_flags |= UpdateFlags::SystrayMenu as i32; + } + if common_tray_icon.is_some() + || sysproxy_tray_icon.is_some() + || tun_tray_icon.is_some() + || tray_icon.is_some() + || enable_tray_speed.is_some() + || enable_tray_icon.is_some() + { + update_flags |= UpdateFlags::SystrayIcon as i32; + } - if patch.hotkeys.is_some() { - update_flags |= UpdateFlags::Hotkey as i32; - update_flags |= UpdateFlags::SystrayMenu as i32; - } + if patch.hotkeys.is_some() { + update_flags |= UpdateFlags::Hotkey as i32; + update_flags |= UpdateFlags::SystrayMenu as i32; + } - if tray_event.is_some() { - update_flags |= UpdateFlags::SystrayClickBehavior as i32; - } + if tray_event.is_some() { + update_flags |= UpdateFlags::SystrayClickBehavior as i32; + } - if enable_auto_light_weight.is_some() { - update_flags |= UpdateFlags::LighteWeight as i32; - } + if enable_auto_light_weight.is_some() { + update_flags |= UpdateFlags::LighteWeight as i32; + } - // 处理 external-controller 的开关 - if enable_external_controller.is_some() { - update_flags |= UpdateFlags::RestartCore as i32; - } + if enable_external_controller.is_some() { + update_flags |= UpdateFlags::RestartCore as i32; + } - // Process updates based on flags - if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 { - Config::generate().await?; - CoreManager::global().restart_core().await?; - } - if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 { - CoreManager::global().update_config().await?; - handle::Handle::refresh_clash(); - } - if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 { - Config::verge().await.draft_mut().enable_global_hotkey = enable_global_hotkey; - handle::Handle::refresh_verge(); - } - if (update_flags & (UpdateFlags::Launch as i32)) != 0 { - sysopt::Sysopt::global().update_launch().await?; - } - if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 { - sysopt::Sysopt::global().update_sysproxy().await?; - } - if (update_flags & (UpdateFlags::Hotkey as i32)) != 0 - && let Some(hotkeys) = patch.hotkeys - { - hotkey::Hotkey::global().update(hotkeys).await?; - } - if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 { - tray::Tray::global().update_menu().await?; - } - if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 { - tray::Tray::global().update_icon().await?; - } - if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 { - tray::Tray::global().update_tooltip().await?; - } - if (update_flags & (UpdateFlags::SystrayClickBehavior as i32)) != 0 { - tray::Tray::global().update_click_behavior().await?; - } - if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 { - if enable_auto_light_weight.unwrap_or(false) { - lightweight::enable_auto_light_weight_mode().await; - } else { - lightweight::disable_auto_light_weight_mode(); - } - } + update_flags +} - >::Ok(()) - }; - match res { - Ok(()) => { - Config::verge().await.apply(); - if !not_save_file { - // 分离数据获取和异步调用 - let verge_data = Config::verge().await.data_mut().clone(); - verge_data.save_file().await?; - } - - Ok(()) - } - Err(err) => { - Config::verge().await.discard(); - Err(err) +async fn process_terminated_flags(update_flags: i32, patch: &IVerge) -> Result<()> { + // Process updates based on flags + if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 { + Config::generate().await?; + CoreManager::global().restart_core().await?; + } + if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 { + CoreManager::global().update_config().await?; + handle::Handle::refresh_clash(); + } + if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 { + Config::verge().await.draft_mut().enable_global_hotkey = patch.enable_global_hotkey; + handle::Handle::refresh_verge(); + } + if (update_flags & (UpdateFlags::Launch as i32)) != 0 { + sysopt::Sysopt::global().update_launch().await?; + } + if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 { + sysopt::Sysopt::global().update_sysproxy().await?; + } + if (update_flags & (UpdateFlags::Hotkey as i32)) != 0 + && let Some(hotkeys) = &patch.hotkeys + { + hotkey::Hotkey::global().update(hotkeys.to_owned()).await?; + } + if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 { + tray::Tray::global().update_menu().await?; + } + if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 { + tray::Tray::global().update_icon().await?; + } + if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 { + tray::Tray::global().update_tooltip().await?; + } + if (update_flags & (UpdateFlags::SystrayClickBehavior as i32)) != 0 { + tray::Tray::global().update_click_behavior().await?; + } + if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 { + if patch.enable_auto_light_weight_mode.unwrap_or(false) { + lightweight::enable_auto_light_weight_mode().await; + } else { + lightweight::disable_auto_light_weight_mode(); } } + Ok(()) +} + +pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { + Config::verge() + .await + .draft_mut() + .patch_config(patch.clone()); + + let update_flags = determine_update_flags(&patch); + let process_flag_result: std::result::Result<(), anyhow::Error> = { + process_terminated_flags(update_flags, &patch).await?; + Ok(()) + }; + + if let Err(err) = process_flag_result { + Config::verge().await.discard(); + return Err(err); + } + Config::verge().await.apply(); + if !not_save_file { + // 分离数据获取和异步调用 + let verge_data = Config::verge().await.data_mut().clone(); + verge_data.save_file().await?; + } + Ok(()) } diff --git a/src-tauri/src/feat/profile.rs b/src-tauri/src/feat/profile.rs index 47e8d402..948ba3af 100644 --- a/src-tauri/src/feat/profile.rs +++ b/src-tauri/src/feat/profile.rs @@ -23,144 +23,125 @@ pub async fn toggle_proxy_profile(profile_index: String) { } } -/// Update a profile -/// If updating current profile, activate it -/// auto_refresh: 是否自动更新配置和刷新前端 +async fn should_update_profile(uid: String) -> Result)>> { + let profiles = Config::profiles().await; + let profiles = profiles.latest_ref(); + let item = profiles.get_item(&uid)?; + let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote"); + + if !is_remote { + log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新"); + Ok(None) + } else if item.url.is_none() { + log::warn!(target: "app", "[订阅更新] {uid} 缺少URL,无法更新"); + bail!("failed to get the profile item url"); + } else if !item + .option + .as_ref() + .and_then(|o| o.allow_auto_update) + .unwrap_or(true) + { + log::info!(target: "app", "[订阅更新] {} 禁止自动更新,跳过更新", uid); + Ok(None) + } else { + log::info!(target: "app", + "[订阅更新] {} 是远程订阅,URL: {}", + uid, + item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))? + ); + Ok(Some(( + item.url + .clone() + .ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?, + item.option.clone(), + ))) + } +} + +async fn perform_profile_update( + uid: String, + url: String, + opt: Option, + option: Option, +) -> Result { + log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容"); + let merged_opt = PrfOption::merge(opt.clone(), option.clone()); + + match PrfItem::from_url(&url, None, None, merged_opt.clone()).await { + Ok(item) => { + log::info!(target: "app", "[订阅更新] 更新订阅配置成功"); + let profiles = Config::profiles().await; + profiles_draft_update_item_safe(uid.clone(), item).await?; + let is_current = Some(uid.clone()) == profiles.latest_ref().get_current(); + log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}"); + Ok(is_current) + } + Err(err) => { + log::warn!(target: "app", "[订阅更新] 正常更新失败: {err},尝试使用Clash代理更新"); + handle::Handle::notice_message("update_retry_with_clash", uid.clone()); + + let original_with_proxy = merged_opt.as_ref().and_then(|o| o.with_proxy); + let original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy); + + let mut fallback_opt = merged_opt.unwrap_or_default(); + fallback_opt.with_proxy = Some(false); + fallback_opt.self_proxy = Some(true); + + match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await { + Ok(mut item) => { + log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功"); + + if let Some(option) = item.option.as_mut() { + option.with_proxy = original_with_proxy; + option.self_proxy = original_self_proxy; + } + + let profiles = Config::profiles().await; + profiles_draft_update_item_safe(uid.clone(), item.clone()).await?; + + let profile_name = item.name.clone().unwrap_or_else(|| uid.clone()); + handle::Handle::notice_message("update_with_clash_proxy", profile_name); + + let is_current = Some(uid.clone()) == profiles.data_ref().get_current(); + log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}"); + Ok(is_current) + } + Err(retry_err) => { + log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}"); + handle::Handle::notice_message( + "update_failed_even_with_clash", + format!("{retry_err}"), + ); + Err(retry_err) + } + } + } + } +} + pub async fn update_profile( uid: String, option: Option, auto_refresh: Option, ) -> Result<()> { logging!(info, Type::Config, "[订阅更新] 开始更新订阅 {}", uid); - let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true,保持兼容性 + let auto_refresh = auto_refresh.unwrap_or(true); - let url_opt = { - let profiles = Config::profiles().await; - let profiles = profiles.latest_ref(); - let item = profiles.get_item(&uid)?; - let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote"); + let url_opt = should_update_profile(uid.clone()).await?; - if !is_remote { - log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新"); - None // 非远程订阅直接更新 - } else if item.url.is_none() { - log::warn!(target: "app", "[订阅更新] {uid} 缺少URL,无法更新"); - bail!("failed to get the profile item url"); - } else if !item - .option - .as_ref() - .and_then(|o| o.allow_auto_update) - .unwrap_or(true) - { - log::info!(target: "app", "[订阅更新] {} 禁止自动更新,跳过更新", uid); - None - } else { - log::info!(target: "app", - "[订阅更新] {} 是远程订阅,URL: {}", - uid, - item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))? - ); - Some(( - item.url - .clone() - .ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?, - item.option.clone(), - )) - } - }; - - let should_update = match url_opt { + let should_refresh = match url_opt { Some((url, opt)) => { - log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容"); - let merged_opt = PrfOption::merge(opt.clone(), option.clone()); - - // 尝试使用正常设置更新 - match PrfItem::from_url(&url, None, None, merged_opt.clone()).await { - Ok(item) => { - log::info!(target: "app", "[订阅更新] 更新订阅配置成功"); - let profiles = Config::profiles().await; - - // 使用Send-safe helper函数 - let result = profiles_draft_update_item_safe(uid.clone(), item).await; - result?; - - let is_current = Some(uid.clone()) == profiles.latest_ref().get_current(); - log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}"); - is_current && auto_refresh - } - Err(err) => { - // 首次更新失败,尝试使用Clash代理 - log::warn!(target: "app", "[订阅更新] 正常更新失败: {err},尝试使用Clash代理更新"); - - // 发送通知 - handle::Handle::notice_message("update_retry_with_clash", uid.clone()); - - // 保存原始代理设置 - let original_with_proxy = merged_opt.as_ref().and_then(|o| o.with_proxy); - let original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy); - - // 创建使用Clash代理的选项 - let mut fallback_opt = merged_opt.unwrap_or_default(); - fallback_opt.with_proxy = Some(false); - fallback_opt.self_proxy = Some(true); - - // 使用Clash代理重试 - match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await { - Ok(mut item) => { - log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功"); - - // 恢复原始代理设置到item - if let Some(option) = item.option.as_mut() { - option.with_proxy = original_with_proxy; - option.self_proxy = original_self_proxy; - } - - // 更新到配置 - let profiles = Config::profiles().await; - - // 使用 Send-safe 方法进行数据操作 - profiles_draft_update_item_safe(uid.clone(), item.clone()).await?; - - // 获取配置名称用于通知 - let profile_name = item.name.clone().unwrap_or_else(|| uid.clone()); - - // 发送通知告知用户自动更新使用了回退机制 - handle::Handle::notice_message("update_with_clash_proxy", profile_name); - - let is_current = Some(uid.clone()) == profiles.data_ref().get_current(); - log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}"); - is_current && auto_refresh - } - Err(retry_err) => { - log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}"); - handle::Handle::notice_message( - "update_failed_even_with_clash", - format!("{retry_err}"), - ); - return Err(retry_err); - } - } - } - } + perform_profile_update(uid.clone(), url, opt, option).await? && auto_refresh } None => auto_refresh, }; - if should_update { + if should_refresh { logging!(info, Type::Config, "[订阅更新] 更新内核配置"); match CoreManager::global().update_config().await { Ok(_) => { logging!(info, Type::Config, "[订阅更新] 更新成功"); handle::Handle::refresh_clash(); - // if let Err(err) = cmd::proxy::force_refresh_proxies().await { - // logging!( - // error, - // Type::Config, - // true, - // "[订阅更新] 代理组刷新失败: {}", - // err - // ); - // } } Err(err) => { logging!(error, Type::Config, "[订阅更新] 更新失败: {}", err); diff --git a/src-tauri/src/feat/proxy.rs b/src-tauri/src/feat/proxy.rs index ef063026..9178bd2d 100644 --- a/src-tauri/src/feat/proxy.rs +++ b/src-tauri/src/feat/proxy.rs @@ -7,18 +7,9 @@ use tauri_plugin_clipboard_manager::ClipboardExt; /// Toggle system proxy on/off pub async fn toggle_system_proxy() { - // 获取当前系统代理状态 - let enable = { - let verge = Config::verge().await; - - verge.latest_ref().enable_system_proxy.unwrap_or(false) - }; - // 获取自动关闭连接设置 - let auto_close_connection = { - let verge = Config::verge().await; - - verge.latest_ref().auto_close_connection.unwrap_or(false) - }; + let verge = Config::verge().await; + let enable = verge.latest_ref().enable_system_proxy.unwrap_or(false); + let auto_close_connection = verge.latest_ref().auto_close_connection.unwrap_or(false); // 如果当前系统代理即将关闭,且自动关闭连接设置为true,则关闭所有连接 if enable diff --git a/src-tauri/src/utils/i18n.rs b/src-tauri/src/utils/i18n.rs index 3ddcb5da..1d616392 100644 --- a/src-tauri/src/utils/i18n.rs +++ b/src-tauri/src/utils/i18n.rs @@ -1,6 +1,7 @@ use crate::{config::Config, utils::dirs}; use once_cell::sync::Lazy; use serde_json::Value; +use smartstring::alias::String; use std::{fs, path::PathBuf, sync::RwLock}; use sys_locale;