Refactor configuration access to use latest_arc() instead of latest_ref()

- Updated multiple instances in the codebase to replace calls to latest_ref() with latest_arc() for improved performance and memory management.
- This change affects various modules including validate, enhance, feat (backup, clash, config, profile, proxy, window), utils (draft, i18n, init, network, resolve, server).
- Ensured that all references to configuration data are now using the new arc-based approach to enhance concurrency and reduce cloning overhead.

refactor: update imports to explicitly include ClashInfo and Config in command files
This commit is contained in:
Tunglies 2025-11-03 05:41:53 +08:00
parent 48a19f99e2
commit 2287ea5f0b
No known key found for this signature in database
GPG Key ID: B9B01B389469B3E8
37 changed files with 533 additions and 331 deletions

View File

@ -7,13 +7,13 @@ use app_lib::config::IVerge;
use app_lib::utils::Draft as DraftNew; use app_lib::utils::Draft as DraftNew;
/// 创建测试数据 /// 创建测试数据
fn make_draft() -> DraftNew<Box<IVerge>> { fn make_draft() -> DraftNew<IVerge> {
let verge = Box::new(IVerge { let verge = IVerge {
enable_auto_launch: Some(true), enable_auto_launch: Some(true),
enable_tun_mode: Some(false), enable_tun_mode: Some(false),
..Default::default() ..Default::default()
}); };
DraftNew::from(verge) DraftNew::new(verge)
} }
pub fn bench_draft(c: &mut Criterion) { pub fn bench_draft(c: &mut Criterion) {
@ -30,18 +30,17 @@ pub fn bench_draft(c: &mut Criterion) {
group.bench_function("data_mut", |b| { group.bench_function("data_mut", |b| {
b.iter(|| { b.iter(|| {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
let mut data = draft.data_mut(); draft.edit_draft(|d| d.enable_tun_mode = Some(true));
data.enable_tun_mode = Some(true); black_box(&draft.latest_arc().enable_tun_mode);
black_box(&data.enable_tun_mode);
}); });
}); });
group.bench_function("draft_mut_first", |b| { group.bench_function("draft_mut_first", |b| {
b.iter(|| { b.iter(|| {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
let mut d = draft.draft_mut(); draft.edit_draft(|d| d.enable_auto_launch = Some(false));
d.enable_auto_launch = Some(false); let latest = draft.latest_arc();
black_box(&d.enable_auto_launch); black_box(&latest.enable_auto_launch);
}); });
}); });
@ -49,20 +48,24 @@ pub fn bench_draft(c: &mut Criterion) {
b.iter(|| { b.iter(|| {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
{ {
let mut first = draft.draft_mut(); draft.edit_draft(|d| {
first.enable_tun_mode = Some(true); d.enable_tun_mode = Some(true);
black_box(&first.enable_tun_mode); });
let latest1 = draft.latest_arc();
black_box(&latest1.enable_tun_mode);
} }
let mut second = draft.draft_mut(); draft.edit_draft(|d| {
second.enable_tun_mode = Some(false); d.enable_tun_mode = Some(false);
black_box(&second.enable_tun_mode); });
let latest2 = draft.latest_arc();
black_box(&latest2.enable_tun_mode);
}); });
}); });
group.bench_function("latest_ref", |b| { group.bench_function("latest_arc", |b| {
b.iter(|| { b.iter(|| {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
let latest = draft.latest_ref(); let latest = draft.latest_arc();
black_box(&latest.enable_auto_launch); black_box(&latest.enable_auto_launch);
}); });
}); });
@ -71,8 +74,9 @@ pub fn bench_draft(c: &mut Criterion) {
b.iter(|| { b.iter(|| {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
{ {
let mut d = draft.draft_mut(); draft.edit_draft(|d| {
d.enable_auto_launch = Some(false); d.enable_auto_launch = Some(false);
});
} }
draft.apply(); draft.apply();
black_box(&draft); black_box(&draft);
@ -83,8 +87,9 @@ pub fn bench_draft(c: &mut Criterion) {
b.iter(|| { b.iter(|| {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
{ {
let mut d = draft.draft_mut(); draft.edit_draft(|d| {
d.enable_auto_launch = Some(false); d.enable_auto_launch = Some(false);
});
} }
draft.discard(); draft.discard();
black_box(&draft); black_box(&draft);
@ -95,7 +100,7 @@ pub fn bench_draft(c: &mut Criterion) {
b.to_async(&rt).iter(|| async { b.to_async(&rt).iter(|| async {
let draft = black_box(make_draft()); let draft = black_box(make_draft());
let _: Result<(), anyhow::Error> = draft let _: Result<(), anyhow::Error> = draft
.with_data_modify::<_, _, _, anyhow::Error>(|mut box_data| async move { .with_data_modify::<_, _, _>(|mut box_data| async move {
box_data.enable_auto_launch = box_data.enable_auto_launch =
Some(!box_data.enable_auto_launch.unwrap_or(false)); Some(!box_data.enable_auto_launch.unwrap_or(false));
Ok((box_data, ())) Ok((box_data, ()))

View File

@ -2,11 +2,11 @@ use super::CmdResult;
use crate::utils::dirs; use crate::utils::dirs;
use crate::{ use crate::{
cmd::StringifyErr, cmd::StringifyErr,
config::Config, config::{ClashInfo, Config},
constants, constants,
core::{CoreManager, handle, validate::CoreConfigValidator}, core::{CoreManager, handle, validate::CoreConfigValidator},
}; };
use crate::{config::*, feat, logging, utils::logging::Type}; use crate::{feat, logging, utils::logging::Type};
use compact_str::CompactString; use compact_str::CompactString;
use serde_yaml_ng::Mapping; use serde_yaml_ng::Mapping;
use smartstring::alias::String; use smartstring::alias::String;
@ -22,7 +22,7 @@ pub async fn copy_clash_env() -> CmdResult {
/// 获取Clash信息 /// 获取Clash信息
#[tauri::command] #[tauri::command]
pub async fn get_clash_info() -> CmdResult<ClashInfo> { pub async fn get_clash_info() -> CmdResult<ClashInfo> {
Ok(Config::clash().await.latest_ref().get_client_info()) Ok(Config::clash().await.latest_arc().get_client_info())
} }
/// 修改Clash配置 /// 修改Clash配置
@ -141,12 +141,6 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
/// 应用或撤销DNS配置 /// 应用或撤销DNS配置
#[tauri::command] #[tauri::command]
pub async fn apply_dns_config(apply: bool) -> CmdResult { pub async fn apply_dns_config(apply: bool) -> CmdResult {
use crate::{
config::Config,
core::{CoreManager, handle},
utils::dirs,
};
if apply { if apply {
// 读取DNS配置文件 // 读取DNS配置文件
let dns_path = dirs::app_home_dir() let dns_path = dirs::app_home_dir()
@ -175,7 +169,9 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
patch.insert("dns".into(), patch_config.into()); patch.insert("dns".into(), patch_config.into());
// 应用DNS配置到运行时配置 // 应用DNS配置到运行时配置
Config::runtime().await.draft_mut().patch_config(patch); Config::runtime().await.edit_draft(|d| {
d.patch_config(patch);
});
// 重新生成配置 // 重新生成配置
Config::generate().await.stringify_err_log(|err| { Config::generate().await.stringify_err_log(|err| {

View File

@ -1,5 +1,6 @@
use super::CmdResult; use super::CmdResult;
use super::StringifyErr; use super::StringifyErr;
use crate::utils::draft::SharedBox;
use crate::{ use crate::{
config::{ config::{
Config, IProfiles, PrfItem, PrfOption, Config, IProfiles, PrfItem, PrfOption,
@ -23,11 +24,11 @@ use std::time::Duration;
static CURRENT_SWITCHING_PROFILE: AtomicBool = AtomicBool::new(false); static CURRENT_SWITCHING_PROFILE: AtomicBool = AtomicBool::new(false);
#[tauri::command] #[tauri::command]
pub async fn get_profiles() -> CmdResult<IProfiles> { pub async fn get_profiles() -> CmdResult<SharedBox<IProfiles>> {
logging!(debug, Type::Cmd, "获取配置文件列表"); logging!(debug, Type::Cmd, "获取配置文件列表");
let draft = Config::profiles().await; let draft = Config::profiles().await;
let latest = draft.latest_ref(); let latest = draft.latest_arc();
Ok((**latest).clone()) Ok(latest)
} }
/// 增强配置文件 /// 增强配置文件
@ -172,7 +173,7 @@ async fn validate_new_profile(new_profile: &String) -> Result<(), ()> {
// 获取目标配置文件路径 // 获取目标配置文件路径
let config_file_result = { let config_file_result = {
let profiles_config = Config::profiles().await; let profiles_config = Config::profiles().await;
let profiles_data = profiles_config.latest_ref(); let profiles_data = profiles_config.latest_arc();
match profiles_data.get_item(new_profile) { match profiles_data.get_item(new_profile) {
Ok(item) => { Ok(item) => {
if let Some(file) = &item.file { if let Some(file) = &item.file {
@ -282,8 +283,7 @@ async fn restore_previous_profile(prev_profile: String) -> CmdResult<()> {
}; };
Config::profiles() Config::profiles()
.await .await
.draft_mut() .edit_draft(|d| d.patch_config(&restore_profiles))
.patch_config(&restore_profiles)
.stringify_err()?; .stringify_err()?;
Config::profiles().await.apply(); Config::profiles().await.apply();
crate::process::AsyncHandler::spawn(|| async move { crate::process::AsyncHandler::spawn(|| async move {
@ -392,7 +392,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
); );
// 保存当前配置,以便在验证失败时恢复 // 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().await.latest_ref().current.clone(); let current_profile = Config::profiles().await.latest_arc().current.clone();
logging!(info, Type::Cmd, "当前配置: {:?}", current_profile); logging!(info, Type::Cmd, "当前配置: {:?}", current_profile);
// 如果要切换配置,先检查目标配置文件是否有语法错误 // 如果要切换配置,先检查目标配置文件是否有语法错误
@ -403,7 +403,10 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
return Ok(false); return Ok(false);
} }
let _ = Config::profiles().await.draft_mut().patch_config(&profiles); let _ = Config::profiles()
.await
.edit_draft(|d| d.patch_config(&profiles));
let current_value = profiles.current.clone(); let current_value = profiles.current.clone();
perform_config_update(current_value, current_profile).await perform_config_update(current_value, current_profile).await
@ -426,7 +429,7 @@ pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> Cm
pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult { pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
// 保存修改前检查是否有更新 update_interval // 保存修改前检查是否有更新 update_interval
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let should_refresh_timer = if let Ok(old_profile) = profiles.latest_ref().get_item(&index) let should_refresh_timer = if let Ok(old_profile) = profiles.latest_arc().get_item(&index)
&& let Some(new_option) = profile.option.as_ref() && let Some(new_option) = profile.option.as_ref()
{ {
let old_interval = old_profile.option.as_ref().and_then(|o| o.update_interval); let old_interval = old_profile.option.as_ref().and_then(|o| o.update_interval);
@ -465,7 +468,7 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
#[tauri::command] #[tauri::command]
pub async fn view_profile(index: String) -> CmdResult { pub async fn view_profile(index: String) -> CmdResult {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles_ref = profiles.latest_ref(); let profiles_ref = profiles.latest_arc();
let file = profiles_ref let file = profiles_ref
.get_item(&index) .get_item(&index)
.stringify_err()? .stringify_err()?
@ -488,7 +491,7 @@ pub async fn view_profile(index: String) -> CmdResult {
pub async fn read_profile_file(index: String) -> CmdResult<String> { pub async fn read_profile_file(index: String) -> CmdResult<String> {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles_ref = profiles.latest_ref(); let profiles_ref = profiles.latest_arc();
PrfItem { PrfItem {
file: profiles_ref.get_item(&index).stringify_err()?.file.clone(), file: profiles_ref.get_item(&index).stringify_err()?.file.clone(),
..Default::default() ..Default::default()

View File

@ -8,14 +8,14 @@ use std::collections::HashMap;
/// 获取运行时配置 /// 获取运行时配置
#[tauri::command] #[tauri::command]
pub async fn get_runtime_config() -> CmdResult<Option<Mapping>> { pub async fn get_runtime_config() -> CmdResult<Option<Mapping>> {
Ok(Config::runtime().await.latest_ref().config.clone()) Ok(Config::runtime().await.latest_arc().config.clone())
} }
/// 获取运行时YAML配置 /// 获取运行时YAML配置
#[tauri::command] #[tauri::command]
pub async fn get_runtime_yaml() -> CmdResult<String> { pub async fn get_runtime_yaml() -> CmdResult<String> {
let runtime = Config::runtime().await; let runtime = Config::runtime().await;
let runtime = runtime.latest_ref(); let runtime = runtime.latest_arc();
let config = runtime.config.as_ref(); let config = runtime.config.as_ref();
config config
@ -31,19 +31,19 @@ pub async fn get_runtime_yaml() -> CmdResult<String> {
/// 获取运行时存在的键 /// 获取运行时存在的键
#[tauri::command] #[tauri::command]
pub async fn get_runtime_exists() -> CmdResult<Vec<String>> { pub async fn get_runtime_exists() -> CmdResult<Vec<String>> {
Ok(Config::runtime().await.latest_ref().exists_keys.clone()) Ok(Config::runtime().await.latest_arc().exists_keys.clone())
} }
/// 获取运行时日志 /// 获取运行时日志
#[tauri::command] #[tauri::command]
pub async fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> { pub async fn get_runtime_logs() -> CmdResult<HashMap<String, Vec<(String, String)>>> {
Ok(Config::runtime().await.latest_ref().chain_logs.clone()) Ok(Config::runtime().await.latest_arc().chain_logs.clone())
} }
#[tauri::command] #[tauri::command]
pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> CmdResult<String> { pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> CmdResult<String> {
let runtime = Config::runtime().await; let runtime = Config::runtime().await;
let runtime = runtime.latest_ref(); let runtime = runtime.latest_arc();
let config = runtime let config = runtime
.config .config
@ -98,9 +98,7 @@ pub async fn update_proxy_chain_config_in_runtime(
) -> CmdResult<()> { ) -> CmdResult<()> {
{ {
let runtime = Config::runtime().await; let runtime = Config::runtime().await;
let mut draft = runtime.draft_mut(); runtime.edit_draft(|d| d.update_proxy_chain_config(proxy_chain_config));
draft.update_proxy_chain_config(proxy_chain_config);
drop(draft);
runtime.apply(); runtime.apply();
} }

View File

@ -20,7 +20,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
// 在异步操作前获取必要元数据并释放锁 // 在异步操作前获取必要元数据并释放锁
let (rel_path, is_merge_file) = { let (rel_path, is_merge_file) = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles_guard = profiles.latest_ref(); let profiles_guard = profiles.latest_arc();
let item = profiles_guard.get_item(&index).stringify_err()?; let item = profiles_guard.get_item(&index).stringify_err()?;
let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge"); let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge");
let path = item.file.clone().ok_or("file field is null")?; let path = item.file.clone().ok_or("file field is null")?;

View File

@ -1,16 +1,16 @@
use super::CmdResult; use super::CmdResult;
use crate::{cmd::StringifyErr, config::*, feat}; use crate::{
cmd::StringifyErr,
config::{Config, IVerge},
feat,
utils::draft::SharedBox,
};
/// 获取Verge配置 /// 获取Verge配置
#[tauri::command] #[tauri::command]
pub async fn get_verge_config() -> CmdResult<IVergeResponse> { pub async fn get_verge_config() -> CmdResult<SharedBox<IVerge>> {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge_data = { Ok(verge.latest_arc())
let ref_data = verge.latest_ref();
ref_data.clone()
};
let verge_response = IVergeResponse::from(verge_data);
Ok(verge_response)
} }
/// 修改Verge配置 /// 修改Verge配置

View File

@ -1,5 +1,9 @@
use super::CmdResult; use super::CmdResult;
use crate::{cmd::StringifyErr, config::*, core, feat}; use crate::{
cmd::StringifyErr,
config::{Config, IVerge},
core, feat,
};
use reqwest_dav::list_cmd::ListFile; use reqwest_dav::list_cmd::ListFile;
use smartstring::alias::String; use smartstring::alias::String;
@ -12,15 +16,11 @@ pub async fn save_webdav_config(url: String, username: String, password: String)
webdav_password: Some(password), webdav_password: Some(password),
..IVerge::default() ..IVerge::default()
}; };
Config::verge().await.draft_mut().patch_config(&patch); Config::verge().await.edit_draft(|e| e.patch_config(&patch));
Config::verge().await.apply(); Config::verge().await.apply();
// 分离数据获取和异步调用 let verge_data = Config::verge().await.latest_arc();
let verge_data = Config::verge().await.latest_ref().clone(); verge_data.save_file().await.stringify_err()?;
verge_data
.save_file()
.await
.map_err(|err| err.to_string())?;
core::backup::WebDavClient::global().reset(); core::backup::WebDavClient::global().reset();
Ok(()) Ok(())
} }

View File

@ -300,7 +300,7 @@ impl IClashTemp {
// 检查 enable_external_controller 设置,用于运行时配置生成 // 检查 enable_external_controller 设置,用于运行时配置生成
let enable_external_controller = Config::verge() let enable_external_controller = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_external_controller .enable_external_controller
.unwrap_or(false); .unwrap_or(false);

View File

@ -15,10 +15,10 @@ use tokio::sync::OnceCell;
use tokio::time::sleep; use tokio::time::sleep;
pub struct Config { pub struct Config {
clash_config: Draft<Box<IClashTemp>>, clash_config: Draft<IClashTemp>,
verge_config: Draft<Box<IVerge>>, verge_config: Draft<IVerge>,
profiles_config: Draft<Box<IProfiles>>, profiles_config: Draft<IProfiles>,
runtime_config: Draft<Box<IRuntime>>, runtime_config: Draft<IRuntime>,
} }
impl Config { impl Config {
@ -27,28 +27,28 @@ impl Config {
CONFIG CONFIG
.get_or_init(|| async { .get_or_init(|| async {
Config { Config {
clash_config: Draft::from(Box::new(IClashTemp::new().await)), clash_config: Draft::new(IClashTemp::new().await),
verge_config: Draft::from(Box::new(IVerge::new().await)), verge_config: Draft::new(IVerge::new().await),
profiles_config: Draft::from(Box::new(IProfiles::new().await)), profiles_config: Draft::new(IProfiles::new().await),
runtime_config: Draft::from(Box::new(IRuntime::new())), runtime_config: Draft::new(IRuntime::new()),
} }
}) })
.await .await
} }
pub async fn clash() -> Draft<Box<IClashTemp>> { pub async fn clash() -> Draft<IClashTemp> {
Self::global().await.clash_config.clone() Self::global().await.clash_config.clone()
} }
pub async fn verge() -> Draft<Box<IVerge>> { pub async fn verge() -> Draft<IVerge> {
Self::global().await.verge_config.clone() Self::global().await.verge_config.clone()
} }
pub async fn profiles() -> Draft<Box<IProfiles>> { pub async fn profiles() -> Draft<IProfiles> {
Self::global().await.profiles_config.clone() Self::global().await.profiles_config.clone()
} }
pub async fn runtime() -> Draft<Box<IRuntime>> { pub async fn runtime() -> Draft<IRuntime> {
Self::global().await.runtime_config.clone() Self::global().await.runtime_config.clone()
} }
@ -61,12 +61,14 @@ impl Config {
&& service::is_service_available().await.is_err() && service::is_service_available().await.is_err()
{ {
let verge = Config::verge().await; let verge = Config::verge().await;
verge.draft_mut().enable_tun_mode = Some(false); verge.edit_draft(|d| {
d.enable_tun_mode = Some(false);
});
verge.apply(); verge.apply();
let _ = tray::Tray::global().update_tray_display().await; let _ = tray::Tray::global().update_tray_display().await;
// 分离数据获取和异步调用避免Send问题 // 分离数据获取和异步调用避免Send问题
let verge_data = Config::verge().await.latest_ref().clone(); let verge_data = Config::verge().await.latest_arc();
logging_error!(Type::Core, verge_data.save_file().await); logging_error!(Type::Core, verge_data.save_file().await);
} }
@ -83,11 +85,11 @@ impl Config {
// Ensure "Merge" and "Script" profile items exist, adding them if missing. // Ensure "Merge" and "Script" profile items exist, adding them if missing.
async fn ensure_default_profile_items() -> Result<()> { async fn ensure_default_profile_items() -> Result<()> {
let profiles = Self::profiles().await; let profiles = Self::profiles().await;
if profiles.latest_ref().get_item("Merge").is_err() { if profiles.latest_arc().get_item("Merge").is_err() {
let merge_item = &mut PrfItem::from_merge(Some("Merge".into()))?; let merge_item = &mut PrfItem::from_merge(Some("Merge".into()))?;
profiles_append_item_safe(merge_item).await?; profiles_append_item_safe(merge_item).await?;
} }
if profiles.latest_ref().get_item("Script").is_err() { if profiles.latest_arc().get_item("Script").is_err() {
let script_item = &mut PrfItem::from_script(Some("Script".into()))?; let script_item = &mut PrfItem::from_script(Some("Script".into()))?;
profiles_append_item_safe(script_item).await?; profiles_append_item_safe(script_item).await?;
} }
@ -154,7 +156,7 @@ impl Config {
let runtime = Config::runtime().await; let runtime = Config::runtime().await;
let config = runtime let config = runtime
.latest_ref() .latest_arc()
.config .config
.as_ref() .as_ref()
.ok_or_else(|| anyhow!("failed to get runtime config"))? .ok_or_else(|| anyhow!("failed to get runtime config"))?
@ -168,11 +170,13 @@ impl Config {
pub async fn generate() -> Result<()> { pub async fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance().await; let (config, exists_keys, logs) = enhance::enhance().await;
**Config::runtime().await.draft_mut() = IRuntime { Config::runtime().await.edit_draft(|d| {
*d = IRuntime {
config: Some(config), config: Some(config),
exists_keys, exists_keys,
chain_logs: logs, chain_logs: logs,
}; }
});
Ok(()) Ok(())
} }
@ -187,7 +191,7 @@ impl Config {
}; };
let operation = || async { let operation = || async {
if Config::runtime().await.latest_ref().config.is_some() { if Config::runtime().await.latest_arc().config.is_some() {
return Ok::<(), BackoffError<anyhow::Error>>(()); return Ok::<(), BackoffError<anyhow::Error>>(());
} }
@ -228,7 +232,7 @@ mod tests {
#[test] #[test]
#[allow(unused_variables)] #[allow(unused_variables)]
fn test_draft_size_non_boxed() { fn test_draft_size_non_boxed() {
let draft = Draft::from(IRuntime::new()); let draft = Draft::new(IRuntime::new());
let iruntime_size = std::mem::size_of_val(&draft); let iruntime_size = std::mem::size_of_val(&draft);
assert_eq!(iruntime_size, std::mem::size_of::<Draft<IRuntime>>()); assert_eq!(iruntime_size, std::mem::size_of::<Draft<IRuntime>>());
} }
@ -236,7 +240,7 @@ mod tests {
#[test] #[test]
#[allow(unused_variables)] #[allow(unused_variables)]
fn test_draft_size_boxed() { fn test_draft_size_boxed() {
let draft = Draft::from(Box::new(IRuntime::new())); let draft = Draft::new(Box::new(IRuntime::new()));
let box_iruntime_size = std::mem::size_of_val(&draft); let box_iruntime_size = std::mem::size_of_val(&draft);
assert_eq!( assert_eq!(
box_iruntime_size, box_iruntime_size,

View File

@ -313,7 +313,9 @@ impl IVerge {
); );
let config_draft = Config::verge().await; let config_draft = Config::verge().await;
**config_draft.draft_mut() = updated_config; config_draft.edit_draft(|d| {
*d = updated_config;
});
config_draft.apply(); config_draft.apply();
Ok(()) Ok(())
@ -696,9 +698,3 @@ impl From<IVerge> for IVergeResponse {
} }
} }
} }
impl From<Box<IVerge>> for IVergeResponse {
fn from(verge: Box<IVerge>) -> Self {
IVergeResponse::from(*verge)
}
}

View File

@ -87,7 +87,7 @@ impl WebDavClient {
(*cfg_arc).clone() (*cfg_arc).clone()
} else { } else {
// 释放锁后获取异步配置 // 释放锁后获取异步配置
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
if verge.webdav_url.is_none() if verge.webdav_url.is_none()
|| verge.webdav_username.is_none() || verge.webdav_username.is_none()
|| verge.webdav_password.is_none() || verge.webdav_password.is_none()
@ -99,11 +99,13 @@ impl WebDavClient {
let config = WebDavConfig { let config = WebDavConfig {
url: verge url: verge
.webdav_url .webdav_url
.as_ref()
.cloned()
.unwrap_or_default() .unwrap_or_default()
.trim_end_matches('/') .trim_end_matches('/')
.into(), .into(),
username: verge.webdav_username.unwrap_or_default(), username: verge.webdav_username.as_ref().cloned().unwrap_or_default(),
password: verge.webdav_password.unwrap_or_default(), password: verge.webdav_password.as_ref().cloned().unwrap_or_default(),
}; };
// 存储配置到 ArcSwapOption // 存储配置到 ArcSwapOption

View File

@ -387,7 +387,7 @@ impl EventDrivenProxyManager {
async fn get_proxy_config() -> ProxyConfig { async fn get_proxy_config() -> ProxyConfig {
let (sys_enabled, pac_enabled, guard_enabled, guard_duration) = { let (sys_enabled, pac_enabled, guard_enabled, guard_duration) = {
let verge_config = Config::verge().await; let verge_config = Config::verge().await;
let verge = verge_config.latest_ref(); let verge = verge_config.latest_arc();
( (
verge.enable_system_proxy.unwrap_or(false), verge.enable_system_proxy.unwrap_or(false),
verge.proxy_auto_config.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false),
@ -406,7 +406,7 @@ impl EventDrivenProxyManager {
async fn get_expected_pac_config() -> Autoproxy { async fn get_expected_pac_config() -> Autoproxy {
let proxy_host = { let proxy_host = {
let verge_config = Config::verge().await; let verge_config = Config::verge().await;
let verge = verge_config.latest_ref(); let verge = verge_config.latest_arc();
verge verge
.proxy_host .proxy_host
.clone() .clone()
@ -424,13 +424,13 @@ impl EventDrivenProxyManager {
let (verge_mixed_port, proxy_host) = { let (verge_mixed_port, proxy_host) = {
let verge_config = Config::verge().await; let verge_config = Config::verge().await;
let verge_ref = verge_config.latest_ref(); let verge_ref = verge_config.latest_arc();
(verge_ref.verge_mixed_port, verge_ref.proxy_host.clone()) (verge_ref.verge_mixed_port, verge_ref.proxy_host.clone())
}; };
let default_port = { let default_port = {
let clash_config = Config::clash().await; let clash_config = Config::clash().await;
clash_config.latest_ref().get_mixed_port() clash_config.latest_arc().get_mixed_port()
}; };
let port = verge_mixed_port.unwrap_or(default_port); let port = verge_mixed_port.unwrap_or(default_port);
@ -450,7 +450,7 @@ impl EventDrivenProxyManager {
use crate::constants::bypass; use crate::constants::bypass;
let verge_config = Config::verge().await; let verge_config = Config::verge().await;
let verge = verge_config.latest_ref(); let verge = verge_config.latest_arc();
let use_default = verge.use_default_bypass.unwrap_or(true); let use_default = verge.use_default_bypass.unwrap_or(true);
let custom = verge.system_proxy_bypass.as_deref().unwrap_or(""); let custom = verge.system_proxy_bypass.as_deref().unwrap_or("");

View File

@ -237,7 +237,7 @@ impl Hotkey {
let is_enable_global_hotkey = Config::verge() let is_enable_global_hotkey = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_global_hotkey .enable_global_hotkey
.unwrap_or(true); .unwrap_or(true);
@ -274,7 +274,7 @@ singleton_with_logging!(Hotkey, INSTANCE, "Hotkey");
impl Hotkey { impl Hotkey {
pub async fn init(&self, skip: bool) -> Result<()> { pub async fn init(&self, skip: bool) -> Result<()> {
let verge = Config::verge().await; let verge = Config::verge().await;
let enable_global_hotkey = !skip && verge.latest_ref().enable_global_hotkey.unwrap_or(true); let enable_global_hotkey = !skip && verge.latest_arc().enable_global_hotkey.unwrap_or(true);
logging!( logging!(
debug, debug,
@ -284,7 +284,7 @@ impl Hotkey {
); );
// Extract hotkeys data before async operations // Extract hotkeys data before async operations
let hotkeys = verge.latest_ref().hotkeys.as_ref().cloned(); let hotkeys = verge.latest_arc().hotkeys.as_ref().cloned();
if let Some(hotkeys) = hotkeys { if let Some(hotkeys) = hotkeys {
logging!( logging!(

View File

@ -17,13 +17,15 @@ impl CoreManager {
use crate::constants::files::RUNTIME_CONFIG; use crate::constants::files::RUNTIME_CONFIG;
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG); let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
let clash_config = Config::clash().await.latest_ref().0.clone(); let clash_config = &Config::clash().await.latest_arc().0;
**Config::runtime().await.draft_mut() = IRuntime { Config::runtime().await.edit_draft(|d| {
config: Some(clash_config.clone()), *d = IRuntime {
config: Some(clash_config.to_owned()),
exists_keys: vec![], exists_keys: vec![],
chain_logs: Default::default(), chain_logs: Default::default(),
}; }
});
help::save_yaml(&runtime_path, &clash_config, Some("# Clash Verge Runtime")).await?; help::save_yaml(&runtime_path, &clash_config, Some("# Clash Verge Runtime")).await?;
handle::Handle::notice_message(error_key, error_msg); handle::Handle::notice_message(error_key, error_msg);

View File

@ -47,10 +47,12 @@ impl CoreManager {
return Err(format!("Invalid clash core: {}", clash_core).into()); return Err(format!("Invalid clash core: {}", clash_core).into());
} }
Config::verge().await.draft_mut().clash_core = clash_core.to_owned().into(); Config::verge().await.edit_draft(|d| {
d.clash_core = Some(clash_core.to_owned());
});
Config::verge().await.apply(); Config::verge().await.apply();
let verge_data = Config::verge().await.latest_ref().clone(); let verge_data = Config::verge().await.latest_arc();
verge_data.save_file().await.map_err(|e| e.to_string())?; verge_data.save_file().await.map_err(|e| e.to_string())?;
let run_path = Config::generate_file(ConfigType::Run) let run_path = Config::generate_file(ConfigType::Run)
@ -82,7 +84,7 @@ impl CoreManager {
let needs_service = Config::verge() let needs_service = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_tun_mode .enable_tun_mode
.unwrap_or(false); .unwrap_or(false);

View File

@ -32,7 +32,7 @@ impl CoreManager {
let config_file = Config::generate_file(crate::config::ConfigType::Run).await?; let config_file = Config::generate_file(crate::config::ConfigType::Run).await?;
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();
let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); let clash_core = Config::verge().await.latest_arc().get_valid_clash_core();
let config_dir = dirs::app_home_dir()?; let config_dir = dirs::app_home_dir()?;
let (mut rx, child) = app_handle let (mut rx, child) = app_handle

View File

@ -353,7 +353,7 @@ pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result
logging!(info, Type::Service, "尝试使用现有服务启动核心"); logging!(info, Type::Service, "尝试使用现有服务启动核心");
let verge_config = Config::verge().await; let verge_config = Config::verge().await;
let clash_core = verge_config.latest_ref().get_valid_clash_core(); let clash_core = verge_config.latest_arc().get_valid_clash_core();
drop(verge_config); drop(verge_config);
let bin_ext = if cfg!(windows) { ".exe" } else { "" }; let bin_ext = if cfg!(windows) { ".exe" } else { "" };

View File

@ -31,12 +31,12 @@ static DEFAULT_BYPASS: &str = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12
async fn get_bypass() -> String { async fn get_bypass() -> String {
let use_default = Config::verge() let use_default = Config::verge()
.await .await
.latest_ref() .latest_arc()
.use_default_bypass .use_default_bypass
.unwrap_or(true); .unwrap_or(true);
let res = { let res = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
verge.system_proxy_bypass.clone() verge.system_proxy_bypass.clone()
}; };
let custom_bypass = match res { let custom_bypass = match res {
@ -124,17 +124,17 @@ impl Sysopt {
} }
let port = { let port = {
let verge_port = Config::verge().await.latest_ref().verge_mixed_port; let verge_port = Config::verge().await.latest_arc().verge_mixed_port;
match verge_port { match verge_port {
Some(port) => port, Some(port) => port,
None => Config::clash().await.latest_ref().get_mixed_port(), None => Config::clash().await.latest_arc().get_mixed_port(),
} }
}; };
let pac_port = IVerge::get_singleton_port(); let pac_port = IVerge::get_singleton_port();
let (sys_enable, pac_enable, proxy_host) = { let (sys_enable, pac_enable, proxy_host) = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
( (
verge.enable_system_proxy.unwrap_or(false), verge.enable_system_proxy.unwrap_or(false),
verge.proxy_auto_config.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false),
@ -266,7 +266,7 @@ impl Sysopt {
/// update the startup /// update the startup
pub async fn update_launch(&self) -> Result<()> { pub async fn update_launch(&self) -> Result<()> {
let enable_auto_launch = { Config::verge().await.latest_ref().enable_auto_launch }; let enable_auto_launch = { Config::verge().await.latest_arc().enable_auto_launch };
let is_enable = enable_auto_launch.unwrap_or(false); let is_enable = enable_auto_launch.unwrap_or(false);
logging!( logging!(
info, info,

View File

@ -100,7 +100,7 @@ impl Timer {
// Collect profiles that need immediate update // Collect profiles that need immediate update
let profiles_to_update = let profiles_to_update =
if let Some(items) = Config::profiles().await.latest_ref().get_items() { if let Some(items) = Config::profiles().await.latest_arc().get_items() {
items items
.iter() .iter()
.filter_map(|item| { .filter_map(|item| {
@ -273,7 +273,7 @@ impl Timer {
async fn gen_map(&self) -> HashMap<String, u64> { async fn gen_map(&self) -> HashMap<String, u64> {
let mut new_map = HashMap::new(); let mut new_map = HashMap::new();
if let Some(items) = Config::profiles().await.latest_ref().get_items() { if let Some(items) = Config::profiles().await.latest_arc().get_items() {
for item in items.iter() { for item in items.iter() {
if let Some(option) = item.option.as_ref() if let Some(option) = item.option.as_ref()
&& let Some(allow_auto_update) = option.allow_auto_update && let Some(allow_auto_update) = option.allow_auto_update
@ -427,7 +427,7 @@ impl Timer {
// Get the profile updated timestamp - now safe to await // Get the profile updated timestamp - now safe to await
let items = { let items = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles_guard = profiles.latest_ref(); let profiles_guard = profiles.latest_arc();
match profiles_guard.get_items() { match profiles_guard.get_items() {
Some(i) => i.clone(), Some(i) => i.clone(),
None => { None => {
@ -489,7 +489,7 @@ impl Timer {
match tokio::time::timeout(std::time::Duration::from_secs(40), async { match tokio::time::timeout(std::time::Duration::from_secs(40), async {
Self::emit_update_event(uid, true); Self::emit_update_event(uid, true);
let is_current = Config::profiles().await.latest_ref().current.as_ref() == Some(uid); let is_current = Config::profiles().await.latest_arc().current.as_ref() == Some(uid);
logging!( logging!(
info, info,
Type::Timer, Type::Timer,

View File

@ -86,7 +86,7 @@ pub struct Tray {
impl TrayState { impl TrayState {
pub async fn get_common_tray_icon() -> (bool, Vec<u8>) { pub async fn get_common_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false); let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false);
if is_common_tray_icon if is_common_tray_icon
&& let Ok(Some(common_icon_path)) = find_target_icons("common") && let Ok(Some(common_icon_path)) = find_target_icons("common")
@ -123,7 +123,7 @@ impl TrayState {
} }
pub async fn get_sysproxy_tray_icon() -> (bool, Vec<u8>) { pub async fn get_sysproxy_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false); let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false);
if is_sysproxy_tray_icon if is_sysproxy_tray_icon
&& let Ok(Some(sysproxy_icon_path)) = find_target_icons("sysproxy") && let Ok(Some(sysproxy_icon_path)) = find_target_icons("sysproxy")
@ -160,7 +160,7 @@ impl TrayState {
} }
pub async fn get_tun_tray_icon() -> (bool, Vec<u8>) { pub async fn get_tun_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false); let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false);
if is_tun_tray_icon if is_tun_tray_icon
&& let Ok(Some(tun_icon_path)) = find_target_icons("tun") && let Ok(Some(tun_icon_path)) = find_target_icons("tun")
@ -243,7 +243,7 @@ impl Tray {
} }
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();
let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
let tray_event = tray_event.unwrap_or_else(|| "main_window".into()); let tray_event = tray_event.unwrap_or_else(|| "main_window".into());
let tray = app_handle let tray = app_handle
.tray_by_id("main") .tray_by_id("main")
@ -303,7 +303,7 @@ impl Tray {
} }
async fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> { async fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> {
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); 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 tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
let tun_mode_available = cmd::system::is_admin().unwrap_or_default() let tun_mode_available = cmd::system::is_admin().unwrap_or_default()
@ -311,7 +311,7 @@ impl Tray {
let mode = { let mode = {
Config::clash() Config::clash()
.await .await
.latest_ref() .latest_arc()
.0 .0
.get("mode") .get("mode")
.map(|val| val.as_str().unwrap_or("rule")) .map(|val| val.as_str().unwrap_or("rule"))
@ -320,7 +320,7 @@ impl Tray {
}; };
let profile_uid_and_name = Config::profiles() let profile_uid_and_name = Config::profiles()
.await .await
.data_mut() .latest_arc()
.all_profile_uid_and_name() .all_profile_uid_and_name()
.unwrap_or_default(); .unwrap_or_default();
let is_lightweight_mode = is_in_lightweight_mode(); let is_lightweight_mode = is_in_lightweight_mode();
@ -375,7 +375,7 @@ impl Tray {
} }
}; };
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false); let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
@ -418,7 +418,7 @@ impl Tray {
} }
}; };
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false); let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
@ -460,7 +460,7 @@ impl Tray {
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();
let verge = Config::verge().await.latest_ref().clone(); let verge = Config::verge().await.latest_arc();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); 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 tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
@ -474,7 +474,7 @@ impl Tray {
let mut current_profile_name = "None".into(); let mut current_profile_name = "None".into();
{ {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
if let Some(current_profile_uid) = profiles.get_current() if let Some(current_profile_uid) = profiles.get_current()
&& let Ok(profile) = profiles.get_item(&current_profile_uid) && let Ok(profile) = profiles.get_item(&current_profile_uid)
{ {
@ -552,7 +552,7 @@ impl Tray {
#[cfg(any(target_os = "macos", target_os = "windows"))] #[cfg(any(target_os = "macos", target_os = "windows"))]
let show_menu_on_left_click = { let show_menu_on_left_click = {
let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into()); let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
tray_event.as_str() == "tray_menu" tray_event.as_str() == "tray_menu"
}; };
@ -583,7 +583,7 @@ impl Tray {
} }
AsyncHandler::spawn(|| async move { AsyncHandler::spawn(|| async move {
let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event = { Config::verge().await.latest_arc().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into()); let tray_event: String = tray_event.unwrap_or_else(|| "main_window".into());
logging!(debug, Type::Tray, "tray event: {tray_event:?}"); logging!(debug, Type::Tray, "tray event: {tray_event:?}");
@ -675,7 +675,7 @@ async fn create_profile_menu_item(
async move { async move {
let is_current_profile = Config::profiles() let is_current_profile = Config::profiles()
.await .await
.latest_ref() .latest_arc()
.is_current_profile_index(profile_uid); .is_current_profile_index(profile_uid);
CheckMenuItem::with_id( CheckMenuItem::with_id(
&app_handle, &app_handle,
@ -878,7 +878,7 @@ async fn create_tray_menu(
// 获取当前配置文件的选中代理组信息 // 获取当前配置文件的选中代理组信息
let current_profile_selected = { let current_profile_selected = {
let profiles_config = Config::profiles().await; let profiles_config = Config::profiles().await;
let profiles_ref = profiles_config.latest_ref(); let profiles_ref = profiles_config.latest_arc();
profiles_ref profiles_ref
.get_current() .get_current()
.and_then(|uid| profiles_ref.get_item(&uid).ok()) .and_then(|uid| profiles_ref.get_item(&uid).ok())
@ -924,7 +924,7 @@ async fn create_tray_menu(
.collect::<HashMap<String, usize>>() .collect::<HashMap<String, usize>>()
}); });
let verge_settings = Config::verge().await.latest_ref().clone(); let verge_settings = Config::verge().await.latest_arc();
let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false); let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false);
let version = env!("CARGO_PKG_VERSION"); let version = env!("CARGO_PKG_VERSION");

View File

@ -266,7 +266,7 @@ impl CoreConfigValidator {
logging!(info, Type::Validate, "开始验证配置文件: {}", config_path); logging!(info, Type::Validate, "开始验证配置文件: {}", config_path);
let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); let clash_core = Config::verge().await.latest_arc().get_valid_clash_core();
logging!(info, Type::Validate, "使用内核: {}", clash_core); logging!(info, Type::Validate, "使用内核: {}", clash_core);
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();

View File

@ -45,11 +45,11 @@ struct ProfileItems {
} }
async fn get_config_values() -> ConfigValues { async fn get_config_values() -> ConfigValues {
let clash_config = { Config::clash().await.latest_ref().0.clone() }; let clash_config = { Config::clash().await.latest_arc().0.clone() };
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = { let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
( (
Some(verge.get_valid_clash_core()), Some(verge.get_valid_clash_core()),
verge.enable_tun_mode.unwrap_or(false), verge.enable_tun_mode.unwrap_or(false),
@ -63,14 +63,14 @@ async fn get_config_values() -> ConfigValues {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
let redir_enabled = { let redir_enabled = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
verge.verge_redir_enabled.unwrap_or(false) verge.verge_redir_enabled.unwrap_or(false)
}; };
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
let tproxy_enabled = { let tproxy_enabled = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
verge.verge_tproxy_enabled.unwrap_or(false) verge.verge_tproxy_enabled.unwrap_or(false)
}; };
@ -103,12 +103,12 @@ async fn collect_profile_items() -> ProfileItems {
) = { ) = {
let current = { let current = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles_clone = profiles.latest_ref().clone(); let profiles_clone = profiles.latest_arc();
profiles_clone.current_mapping().await.unwrap_or_default() profiles_clone.current_mapping().await.unwrap_or_default()
}; };
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles_ref = profiles.latest_ref(); let profiles_ref = profiles.latest_arc();
let merge_uid = profiles_ref.current_merge().unwrap_or_default(); let merge_uid = profiles_ref.current_merge().unwrap_or_default();
let script_uid = profiles_ref.current_script().unwrap_or_default(); let script_uid = profiles_ref.current_script().unwrap_or_default();
@ -139,7 +139,7 @@ async fn collect_profile_items() -> ProfileItems {
let merge_item = { let merge_item = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item(merge_uid).ok().cloned() profiles.get_item(merge_uid).ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -156,7 +156,7 @@ async fn collect_profile_items() -> ProfileItems {
let script_item = { let script_item = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item(script_uid).ok().cloned() profiles.get_item(script_uid).ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -173,7 +173,7 @@ async fn collect_profile_items() -> ProfileItems {
let rules_item = { let rules_item = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item(rules_uid).ok().cloned() profiles.get_item(rules_uid).ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -190,7 +190,7 @@ async fn collect_profile_items() -> ProfileItems {
let proxies_item = { let proxies_item = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item(proxies_uid).ok().cloned() profiles.get_item(proxies_uid).ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -207,7 +207,7 @@ async fn collect_profile_items() -> ProfileItems {
let groups_item = { let groups_item = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item(groups_uid).ok().cloned() profiles.get_item(groups_uid).ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -224,7 +224,7 @@ async fn collect_profile_items() -> ProfileItems {
let global_merge = { let global_merge = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item("Merge").ok().cloned() profiles.get_item("Merge").ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -241,7 +241,7 @@ async fn collect_profile_items() -> ProfileItems {
let global_script = { let global_script = {
let item = { let item = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
profiles.get_item("Script").ok().cloned() profiles.get_item("Script").ok().cloned()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -394,7 +394,7 @@ async fn merge_default_config(
if key.as_str() == Some("external-controller") { if key.as_str() == Some("external-controller") {
let enable_external_controller = Config::verge() let enable_external_controller = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_external_controller .enable_external_controller
.unwrap_or(false); .unwrap_or(false);

View File

@ -78,7 +78,7 @@ pub async fn delete_webdav_backup(filename: String) -> Result<()> {
/// Restore WebDAV backup /// Restore WebDAV backup
pub async fn restore_webdav_backup(filename: String) -> Result<()> { pub async fn restore_webdav_backup(filename: String) -> Result<()> {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge_data = verge.latest_ref().clone(); let verge_data = verge.latest_arc();
let webdav_url = verge_data.webdav_url.clone(); let webdav_url = verge_data.webdav_url.clone();
let webdav_username = verge_data.webdav_username.clone(); let webdav_username = verge_data.webdav_username.clone();
let webdav_password = verge_data.webdav_password.clone(); let webdav_password = verge_data.webdav_password.clone();
@ -243,7 +243,7 @@ pub async fn restore_local_backup(filename: String) -> Result<()> {
let (webdav_url, webdav_username, webdav_password) = { let (webdav_url, webdav_username, webdav_password) = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
( (
verge.webdav_url.clone(), verge.webdav_url.clone(),
verge.webdav_username.clone(), verge.webdav_username.clone(),

View File

@ -72,10 +72,12 @@ pub async fn change_clash_mode(mode: String) {
{ {
Ok(_) => { Ok(_) => {
// 更新订阅 // 更新订阅
Config::clash().await.data_mut().patch_config(mapping); Config::clash()
.await
.edit_draft(|d| d.patch_config(mapping));
// 分离数据获取和异步调用 // 分离数据获取和异步调用
let clash_data = Config::clash().await.data_mut().clone(); let clash_data = Config::clash().await.data_arc();
if clash_data.save_config().await.is_ok() { if clash_data.save_config().await.is_ok() {
handle::Handle::refresh_clash(); handle::Handle::refresh_clash();
logging_error!(Type::Tray, tray::Tray::global().update_menu().await); logging_error!(Type::Tray, tray::Tray::global().update_menu().await);
@ -84,7 +86,7 @@ pub async fn change_clash_mode(mode: String) {
let is_auto_close_connection = Config::verge() let is_auto_close_connection = Config::verge()
.await .await
.data_mut() .data_arc()
.auto_close_connection .auto_close_connection
.unwrap_or(false); .unwrap_or(false);
if is_auto_close_connection { if is_auto_close_connection {
@ -102,7 +104,7 @@ pub async fn test_delay(url: String) -> anyhow::Result<u32> {
let tun_mode = Config::verge() let tun_mode = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_tun_mode .enable_tun_mode
.unwrap_or(false); .unwrap_or(false);

View File

@ -12,8 +12,7 @@ use serde_yaml_ng::Mapping;
pub async fn patch_clash(patch: Mapping) -> Result<()> { pub async fn patch_clash(patch: Mapping) -> Result<()> {
Config::clash() Config::clash()
.await .await
.draft_mut() .edit_draft(|d| d.patch_config(patch.clone()));
.patch_config(patch.clone());
let res = { let res = {
// 激活订阅 // 激活订阅
@ -25,7 +24,9 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
logging_error!(Type::Tray, tray::Tray::global().update_menu().await); logging_error!(Type::Tray, tray::Tray::global().update_menu().await);
logging_error!(Type::Tray, tray::Tray::global().update_icon().await); logging_error!(Type::Tray, tray::Tray::global().update_icon().await);
} }
Config::runtime().await.draft_mut().patch_config(patch); Config::runtime()
.await
.edit_draft(|d| d.patch_config(patch));
CoreManager::global().update_config().await?; CoreManager::global().update_config().await?;
} }
handle::Handle::refresh_clash(); handle::Handle::refresh_clash();
@ -35,7 +36,7 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
Ok(()) => { Ok(()) => {
Config::clash().await.apply(); Config::clash().await.apply();
// 分离数据获取和异步调用 // 分离数据获取和异步调用
let clash_data = Config::clash().await.data_mut().clone(); let clash_data = Config::clash().await.data_arc();
clash_data.save_config().await?; clash_data.save_config().await?;
Ok(()) Ok(())
} }
@ -190,7 +191,9 @@ async fn process_terminated_flags(update_flags: i32, patch: &IVerge) -> Result<(
handle::Handle::refresh_clash(); handle::Handle::refresh_clash();
} }
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 { if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
Config::verge().await.draft_mut().enable_global_hotkey = patch.enable_global_hotkey; Config::verge()
.await
.edit_draft(|d| d.enable_global_hotkey = patch.enable_global_hotkey);
handle::Handle::refresh_verge(); handle::Handle::refresh_verge();
} }
if (update_flags & (UpdateFlags::Launch as i32)) != 0 { if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
@ -227,7 +230,7 @@ async fn process_terminated_flags(update_flags: i32, patch: &IVerge) -> Result<(
} }
pub async fn patch_verge(patch: &IVerge, not_save_file: bool) -> Result<()> { pub async fn patch_verge(patch: &IVerge, not_save_file: bool) -> Result<()> {
Config::verge().await.draft_mut().patch_config(patch); Config::verge().await.edit_draft(|d| d.patch_config(patch));
let update_flags = determine_update_flags(patch); let update_flags = determine_update_flags(patch);
let process_flag_result: std::result::Result<(), anyhow::Error> = { let process_flag_result: std::result::Result<(), anyhow::Error> = {
@ -242,7 +245,7 @@ pub async fn patch_verge(patch: &IVerge, not_save_file: bool) -> Result<()> {
Config::verge().await.apply(); Config::verge().await.apply();
if !not_save_file { if !not_save_file {
// 分离数据获取和异步调用 // 分离数据获取和异步调用
let verge_data = Config::verge().await.data_ref().clone(); let verge_data = Config::verge().await.data_arc();
verge_data.save_file().await?; verge_data.save_file().await?;
} }
Ok(()) Ok(())

View File

@ -28,7 +28,7 @@ async fn should_update_profile(
ignore_auto_update: bool, ignore_auto_update: bool,
) -> Result<Option<(String, Option<PrfOption>)>> { ) -> Result<Option<(String, Option<PrfOption>)>> {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
let profiles = profiles.latest_ref(); let profiles = profiles.latest_arc();
let item = profiles.get_item(uid)?; let item = profiles.get_item(uid)?;
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote"); let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
@ -89,12 +89,12 @@ async fn perform_profile_update(
let mut merged_opt = PrfOption::merge(opt, option); let mut merged_opt = PrfOption::merge(opt, option);
let is_current = { let is_current = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
profiles.latest_ref().is_current_profile_index(uid) profiles.latest_arc().is_current_profile_index(uid)
}; };
let profile_name = { let profile_name = {
let profiles = Config::profiles().await; let profiles = Config::profiles().await;
profiles profiles
.latest_ref() .latest_arc()
.get_name_by_uid(uid) .get_name_by_uid(uid)
.unwrap_or_default() .unwrap_or_default()
}; };

View File

@ -10,8 +10,8 @@ use tauri_plugin_clipboard_manager::ClipboardExt;
/// Toggle system proxy on/off /// Toggle system proxy on/off
pub async fn toggle_system_proxy() { pub async fn toggle_system_proxy() {
let verge = Config::verge().await; let verge = Config::verge().await;
let enable = verge.latest_ref().enable_system_proxy.unwrap_or(false); let enable = verge.latest_arc().enable_system_proxy.unwrap_or(false);
let auto_close_connection = verge.latest_ref().auto_close_connection.unwrap_or(false); let auto_close_connection = verge.latest_arc().auto_close_connection.unwrap_or(false);
// 如果当前系统代理即将关闭且自动关闭连接设置为true则关闭所有连接 // 如果当前系统代理即将关闭且自动关闭连接设置为true则关闭所有连接
if enable if enable
@ -42,7 +42,7 @@ pub async fn toggle_system_proxy() {
/// Toggle TUN mode on/off /// Toggle TUN mode on/off
pub async fn toggle_tun_mode(not_save_file: Option<bool>) { pub async fn toggle_tun_mode(not_save_file: Option<bool>) {
let enable = Config::verge().await.data_mut().enable_tun_mode; let enable = Config::verge().await.latest_arc().enable_tun_mode;
let enable = enable.unwrap_or(false); let enable = enable.unwrap_or(false);
match super::patch_verge( match super::patch_verge(
@ -66,7 +66,7 @@ pub async fn copy_clash_env() {
Ok(ip) => ip.into(), Ok(ip) => ip.into(),
Err(_) => Config::verge() Err(_) => Config::verge()
.await .await
.latest_ref() .latest_arc()
.proxy_host .proxy_host
.clone() .clone()
.unwrap_or_else(|| "127.0.0.1".into()), .unwrap_or_else(|| "127.0.0.1".into()),
@ -76,7 +76,7 @@ pub async fn copy_clash_env() {
let port = { let port = {
Config::verge() Config::verge()
.await .await
.latest_ref() .latest_arc()
.verge_mixed_port .verge_mixed_port
.unwrap_or(7897) .unwrap_or(7897)
}; };
@ -84,7 +84,7 @@ pub async fn copy_clash_env() {
let socks5_proxy = format!("socks5://{clash_verge_rev_ip}:{port}"); let socks5_proxy = format!("socks5://{clash_verge_rev_ip}:{port}");
let cliboard = app_handle.clipboard(); let cliboard = app_handle.clipboard();
let env_type = { Config::verge().await.latest_ref().env_type.clone() }; let env_type = { Config::verge().await.latest_arc().env_type.clone() };
let env_type = match env_type { let env_type = match env_type {
Some(env_type) => env_type, Some(env_type) => env_type,
None => { None => {

View File

@ -47,7 +47,7 @@ pub async fn clean_async() -> bool {
let tun_task = async { let tun_task = async {
let tun_enabled = Config::verge() let tun_enabled = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_tun_mode .enable_tun_mode
.unwrap_or(false); .unwrap_or(false);
@ -100,7 +100,7 @@ pub async fn clean_async() -> bool {
// 检查系统代理是否开启 // 检查系统代理是否开启
let sys_proxy_enabled = Config::verge() let sys_proxy_enabled = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_system_proxy .enable_system_proxy
.unwrap_or(false); .unwrap_or(false);
@ -176,7 +176,7 @@ pub async fn clean_async() -> bool {
{ {
let sys_proxy_enabled = Config::verge() let sys_proxy_enabled = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_system_proxy .enable_system_proxy
.unwrap_or(false); .unwrap_or(false);
@ -316,7 +316,7 @@ pub async fn hide() {
let enable_auto_light_weight_mode = Config::verge() let enable_auto_light_weight_mode = Config::verge()
.await .await
.data_mut() .latest_arc()
.enable_auto_light_weight_mode .enable_auto_light_weight_mode
.unwrap_or(false); .unwrap_or(false);

View File

@ -319,7 +319,7 @@ pub fn run() {
AsyncHandler::spawn(move || async move { AsyncHandler::spawn(move || async move {
let is_enable_global_hotkey = Config::verge() let is_enable_global_hotkey = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_global_hotkey .enable_global_hotkey
.unwrap_or(true); .unwrap_or(true);
@ -360,7 +360,7 @@ pub fn run() {
let _ = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW); let _ = hotkey::Hotkey::global().unregister_system_hotkey(SystemHotkey::CmdW);
let is_enable_global_hotkey = Config::verge() let is_enable_global_hotkey = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_global_hotkey .enable_global_hotkey
.unwrap_or(true); .unwrap_or(true);
if !is_enable_global_hotkey { if !is_enable_global_hotkey {

View File

@ -87,11 +87,11 @@ async fn refresh_lightweight_tray_state() {
pub async fn auto_lightweight_boot() -> Result<()> { pub async fn auto_lightweight_boot() -> Result<()> {
let verge_config = Config::verge().await; let verge_config = Config::verge().await;
let enable_auto = verge_config let enable_auto = verge_config
.data_mut() .latest_arc()
.enable_auto_light_weight_mode .enable_auto_light_weight_mode
.unwrap_or(false); .unwrap_or(false);
let is_silent_start = verge_config let is_silent_start = verge_config
.latest_ref() .latest_arc()
.enable_silent_start .enable_silent_start
.unwrap_or(false); .unwrap_or(false);
@ -236,7 +236,7 @@ async fn setup_light_weight_timer() -> Result<()> {
let once_by_minutes = Config::verge() let once_by_minutes = Config::verge()
.await .await
.latest_ref() .latest_arc()
.auto_light_weight_minutes .auto_light_weight_minutes
.unwrap_or(10); .unwrap_or(10);

View File

@ -1,179 +1,368 @@
use parking_lot::RwLock;
use std::sync::Arc; use std::sync::Arc;
use parking_lot::{ pub type SharedBox<T> = Arc<Box<T>>;
MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, type DraftInner<T> = (SharedBox<T>, Option<SharedBox<T>>);
RwLockUpgradableReadGuard, RwLockWriteGuard,
};
/// Draft 管理committed 与 optional draft 都以 Arc<Box<T>> 存储,
// (committed_snapshot, optional_draft_snapshot)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Draft<T: Clone + ToOwned> { pub struct Draft<T: Clone> {
inner: Arc<RwLock<(T, Option<T>)>>, inner: Arc<RwLock<DraftInner<T>>>,
} }
impl<T: Clone + ToOwned> From<T> for Draft<T> { impl<T: Clone> Draft<T> {
fn from(data: T) -> Self { pub fn new(data: T) -> Self {
Self { Self {
inner: Arc::new(RwLock::new((data, None))), inner: Arc::new(RwLock::new((Arc::new(Box::new(data)), None))),
} }
} }
} /// 以 Arc<Box<T>> 的形式获取当前“已提交(正式)”数据的快照(零拷贝,仅 clone Arc
pub fn data_arc(&self) -> SharedBox<T> {
/// Implements draft management for `Box<T>`, allowing for safe concurrent editing and committing of draft data. let guard = self.inner.read();
/// # Type Parameters Arc::clone(&guard.0)
/// - `T`: The underlying data type, which must implement `Clone` and `ToOwned`.
///
/// # Methods
/// - `data_mut`: Returns a mutable 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<T: Clone + ToOwned> Draft<Box<T>> {
/// 正式数据视图
pub fn data_ref(&self) -> MappedRwLockReadGuard<'_, Box<T>> {
RwLockReadGuard::map(self.inner.read(), |inner| &inner.0)
} }
/// 可写正式数据 /// 获取当前(草稿若存在则返回草稿,否则返回已提交)的快照
pub fn data_mut(&self) -> MappedRwLockWriteGuard<'_, Box<T>> { /// 这也是零拷贝:只 clone Arc不 clone T
RwLockWriteGuard::map(self.inner.write(), |inner| &mut inner.0) pub fn latest_arc(&self) -> SharedBox<T> {
} let guard = self.inner.read();
guard
/// 创建或获取草稿并返回可写引用
pub fn draft_mut(&self) -> MappedRwLockWriteGuard<'_, Box<T>> {
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_or_else(|| {
unreachable!("Draft was just created above, this should never fail")
})
});
}
// 已存在草稿,升级为写锁映射
RwLockWriteGuard::map(RwLockUpgradableReadGuard::upgrade(guard), |inner| {
inner
.1 .1
.as_mut() .as_ref()
.unwrap_or_else(|| unreachable!("Draft should exist when guard.1.is_some()")) .cloned()
}) .unwrap_or_else(|| Arc::clone(&guard.0))
} }
/// 零拷贝只读视图:返回草稿(若存在)或正式值 /// 通过闭包以可变方式编辑草稿(在闭包中我们给出 &mut T
pub fn latest_ref(&self) -> MappedRwLockReadGuard<'_, Box<T>> { /// - 延迟拷贝:如果只有这一个 Arc 引用,则直接修改,不会克隆 T
RwLockReadGuard::map(self.inner.read(), |inner| { /// - 若草稿被其他读者共享Arc::make_mut 会做一次 T.clone最小必要拷贝
inner.1.as_ref().unwrap_or(&inner.0) pub fn edit_draft<F, R>(&self, f: F) -> R
}) where
} F: FnOnce(&mut T) -> R,
{
/// 提交草稿,返回旧正式数据 // 先获得写锁以创建或取出草稿 Arc 的可变引用位置
pub fn apply(&self) { let mut guard = self.inner.write();
let guard = self.inner.upgradable_read();
if guard.1.is_none() { if guard.1.is_none() {
return; // 创建草稿 snapshotArc clonecheap
guard.1 = Some(Arc::clone(&guard.0));
}
// 此时 guaranteed: guard.1 is Some(Arc<Box<T>>)
#[allow(clippy::unwrap_used)]
let arc_box = guard.1.as_mut().unwrap();
// Arc::make_mut: 如果只有一个引用则返回可变引用;否则会克隆底层 Box<T>(要求 T: Clone
let boxed = Arc::make_mut(arc_box); // &mut Box<T>
// 对 Box<T> 解引用得到 &mut T
f(&mut **boxed)
} }
let mut guard = RwLockUpgradableReadGuard::upgrade(guard); /// 将草稿提交到已提交位置(替换),并清除草稿
if let Some(draft) = guard.1.take() { pub fn apply(&self) {
guard.0 = draft; let mut guard = self.inner.write();
if let Some(d) = guard.1.take() {
guard.0 = d;
} }
} }
/// 丢弃草稿,返回被丢弃的草稿 /// 丢弃草稿(如果存在)
pub fn discard(&self) { pub fn discard(&self) {
self.inner.write().1.take(); let mut guard = self.inner.write();
guard.1 = None;
} }
/// 异步修改正式数据,闭包直接获得 Box<T> 所有权 /// 异步地以拥有 Box<T> 的方式修改已提交数据:将克隆一次已提交数据到本地,
pub async fn with_data_modify<F, Fut, R, E>(&self, f: F) -> Result<R, E> /// 异步闭包返回新的 Box<T>(替换已提交数据)和业务返回值 R。
pub async fn with_data_modify<F, Fut, R>(&self, f: F) -> Result<R, anyhow::Error>
where where
T: Send + Sync + 'static, T: Send + Sync + 'static,
F: FnOnce(Box<T>) -> Fut + Send, F: FnOnce(Box<T>) -> Fut + Send,
Fut: std::future::Future<Output = Result<(Box<T>, R), E>> + Send, Fut: std::future::Future<Output = Result<(Box<T>, R), anyhow::Error>> + Send,
E: From<anyhow::Error>,
{ {
// 克隆正式数据 // 读取已提交快照cheap Arc clone, 然后得到 Box<T> 所有权 via clone
let local = { // 注意:为了让闭包接收 Box<T> 所有权,我们需要 clone 底层 T不可避免
let local: Box<T> = {
let guard = self.inner.read(); let guard = self.inner.read();
guard.0.clone() // 将 Arc<Box<T>> 的 Box<T> clone 出来(会调用 T: Clone
(*guard.0).clone()
}; };
// 异步闭包执行,返回修改后的 Box<T> 和业务结果 R
let (new_local, res) = f(local).await?; let (new_local, res) = f(local).await?;
// 写回正式数据 // 将新的 Box<T> 放到已提交位置(包进 Arc
let mut guard = self.inner.write(); let mut guard = self.inner.write();
guard.0 = new_local; guard.0 = Arc::new(new_local);
Ok(res) Ok(res)
} }
} }
#[test] #[cfg(test)]
fn test_draft_box() { mod tests {
use crate::config::IVerge; use super::*;
use anyhow::anyhow;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
// 1. 创建 Draft<Box<IVerge>> #[derive(Clone, Debug, Default, PartialEq)]
let verge = Box::new(IVerge { struct IVerge {
enable_auto_launch: Option<bool>,
enable_tun_mode: Option<bool>,
}
// Minimal single-threaded executor for immediately-ready futures
fn block_on_ready<F: Future>(fut: F) -> F::Output {
fn no_op_raw_waker() -> RawWaker {
fn clone(_: *const ()) -> RawWaker {
no_op_raw_waker()
}
fn wake(_: *const ()) {}
fn wake_by_ref(_: *const ()) {}
fn drop(_: *const ()) {}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
RawWaker::new(std::ptr::null(), &VTABLE)
}
let waker = unsafe { Waker::from_raw(no_op_raw_waker()) };
let mut cx = Context::from_waker(&waker);
let mut fut = Box::pin(fut);
loop {
match Pin::as_mut(&mut fut).poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => std::thread::yield_now(),
}
}
}
#[test]
fn test_draft_basic_flow() {
let verge = IVerge {
enable_auto_launch: Some(true), enable_auto_launch: Some(true),
enable_tun_mode: Some(false), enable_tun_mode: Some(false),
..IVerge::default() };
}); let draft = Draft::new(verge);
let draft = Draft::from(verge);
// 2. 读取正式数据data_mut // 读取正式数据data_arc
{ {
let data = draft.data_mut(); let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(true)); assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false)); assert_eq!(data.enable_tun_mode, Some(false));
} }
// 3. 初次获取草稿draft_mut 会自动 clone 一份) // 修改草稿(使用 edit_draft
{ draft.edit_draft(|d| {
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_auto_launch = Some(false);
d.enable_tun_mode = Some(true); d.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 data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(true));
assert_eq!(data.enable_tun_mode, Some(false));
}
// 草稿已变 // 草稿已变
{ {
let latest = draft.latest_ref(); let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(false)); assert_eq!(latest.enable_auto_launch, Some(false));
assert_eq!(latest.enable_tun_mode, Some(true)); assert_eq!(latest.enable_tun_mode, Some(true));
} }
// 5. 提交草稿 // 提交草稿
draft.apply(); draft.apply();
// 正式数据已更新 // 正式数据已更新
{ {
let data = draft.data_mut(); let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(false)); assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true)); assert_eq!(data.enable_tun_mode, Some(true));
} }
// 6. 新建并修改下一轮草稿 // 新一轮草稿并修改
{ draft.edit_draft(|d| {
let mut d = draft.draft_mut();
d.enable_auto_launch = Some(true); d.enable_auto_launch = Some(true);
});
{
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(true));
assert_eq!(latest.enable_tun_mode, Some(true));
} }
assert_eq!(draft.draft_mut().enable_auto_launch, Some(true));
// 7. 丢弃草稿 // 丢弃草稿
draft.discard(); draft.discard();
// 8. 草稿已被丢弃,新的 draft_mut() 会重新 clone // 丢弃后再次创建草稿,会从已提交重新 clone
assert_eq!(draft.draft_mut().enable_auto_launch, Some(false)); {
draft.edit_draft(|d| {
// 原 committed 是 enable_auto_launch = Some(false)
assert_eq!(d.enable_auto_launch, Some(false));
// 再修改一下
d.enable_tun_mode = Some(false);
});
// 草稿中值已修改,但正式数据仍是 apply 后的值
let data = draft.data_arc();
assert_eq!(data.enable_auto_launch, Some(false));
assert_eq!(data.enable_tun_mode, Some(true));
}
}
#[test]
fn test_arc_pointer_behavior_on_edit_and_apply() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(true),
enable_tun_mode: Some(false),
});
// 初始 latest == committed
let committed = draft.data_arc();
let latest = draft.latest_arc();
assert!(std::sync::Arc::ptr_eq(&committed, &latest));
// 第一次 edit由于与 committed 共享Arc::make_mut 会克隆
draft.edit_draft(|d| d.enable_tun_mode = Some(true));
let committed_after_first_edit = draft.data_arc();
let draft_after_first_edit = draft.latest_arc();
assert!(!std::sync::Arc::ptr_eq(
&committed_after_first_edit,
&draft_after_first_edit
));
// 提交会把 committed 指向草稿的 Arc
let prev_draft_ptr = std::sync::Arc::as_ptr(&draft_after_first_edit);
draft.apply();
let committed_after_apply = draft.data_arc();
assert_eq!(
std::sync::Arc::as_ptr(&committed_after_apply),
prev_draft_ptr
);
// 第二次编辑:此时草稿唯一持有(无其它引用),不应再克隆
// 获取草稿 Arc 的指针并立即丢弃本地引用,避免增加 strong_count
draft.edit_draft(|d| d.enable_auto_launch = Some(false));
let latest1 = draft.latest_arc();
let latest1_ptr = std::sync::Arc::as_ptr(&latest1);
drop(latest1); // 确保只有 Draft 内部持有草稿 Arc
// 再次编辑uniqueArc::make_mut 不应克隆)
draft.edit_draft(|d| d.enable_tun_mode = Some(false));
let latest2 = draft.latest_arc();
let latest2_ptr = std::sync::Arc::as_ptr(&latest2);
assert_eq!(latest1_ptr, latest2_ptr, "Unique edit should not clone Arc");
assert_eq!(latest2.enable_auto_launch, Some(false));
assert_eq!(latest2.enable_tun_mode, Some(false));
}
#[test]
fn test_discard_restores_latest_to_committed() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 创建草稿并修改
draft.edit_draft(|d| d.enable_auto_launch = Some(true));
let committed = draft.data_arc();
let latest = draft.latest_arc();
assert!(!std::sync::Arc::ptr_eq(&committed, &latest));
// 丢弃草稿后 latest 应回到 committed
draft.discard();
let committed2 = draft.data_arc();
let latest2 = draft.latest_arc();
assert!(std::sync::Arc::ptr_eq(&committed2, &latest2));
assert_eq!(latest2.enable_auto_launch, Some(false));
}
#[test]
fn test_edit_draft_returns_closure_result() {
let draft = Draft::new(IVerge::default());
let ret = draft.edit_draft(|d| {
d.enable_tun_mode = Some(true);
123usize
});
assert_eq!(ret, 123);
let latest = draft.latest_arc();
assert_eq!(latest.enable_tun_mode, Some(true));
}
#[test]
fn test_with_data_modify_ok_and_replaces_committed() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 使用 with_data_modify 异步(立即就绪)地更新 committed
let res = block_on_ready(draft.with_data_modify(|mut v| async move {
v.enable_auto_launch = Some(true);
Ok((Box::new(*v), "done")) // Dereference v to get Box<T>
}));
assert_eq!(
{
#[allow(clippy::unwrap_used)]
res.unwrap()
},
"done"
);
let committed = draft.data_arc();
assert_eq!(committed.enable_auto_launch, Some(true));
assert_eq!(committed.enable_tun_mode, Some(false));
}
#[test]
fn test_with_data_modify_error_propagation() {
let draft = Draft::new(IVerge::default());
#[allow(clippy::unwrap_used)]
let err = block_on_ready(draft.with_data_modify(|v| async move {
drop(v);
Err::<(Box<IVerge>, ()), _>(anyhow!("boom"))
}))
.unwrap_err();
assert_eq!(format!("{err}"), "boom");
}
#[test]
fn test_with_data_modify_does_not_touch_existing_draft() {
let draft = Draft::new(IVerge {
enable_auto_launch: Some(false),
enable_tun_mode: Some(false),
});
// 创建草稿并修改
draft.edit_draft(|d| {
d.enable_auto_launch = Some(true);
d.enable_tun_mode = Some(true);
});
let draft_before = draft.latest_arc();
let draft_before_ptr = std::sync::Arc::as_ptr(&draft_before);
// 同时通过 with_data_modify 修改 committed
#[allow(clippy::unwrap_used)]
block_on_ready(draft.with_data_modify(|mut v| async move {
v.enable_auto_launch = Some(false); // 与草稿不同
Ok((Box::new(*v), ())) // Dereference v to get Box<T>
}))
.unwrap();
// 草稿应保持不变
let draft_after = draft.latest_arc();
assert_eq!(
std::sync::Arc::as_ptr(&draft_after),
draft_before_ptr,
"Existing draft should not be replaced by with_data_modify"
);
assert_eq!(draft_after.enable_auto_launch, Some(true));
assert_eq!(draft_after.enable_tun_mode, Some(true));
// 丢弃草稿后 latest == committed且 committed 为异步修改结果
draft.discard();
let latest = draft.latest_arc();
assert_eq!(latest.enable_auto_launch, Some(false));
assert_eq!(latest.enable_tun_mode, Some(false));
}
} }

View File

@ -43,7 +43,7 @@ pub fn get_supported_languages() -> Vec<String> {
pub async fn current_language() -> String { pub async fn current_language() -> String {
Config::verge() Config::verge()
.await .await
.latest_ref() .latest_arc()
.language .language
.as_deref() .as_deref()
.map(String::from) .map(String::from)

View File

@ -31,7 +31,7 @@ pub async fn init_logger() -> Result<()> {
// TODO 提供 runtime 级别实时修改 // TODO 提供 runtime 级别实时修改
let (log_level, log_max_size, log_max_count) = { let (log_level, log_max_size, log_max_count) = {
let verge_guard = Config::verge().await; let verge_guard = Config::verge().await;
let verge = verge_guard.latest_ref(); let verge = verge_guard.latest_arc();
( (
verge.get_log_level(), verge.get_log_level(),
verge.app_log_max_size.unwrap_or(128), verge.app_log_max_size.unwrap_or(128),
@ -81,7 +81,7 @@ pub async fn init_logger() -> Result<()> {
pub async fn sidecar_writer() -> Result<FileLogWriter> { pub async fn sidecar_writer() -> Result<FileLogWriter> {
let (log_max_size, log_max_count) = { let (log_max_size, log_max_count) = {
let verge_guard = Config::verge().await; let verge_guard = Config::verge().await;
let verge = verge_guard.latest_ref(); let verge = verge_guard.latest_arc();
( (
verge.app_log_max_size.unwrap_or(128), verge.app_log_max_size.unwrap_or(128),
verge.app_log_max_count.unwrap_or(8), verge.app_log_max_count.unwrap_or(8),
@ -109,7 +109,7 @@ pub async fn sidecar_writer() -> Result<FileLogWriter> {
pub async fn service_writer_config() -> Result<WriterConfig> { pub async fn service_writer_config() -> Result<WriterConfig> {
let (log_max_size, log_max_count) = { let (log_max_size, log_max_count) = {
let verge_guard = Config::verge().await; let verge_guard = Config::verge().await;
let verge = verge_guard.latest_ref(); let verge = verge_guard.latest_arc();
( (
verge.app_log_max_size.unwrap_or(128), verge.app_log_max_size.unwrap_or(128),
verge.app_log_max_count.unwrap_or(8), verge.app_log_max_count.unwrap_or(8),
@ -134,7 +134,7 @@ pub async fn delete_log() -> Result<()> {
let auto_log_clean = { let auto_log_clean = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
verge.auto_log_clean.unwrap_or(0) verge.auto_log_clean.unwrap_or(0)
}; };
@ -509,7 +509,7 @@ pub async fn startup_script() -> Result<()> {
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();
let script_path = { let script_path = {
let verge = Config::verge().await; let verge = Config::verge().await;
let verge = verge.latest_ref(); let verge = verge.latest_arc();
verge.startup_script.clone().unwrap_or_else(|| "".into()) verge.startup_script.clone().unwrap_or_else(|| "".into())
}; };

View File

@ -165,10 +165,10 @@ impl NetworkManager {
ProxyType::None => None, ProxyType::None => None,
ProxyType::Localhost => { ProxyType::Localhost => {
let port = { let port = {
let verge_port = Config::verge().await.latest_ref().verge_mixed_port; let verge_port = Config::verge().await.latest_arc().verge_mixed_port;
match verge_port { match verge_port {
Some(port) => port, Some(port) => port,
None => Config::clash().await.latest_ref().get_mixed_port(), None => Config::clash().await.latest_arc().get_mixed_port(),
} }
}; };
let proxy_scheme = format!("http://127.0.0.1:{port}"); let proxy_scheme = format!("http://127.0.0.1:{port}");

View File

@ -179,7 +179,7 @@ pub(super) async fn refresh_tray_menu() {
pub(super) async fn init_window() { pub(super) async fn init_window() {
let is_silent_start = Config::verge() let is_silent_start = Config::verge()
.await .await
.latest_ref() .latest_arc()
.enable_silent_start .enable_silent_start
.unwrap_or(false); .unwrap_or(false);
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View File

@ -22,7 +22,7 @@ pub async fn build_new_window() -> Result<WebviewWindow, String> {
let app_handle = handle::Handle::app_handle(); let app_handle = handle::Handle::app_handle();
let config = Config::verge().await; let config = Config::verge().await;
let latest = config.latest_ref(); let latest = config.latest_arc();
let start_page = latest.start_page.as_deref().unwrap_or("/"); let start_page = latest.start_page.as_deref().unwrap_or("/");
match tauri::WebviewWindowBuilder::new( match tauri::WebviewWindowBuilder::new(

View File

@ -89,15 +89,15 @@ pub fn embed_server() {
let clash_config = Config::clash().await; let clash_config = Config::clash().await;
let pac_content = verge_config let pac_content = verge_config
.latest_ref() .latest_arc()
.pac_file_content .pac_file_content
.clone() .clone()
.unwrap_or_else(|| DEFAULT_PAC.into()); .unwrap_or_else(|| DEFAULT_PAC.into());
let pac_port = verge_config let pac_port = verge_config
.latest_ref() .latest_arc()
.verge_mixed_port .verge_mixed_port
.unwrap_or_else(|| clash_config.latest_ref().get_mixed_port()); .unwrap_or_else(|| clash_config.latest_arc().get_mixed_port());
let pac = warp::path!("commands" / "pac").map(move || { let pac = warp::path!("commands" / "pac").map(move || {
let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}")); let processed_content = pac_content.replace("%mixed-port%", &format!("{pac_port}"));