refactor: activate selected proxies (#7285)

* feat(profiles): add automatic node activation after profile switch

* refactor(use-profiles): simplify patchCurrent logic by always mutating profiles

* refactor(use-profiles): remove unused activateSelected function and related imports

* refactor: rename worker handle to activate selected task

* fix: clippy

* refactor(config): replace log statements with structured logging

* refactor(config): fix incorrect retry logic in profile activation

* feat(profiles): update proxy cache when switching profiles

* docs: update changelog with bug fixes and logic improvements

* refactor(profiles): remove unused imports and redundant cache update
This commit is contained in:
oomeow
2026-07-01 12:47:03 +00:00
committed by GitHub
parent af0518d578
commit c2ddb5c111
5 changed files with 115 additions and 182 deletions
+2
View File
@@ -1,6 +1,7 @@
use super::CmdResult;
use super::StringifyErr as _;
use crate::cmd::validate::{ValidationNoticeTarget, handle_validation_notice};
use crate::config::profiles;
use crate::utils::window_manager::WindowManager;
use crate::{
config::{
@@ -212,6 +213,7 @@ async fn restore_previous_profile(prev_profile: &String) -> CmdResult<()> {
async fn handle_success(current_value: Option<&String>) -> CmdResult<ValidationOutcome> {
Config::profiles().await.apply();
profiles::activate_selected_nodes().await.stringify_err()?;
handle::Handle::refresh_clash();
if let Err(e) = Tray::global().update_tooltip().await {
+99 -5
View File
@@ -1,15 +1,19 @@
use super::{PrfOption, prfitem::PrfItem};
use crate::utils::{
dirs::{self, PathBufExec as _},
help,
use crate::{
core::handle,
utils::{
dirs::{self, PathBufExec as _},
help,
},
};
use anyhow::{Context as _, Result, bail};
use clash_verge_logging::{Type, logging};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_yaml_ng::Mapping;
use smartstring::alias::String;
use std::{collections::HashSet, sync::LazyLock};
use tokio::fs;
use std::{collections::HashSet, sync::LazyLock, time::Duration};
use tokio::{fs, task::JoinHandle};
/// Regex to check profile file names, eg.
/// R12345678.yaml (remote)
@@ -23,6 +27,9 @@ use tokio::fs;
static REGEX_PROFILE_FILE: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new(r"^(?:[RLmrpg][a-zA-Z0-9]+\.yaml|s[a-zA-Z0-9]+\.js)$").unwrap());
// activate selected nodes task handle
static ACTIVATE_SELECTED_TASK: LazyLock<Mutex<Option<JoinHandle<()>>>> = LazyLock::new(|| Mutex::new(None));
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IProfiles {
@@ -584,3 +591,90 @@ pub async fn profiles_draft_update_item_safe(index: &String, item: &mut PrfItem)
})
.await
}
pub async fn activate_selected_nodes() -> Result<()> {
logging!(info, Type::Config, "starting activating selected nodes");
let value = ACTIVATE_SELECTED_TASK.lock().take();
if let Some(handle) = value {
logging!(info, Type::Config, "aborting previous worker");
handle.abort();
}
let profiles = Config::profiles().await.latest_arc();
let Some(current) = profiles.get_current() else {
bail!("no current profile running");
};
let profile = profiles
.get_item(current)
.context("failed to get current profile")?
.clone();
let handle = tokio::spawn(async move {
let mihomo = handle::Handle::mihomo().await;
// check mihomo is running
let mut is_mihomo_ready = false;
for _ in 0..10 {
if mihomo.get_version().await.is_ok() {
logging!(debug, Type::Config, "check mihomo api success");
is_mihomo_ready = true;
break;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
if !is_mihomo_ready {
logging!(
error,
Type::Config,
"check that the mihomo api reaches the maximum number of retries, maybe mihomo core is not running"
);
return;
}
if let Some(selected) = profile.selected.as_ref() {
logging!(debug, Type::Config, "selected nodes: {selected:?}");
for selected_item in selected {
let mut retry = 10;
if let Some(group_name) = selected_item.name.as_ref()
&& let Some(node) = selected_item.now.as_ref()
{
while retry >= 0 {
logging!(debug, Type::Config, "check node[{node}] exists");
if mihomo.get_proxy_by_name(node).await.is_ok() {
logging!(debug, Type::Config, "node[{node}] exists");
break;
}
retry -= 1;
tokio::time::sleep(Duration::from_secs(1)).await;
}
if retry < 0 {
logging!(
error,
Type::Config,
"Failed to select node for proxy: {group_name}, node: {node}, because the node [{node}] does not exist"
);
continue;
}
if mihomo.select_node_for_group(group_name, node).await.is_err() {
logging!(
error,
Type::Config,
"Failed to select node for proxy: {group_name}, node: {node}"
);
} else {
logging!(
info,
Type::Config,
"Selected node for proxy: {group_name}, node: {node}"
);
}
}
}
// refresh clash
handle::Handle::refresh_clash();
}
logging!(info, Type::Config, "activating selected nodes done!");
*ACTIVATE_SELECTED_TASK.lock() = None;
});
*ACTIVATE_SELECTED_TASK.lock() = Some(handle);
Ok(())
}