mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 02:52:07 +08:00
fix(proxy): unify node switching and connection cleanup
Route frontend and tray node selections through the same backend flow for selection, persistence, state refresh, and connection cleanup. Read the group's previous selection before switching and preserve the original rule of closing only connections whose chains contain that selection. Run optional cleanup in the background and keep its failures separate from successful selection. Remove redundant frontend effects and unused IPC commands, and refresh the proxy view for every successful switch. Refs:32095daf90Refs:bea0dde074
This commit is contained in:
@@ -5,16 +5,10 @@ use crate::{
|
||||
core::{
|
||||
handle::Handle,
|
||||
proxy_view::{GroupScope, ProxyViewBuilder, ProxyViewInput, ProxyViewV1},
|
||||
tray::Tray,
|
||||
},
|
||||
process::AsyncHandler,
|
||||
};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
/// Merges one selection pair into fresh backend state to avoid stale-list overwrites.
|
||||
#[tauri::command]
|
||||
@@ -27,15 +21,12 @@ pub async fn record_selected_node(group_name: String, node: String) -> CmdResult
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn forget_selected_node(group_name: String) -> CmdResult<()> {
|
||||
crate::config::profiles::forget_selected_node(&group_name)
|
||||
pub async fn switch_proxy_node(group_name: String, proxy_name: String, unfix: bool) -> CmdResult<()> {
|
||||
crate::feat::switch_proxy_node(&group_name, &proxy_name, unfix)
|
||||
.await
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
static TRAY_SYNC_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
static TRAY_SYNC_PENDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn runtime_group_order(config: Option<&Mapping>) -> Vec<String> {
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
@@ -124,48 +115,3 @@ pub async fn get_proxy_view() -> CmdResult<ProxyViewV1> {
|
||||
providers: providers.ok(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn sync_tray_proxy_selection() -> CmdResult<()> {
|
||||
if TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
{
|
||||
AsyncHandler::spawn(move || async move {
|
||||
run_tray_sync_loop().await;
|
||||
});
|
||||
} else {
|
||||
TRAY_SYNC_PENDING.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_tray_sync_loop() {
|
||||
// Permanent watcher: keeps syncing until a pass finds no pending sync request.
|
||||
loop {
|
||||
match Tray::global().update_menu().await {
|
||||
Ok(_) => {
|
||||
logging!(debug, Type::Cmd, "Tray proxy selection synced successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
if !TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel) {
|
||||
TRAY_SYNC_RUNNING.store(false, Ordering::Release);
|
||||
|
||||
if TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel)
|
||||
&& TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,7 +1000,9 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
Some((g, p)) => (g, p),
|
||||
None => return,
|
||||
};
|
||||
feat::switch_proxy_node(group_name, proxy_name).await;
|
||||
if let Err(err) = feat::switch_proxy_node(group_name, proxy_name, false).await {
|
||||
logging!(error, Type::Tray, "Failed to switch proxy: {err:#}");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
logging!(debug, Type::Tray, "Unhandled tray menu event: {:?}", event.id);
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
cmd,
|
||||
config::{Config, PrfItem, PrfOption, profiles::profiles_update_item_safe},
|
||||
core::{CoreManager, handle, tray, validate::ValidationOutcome},
|
||||
process::AsyncHandler,
|
||||
utils::help::{mask_err, mask_url},
|
||||
};
|
||||
use anyhow::{Result, bail};
|
||||
@@ -21,37 +22,85 @@ pub async fn toggle_proxy_profile(profile_index: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell the profile which node this group is on now.
|
||||
///
|
||||
/// The core is already switched by the time this runs, so failing to record is not a reason to
|
||||
/// report the switch as failed — but it is worth a log line, because what the profile holds is
|
||||
/// what gets re-applied the next time a core starts.
|
||||
async fn record_switched_node(group_name: &str, proxy_name: &str) {
|
||||
if let Err(error) = crate::config::profiles::record_selected_node(group_name, proxy_name).await {
|
||||
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str, unfix: bool) -> Result<()> {
|
||||
let mihomo = handle::Handle::mihomo();
|
||||
let previous_proxy = if Config::verge().await.data_arc().auto_close_connection.unwrap_or(false) {
|
||||
match mihomo.get_proxy_by_name(group_name).await {
|
||||
Ok(group) => group.now.filter(|name| !name.is_empty()),
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to read previous proxy selection for {group_name}: {err}"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if unfix {
|
||||
mihomo.unfixed_proxy(group_name).await?;
|
||||
} else {
|
||||
mihomo.select_node_for_group(group_name, proxy_name).await?;
|
||||
}
|
||||
|
||||
let save_result = if unfix {
|
||||
crate::config::profiles::forget_selected_node(group_name).await
|
||||
} else {
|
||||
crate::config::profiles::record_selected_node(group_name, proxy_name).await
|
||||
};
|
||||
// The core selection is already applied; persistence failure must not report it as failed.
|
||||
if let Err(error) = save_result {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"切换代理成功但未能记录到配置: {} -> {}, 错误: {error:#}",
|
||||
group_name,
|
||||
proxy_name
|
||||
"Failed to persist proxy selection for {group_name}: {error:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
announce(Refresh::Proxies);
|
||||
|
||||
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
|
||||
match handle::Handle::mihomo()
|
||||
.select_node_for_group(group_name, proxy_name)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
record_switched_node(group_name, proxy_name).await;
|
||||
announce(Refresh::Proxies);
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Tray, "切换代理失败: {err:?}");
|
||||
}
|
||||
if let Some(previous_proxy) = previous_proxy {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
let connections = match mihomo.get_connections().await {
|
||||
Ok(connections) => connections,
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to read connections after switching proxy: {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
futures::future::join_all(
|
||||
connections
|
||||
.connections
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|connection| connection.chains.contains(&previous_proxy))
|
||||
.map(|connection| async move {
|
||||
if let Err(err) = mihomo.close_connection(&connection.id).await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to close connection {} after switching proxy: {err}",
|
||||
connection.id
|
||||
);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
if let Err(err) = tray::Tray::global().update_menu().await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Tray,
|
||||
"Failed to refresh tray after switching proxy: {err:#}"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result<Option<(String, Option<PrfOption>)>> {
|
||||
|
||||
@@ -45,9 +45,8 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
cmd::update_proxy_chain_config_in_runtime,
|
||||
cmd::invoke_uwp_tool,
|
||||
cmd::copy_clash_env,
|
||||
cmd::sync_tray_proxy_selection,
|
||||
cmd::record_selected_node,
|
||||
cmd::forget_selected_node,
|
||||
cmd::switch_proxy_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::set_dns_override,
|
||||
|
||||
Reference in New Issue
Block a user