mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-25 13:12:07 +08:00
191 lines
6.8 KiB
Rust
191 lines
6.8 KiB
Rust
use super::effects::{Effect, Effects};
|
|
use crate::core::notify::{Refresh, announce};
|
|
use crate::{
|
|
config::{
|
|
Config, IVerge,
|
|
snapshot::{capture_config_files, restore_files},
|
|
},
|
|
core::auto_backup::AutoBackupManager,
|
|
core::{CoreManager, autostart, hotkey, lightweight, logger, manager::ConfigUpdateGuard, proxy_control, tray},
|
|
};
|
|
use anyhow::Result;
|
|
use clash_verge_draft::DraftTransaction;
|
|
use clash_verge_logging::{Type, logging_error};
|
|
use serde_yaml_ng::Mapping;
|
|
use std::sync::Arc;
|
|
use tokio::sync::MutexGuard;
|
|
|
|
pub(super) enum Patch<'a> {
|
|
Verge { patch: &'a IVerge, persist: bool },
|
|
Clash(&'a Mapping),
|
|
}
|
|
|
|
async fn ensure_restart(manager: &CoreManager) -> Result<()> {
|
|
Config::generate().await?;
|
|
let previous = manager.current_core_readiness_generation();
|
|
let result = manager.restart_core_during_config_update().await;
|
|
if result.is_ok()
|
|
|| manager
|
|
.current_core_readiness_generation()
|
|
.is_some_and(|current| Some(current) != previous)
|
|
{
|
|
Config::runtime().await.apply();
|
|
}
|
|
result?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn ensure_effect(
|
|
effect: Effect,
|
|
patch: &IVerge,
|
|
manager: &CoreManager,
|
|
update: &ConfigUpdateGuard<'_>,
|
|
) -> Result<()> {
|
|
match effect {
|
|
Effect::RestartCore => {
|
|
ensure_restart(manager).await?;
|
|
}
|
|
Effect::ClashConfig => {
|
|
manager.update_config_in_patch(update).await?;
|
|
}
|
|
Effect::Autostart => autostart::update_launch().await?,
|
|
Effect::Language => {
|
|
if let Some(language) = &patch.language {
|
|
clash_verge_i18n::set_locale(language.as_str());
|
|
}
|
|
}
|
|
Effect::SystemProxy => {
|
|
let _lifecycle = manager.lifecycle_lock.lock().await;
|
|
// Disabling only writes OS state and must remain available when the Core is stopped.
|
|
if Config::verge()
|
|
.await
|
|
.latest_arc()
|
|
.enable_system_proxy
|
|
.unwrap_or_default()
|
|
{
|
|
manager.apply_proxy_after_start().await?;
|
|
} else {
|
|
proxy_control::apply().await?;
|
|
proxy_control::refresh_guard().await?;
|
|
}
|
|
}
|
|
Effect::Hotkey => {
|
|
if let Some(hotkeys) = &patch.hotkeys {
|
|
hotkey::Hotkey::global().update(hotkeys.to_owned()).await?;
|
|
}
|
|
}
|
|
Effect::TrayMenu => tray::Tray::global().update_menu().await?,
|
|
Effect::TrayIcon => {
|
|
tray::Tray::global()
|
|
.update_icon(&Config::verge().await.latest_arc())
|
|
.await?;
|
|
#[cfg(target_os = "macos")]
|
|
if let Some(enabled) = patch.enable_tray_speed {
|
|
tray::Tray::global().update_speed_task(enabled);
|
|
}
|
|
}
|
|
Effect::TrayTooltip => tray::Tray::global().update_tooltip().await?,
|
|
Effect::TrayClick => tray::Tray::global().update_click_behavior().await?,
|
|
Effect::Lightweight => {
|
|
if patch.enable_auto_light_weight_mode.unwrap_or(false) {
|
|
lightweight::enable_auto_light_weight_mode().await;
|
|
} else {
|
|
lightweight::disable_auto_light_weight_mode();
|
|
}
|
|
}
|
|
Effect::LogLevel => logger::Logger::global().update_log_level(patch.get_log_level())?,
|
|
Effect::LogFile => {
|
|
logger::update_log_config(
|
|
patch.app_log_max_size.unwrap_or(128),
|
|
patch.app_log_max_count.unwrap_or(8),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) async fn apply(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effects: Effects) -> Result<()> {
|
|
let manager = CoreManager::global();
|
|
let update = manager.claim_config_update(config_write)?;
|
|
let clash = Config::clash().await;
|
|
let verge = Config::verge().await;
|
|
let runtime = Config::runtime().await;
|
|
let transaction = match patch {
|
|
Patch::Verge { .. } => DraftTransaction::begin(vec![&verge, &runtime])?,
|
|
Patch::Clash(_) => DraftTransaction::begin(vec![&clash, &runtime])?,
|
|
};
|
|
let original_runtime = runtime.data_arc();
|
|
let mut snapshots = capture_config_files().await?;
|
|
let empty = IVerge::default();
|
|
let verge_patch = match &patch {
|
|
Patch::Verge { patch, .. } => patch,
|
|
Patch::Clash(_) => &empty,
|
|
};
|
|
match &patch {
|
|
Patch::Verge { patch, .. } => verge.edit_draft(|draft| draft.patch_config(patch)),
|
|
Patch::Clash(patch) => clash.edit_draft(|draft| draft.patch_config(patch)),
|
|
}
|
|
let result: Result<()> = async {
|
|
for effect in effects.iter().copied() {
|
|
let result = Box::pin(ensure_effect(effect, verge_patch, manager, &update)).await;
|
|
if matches!(patch, Patch::Clash(_)) && matches!(effect, Effect::TrayMenu | Effect::TrayIcon) {
|
|
// Tray failures must not reject an applied Clash mode change.
|
|
logging_error!(Type::Tray, result);
|
|
} else {
|
|
result?;
|
|
}
|
|
}
|
|
match patch {
|
|
Patch::Verge { persist: true, .. } => verge.latest_arc().save_file().await?,
|
|
Patch::Verge { persist: false, .. } => {}
|
|
Patch::Clash(_) => clash.latest_arc().save_config().await?,
|
|
}
|
|
Ok(())
|
|
}
|
|
.await;
|
|
if let Err(error) = result {
|
|
transaction.rollback();
|
|
if !Arc::ptr_eq(&original_runtime, &runtime.data_arc()) {
|
|
announce(Refresh::Clash);
|
|
// The Core already loaded this runtime; restoring its file would only hide that state.
|
|
let runtime_path = crate::utils::dirs::app_home_dir()?.join(crate::constants::files::RUNTIME_CONFIG);
|
|
snapshots.retain(|snapshot| snapshot.path != runtime_path);
|
|
}
|
|
return match restore_files(&snapshots).await {
|
|
Ok(()) => Err(error),
|
|
Err(rollback_error) => Err(proxy_control::rollback_failure(error, rollback_error)),
|
|
};
|
|
}
|
|
transaction.commit();
|
|
match patch {
|
|
Patch::Verge { .. } => {
|
|
if effects.contains(&Effect::ClashConfig) {
|
|
announce(Refresh::Clash);
|
|
}
|
|
logging_error!(Type::Backup, AutoBackupManager::global().refresh_settings().await);
|
|
announce(Refresh::Verge);
|
|
}
|
|
Patch::Clash(_) => announce(Refresh::Clash),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn config_claim_is_exclusive_and_released_on_drop() -> Result<()> {
|
|
let write_lock = tokio::sync::Mutex::new(());
|
|
let write = write_lock.lock().await;
|
|
let manager = CoreManager::default();
|
|
let first = manager.claim_config_update(&write)?;
|
|
assert!(manager.claim_config_update(&write).is_err());
|
|
drop(first);
|
|
assert!(manager.claim_config_update(&write).is_ok());
|
|
drop(write);
|
|
Ok(())
|
|
}
|
|
}
|