diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 387f9212..f2445afd 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -119,6 +119,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + [[package]] name = "anyhow" version = "1.0.98" @@ -959,6 +971,12 @@ dependencies = [ "toml", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.27" @@ -1024,6 +1042,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1034,6 +1079,31 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "clash-verge" version = "2.3.2" @@ -1044,6 +1114,7 @@ dependencies = [ "base64 0.22.1", "boa_engine", "chrono", + "criterion", "dashmap 7.0.0-rc2", "deelevate", "delay_timer", @@ -1314,6 +1385,39 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "cron_clock" version = "0.8.0" @@ -3470,6 +3574,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -4608,6 +4721,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -5077,6 +5196,34 @@ dependencies = [ "time", ] +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.17.16" @@ -7524,6 +7671,16 @@ dependencies = [ "zerovec 0.11.2", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.9.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7d1e7327..10c1894a 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -139,7 +139,12 @@ name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] [dev-dependencies] +criterion = "0.6.0" tempfile = "3.20.0" [workspace] members = ["src_crates/crate_mihomo_api"] + +[[bench]] +name = "draft_benchmark" +harness = false diff --git a/src-tauri/benches/draft_benchmark.rs b/src-tauri/benches/draft_benchmark.rs new file mode 100644 index 00000000..999f3385 --- /dev/null +++ b/src-tauri/benches/draft_benchmark.rs @@ -0,0 +1,91 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::hint::black_box; + +// 业务模型 & Draft +use app_lib::config::Draft as DraftNew; +use app_lib::config::IVerge; + +// fn bench_apply_old(c: &mut Criterion) { +// c.bench_function("apply_draft_old", |b| { +// b.iter(|| { +// let verge = Box::new(IVerge { +// enable_auto_launch: Some(true), +// enable_tun_mode: Some(false), +// ..Default::default() +// }); + +// let draft = DraftOld::from(black_box(verge)); + +// { +// let mut d = draft.draft_mut(); +// d.enable_auto_launch = Some(false); +// } + +// let _ = draft.apply(); +// }); +// }); +// } + +// fn bench_discard_old(c: &mut Criterion) { +// c.bench_function("discard_draft_old", |b| { +// b.iter(|| { +// let verge = Box::new(IVerge::default()); +// let draft = DraftOld::from(black_box(verge)); + +// { +// let mut d = draft.draft_mut(); +// d.enable_auto_launch = Some(false); +// } + +// let _ = draft.discard(); +// }); +// }); +// } + +/// 基准:修改草稿并 apply() +fn bench_apply_new(c: &mut Criterion) { + c.bench_function("apply_draft_new", |b| { + b.iter(|| { + let verge = Box::new(IVerge { + enable_auto_launch: Some(true), + enable_tun_mode: Some(false), + ..Default::default() + }); + + let draft = DraftNew::from(black_box(verge)); + + { + let mut d = draft.draft_mut(); + d.enable_auto_launch = Some(false); + } + + let _ = draft.apply(); + }); + }); +} + +/// 基准:修改草稿并 discard() +fn bench_discard_new(c: &mut Criterion) { + c.bench_function("discard_draft_new", |b| { + b.iter(|| { + let verge = Box::new(IVerge::default()); + let draft = DraftNew::from(black_box(verge)); + + { + let mut d = draft.draft_mut(); + d.enable_auto_launch = Some(false); + } + + let _ = draft.discard(); + }); + }); +} + +criterion_group!( + benches, + // bench_apply_old, + // bench_discard_old, + bench_apply_new, + bench_discard_new +); +criterion_main!(benches); diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index e6612caa..02b9ad96 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -14,7 +14,7 @@ pub fn copy_clash_env() -> CmdResult { /// 获取Clash信息 #[tauri::command] pub fn get_clash_info() -> CmdResult { - Ok(Config::clash().latest().get_client_info()) + Ok(Config::clash().latest_ref().get_client_info()) } /// 修改Clash配置 @@ -173,7 +173,7 @@ pub fn apply_dns_config(apply: bool) -> CmdResult { // 重新生成配置,确保DNS配置被正确应用 // 这里不调用patch_clash以避免将DNS配置写入config.yaml Config::runtime() - .latest() + .draft_mut() .patch_config(patch_config.clone()); // 首先重新生成配置 diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 73aea6df..a6ff9153 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -39,7 +39,7 @@ pub async fn get_profiles() -> CmdResult { Duration::from_millis(500), tokio::task::spawn_blocking(move || { let profiles = Config::profiles(); - let latest = profiles.latest(); + let latest = profiles.latest_ref(); IProfiles { current: latest.current.clone(), items: latest.items.clone(), @@ -66,7 +66,7 @@ pub async fn get_profiles() -> CmdResult { Duration::from_secs(2), tokio::task::spawn_blocking(move || { let profiles = Config::profiles(); - let data = profiles.data(); + let data = profiles.latest_ref(); IProfiles { current: data.current.clone(), items: data.items.clone(), @@ -130,20 +130,20 @@ pub async fn enhance_profiles() -> CmdResult { #[tauri::command] pub async fn import_profile(url: String, option: Option) -> CmdResult { let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; - wrap_err!(Config::profiles().data().append_item(item)) + wrap_err!(Config::profiles().data_mut().append_item(item)) } /// 重新排序配置文件 #[tauri::command] pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult { - wrap_err!(Config::profiles().data().reorder(active_id, over_id)) + wrap_err!(Config::profiles().data_mut().reorder(active_id, over_id)) } /// 创建配置文件 #[tauri::command] pub async fn create_profile(item: PrfItem, file_data: Option) -> CmdResult { let item = wrap_err!(PrfItem::from(item, file_data).await)?; - wrap_err!(Config::profiles().data().append_item(item)) + wrap_err!(Config::profiles().data_mut().append_item(item)) } /// 更新配置文件 @@ -155,10 +155,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 should_update = wrap_err!({ Config::profiles().data_mut().delete_item(index) })?; // 删除后自动清理冗余文件 - let _ = Config::profiles().latest().auto_cleanup(); + let _ = Config::profiles().latest_ref().auto_cleanup(); if should_update { wrap_err!(CoreManager::global().update_config().await)?; @@ -226,7 +226,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { } // 保存当前配置,以便在验证失败时恢复 - let current_profile = Config::profiles().latest().current.clone(); + let current_profile = Config::profiles().latest_ref().current.clone(); logging!(info, Type::Cmd, true, "当前配置: {:?}", current_profile); // 如果要切换配置,先检查目标配置文件是否有语法错误 @@ -237,7 +237,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { // 获取目标配置文件路径 let config_file_result = { let profiles_config = Config::profiles(); - let profiles_data = profiles_config.latest(); + let profiles_data = profiles_config.latest_ref(); match profiles_data.get_item(new_profile) { Ok(item) => { if let Some(file) = &item.file { @@ -375,7 +375,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { let current_value = profiles.current.clone(); - let _ = Config::profiles().draft().patch_config(profiles); + let _ = Config::profiles().draft_mut().patch_config(profiles); // 在调用内核前再次验证请求有效性 let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst); @@ -451,7 +451,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { } // 保存配置文件 - if let Err(e) = Config::profiles().data().save_file() { + if let Err(e) = Config::profiles().data_mut().save_file() { log::warn!(target: "app", "异步保存配置文件失败: {e}"); } }); @@ -490,11 +490,15 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { items: None, }; // 静默恢复,不触发验证 - wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?; + wrap_err!({ + Config::profiles() + .draft_mut() + .patch_config(restore_profiles) + })?; Config::profiles().apply(); crate::process::AsyncHandler::spawn(|| async move { - if let Err(e) = Config::profiles().data().save_file() { + if let Err(e) = Config::profiles().data_mut().save_file() { log::warn!(target: "app", "异步保存恢复配置文件失败: {e}"); } }); @@ -551,7 +555,11 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { current: Some(prev_profile), items: None, }; - wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?; + wrap_err!({ + Config::profiles() + .draft_mut() + .patch_config(restore_profiles) + })?; Config::profiles().apply(); } @@ -584,7 +592,7 @@ pub async fn patch_profiles_config_by_profile_index( pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult { // 保存修改前检查是否有更新 update_interval let update_interval_changed = - if let Ok(old_profile) = Config::profiles().latest().get_item(&index) { + if let Ok(old_profile) = Config::profiles().latest_ref().get_item(&index) { let old_interval = old_profile.option.as_ref().and_then(|o| o.update_interval); let new_interval = profile.option.as_ref().and_then(|o| o.update_interval); old_interval != new_interval @@ -593,7 +601,9 @@ pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult { }; // 保存修改 - wrap_err!(Config::profiles().data().patch_item(index.clone(), profile))?; + wrap_err!(Config::profiles() + .data_mut() + .patch_item(index.clone(), profile))?; // 如果更新间隔变更,异步刷新定时器 if update_interval_changed { @@ -616,7 +626,7 @@ pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult { #[tauri::command] pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult { let file = { - wrap_err!(Config::profiles().latest().get_item(&index))? + wrap_err!(Config::profiles().latest_ref().get_item(&index))? .file .clone() .ok_or("the file field is null") @@ -634,7 +644,7 @@ pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult { #[tauri::command] pub fn read_profile_file(index: String) -> CmdResult { let profiles = Config::profiles(); - let profiles = profiles.latest(); + let profiles = profiles.latest_ref(); let item = wrap_err!(profiles.get_item(&index))?; let data = wrap_err!(item.read_file())?; Ok(data) diff --git a/src-tauri/src/cmd/runtime.rs b/src-tauri/src/cmd/runtime.rs index f3193e54..ff539b79 100644 --- a/src-tauri/src/cmd/runtime.rs +++ b/src-tauri/src/cmd/runtime.rs @@ -7,14 +7,14 @@ use std::collections::HashMap; /// 获取运行时配置 #[tauri::command] pub fn get_runtime_config() -> CmdResult> { - Ok(Config::runtime().latest().config.clone()) + Ok(Config::runtime().latest_ref().config.clone()) } /// 获取运行时YAML配置 #[tauri::command] pub fn get_runtime_yaml() -> CmdResult { let runtime = Config::runtime(); - let runtime = runtime.latest(); + let runtime = runtime.latest_ref(); let config = runtime.config.as_ref(); wrap_err!(config .ok_or(anyhow::anyhow!("failed to parse config to yaml file")) @@ -26,11 +26,11 @@ pub fn get_runtime_yaml() -> CmdResult { /// 获取运行时存在的键 #[tauri::command] pub fn get_runtime_exists() -> CmdResult> { - Ok(Config::runtime().latest().exists_keys.clone()) + Ok(Config::runtime().latest_ref().exists_keys.clone()) } /// 获取运行时日志 #[tauri::command] pub fn get_runtime_logs() -> CmdResult>> { - Ok(Config::runtime().latest().chain_logs.clone()) + Ok(Config::runtime().latest_ref().chain_logs.clone()) } diff --git a/src-tauri/src/cmd/save_profile.rs b/src-tauri/src/cmd/save_profile.rs index 5e2b7bc1..50fa1a8e 100644 --- a/src-tauri/src/cmd/save_profile.rs +++ b/src-tauri/src/cmd/save_profile.rs @@ -18,7 +18,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR // 在异步操作前完成所有文件操作 let (file_path, original_content, is_merge_file) = { let profiles = Config::profiles(); - let profiles_guard = profiles.latest(); + let profiles_guard = profiles.latest_ref(); let item = wrap_err!(profiles_guard.get_item(&index))?; // 确定是否为merge类型文件 let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge"); diff --git a/src-tauri/src/cmd/verge.rs b/src-tauri/src/cmd/verge.rs index 88a19923..d183d509 100644 --- a/src-tauri/src/cmd/verge.rs +++ b/src-tauri/src/cmd/verge.rs @@ -4,12 +4,9 @@ use crate::{config::*, feat, wrap_err}; /// 获取Verge配置 #[tauri::command] pub fn get_verge_config() -> CmdResult { - let verge_data = { - let verge = Config::verge(); - let data = verge.data(); - (**data).clone() - }; - Ok(IVergeResponse::from(verge_data)) + let verge = Config::verge(); + let verge_data = verge.latest_ref().clone(); + Ok(IVergeResponse::from(*verge_data)) } /// 修改Verge配置 diff --git a/src-tauri/src/cmd/webdav.rs b/src-tauri/src/cmd/webdav.rs index e4cd51d3..d9f85731 100644 --- a/src-tauri/src/cmd/webdav.rs +++ b/src-tauri/src/cmd/webdav.rs @@ -11,10 +11,10 @@ pub async fn save_webdav_config(url: String, username: String, password: String) webdav_password: Some(password), ..IVerge::default() }; - Config::verge().draft().patch_config(patch.clone()); + Config::verge().draft_mut().patch_config(patch.clone()); Config::verge().apply(); Config::verge() - .data() + .latest_ref() .save_file() .map_err(|err| err.to_string())?; core::backup::WebDavClient::global().reset(); diff --git a/src-tauri/src/config/config.rs b/src-tauri/src/config/config.rs index c0429b12..ef462087 100644 --- a/src-tauri/src/config/config.rs +++ b/src-tauri/src/config/config.rs @@ -52,20 +52,24 @@ impl Config { /// 初始化订阅 pub async fn init_config() -> Result<()> { if Self::profiles() - .data() + .latest_ref() .get_item(&"Merge".to_string()) .is_err() { let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?; - Self::profiles().data().append_item(merge_item.clone())?; + Self::profiles() + .data_mut() + .append_item(merge_item.clone())?; } if Self::profiles() - .data() + .latest_ref() .get_item(&"Script".to_string()) .is_err() { let script_item = PrfItem::from_script(Some("Script".to_string()))?; - Self::profiles().data().append_item(script_item.clone())?; + Self::profiles() + .data_mut() + .append_item(script_item.clone())?; } // 生成运行时配置 if let Err(err) = Self::generate().await { @@ -135,7 +139,7 @@ impl Config { }; let runtime = Config::runtime(); - let runtime = runtime.latest(); + let runtime = runtime.latest_ref(); let config = runtime .config .as_ref() @@ -149,7 +153,7 @@ impl Config { pub async fn generate() -> Result<()> { let (config, exists_keys, logs) = enhance::enhance().await; - *Config::runtime().draft() = Box::new(IRuntime { + *Config::runtime().draft_mut() = Box::new(IRuntime { config: Some(config), exists_keys, chain_logs: logs, diff --git a/src-tauri/src/config/draft.rs b/src-tauri/src/config/draft.rs index 97ef7412..041c79e6 100644 --- a/src-tauri/src/config/draft.rs +++ b/src-tauri/src/config/draft.rs @@ -1,135 +1,147 @@ -use super::{IClashTemp, IProfiles, IRuntime, IVerge}; -use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; use std::sync::Arc; +use parking_lot::{ + MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, + RwLockUpgradableReadGuard, RwLockWriteGuard, +}; + #[derive(Debug, Clone)] pub struct Draft { - inner: Arc)>>, + inner: Arc)>>, } -macro_rules! draft_define { - ($id: ident) => { - impl From<$id> for Draft<$id> { - fn from(data: $id) -> Self { - Draft { - inner: Arc::new(Mutex::new((data, None))), - } - } +impl From for Draft { + fn from(data: T) -> Self { + Self { + inner: Arc::new(RwLock::new((data, None))), } - - impl Draft> { - #[allow(unused)] - pub fn data(&self) -> MappedMutexGuard> { - MutexGuard::map(self.inner.lock(), |guard| &mut guard.0) - } - - pub fn latest(&self) -> MappedMutexGuard> { - MutexGuard::map(self.inner.lock(), |inner| { - if inner.1.is_none() { - &mut inner.0 - } else { - inner.1.as_mut().unwrap() - } - }) - } - - pub fn draft(&self) -> MappedMutexGuard> { - MutexGuard::map(self.inner.lock(), |inner| { - if inner.1.is_none() { - inner.1 = Some(inner.0.clone()); - } - - inner.1.as_mut().unwrap() - }) - } - - pub fn apply(&self) -> Option> { - let mut inner = self.inner.lock(); - - match inner.1.take() { - Some(draft) => { - let old_value = inner.0.to_owned(); - inner.0 = draft.to_owned(); - Some(old_value) - } - None => None, - } - } - - pub fn discard(&self) -> Option> { - let mut inner = self.inner.lock(); - inner.1.take() - } - } - - impl From> for Draft> { - fn from(data: Box<$id>) -> Self { - Draft { - inner: Arc::new(Mutex::new((data, None))), - } - } - } - }; + } } -// draft_define!(IClash); -draft_define!(IClashTemp); -draft_define!(IProfiles); -draft_define!(IRuntime); -draft_define!(IVerge); +/// Implements draft management for `Box`, allowing for safe concurrent editing and committing of draft data. +/// # Type Parameters +/// - `T`: The underlying data type, which must implement `Clone` and `ToOwned`. +/// +/// # Methods +/// - `data_mut`: Returns a mutable reference to the committed data. +/// - `data_ref`: Returns an immutable reference to the committed data. +/// - `draft_mut`: Creates or retrieves a mutable reference to the draft data, cloning the committed data if no draft exists. +/// - `latest_ref`: Returns an immutable reference to the draft data if it exists, otherwise to the committed data. +/// - `apply`: Commits the draft data, replacing the committed data and returning the old committed value if a draft existed. +/// - `discard`: Discards the draft data and returns it if it existed. +impl Draft> { + /// 可写正式数据 + pub fn data_mut(&self) -> MappedRwLockWriteGuard<'_, Box> { + RwLockWriteGuard::map(self.inner.write(), |inner| &mut inner.0) + } + + /// 返回正式数据的只读视图(不包含草稿) + pub fn data_ref(&self) -> MappedRwLockReadGuard<'_, Box> { + RwLockReadGuard::map(self.inner.read(), |inner| &inner.0) + } + + /// 创建或获取草稿并返回可写引用 + pub fn draft_mut(&self) -> MappedRwLockWriteGuard<'_, Box> { + let guard = self.inner.upgradable_read(); + if guard.1.is_none() { + let mut guard = RwLockUpgradableReadGuard::upgrade(guard); + guard.1 = Some(guard.0.clone()); + return RwLockWriteGuard::map(guard, |inner| inner.1.as_mut().unwrap()); + } + // 已存在草稿,升级为写锁映射 + RwLockWriteGuard::map(RwLockUpgradableReadGuard::upgrade(guard), |inner| { + inner.1.as_mut().unwrap() + }) + } + + /// 零拷贝只读视图:返回草稿(若存在)或正式值 + pub fn latest_ref(&self) -> MappedRwLockReadGuard<'_, Box> { + RwLockReadGuard::map(self.inner.read(), |inner| { + inner.1.as_ref().unwrap_or(&inner.0) + }) + } + + /// 提交草稿,返回旧正式数据 + pub fn apply(&self) -> Option> { + let mut inner = self.inner.write(); + inner + .1 + .take() + .map(|draft| std::mem::replace(&mut inner.0, draft)) + } + + /// 丢弃草稿,返回被丢弃的草稿 + pub fn discard(&self) -> Option> { + self.inner.write().1.take() + } +} #[test] fn test_draft_box() { + use super::IVerge; + + // 1. 创建 Draft> let verge = Box::new(IVerge { enable_auto_launch: Some(true), enable_tun_mode: Some(false), ..IVerge::default() }); - let draft = Draft::from(verge); - assert_eq!(draft.data().enable_auto_launch, Some(true)); - assert_eq!(draft.data().enable_tun_mode, Some(false)); - - assert_eq!(draft.draft().enable_auto_launch, Some(true)); - assert_eq!(draft.draft().enable_tun_mode, Some(false)); - + // 2. 读取正式数据(data_mut) { - let mut d = draft.draft(); + let data = draft.data_mut(); + assert_eq!(data.enable_auto_launch, Some(true)); + assert_eq!(data.enable_tun_mode, Some(false)); + } + + // 3. 初次获取草稿(draft_mut 会自动 clone 一份) + { + let draft_view = draft.draft_mut(); + assert_eq!(draft_view.enable_auto_launch, Some(true)); + assert_eq!(draft_view.enable_tun_mode, Some(false)); + } + + // 4. 修改草稿 + { + let mut d = draft.draft_mut(); d.enable_auto_launch = Some(false); d.enable_tun_mode = Some(true); } - assert_eq!(draft.data().enable_auto_launch, Some(true)); - assert_eq!(draft.data().enable_tun_mode, Some(false)); - - assert_eq!(draft.draft().enable_auto_launch, Some(false)); - assert_eq!(draft.draft().enable_tun_mode, Some(true)); - - assert_eq!(draft.latest().enable_auto_launch, Some(false)); - assert_eq!(draft.latest().enable_tun_mode, Some(true)); - - assert!(draft.apply().is_some()); - assert!(draft.apply().is_none()); - - assert_eq!(draft.data().enable_auto_launch, Some(false)); - assert_eq!(draft.data().enable_tun_mode, Some(true)); - - assert_eq!(draft.draft().enable_auto_launch, Some(false)); - assert_eq!(draft.draft().enable_tun_mode, Some(true)); + // 正式数据未变 + assert_eq!(draft.data_mut().enable_auto_launch, Some(true)); + assert_eq!(draft.data_mut().enable_tun_mode, Some(false)); + // 草稿已变 { - let mut d = draft.draft(); - d.enable_auto_launch = Some(true); + let latest = draft.latest_ref(); + assert_eq!(latest.enable_auto_launch, Some(false)); + assert_eq!(latest.enable_tun_mode, Some(true)); } - assert_eq!(draft.data().enable_auto_launch, Some(false)); - assert_eq!(draft.draft().enable_auto_launch, Some(true)); + // 5. 提交草稿 + assert!(draft.apply().is_some()); // 第一次提交应有返回 + assert!(draft.apply().is_none()); // 第二次提交返回 None - assert!(draft.discard().is_some()); + // 正式数据已更新 + { + let data = draft.data_mut(); + assert_eq!(data.enable_auto_launch, Some(false)); + assert_eq!(data.enable_tun_mode, Some(true)); + } - assert_eq!(draft.data().enable_auto_launch, Some(false)); - assert!(draft.discard().is_none()); + // 6. 新建并修改下一轮草稿 + { + let mut d = draft.draft_mut(); + d.enable_auto_launch = Some(true); + } + assert_eq!(draft.draft_mut().enable_auto_launch, Some(true)); - assert_eq!(draft.draft().enable_auto_launch, Some(false)); + // 7. 丢弃草稿 + assert!(draft.discard().is_some()); // 第一次丢弃返回 Some + assert!(draft.discard().is_none()); // 再次丢弃返回 None + + // 8. 草稿已被丢弃,新的 draft_mut() 会重新 clone + assert_eq!(draft.draft_mut().enable_auto_launch, Some(false)); } diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 76a86a8f..a526cc56 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -186,29 +186,37 @@ impl PrfItem { if merge.is_none() { let merge_item = PrfItem::from_merge(None)?; - Config::profiles().data().append_item(merge_item.clone())?; + Config::profiles() + .data_mut() + .append_item(merge_item.clone())?; merge = merge_item.uid; } if script.is_none() { let script_item = PrfItem::from_script(None)?; - Config::profiles().data().append_item(script_item.clone())?; + Config::profiles() + .data_mut() + .append_item(script_item.clone())?; script = script_item.uid; } if rules.is_none() { let rules_item = PrfItem::from_rules()?; - Config::profiles().data().append_item(rules_item.clone())?; + Config::profiles() + .data_mut() + .append_item(rules_item.clone())?; rules = rules_item.uid; } if proxies.is_none() { let proxies_item = PrfItem::from_proxies()?; Config::profiles() - .data() + .data_mut() .append_item(proxies_item.clone())?; proxies = proxies_item.uid; } if groups.is_none() { let groups_item = PrfItem::from_groups()?; - Config::profiles().data().append_item(groups_item.clone())?; + Config::profiles() + .data_mut() + .append_item(groups_item.clone())?; groups = groups_item.uid; } Ok(PrfItem { @@ -366,29 +374,37 @@ impl PrfItem { if merge.is_none() { let merge_item = PrfItem::from_merge(None)?; - Config::profiles().data().append_item(merge_item.clone())?; + Config::profiles() + .data_mut() + .append_item(merge_item.clone())?; merge = merge_item.uid; } if script.is_none() { let script_item = PrfItem::from_script(None)?; - Config::profiles().data().append_item(script_item.clone())?; + Config::profiles() + .data_mut() + .append_item(script_item.clone())?; script = script_item.uid; } if rules.is_none() { let rules_item = PrfItem::from_rules()?; - Config::profiles().data().append_item(rules_item.clone())?; + Config::profiles() + .data_mut() + .append_item(rules_item.clone())?; rules = rules_item.uid; } if proxies.is_none() { let proxies_item = PrfItem::from_proxies()?; Config::profiles() - .data() + .data_mut() .append_item(proxies_item.clone())?; proxies = proxies_item.uid; } if groups.is_none() { let groups_item = PrfItem::from_groups()?; - Config::profiles().data().append_item(groups_item.clone())?; + Config::profiles() + .data_mut() + .append_item(groups_item.clone())?; groups = groups_item.uid; } diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index bf2e0dc4..1a9a7bf1 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -300,7 +300,7 @@ impl IVerge { use crate::config::Config; let config_draft = Config::verge(); - *config_draft.draft() = Box::new(updated_config.clone()); + *config_draft.draft_mut() = Box::new(updated_config.clone()); config_draft.apply(); logging!( diff --git a/src-tauri/src/core/backup.rs b/src-tauri/src/core/backup.rs index fa6e831e..47052eef 100644 --- a/src-tauri/src/core/backup.rs +++ b/src-tauri/src/core/backup.rs @@ -78,7 +78,7 @@ impl WebDavClient { if let Some(cfg) = lock.as_ref() { cfg.clone() } else { - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); if verge.webdav_url.is_none() || verge.webdav_username.is_none() || verge.webdav_password.is_none() diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 6a4224a6..fe1481ae 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -138,14 +138,14 @@ impl CoreManager { /// 使用默认配置 pub async fn use_default_config(&self, msg_type: &str, msg_content: &str) -> Result<()> { let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG); - *Config::runtime().draft() = Box::new(IRuntime { - config: Some(Config::clash().latest().0.clone()), + *Config::runtime().draft_mut() = Box::new(IRuntime { + config: Some(Config::clash().latest_ref().0.clone()), exists_keys: vec![], chain_logs: Default::default(), }); help::save_yaml( &runtime_path, - &Config::clash().latest().0, + &Config::clash().latest_ref().0, Some("# Clash Verge Runtime"), )?; handle::Handle::notice_message(msg_type, msg_content); @@ -247,7 +247,7 @@ impl CoreManager { config_path ); - let clash_core = Config::verge().latest().get_valid_clash_core(); + let clash_core = Config::verge().latest_ref().get_valid_clash_core(); logging!(info, Type::Config, true, "使用内核: {}", clash_core); let app_handle = handle::Handle::global().app_handle().unwrap(); @@ -739,7 +739,7 @@ impl CoreManager { let app_handle = handle::Handle::global() .app_handle() .ok_or(anyhow::anyhow!("failed to get app handle"))?; - let clash_core = Config::verge().latest().get_valid_clash_core(); + let clash_core = Config::verge().latest_ref().get_valid_clash_core(); let config_dir = dirs::app_home_dir()?; let service_log_dir = dirs::app_home_dir()?.join("logs").join("service"); @@ -1114,9 +1114,9 @@ impl CoreManager { return Err(error_message); } - Config::verge().draft().clash_core = clash_core.clone(); + Config::verge().draft_mut().clash_core = clash_core.clone(); Config::verge().apply(); - logging_error!(Type::Core, true, Config::verge().latest().save_file()); + logging_error!(Type::Core, true, Config::verge().latest_ref().save_file()); let run_path = Config::generate_file(ConfigType::Run).map_err(|e| { let msg = e.to_string(); diff --git a/src-tauri/src/core/event_driven_proxy.rs b/src-tauri/src/core/event_driven_proxy.rs index 0216a26b..9e0bd68b 100644 --- a/src-tauri/src/core/event_driven_proxy.rs +++ b/src-tauri/src/core/event_driven_proxy.rs @@ -428,7 +428,7 @@ impl EventDrivenProxyManager { fn get_proxy_config() -> ProxyConfig { let (sys_enabled, pac_enabled, guard_enabled) = { let verge_config = Config::verge(); - let verge = verge_config.latest(); + let verge = verge_config.latest_ref(); ( verge.enable_system_proxy.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false), @@ -445,7 +445,7 @@ impl EventDrivenProxyManager { fn get_expected_pac_config() -> Autoproxy { let proxy_host = { let verge_config = Config::verge(); - let verge = verge_config.latest(); + let verge = verge_config.latest_ref(); verge .proxy_host .clone() @@ -459,19 +459,15 @@ impl EventDrivenProxyManager { } fn get_expected_sys_proxy() -> Sysproxy { - let (verge_mixed_port, proxy_host) = { - let verge_config = Config::verge(); - let verge = verge_config.latest(); - ( - verge.verge_mixed_port, - verge - .proxy_host - .clone() - .unwrap_or_else(|| "127.0.0.1".to_string()), - ) - }; - - let port = verge_mixed_port.unwrap_or_else(|| Config::clash().data().get_mixed_port()); + let verge_config = Config::verge(); + let verge = verge_config.latest_ref(); + let port = verge + .verge_mixed_port + .unwrap_or(Config::clash().latest_ref().get_mixed_port()); + let proxy_host = verge + .proxy_host + .clone() + .unwrap_or_else(|| "127.0.0.1".to_string()); Sysproxy { enable: true, @@ -484,7 +480,7 @@ impl EventDrivenProxyManager { fn get_bypass_config() -> String { let (use_default, custom_bypass) = { let verge_config = Config::verge(); - let verge = verge_config.latest(); + let verge = verge_config.latest_ref(); ( verge.use_default_bypass.unwrap_or(true), verge.system_proxy_bypass.clone().unwrap_or_default(), diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 44cdb041..fae8240d 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -25,7 +25,7 @@ impl Hotkey { pub fn init(&self) -> Result<()> { let verge = Config::verge(); - let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true); + let enable_global_hotkey = verge.latest_ref().enable_global_hotkey.unwrap_or(true); logging!( debug, @@ -39,7 +39,7 @@ impl Hotkey { return Ok(()); } - if let Some(hotkeys) = verge.latest().hotkeys.as_ref() { + if let Some(hotkeys) = verge.latest_ref().hotkeys.as_ref() { logging!( debug, Type::Hotkey, @@ -252,7 +252,7 @@ impl Hotkey { logging!(debug, Type::Hotkey, "Executing function directly"); let is_enable_global_hotkey = Config::verge() - .latest() + .latest_ref() .enable_global_hotkey .unwrap_or(true); diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 5c269db4..3ec24829 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -32,7 +32,7 @@ pub struct ServiceState { impl ServiceState { // 获取当前的服务状态 pub fn get() -> Self { - if let Some(state) = Config::verge().latest().service_state.clone() { + if let Some(state) = Config::verge().latest_ref().service_state.clone() { return state; } Self::default() @@ -41,11 +41,11 @@ impl ServiceState { // 保存服务状态 pub fn save(&self) -> Result<()> { let config = Config::verge(); - let mut latest = config.latest().clone(); + let mut latest = config.latest_ref().clone(); latest.service_state = Some(self.clone()); - *config.draft() = latest; + *config.draft_mut() = latest; config.apply(); - let result = config.latest().save_file(); + let result = config.latest_ref().save_file(); result } @@ -741,7 +741,7 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result log::info!(target:"app", "尝试使用现有服务启动核心 (IPC)"); // logging!(info, Type::Service, true, "尝试使用现有服务启动核心"); - let clash_core = Config::verge().latest().get_valid_clash_core(); + let clash_core = Config::verge().latest_ref().get_valid_clash_core(); let bin_ext = if cfg!(windows) { ".exe" } else { "" }; let clash_bin = format!("{clash_core}{bin_ext}"); diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 8e72fa39..758331fb 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -29,10 +29,13 @@ static DEFAULT_BYPASS: &str = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,"; fn get_bypass() -> String { - let use_default = Config::verge().latest().use_default_bypass.unwrap_or(true); + let use_default = Config::verge() + .latest_ref() + .use_default_bypass + .unwrap_or(true); let res = { let verge = Config::verge(); - let verge = verge.latest(); + let verge = verge.latest_ref(); verge.system_proxy_bypass.clone() }; let custom_bypass = match res { @@ -72,14 +75,14 @@ impl Sysopt { let _lock = self.update_sysproxy.lock().await; let port = Config::verge() - .latest() + .latest_ref() .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); + .unwrap_or(Config::clash().latest_ref().get_mixed_port()); let pac_port = IVerge::get_singleton_port(); let (sys_enable, pac_enable, proxy_host) = { let verge = Config::verge(); - let verge = verge.latest(); + let verge = verge.latest_ref(); ( verge.enable_system_proxy.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false), @@ -239,7 +242,7 @@ impl Sysopt { /// update the startup pub fn update_launch(&self) -> Result<()> { - let enable_auto_launch = { Config::verge().latest().enable_auto_launch }; + let enable_auto_launch = { Config::verge().latest_ref().enable_auto_launch }; let is_enable = enable_auto_launch.unwrap_or(false); logging!(info, true, "Setting auto-launch state to: {:?}", is_enable); diff --git a/src-tauri/src/core/timer.rs b/src-tauri/src/core/timer.rs index 8f38275d..668a23e0 100644 --- a/src-tauri/src/core/timer.rs +++ b/src-tauri/src/core/timer.rs @@ -91,7 +91,7 @@ impl Timer { let cur_timestamp = chrono::Local::now().timestamp(); // Collect profiles that need immediate update - let profiles_to_update = if let Some(items) = Config::profiles().latest().get_items() { + let profiles_to_update = if let Some(items) = Config::profiles().latest_ref().get_items() { items .iter() .filter_map(|item| { @@ -229,7 +229,7 @@ impl Timer { fn gen_map(&self) -> HashMap { let mut new_map = HashMap::new(); - if let Some(items) = Config::profiles().latest().get_items() { + if let Some(items) = Config::profiles().latest_ref().get_items() { for item in items.iter() { if let Some(option) = item.option.as_ref() { if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) { @@ -376,7 +376,7 @@ impl Timer { // Get the profile updated timestamp let profiles_config = Config::profiles(); - let profiles = profiles_config.latest(); + let profiles = profiles_config.latest_ref(); let items = match profiles.get_items() { Some(i) => i, None => { @@ -438,7 +438,7 @@ impl Timer { match tokio::time::timeout(std::time::Duration::from_secs(40), async { Self::emit_update_event(&uid, true); - let is_current = Config::profiles().latest().current.as_ref() == Some(&uid); + let is_current = Config::profiles().latest_ref().current.as_ref() == Some(&uid); logging!( info, Type::Timer, diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 9bdbba59..a2b822d8 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -66,7 +66,7 @@ pub struct Tray { impl TrayState { pub fn get_common_tray_icon() -> (bool, Vec) { - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false); if is_common_tray_icon { if let Some(common_icon_path) = find_target_icons("common").unwrap() { @@ -100,7 +100,7 @@ impl TrayState { } pub fn get_sysproxy_tray_icon() -> (bool, Vec) { - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false); if is_sysproxy_tray_icon { if let Some(sysproxy_icon_path) = find_target_icons("sysproxy").unwrap() { @@ -134,7 +134,7 @@ impl TrayState { } pub fn get_tun_tray_icon() -> (bool, Vec) { - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false); if is_tun_tray_icon { if let Some(tun_icon_path) = find_target_icons("tun").unwrap() { @@ -191,7 +191,7 @@ impl Tray { /// 更新托盘点击行为 pub fn update_click_behavior(&self) -> Result<()> { let app_handle = handle::Handle::global().app_handle().unwrap(); - let tray_event = { Config::verge().latest().tray_event.clone() }; + let tray_event = { Config::verge().latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); let tray = app_handle.tray_by_id("main").unwrap(); match tray_event.as_str() { @@ -251,12 +251,12 @@ impl Tray { } fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> { - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); let mode = { Config::clash() - .latest() + .latest_ref() .0 .get("mode") .map(|val| val.as_str().unwrap_or("rule")) @@ -264,7 +264,7 @@ impl Tray { .to_owned() }; let profile_uid_and_name = Config::profiles() - .data() + .data_mut() .all_profile_uid_and_name() .unwrap_or_default(); let is_lightweight_mode = is_in_lightweight_mode(); @@ -308,7 +308,7 @@ impl Tray { } }; - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); @@ -345,7 +345,7 @@ impl Tray { } }; - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); @@ -389,7 +389,7 @@ impl Tray { } }; - let verge = Config::verge().latest().clone(); + let verge = Config::verge().latest_ref().clone(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); @@ -402,7 +402,7 @@ impl Tray { let mut current_profile_name = "None".to_string(); let profiles = Config::profiles(); - let profiles = profiles.latest(); + let profiles = profiles.latest_ref(); if let Some(current_profile_uid) = profiles.get_current() { if let Ok(profile) = profiles.get_item(¤t_profile_uid) { current_profile_name = match &profile.name { @@ -461,7 +461,7 @@ impl Tray { #[cfg(any(target_os = "macos", target_os = "windows"))] { - let tray_event = { Config::verge().latest().tray_event.clone() }; + let tray_event = { Config::verge().latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); if tray_event.as_str() != "tray_menu" { builder = builder.show_menu_on_left_click(false); @@ -471,7 +471,7 @@ impl Tray { let tray = builder.build(app_handle)?; tray.on_tray_icon_event(|_, event| { - let tray_event = { Config::verge().latest().tray_event.clone() }; + let tray_event = { Config::verge().latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); log::debug!(target: "app","tray event: {tray_event:?}"); @@ -534,7 +534,7 @@ fn create_tray_menu( let version = VERSION.get().unwrap_or(&unknown_version); let hotkeys = Config::verge() - .latest() + .latest_ref() .hotkeys .as_ref() .map(|h| { @@ -554,7 +554,7 @@ fn create_tray_menu( .iter() .map(|(profile_uid, profile_name)| { let is_current_profile = Config::profiles() - .data() + .data_mut() .is_current_profile_index(profile_uid.to_string()); CheckMenuItem::with_id( app_handle, diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index 1838811e..a3fa567f 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -16,11 +16,11 @@ type ResultLog = Vec<(String, String)>; /// 返回最终订阅、该订阅包含的键、和script执行的结果 pub async fn enhance() -> (Mapping, Vec, HashMap) { // config.yaml 的订阅 - let clash_config = { Config::clash().latest().0.clone() }; + let clash_config = { Config::clash().latest_ref().0.clone() }; let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = { let verge = Config::verge(); - let verge = verge.latest(); + let verge = verge.latest_ref(); ( Some(verge.get_valid_clash_core()), verge.enable_tun_mode.unwrap_or(false), @@ -33,13 +33,13 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { #[cfg(not(target_os = "windows"))] let redir_enabled = { let verge = Config::verge(); - let verge = verge.latest(); + let verge = verge.latest_ref(); verge.verge_redir_enabled.unwrap_or(false) }; #[cfg(target_os = "linux")] let tproxy_enabled = { let verge = Config::verge(); - let verge = verge.latest(); + let verge = verge.latest_ref(); verge.verge_tproxy_enabled.unwrap_or(false) }; @@ -56,7 +56,7 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { profile_name, ) = { let profiles = Config::profiles(); - let profiles = profiles.latest(); + let profiles = profiles.latest_ref(); let current = profiles.current_mapping().unwrap_or_default(); let merge = profiles diff --git a/src-tauri/src/feat/backup.rs b/src-tauri/src/feat/backup.rs index 08ace869..07adc5fc 100644 --- a/src-tauri/src/feat/backup.rs +++ b/src-tauri/src/feat/backup.rs @@ -52,7 +52,7 @@ pub async fn delete_webdav_backup(filename: String) -> Result<()> { /// Restore WebDAV backup pub async fn restore_webdav_backup(filename: String) -> Result<()> { let verge = Config::verge(); - let verge_data = verge.data().clone(); + let verge_data = verge.latest_ref().clone(); let webdav_url = verge_data.webdav_url.clone(); let webdav_username = verge_data.webdav_username.clone(); let webdav_password = verge_data.webdav_password.clone(); diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index 8aa6d72f..3afab1f3 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -68,16 +68,16 @@ pub fn change_clash_mode(mode: String) { match MihomoManager::global().patch_configs(json_value).await { Ok(_) => { // 更新订阅 - Config::clash().data().patch_config(mapping); + Config::clash().data_mut().patch_config(mapping); - if Config::clash().data().save_config().is_ok() { + if Config::clash().data_mut().save_config().is_ok() { handle::Handle::refresh_clash(); logging_error!(Type::Tray, true, tray::Tray::global().update_menu()); logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None)); } let is_auto_close_connection = Config::verge() - .data() + .data_mut() .auto_close_connection .unwrap_or(false); if is_auto_close_connection { @@ -94,7 +94,10 @@ pub async fn test_delay(url: String) -> anyhow::Result { use crate::utils::network::{NetworkManager, ProxyType}; use tokio::time::Instant; - let tun_mode = Config::verge().latest().enable_tun_mode.unwrap_or(false); + let tun_mode = Config::verge() + .latest_ref() + .enable_tun_mode + .unwrap_or(false); // 如果是TUN模式,不使用代理,否则使用自身代理 let proxy_type = if !tun_mode { diff --git a/src-tauri/src/feat/config.rs b/src-tauri/src/feat/config.rs index f4810d0b..2436cbdb 100644 --- a/src-tauri/src/feat/config.rs +++ b/src-tauri/src/feat/config.rs @@ -10,7 +10,7 @@ use serde_yaml::Mapping; /// Patch Clash configuration pub async fn patch_clash(patch: Mapping) -> Result<()> { - Config::clash().draft().patch_config(patch.clone()); + Config::clash().draft_mut().patch_config(patch.clone()); let res = { // 激活订阅 @@ -22,7 +22,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { logging_error!(Type::Tray, true, tray::Tray::global().update_menu()); logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None)); } - Config::runtime().latest().patch_config(patch); + Config::runtime().draft_mut().patch_config(patch); CoreManager::global().update_config().await?; } handle::Handle::refresh_clash(); @@ -31,7 +31,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { match res { Ok(()) => { Config::clash().apply(); - Config::clash().data().save_config()?; + Config::clash().data_mut().save_config()?; Ok(()) } Err(err) => { @@ -60,7 +60,7 @@ enum UpdateFlags { /// Patch Verge configuration pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { - Config::verge().draft().patch_config(patch.clone()); + Config::verge().draft_mut().patch_config(patch.clone()); let tun_mode = patch.enable_tun_mode; let auto_launch = patch.enable_auto_launch; @@ -175,7 +175,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { handle::Handle::refresh_clash(); } if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 { - Config::verge().draft().enable_global_hotkey = enable_global_hotkey; + Config::verge().draft_mut().enable_global_hotkey = enable_global_hotkey; handle::Handle::refresh_verge(); } if (update_flags & (UpdateFlags::Launch as i32)) != 0 { @@ -213,7 +213,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { Ok(()) => { Config::verge().apply(); if !not_save_file { - Config::verge().data().save_file()?; + Config::verge().data_mut().save_file()?; } Ok(()) diff --git a/src-tauri/src/feat/profile.rs b/src-tauri/src/feat/profile.rs index 83b840a6..8b2c088f 100644 --- a/src-tauri/src/feat/profile.rs +++ b/src-tauri/src/feat/profile.rs @@ -36,7 +36,7 @@ pub async fn update_profile( let url_opt = { let profiles = Config::profiles(); - let profiles = profiles.latest(); + let profiles = profiles.latest_ref(); let item = profiles.get_item(&uid)?; let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote"); @@ -66,7 +66,7 @@ pub async fn update_profile( Ok(item) => { log::info!(target: "app", "[订阅更新] 更新订阅配置成功"); let profiles = Config::profiles(); - let mut profiles = profiles.latest(); + let mut profiles = profiles.draft_mut(); profiles.update_item(uid.clone(), item)?; let is_current = Some(uid.clone()) == profiles.get_current(); @@ -102,7 +102,7 @@ pub async fn update_profile( // 更新到配置 let profiles = Config::profiles(); - let mut profiles = profiles.latest(); + let mut profiles = profiles.draft_mut(); profiles.update_item(uid.clone(), item.clone())?; // 获取配置名称用于通知 diff --git a/src-tauri/src/feat/proxy.rs b/src-tauri/src/feat/proxy.rs index fed2e07a..1568d505 100644 --- a/src-tauri/src/feat/proxy.rs +++ b/src-tauri/src/feat/proxy.rs @@ -8,10 +8,10 @@ use tauri_plugin_clipboard_manager::ClipboardExt; /// Toggle system proxy on/off pub fn toggle_system_proxy() { - let enable = Config::verge().draft().enable_system_proxy; + let enable = Config::verge().draft_mut().enable_system_proxy; let enable = enable.unwrap_or(false); let auto_close_connection = Config::verge() - .data() + .data_mut() .auto_close_connection .unwrap_or(false); @@ -43,7 +43,7 @@ pub fn toggle_system_proxy() { /// Toggle TUN mode on/off pub fn toggle_tun_mode(not_save_file: Option) { - let enable = Config::verge().data().enable_tun_mode; + let enable = Config::verge().data_mut().enable_tun_mode; let enable = enable.unwrap_or(false); AsyncHandler::spawn(async move || { @@ -67,19 +67,24 @@ pub fn copy_clash_env() { // 从环境变量获取IP地址,如果没有则从配置中获取 proxy_host,默认为 127.0.0.1 let clash_verge_rev_ip = env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| { Config::verge() - .latest() + .latest_ref() .proxy_host .clone() .unwrap_or_else(|| "127.0.0.1".to_string()) }); let app_handle = handle::Handle::global().app_handle().unwrap(); - let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) }; + let port = { + Config::verge() + .latest_ref() + .verge_mixed_port + .unwrap_or(7897) + }; let http_proxy = format!("http://{clash_verge_rev_ip}:{port}"); let socks5_proxy = format!("socks5://{clash_verge_rev_ip}:{port}"); let cliboard = app_handle.clipboard(); - let env_type = { Config::verge().latest().env_type.clone() }; + let env_type = { Config::verge().latest_ref().env_type.clone() }; let env_type = match env_type { Some(env_type) => env_type, None => { diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index ba61640b..40dfb435 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -99,7 +99,7 @@ async fn clean_async() -> bool { // 1. 处理TUN模式 let tun_task = async { - if Config::verge().data().enable_tun_mode.unwrap_or(false) { + if Config::verge().data_mut().enable_tun_mode.unwrap_or(false) { let disable_tun = serde_json::json!({ "tun": { "enable": false @@ -240,7 +240,7 @@ pub fn hide() { use crate::module::lightweight::add_light_weight_timer; let enable_auto_light_weight_mode = Config::verge() - .data() + .data_mut() .enable_auto_light_weight_mode .unwrap_or(false); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 461c8cc3..cd5fa763 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,5 +1,5 @@ mod cmd; -mod config; +pub mod config; mod core; mod enhance; mod feat; @@ -401,7 +401,7 @@ pub fn run() { } { let is_enable_global_hotkey = Config::verge() - .latest() + .latest_ref() .enable_global_hotkey .unwrap_or(true); if !is_enable_global_hotkey { @@ -425,7 +425,7 @@ pub fn run() { } { let is_enable_global_hotkey = Config::verge() - .latest() + .latest_ref() .enable_global_hotkey .unwrap_or(true); if !is_enable_global_hotkey { diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index 6e77e263..b69c8b1d 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -36,9 +36,12 @@ where pub fn run_once_auto_lightweight() { LightWeightState::default().run_once_time(|| { - let is_silent_start = Config::verge().data().enable_silent_start.unwrap_or(false); + let is_silent_start = Config::verge() + .latest_ref() + .enable_silent_start + .unwrap_or(false); let enable_auto = Config::verge() - .data() + .data_mut() .enable_auto_light_weight_mode .unwrap_or(false); if enable_auto && is_silent_start { @@ -62,8 +65,9 @@ pub fn run_once_auto_lightweight() { pub fn auto_lightweight_mode_init() { if let Some(app_handle) = handle::Handle::global().app_handle() { let _ = app_handle.state::>(); - let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false); - let enable_auto = { Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false); + let is_silent_start = { Config::verge().latest_ref().enable_silent_start }.unwrap_or(false); + let enable_auto = + { Config::verge().latest_ref().enable_auto_light_weight_mode }.unwrap_or(false); if enable_auto && !is_silent_start { logging!( @@ -225,7 +229,7 @@ fn cancel_window_close_listener() { fn setup_light_weight_timer() -> Result<()> { Timer::global().init()?; let once_by_minutes = Config::verge() - .latest() + .latest_ref() .auto_light_weight_minutes .unwrap_or(10); diff --git a/src-tauri/src/module/mihomo.rs b/src-tauri/src/module/mihomo.rs index c425510f..c11ea58b 100644 --- a/src-tauri/src/module/mihomo.rs +++ b/src-tauri/src/module/mihomo.rs @@ -92,7 +92,7 @@ impl MihomoManager { impl MihomoManager { pub fn get_clash_client_info() -> Option<(String, HeaderMap)> { - let client = { Config::clash().data().get_client_info() }; + let client = { Config::clash().latest_ref().get_client_info() }; let server = format!("http://{}", client.server); let mut headers = HeaderMap::new(); headers.insert("Content-Type", "application/json".parse().unwrap()); diff --git a/src-tauri/src/utils/i18n.rs b/src-tauri/src/utils/i18n.rs index 247a7ee0..d6b6f0bc 100644 --- a/src-tauri/src/utils/i18n.rs +++ b/src-tauri/src/utils/i18n.rs @@ -59,7 +59,7 @@ fn get_system_language() -> String { pub fn t(key: &str) -> String { let current_lang = Config::verge() - .latest() + .latest_ref() .language .as_deref() .map(String::from) diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index d2c916ea..cfa39da0 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -25,7 +25,7 @@ fn init_log() -> Result<()> { let _ = fs::create_dir_all(&log_dir); } - let log_level = Config::verge().data().get_log_level(); + let log_level = Config::verge().latest_ref().get_log_level(); if log_level == LevelFilter::Off { return Ok(()); } @@ -74,7 +74,7 @@ pub fn delete_log() -> Result<()> { let auto_log_clean = { let verge = Config::verge(); - let verge = verge.data(); + let verge = verge.latest_ref(); verge.auto_log_clean.unwrap_or(0) }; @@ -405,7 +405,7 @@ pub async fn startup_script() -> Result<()> { let script_path = { let verge = Config::verge(); - let verge = verge.latest(); + let verge = verge.latest_ref(); verge.startup_script.clone().unwrap_or("".to_string()) }; diff --git a/src-tauri/src/utils/network.rs b/src-tauri/src/utils/network.rs index 9ac0fe75..b84289cd 100644 --- a/src-tauri/src/utils/network.rs +++ b/src-tauri/src/utils/network.rs @@ -141,9 +141,9 @@ impl NetworkManager { if client_guard.is_none() { let port = Config::verge() - .latest() + .latest_ref() .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); + .unwrap_or(Config::clash().latest_ref().get_mixed_port()); let proxy_scheme = format!("http://127.0.0.1:{port}"); @@ -279,9 +279,9 @@ impl NetworkManager { } ProxyType::Localhost => { let port = Config::verge() - .latest() + .latest_ref() .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); + .unwrap_or(Config::clash().latest_ref().get_mixed_port()); let proxy_scheme = format!("http://127.0.0.1:{port}"); diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index ad28365c..a97b0821 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -118,9 +118,9 @@ pub async fn find_unused_port() -> Result { } Err(_) => { let port = Config::verge() - .latest() + .latest_ref() .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); + .unwrap_or(Config::clash().latest_ref().get_mixed_port()); log::warn!(target: "app", "use default port: {port}"); Ok(port) } @@ -160,7 +160,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) { // 启动时清理冗余的 Profile 文件 logging!(info, Type::Setup, true, "清理冗余的Profile文件..."); let profiles = Config::profiles(); - if let Err(e) = profiles.latest().auto_cleanup() { + if let Err(e) = profiles.latest_ref().auto_cleanup() { logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e); } else { logging!(info, Type::Setup, true, "启动时Profile文件清理完成"); @@ -204,7 +204,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) { ); // 创建窗口 - let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false); + let is_silent_start = { Config::verge().latest_ref().enable_silent_start }.unwrap_or(false); #[cfg(target_os = "macos")] { if is_silent_start { @@ -574,7 +574,7 @@ pub async fn resolve_scheme(param: String) -> Result<()> { match PrfItem::from_url(url.as_ref(), name, None, None).await { Ok(item) => { let uid = item.uid.clone().unwrap(); - let _ = wrap_err!(Config::profiles().data().append_item(item)); + let _ = wrap_err!(Config::profiles().data_mut().append_item(item)); handle::Handle::notice_message("import_sub_url::ok", uid); } Err(e) => { @@ -592,12 +592,15 @@ pub async fn resolve_scheme(param: String) -> Result<()> { async fn resolve_random_port_config() -> Result<()> { let verge_config = Config::verge(); let clash_config = Config::clash(); - let enable_random_port = verge_config.latest().enable_random_port.unwrap_or(false); + let enable_random_port = verge_config + .latest_ref() + .enable_random_port + .unwrap_or(false); let default_port = verge_config - .latest() + .latest_ref() .verge_mixed_port - .unwrap_or(clash_config.data().get_mixed_port()); + .unwrap_or(clash_config.latest_ref().get_mixed_port()); let port = if enable_random_port { find_unused_port().await.unwrap_or(default_port) @@ -609,7 +612,7 @@ async fn resolve_random_port_config() -> Result<()> { tokio::task::spawn_blocking(move || { let verge_config_accessor = Config::verge(); - let mut verge_data = verge_config_accessor.data(); + let mut verge_data = verge_config_accessor.data_mut(); verge_data.patch_config(IVerge { verge_mixed_port: Some(port_to_save), ..IVerge::default() @@ -620,7 +623,7 @@ async fn resolve_random_port_config() -> Result<()> { tokio::task::spawn_blocking(move || { let clash_config_accessor = Config::clash(); // Extend lifetime of the accessor - let mut clash_data = clash_config_accessor.data(); // Access within blocking task, made mutable + let mut clash_data = clash_config_accessor.data_mut(); // Access within blocking task, made mutable let mut mapping = Mapping::new(); mapping.insert("mixed-port".into(), port_to_save.into()); clash_data.patch_config(mapping); diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index 6c8b3503..7747d30a 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -56,14 +56,14 @@ pub fn embed_server() { let pac = warp::path!("commands" / "pac").map(move || { let content = Config::verge() - .latest() + .latest_ref() .pac_file_content .clone() .unwrap_or(DEFAULT_PAC.to_string()); let port = Config::verge() - .latest() + .latest_ref() .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); + .unwrap_or(Config::clash().latest_ref().get_mixed_port()); let content = content.replace("%mixed-port%", &format!("{port}")); warp::http::Response::builder() .header("Content-Type", "application/x-ns-proxy-autoconfig")