Files
clash-verge-rev/src-tauri/src/cmd/proxy.rs
T
Tunglies 05a6e9a64f fix(proxy): unify node switching and connection cleanup (#8592)
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: 32095daf90
Refs: bea0dde074
2026-11-09 14:28:44 +00:00

118 lines
3.9 KiB
Rust

use super::CmdResult;
use crate::{
cmd::StringifyErr as _,
config::Config,
core::{
handle::Handle,
proxy_view::{GroupScope, ProxyViewBuilder, ProxyViewInput, ProxyViewV1},
},
};
use serde_yaml_ng::Mapping;
use std::collections::{BTreeMap, HashSet};
/// Merges one selection pair into fresh backend state to avoid stale-list overwrites.
#[tauri::command]
#[specta::specta]
pub async fn record_selected_node(group_name: String, node: String) -> CmdResult<()> {
crate::config::profiles::record_selected_node(&group_name, &node)
.await
.stringify_err()
}
#[tauri::command]
#[specta::specta]
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()
}
fn runtime_group_order(config: Option<&Mapping>) -> Vec<String> {
let mut seen = HashSet::new();
config
.and_then(|config| config.get("proxy-groups"))
.and_then(|groups| groups.as_sequence())
.into_iter()
.flatten()
.filter_map(|group| group.get("name"))
.filter_map(|name| name.as_str())
.filter(|name| !name.is_empty() && *name != "GLOBAL")
.filter(|name| seen.insert(*name))
.map(str::to_owned)
.collect()
}
/// Mirrors Mihomo: `include-all`/`include-all-providers` replace `use` with every provider, sorted.
fn runtime_group_scopes(config: Option<&Mapping>) -> BTreeMap<String, GroupScope> {
let Some(config) = config else {
return BTreeMap::new();
};
let mut all_providers = config
.get("proxy-providers")
.and_then(|providers| providers.as_mapping())
.into_iter()
.flat_map(|providers| providers.keys())
.filter_map(|name| name.as_str())
.map(str::to_owned)
.collect::<Vec<_>>();
all_providers.sort();
let flag = |group: &Mapping, key: &str| group.get(key).and_then(|value| value.as_bool()) == Some(true);
config
.get("proxy-groups")
.and_then(|groups| groups.as_sequence())
.into_iter()
.flatten()
.filter_map(|group| group.as_mapping())
.filter_map(|group| {
let name = group.get("name")?.as_str()?.to_owned();
let providers = if flag(group, "include-all") || flag(group, "include-all-providers") {
all_providers.clone()
} else {
group
.get("use")
.and_then(|providers| providers.as_sequence())
.into_iter()
.flatten()
.filter_map(|provider| provider.as_str())
.map(str::to_owned)
.collect()
};
let exclude_types = group
.get("exclude-type")
.and_then(|types| types.as_str())
.filter(|types| !types.is_empty())
.map(|types| types.split('|').map(str::to_owned).collect())
.unwrap_or_default();
Some((
name,
GroupScope {
providers,
exclude_types,
},
))
})
.collect()
}
#[tauri::command]
#[specta::specta]
pub async fn get_proxy_view() -> CmdResult<ProxyViewV1> {
let runtime = Config::runtime().await;
let latest_runtime = runtime.latest_arc();
let runtime_group_order = runtime_group_order(latest_runtime.config.as_ref());
let group_scopes = runtime_group_scopes(latest_runtime.config.as_ref());
let mihomo = Handle::mihomo();
let (proxies, providers) = tokio::join!(mihomo.get_proxies(), mihomo.get_proxy_providers(),);
let proxies = proxies.stringify_err()?;
Ok(ProxyViewBuilder::build(ProxyViewInput {
runtime_group_order,
group_scopes,
proxies,
providers: providers.ok(),
}))
}