From bfb50ac0d74e9ae2254a069092197e3669910499 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:24:28 +0800 Subject: [PATCH] refactor(config): share patch macro and profiles helpers IVerge::patch_config, IProfiles::patch_item and PrfOption::merge each hand-rolled the same copy-Some-overwrite pattern; they now share one patch_opt_field macro (the or-else merge form is field-for-field equivalent for Option fields). The four profiles *_safe wrappers that inlined Config::profiles().with_data_modify boilerplate delegate to a with_profiles_edit helper, and record/forget_selected_node share the current-item lookup prelude via current_item_mut. --- src-tauri/src/config/mod.rs | 9 ++ src-tauri/src/config/prfitem.rs | 27 +++--- src-tauri/src/config/profiles.rs | 102 ++++++++------------ src-tauri/src/config/verge.rs | 158 +++++++++++++++---------------- 4 files changed, 138 insertions(+), 158 deletions(-) diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index add685f62..1be472f88 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -14,6 +14,15 @@ mod verge; pub(crate) use self::config::Config; pub use self::{clash::*, encrypt::*, mixed_port::*, prfitem::*, profiles::*, verge::*}; +macro_rules! patch_opt_field { + ($lv:expr, $rv:expr, $key:tt) => { + if ($rv.$key).is_some() { + $lv.$key = $rv.$key.clone(); + } + }; +} +pub(crate) use patch_opt_field; + pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) { return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;"; } diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index eb9ddb325..23278dcfc 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -1,3 +1,5 @@ +use super::patch_opt_field; + use crate::{ config::profiles, utils::{ @@ -131,19 +133,18 @@ impl PrfOption { match (one, other) { (Some(a_ref), Some(b_ref)) => { let mut result = a_ref.clone(); - result.user_agent = b_ref.user_agent.clone().or(result.user_agent); - result.with_proxy = b_ref.with_proxy.or(result.with_proxy); - result.self_proxy = b_ref.self_proxy.or(result.self_proxy); - result.danger_accept_invalid_certs = - b_ref.danger_accept_invalid_certs.or(result.danger_accept_invalid_certs); - result.allow_auto_update = b_ref.allow_auto_update.or(result.allow_auto_update); - result.update_interval = b_ref.update_interval.or(result.update_interval); - result.merge = b_ref.merge.clone().or(result.merge); - result.script = b_ref.script.clone().or(result.script); - result.rules = b_ref.rules.clone().or(result.rules); - result.proxies = b_ref.proxies.clone().or(result.proxies); - result.groups = b_ref.groups.clone().or(result.groups); - result.timeout_seconds = b_ref.timeout_seconds.or(result.timeout_seconds); + patch_opt_field!(result, b_ref, user_agent); + patch_opt_field!(result, b_ref, with_proxy); + patch_opt_field!(result, b_ref, self_proxy); + patch_opt_field!(result, b_ref, danger_accept_invalid_certs); + patch_opt_field!(result, b_ref, allow_auto_update); + patch_opt_field!(result, b_ref, update_interval); + patch_opt_field!(result, b_ref, merge); + patch_opt_field!(result, b_ref, script); + patch_opt_field!(result, b_ref, rules); + patch_opt_field!(result, b_ref, proxies); + patch_opt_field!(result, b_ref, groups); + patch_opt_field!(result, b_ref, timeout_seconds); Some(result) } (Some(a_ref), None) => Some(a_ref.clone()), diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index 4b149b716..23826cdc1 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -1,5 +1,5 @@ use super::{ - PrfOption, + PrfOption, patch_opt_field, prfitem::{PrfItem, PrfSelected, normalize_profile_home_url}, }; use crate::core::notify::{Refresh, announce}; @@ -86,14 +86,6 @@ impl ProfileDeletePlan { } } -macro_rules! patch { - ($lv: expr, $rv: expr, $key: tt) => { - if ($rv.$key).is_some() { - $lv.$key = $rv.$key.to_owned(); - } - }; -} - impl IProfiles { fn take_item_file_by_uid(items: &mut Vec, target_uid: Option<&str>) -> Option { let index = items.iter().position(|item| item.uid.as_deref() == target_uid)?; @@ -244,15 +236,15 @@ impl IProfiles { for each in items.iter_mut() { if each.uid.as_deref() == Some(uid) { - patch!(each, item, itype); - patch!(each, item, name); - patch!(each, item, desc); - patch!(each, item, file); - patch!(each, item, url); - patch!(each, item, selected); - patch!(each, item, extra); - patch!(each, item, updated); - patch!(each, item, option); + patch_opt_field!(each, item, itype); + patch_opt_field!(each, item, name); + patch_opt_field!(each, item, desc); + patch_opt_field!(each, item, file); + patch_opt_field!(each, item, url); + patch_opt_field!(each, item, selected); + patch_opt_field!(each, item, extra); + patch_opt_field!(each, item, updated); + patch_opt_field!(each, item, option); self.items = Some(items); return self.save_file().await; @@ -420,6 +412,21 @@ pub(crate) async fn profiles_append_item_with_filedata_safe(item: &PrfItem, file profiles_append_item_safe(item).await } +/// Applies `edit` to the committed profiles; commits only when the edit succeeds. +async fn with_profiles_edit(edit: F) -> Result<()> +where + F: FnOnce(IProfiles) -> Fut + Send, + Fut: std::future::Future> + Send, +{ + Config::profiles() + .await + .with_data_modify(|profiles| async move { + let profiles = edit(profiles).await?; + Ok((profiles, ())) + }) + .await +} + pub(crate) async fn profiles_append_item_safe(item: &mut PrfItem) -> Result<()> { let profiles = Config::profiles().await; profiles_append_item_to_safe(&profiles, item).await @@ -435,43 +442,19 @@ pub(super) async fn profiles_append_item_to_safe(profiles: &Draft, it } pub(crate) async fn profiles_patch_item_safe(index: &str, item: &PrfItem) -> Result<()> { - Config::profiles() - .await - .with_data_modify(|mut profiles| async move { - profiles.patch_item(index, item).await?; - Ok((profiles, ())) - }) - .await + with_profiles_edit(|mut profiles| async move { profiles.patch_item(index, item).await.map(|_| profiles) }).await } pub(crate) async fn profiles_reorder_safe(active_id: &str, over_id: &str) -> Result<()> { - Config::profiles() - .await - .with_data_modify(|mut profiles| async move { - profiles.reorder(active_id, over_id).await?; - Ok((profiles, ())) - }) - .await + with_profiles_edit(|mut profiles| async move { profiles.reorder(active_id, over_id).await.map(|_| profiles) }).await } pub(crate) async fn profiles_save_file_safe() -> Result<()> { - Config::profiles() - .await - .with_data_modify(|profiles| async move { - profiles.save_file().await?; - Ok((profiles, ())) - }) - .await + with_profiles_edit(|profiles| async move { profiles.save_file().await.map(|_| profiles) }).await } pub(crate) async fn profiles_update_item_safe(index: &str, item: &mut PrfItem) -> Result<()> { - Config::profiles() - .await - .with_data_modify(|mut profiles| async move { - profiles.update_item(index, item).await?; - Ok((profiles, ())) - }) - .await + with_profiles_edit(|mut profiles| async move { profiles.update_item(index, item).await.map(|_| profiles) }).await } #[derive(Debug, PartialEq, Eq)] @@ -713,19 +696,21 @@ where Ok(()) } +fn current_item_mut(profiles: &mut IProfiles) -> Option<&mut PrfItem> { + let current = profiles.current.clone()?; + profiles + .items + .as_mut()? + .iter_mut() + .find(|item| item.uid.as_ref() == Some(¤t)) +} + /// Records a backend-made selection so the next core start restores it. pub(crate) async fn record_selected_node(group_name: &str, node: &str) -> Result<()> { let group_name = String::from(group_name); let node = String::from(node); persist_selected_update(true, move |mut profiles| async move { - let Some(current) = profiles.current.clone() else { - return Ok((profiles, false)); - }; - let Some(item) = profiles - .items - .as_mut() - .and_then(|items| items.iter_mut().find(|item| item.uid.as_ref() == Some(¤t))) - else { + let Some(item) = current_item_mut(&mut profiles) else { return Ok((profiles, false)); }; @@ -760,14 +745,7 @@ fn remove_selected_node(selected: &mut Vec, group_name: &str) -> bo pub(crate) async fn forget_selected_node(group_name: &str) -> Result<()> { persist_selected_update(true, move |mut profiles| async move { - let Some(current) = profiles.current.clone() else { - return Ok((profiles, false)); - }; - let Some(item) = profiles - .items - .as_mut() - .and_then(|items| items.iter_mut().find(|item| item.uid.as_ref() == Some(¤t))) - else { + let Some(item) = current_item_mut(&mut profiles) else { return Ok((profiles, false)); }; diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index 45b0e18ec..ab7962ee8 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -1,6 +1,6 @@ use crate::config::Config; use crate::{ - config::{DEFAULT_PAC, deserialize_encrypted, serialize_encrypted}, + config::{DEFAULT_PAC, deserialize_encrypted, patch_opt_field, serialize_encrypted}, constants::network, utils::{dirs, help}, }; @@ -432,98 +432,90 @@ impl IVerge { #[allow(clippy::cognitive_complexity)] pub fn patch_config(&mut self, patch: &Self) { - macro_rules! patch { - ($key: tt) => { - if patch.$key.is_some() { - self.$key = patch.$key.clone(); - } - }; - } + patch_opt_field!(self, patch, app_log_level); + patch_opt_field!(self, patch, app_log_max_size); + patch_opt_field!(self, patch, app_log_max_count); - patch!(app_log_level); - patch!(app_log_max_size); - patch!(app_log_max_count); - - patch!(language); - patch!(theme_mode); - patch!(tray_event); - patch!(env_type); - patch!(start_page); - patch!(startup_script); - patch!(traffic_graph); - patch!(enable_memory_usage); - patch!(enable_group_icon); - patch!(pause_render_traffic_stats_on_blur); + patch_opt_field!(self, patch, language); + patch_opt_field!(self, patch, theme_mode); + patch_opt_field!(self, patch, tray_event); + patch_opt_field!(self, patch, env_type); + patch_opt_field!(self, patch, start_page); + patch_opt_field!(self, patch, startup_script); + patch_opt_field!(self, patch, traffic_graph); + patch_opt_field!(self, patch, enable_memory_usage); + patch_opt_field!(self, patch, enable_group_icon); + patch_opt_field!(self, patch, pause_render_traffic_stats_on_blur); #[cfg(target_os = "macos")] - patch!(tray_icon); - patch!(menu_icon); - patch!(menu_order); - patch!(proxy_group_tools_position); - patch!(notice_position); - patch!(collapse_navbar); - patch!(common_tray_icon); - patch!(sysproxy_tray_icon); - patch!(tun_tray_icon); + patch_opt_field!(self, patch, tray_icon); + patch_opt_field!(self, patch, menu_icon); + patch_opt_field!(self, patch, menu_order); + patch_opt_field!(self, patch, proxy_group_tools_position); + patch_opt_field!(self, patch, notice_position); + patch_opt_field!(self, patch, collapse_navbar); + patch_opt_field!(self, patch, common_tray_icon); + patch_opt_field!(self, patch, sysproxy_tray_icon); + patch_opt_field!(self, patch, tun_tray_icon); - patch!(enable_tun_mode); - patch!(enable_auto_launch); - patch!(enable_silent_start); - patch!(enable_hover_jump_navigator); - patch!(hover_jump_navigator_delay); + patch_opt_field!(self, patch, enable_tun_mode); + patch_opt_field!(self, patch, enable_auto_launch); + patch_opt_field!(self, patch, enable_silent_start); + patch_opt_field!(self, patch, enable_hover_jump_navigator); + patch_opt_field!(self, patch, hover_jump_navigator_delay); #[cfg(not(target_os = "windows"))] - patch!(verge_redir_port); + patch_opt_field!(self, patch, verge_redir_port); #[cfg(not(target_os = "windows"))] - patch!(verge_redir_enabled); + patch_opt_field!(self, patch, verge_redir_enabled); #[cfg(target_os = "linux")] - patch!(verge_tproxy_port); + patch_opt_field!(self, patch, verge_tproxy_port); #[cfg(target_os = "linux")] - patch!(verge_tproxy_enabled); - patch!(verge_mixed_port); - patch!(verge_socks_port); - patch!(verge_socks_enabled); - patch!(verge_port); - patch!(verge_http_enabled); - patch!(enable_system_proxy); - patch!(enable_proxy_guard); - patch!(enable_bypass_check); - patch!(use_default_bypass); - patch!(system_proxy_bypass); - patch!(proxy_guard_duration); - patch!(proxy_auto_config); - patch!(pac_file_content); - patch!(proxy_host); - patch!(theme_setting); - patch!(web_ui_list); - patch!(clash_core); - patch!(hotkeys); - patch!(enable_global_hotkey); + patch_opt_field!(self, patch, verge_tproxy_enabled); + patch_opt_field!(self, patch, verge_mixed_port); + patch_opt_field!(self, patch, verge_socks_port); + patch_opt_field!(self, patch, verge_socks_enabled); + patch_opt_field!(self, patch, verge_port); + patch_opt_field!(self, patch, verge_http_enabled); + patch_opt_field!(self, patch, enable_system_proxy); + patch_opt_field!(self, patch, enable_proxy_guard); + patch_opt_field!(self, patch, enable_bypass_check); + patch_opt_field!(self, patch, use_default_bypass); + patch_opt_field!(self, patch, system_proxy_bypass); + patch_opt_field!(self, patch, proxy_guard_duration); + patch_opt_field!(self, patch, proxy_auto_config); + patch_opt_field!(self, patch, pac_file_content); + patch_opt_field!(self, patch, proxy_host); + patch_opt_field!(self, patch, theme_setting); + patch_opt_field!(self, patch, web_ui_list); + patch_opt_field!(self, patch, clash_core); + patch_opt_field!(self, patch, hotkeys); + patch_opt_field!(self, patch, enable_global_hotkey); - patch!(auto_close_connection); - patch!(auto_check_update); - patch!(default_latency_test); - patch!(default_latency_timeout); - patch!(enable_auto_delay_detection); - patch!(auto_delay_detection_interval_minutes); - patch!(enable_builtin_enhanced); - patch!(proxy_layout_column); - patch!(test_list); - patch!(auto_log_clean); - patch!(enable_auto_backup_schedule); - patch!(auto_backup_interval_hours); - patch!(auto_backup_on_change); + patch_opt_field!(self, patch, auto_close_connection); + patch_opt_field!(self, patch, auto_check_update); + patch_opt_field!(self, patch, default_latency_test); + patch_opt_field!(self, patch, default_latency_timeout); + patch_opt_field!(self, patch, enable_auto_delay_detection); + patch_opt_field!(self, patch, auto_delay_detection_interval_minutes); + patch_opt_field!(self, patch, enable_builtin_enhanced); + patch_opt_field!(self, patch, proxy_layout_column); + patch_opt_field!(self, patch, test_list); + patch_opt_field!(self, patch, auto_log_clean); + patch_opt_field!(self, patch, enable_auto_backup_schedule); + patch_opt_field!(self, patch, auto_backup_interval_hours); + patch_opt_field!(self, patch, auto_backup_on_change); - patch!(webdav_url); - patch!(webdav_username); - patch!(webdav_password); + patch_opt_field!(self, patch, webdav_url); + patch_opt_field!(self, patch, webdav_username); + patch_opt_field!(self, patch, webdav_password); #[cfg(target_os = "macos")] - patch!(enable_tray_speed); - patch!(tray_proxy_groups_display_mode); - patch!(tray_inline_outbound_modes); - patch!(enable_auto_light_weight_mode); - patch!(auto_light_weight_minutes); - patch!(enable_dns_settings); - patch!(home_cards); - patch!(enable_external_controller); + patch_opt_field!(self, patch, enable_tray_speed); + patch_opt_field!(self, patch, tray_proxy_groups_display_mode); + patch_opt_field!(self, patch, tray_inline_outbound_modes); + patch_opt_field!(self, patch, enable_auto_light_weight_mode); + patch_opt_field!(self, patch, auto_light_weight_minutes); + patch_opt_field!(self, patch, enable_dns_settings); + patch_opt_field!(self, patch, home_cards); + patch_opt_field!(self, patch, enable_external_controller); } pub fn get_log_level(&self) -> LevelFilter {