mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-24 04:32:07 +08:00
chore: sync reviewed backend fixes into once-cell cleanup
This commit is contained in:
+7
-4
@@ -6,10 +6,13 @@ These rules supplement the repository's root `AGENTS.md`.
|
||||
its draft and persistence APIs. Patch orchestration must use
|
||||
`feat/effects.rs` and `feat/executor.rs`; refresh notifications must use
|
||||
`core::notify::announce` after successful persistence and commit. This keeps
|
||||
failed transactions from publishing state. Independent transactions retain
|
||||
their boundaries and announce via `after_commit`; runtime-only profile
|
||||
activation announces after successful application. Cross-layer operations
|
||||
must preserve these ownership and transaction boundaries.
|
||||
rolled-back transactions from publishing state. If runtime and memory state
|
||||
are already committed and cannot be rolled back, announce that state even
|
||||
when persistence fails, while preserving the persistence error. Independent
|
||||
transactions retain their boundaries and announce their own committed
|
||||
results; runtime-only profile activation announces after successful
|
||||
application. Cross-layer operations must preserve these ownership and
|
||||
transaction boundaries.
|
||||
2. **Register every new `IVerge` field.** Register the field in the
|
||||
exhaustive effect registry, including an explicit `NoEffect` where
|
||||
appropriate, and preserve its platform attributes. Manual field-to-flag
|
||||
|
||||
@@ -143,9 +143,9 @@ impl Config {
|
||||
PENDING_DNS_OVERRIDE_NOTICE.store(true, Ordering::Relaxed);
|
||||
Handle::notice(NoticeStatus::DnsOverrideAutoDisabled, "");
|
||||
}
|
||||
verge.data_arc().save_file().await?;
|
||||
let result = verge.data_arc().save_file().await;
|
||||
Handle::refresh_verge();
|
||||
Ok(())
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,7 @@ use clash_verge_draft::DraftTransaction;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use parking_lot::Mutex;
|
||||
use serde_yaml_ng::Value;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
net::SocketAddr,
|
||||
str::FromStr as _,
|
||||
sync::{LazyLock, OnceLock},
|
||||
};
|
||||
use std::{collections::HashSet, net::SocketAddr, str::FromStr as _, sync::LazyLock};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct MixedPortFallback {
|
||||
@@ -31,7 +26,7 @@ struct MixedPortFallback {
|
||||
}
|
||||
|
||||
static PENDING_FALLBACK_NOTICE: LazyLock<Mutex<Option<MixedPortFallback>>> = LazyLock::new(|| Mutex::new(None));
|
||||
static STARTUP_CORE_BLOCK_REASON: OnceLock<String> = OnceLock::new();
|
||||
static STARTUP_CORE_BLOCK_REASON: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
impl Config {
|
||||
pub(crate) async fn resolve_startup_mixed_port() -> Result<bool> {
|
||||
@@ -146,12 +141,12 @@ impl Config {
|
||||
}
|
||||
|
||||
pub(crate) fn block_startup_core(error: &anyhow::Error) {
|
||||
let _ = STARTUP_CORE_BLOCK_REASON.set(error.to_string());
|
||||
*STARTUP_CORE_BLOCK_REASON.lock() = Some(error.to_string());
|
||||
report_fallback_error(error.to_string());
|
||||
}
|
||||
|
||||
pub(crate) fn startup_core_block_reason() -> Option<String> {
|
||||
STARTUP_CORE_BLOCK_REASON.get().cloned()
|
||||
STARTUP_CORE_BLOCK_REASON.lock().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn notify_startup_mixed_port_fallback() {
|
||||
|
||||
@@ -3,7 +3,7 @@ use anyhow::{Context as _, Result, bail};
|
||||
use std::{io::ErrorKind, path::PathBuf};
|
||||
|
||||
pub(crate) struct FileSnapshot {
|
||||
path: PathBuf,
|
||||
pub(crate) path: PathBuf,
|
||||
content: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
use crate::{
|
||||
config::{Config, IVerge},
|
||||
core::{autostart, hotkey, lightweight, tray},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
pub(crate) fn ensure_autostart(_patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move { autostart::update_launch().await })
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_hotkey(patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move {
|
||||
if let Some(hotkeys) = &patch.hotkeys {
|
||||
hotkey::Hotkey::global().update(hotkeys.to_owned()).await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_tray_menu(_patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move { tray::Tray::global().update_menu().await })
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_tray_icon(patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move {
|
||||
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);
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let _ = patch;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_tray_tooltip(_patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move { tray::Tray::global().update_tooltip().await })
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_tray_click(_patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move { tray::Tray::global().update_click_behavior().await })
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_lightweight(patch: &IVerge) -> BoxFuture<'_, Result<()>> {
|
||||
Box::pin(async move {
|
||||
if patch.enable_auto_light_weight_mode.unwrap_or(false) {
|
||||
lightweight::enable_auto_light_weight_mode().await;
|
||||
} else {
|
||||
lightweight::disable_auto_light_weight_mode();
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -238,7 +238,7 @@ impl CoreManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn current_core_readiness_generation(&self) -> Option<u64> {
|
||||
pub(crate) fn current_core_readiness_generation(&self) -> Option<u64> {
|
||||
active_core_readiness_generation(self.core_readiness_state.load(Ordering::Acquire))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub(crate) mod app_effects;
|
||||
pub mod auto_backup;
|
||||
pub mod autostart;
|
||||
pub mod backup;
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
use crate::{config::Config, core::CoreManager, core::proxy_control, process::AsyncHandler};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use parking_lot::Mutex;
|
||||
use sysproxy::NetworkServiceMonitor;
|
||||
|
||||
static ARMED: AtomicBool = AtomicBool::new(false);
|
||||
static MONITOR: Mutex<Option<NetworkServiceMonitor>> = Mutex::new(None);
|
||||
|
||||
pub fn is_armed() -> bool {
|
||||
ARMED.load(Ordering::Acquire)
|
||||
MONITOR.lock().is_some()
|
||||
}
|
||||
|
||||
/// Subscribe to the primary IPv4 service on the main run loop. Armed on return, so a service
|
||||
@@ -21,14 +21,7 @@ pub fn start() {
|
||||
AsyncHandler::spawn(reapply);
|
||||
}) {
|
||||
Ok(monitor) => {
|
||||
if ARMED
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Dropping the monitor stops network notifications, so it must outlive the call.
|
||||
std::mem::forget(monitor);
|
||||
*MONITOR.lock() = Some(monitor);
|
||||
logging!(
|
||||
debug,
|
||||
Type::Core,
|
||||
|
||||
@@ -1045,6 +1045,8 @@ fn decode_hex(encoded: &str) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
static PROVIDER_SYNC_QUEUED: AtomicBool = AtomicBool::new(false);
|
||||
// Serializing the whole read-and-publish pass prevents an older cache from replacing a newer one.
|
||||
static PROVIDER_SYNC_SERIAL: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
|
||||
static SYNC_TEMP_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||
const RUNTIME_PROVIDER_SYNC_ATTEMPTS: u32 = 4;
|
||||
const CONTENT_COMPARE_CHUNK: usize = 64 * 1024;
|
||||
@@ -1063,10 +1065,13 @@ pub(crate) fn request_runtime_provider_sync(delay: Duration) {
|
||||
),
|
||||
|index| async move {
|
||||
let attempt = index as u32 + 1;
|
||||
if attempt == 1 {
|
||||
PROVIDER_SYNC_QUEUED.store(false, Ordering::Release);
|
||||
}
|
||||
let outcome = sync_runtime_providers_by_service().await;
|
||||
let outcome = {
|
||||
let _serial = PROVIDER_SYNC_SERIAL.lock().await;
|
||||
if attempt == 1 {
|
||||
PROVIDER_SYNC_QUEUED.store(false, Ordering::Release);
|
||||
}
|
||||
sync_runtime_providers_by_service().await
|
||||
};
|
||||
match outcome {
|
||||
Ok(ProviderSync { pending: 0, .. }) => Ok(()),
|
||||
Ok(ProviderSync { pending, .. }) if attempt < RUNTIME_PROVIDER_SYNC_ATTEMPTS => {
|
||||
|
||||
@@ -26,14 +26,6 @@ pub async fn set_dns_override(
|
||||
profile_uid: String,
|
||||
enabled: bool,
|
||||
confirmation: Option<String>,
|
||||
) -> Result<DnsOverrideOutcome> {
|
||||
crate::core::notify::after_commit(Box::pin(set_dns_override_inner(profile_uid, enabled, confirmation))).await
|
||||
}
|
||||
|
||||
async fn set_dns_override_inner(
|
||||
profile_uid: String,
|
||||
enabled: bool,
|
||||
confirmation: Option<String>,
|
||||
) -> Result<DnsOverrideOutcome> {
|
||||
let _profile_write = PROFILE_WRITE_LOCK.lock().await;
|
||||
let _config_write = Config::lock_config_write().await;
|
||||
@@ -83,8 +75,10 @@ async fn set_dns_override_inner(
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
verge.data_arc().save_file().await?;
|
||||
// Core and memory state remain applied even when persistence fails.
|
||||
let result = verge.data_arc().save_file().await;
|
||||
announce(Refresh::Verge);
|
||||
announce(Refresh::Clash);
|
||||
result?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::collections::BTreeSet;
|
||||
pub(super) enum Effect {
|
||||
RestartCore,
|
||||
ClashConfig,
|
||||
VergeConfig,
|
||||
Autostart,
|
||||
SystemProxy,
|
||||
TrayIcon,
|
||||
@@ -90,8 +89,8 @@ verge_registry! {
|
||||
web_ui_list => NoEffect;
|
||||
clash_core => NoEffect;
|
||||
hotkeys => [Hotkey, TrayMenu];
|
||||
enable_global_hotkey => [VergeConfig];
|
||||
home_cards => [VergeConfig];
|
||||
enable_global_hotkey => NoEffect;
|
||||
home_cards => NoEffect;
|
||||
auto_close_connection => NoEffect;
|
||||
auto_check_update => NoEffect;
|
||||
default_latency_test => NoEffect;
|
||||
|
||||
+124
-103
@@ -6,13 +6,13 @@ use crate::{
|
||||
snapshot::{capture_config_files, restore_files},
|
||||
},
|
||||
core::auto_backup::AutoBackupManager,
|
||||
core::{CoreManager, logger, manager::ConfigUpdateGuard, proxy_control},
|
||||
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 futures::future::BoxFuture;
|
||||
use serde_yaml_ng::Mapping;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::MutexGuard;
|
||||
|
||||
pub(super) enum Patch<'a> {
|
||||
@@ -20,101 +20,108 @@ pub(super) enum Patch<'a> {
|
||||
Clash(&'a Mapping),
|
||||
}
|
||||
|
||||
struct EffectContext<'a> {
|
||||
patch: &'a IVerge,
|
||||
manager: &'a CoreManager,
|
||||
update: &'a ConfigUpdateGuard<'a>,
|
||||
}
|
||||
|
||||
type Ensure = for<'a> fn(&'a EffectContext<'_>) -> BoxFuture<'a, Result<()>>;
|
||||
|
||||
const EFFECT_ORDER: &[(Effect, Ensure)] = &[
|
||||
(Effect::RestartCore, ensure_restart),
|
||||
(Effect::ClashConfig, ensure_clash),
|
||||
(Effect::VergeConfig, ensure_verge),
|
||||
(Effect::Autostart, ensure_autostart),
|
||||
(Effect::Language, ensure_language),
|
||||
(Effect::SystemProxy, ensure_system_proxy),
|
||||
(Effect::Hotkey, ensure_hotkey),
|
||||
(Effect::TrayMenu, ensure_tray_menu),
|
||||
(Effect::TrayIcon, ensure_tray_icon),
|
||||
(Effect::TrayTooltip, ensure_tray_tooltip),
|
||||
(Effect::TrayClick, ensure_tray_click),
|
||||
(Effect::Lightweight, ensure_lightweight),
|
||||
(Effect::LogLevel, ensure_log_level),
|
||||
(Effect::LogFile, ensure_log_file),
|
||||
const EFFECT_ORDER: &[Effect] = &[
|
||||
Effect::RestartCore,
|
||||
Effect::ClashConfig,
|
||||
Effect::Autostart,
|
||||
Effect::Language,
|
||||
Effect::SystemProxy,
|
||||
Effect::Hotkey,
|
||||
Effect::TrayMenu,
|
||||
Effect::TrayIcon,
|
||||
Effect::TrayTooltip,
|
||||
Effect::TrayClick,
|
||||
Effect::Lightweight,
|
||||
Effect::LogLevel,
|
||||
Effect::LogFile,
|
||||
];
|
||||
|
||||
macro_rules! ensure {
|
||||
($name:ident, $ctx:ident, $body:block) => {
|
||||
fn $name<'a>($ctx: &'a EffectContext<'_>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move $body)
|
||||
}
|
||||
};
|
||||
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(())
|
||||
}
|
||||
|
||||
ensure!(ensure_restart, ctx, {
|
||||
Config::generate().await?;
|
||||
ctx.manager.restart_core_during_config_update().await
|
||||
});
|
||||
ensure!(ensure_clash, ctx, {
|
||||
ctx.manager.update_config_in_patch(ctx.update).await?;
|
||||
announce(Refresh::Clash);
|
||||
Ok(())
|
||||
});
|
||||
ensure!(ensure_verge, _ctx, { Ok(()) });
|
||||
ensure!(ensure_autostart, ctx, {
|
||||
crate::core::app_effects::ensure_autostart(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_language, ctx, {
|
||||
if let Some(language) = &ctx.patch.language {
|
||||
clash_verge_i18n::set_locale(language.as_str());
|
||||
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?;
|
||||
Config::runtime().await.apply();
|
||||
announce(Refresh::Clash);
|
||||
}
|
||||
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(())
|
||||
});
|
||||
ensure!(ensure_system_proxy, ctx, {
|
||||
let _lifecycle = ctx.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()
|
||||
{
|
||||
ctx.manager.apply_proxy_after_start().await
|
||||
} else {
|
||||
proxy_control::apply().await?;
|
||||
proxy_control::refresh_guard().await
|
||||
}
|
||||
});
|
||||
ensure!(ensure_hotkey, ctx, {
|
||||
crate::core::app_effects::ensure_hotkey(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_tray_menu, ctx, {
|
||||
crate::core::app_effects::ensure_tray_menu(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_tray_icon, ctx, {
|
||||
crate::core::app_effects::ensure_tray_icon(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_tray_tooltip, ctx, {
|
||||
crate::core::app_effects::ensure_tray_tooltip(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_tray_click, ctx, {
|
||||
crate::core::app_effects::ensure_tray_click(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_lightweight, ctx, {
|
||||
crate::core::app_effects::ensure_lightweight(ctx.patch).await
|
||||
});
|
||||
ensure!(ensure_log_level, ctx, {
|
||||
logger::Logger::global().update_log_level(ctx.patch.get_log_level())
|
||||
});
|
||||
ensure!(ensure_log_file, ctx, {
|
||||
logger::update_log_config(
|
||||
ctx.patch.app_log_max_size.unwrap_or(128),
|
||||
ctx.patch.app_log_max_count.unwrap_or(8),
|
||||
)
|
||||
.await
|
||||
});
|
||||
}
|
||||
|
||||
async fn apply_inner(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effects: Effects) -> Result<()> {
|
||||
let manager = CoreManager::global();
|
||||
@@ -126,24 +133,27 @@ async fn apply_inner(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effect
|
||||
Patch::Verge { .. } => DraftTransaction::begin(vec![&verge, &runtime])?,
|
||||
Patch::Clash(_) => DraftTransaction::begin(vec![&clash, &runtime])?,
|
||||
};
|
||||
let snapshots = capture_config_files().await?;
|
||||
let original_runtime = runtime.data_arc();
|
||||
let mut snapshots = capture_config_files().await?;
|
||||
let empty = IVerge::default();
|
||||
let context = EffectContext {
|
||||
patch: match &patch {
|
||||
Patch::Verge { patch, .. } => patch,
|
||||
Patch::Clash(_) => &empty,
|
||||
},
|
||||
manager,
|
||||
update: &update,
|
||||
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, ensure) in EFFECT_ORDER {
|
||||
if effects.contains(effect) {
|
||||
ensure(&context).await?;
|
||||
for &effect in EFFECT_ORDER {
|
||||
if effects.contains(&effect) {
|
||||
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 {
|
||||
@@ -156,6 +166,11 @@ async fn apply_inner(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effect
|
||||
.await;
|
||||
if let Err(error) = result {
|
||||
transaction.rollback();
|
||||
if !Arc::ptr_eq(&original_runtime, &runtime.data_arc()) {
|
||||
// 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)),
|
||||
@@ -173,7 +188,13 @@ async fn apply_inner(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effect
|
||||
}
|
||||
|
||||
pub(super) async fn apply(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effects: Effects) -> Result<()> {
|
||||
crate::core::notify::after_commit(apply_inner(config_write, patch, effects)).await
|
||||
let runtime = Config::runtime().await;
|
||||
let original = runtime.data_arc();
|
||||
let result = crate::core::notify::after_commit(apply_inner(config_write, patch, effects)).await;
|
||||
if result.is_err() && !Arc::ptr_eq(&original, &runtime.data_arc()) {
|
||||
announce(Refresh::Clash);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -27,12 +27,8 @@ pub async fn probe_listener(request: ListenerProbe) -> Result<ListenerProbeOutco
|
||||
.context("listener probe task failed")
|
||||
}
|
||||
|
||||
pub async fn save_proxy_ports(settings: ProxyPortSettings) -> Result<SaveProxyPortsOutcome> {
|
||||
crate::core::notify::after_commit(Box::pin(save_proxy_ports_inner(settings))).await
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
async fn save_proxy_ports_inner(settings: ProxyPortSettings) -> Result<SaveProxyPortsOutcome> {
|
||||
pub async fn save_proxy_ports(settings: ProxyPortSettings) -> Result<SaveProxyPortsOutcome> {
|
||||
settings.validate()?;
|
||||
let _config_write = Config::lock_config_write().await;
|
||||
let manager = CoreManager::global();
|
||||
|
||||
Reference in New Issue
Block a user