Files
clash-verge-rev/src-tauri/src/feat/profile.rs
T
Sline f439e93a2b feat(hotkey): add global reactivate_profiles shortcut (#5527)
* feat(hotkey): add global reactivate_profiles shortcut

* feat(profile): expose validation state for reactivation shortcuts
2025-11-19 17:06:23 +08:00

265 lines
7.9 KiB
Rust

use crate::{
cmd,
config::{Config, PrfItem, PrfOption, profiles::profiles_draft_update_item_safe},
core::{CoreManager, handle, tray},
};
use anyhow::{Result, bail};
use clash_verge_logging::{Type, logging, logging_error};
use smartstring::alias::String;
use tauri::Emitter as _;
/// Toggle proxy profile
pub async fn toggle_proxy_profile(profile_index: String) {
logging_error!(
Type::Config,
cmd::patch_profiles_config_by_profile_index(profile_index).await
);
}
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
match handle::Handle::mihomo()
.await
.select_node_for_group(group_name, proxy_name)
.await
{
Ok(_) => {
logging!(
info,
Type::Tray,
"切换代理成功: {} -> {}",
group_name,
proxy_name
);
let _ = handle::Handle::app_handle().emit("verge://refresh-proxy-config", ());
let _ = tray::Tray::global().update_menu().await;
return;
}
Err(err) => {
logging!(
error,
Type::Tray,
"切换代理失败: {} -> {}, 错误: {:?}",
group_name,
proxy_name,
err
);
}
}
match handle::Handle::mihomo()
.await
.select_node_for_group(group_name, proxy_name)
.await
{
Ok(_) => {
logging!(
info,
Type::Tray,
"代理切换回退成功: {} -> {}",
group_name,
proxy_name
);
let _ = tray::Tray::global().update_menu().await;
}
Err(err) => {
logging!(
error,
Type::Tray,
"代理切换最终失败: {} -> {}, 错误: {:?}",
group_name,
proxy_name,
err
);
}
}
}
async fn should_update_profile(
uid: &String,
ignore_auto_update: bool,
) -> Result<Option<(String, Option<PrfOption>)>> {
let profiles = Config::profiles().await;
let profiles = profiles.latest_arc();
let item = profiles.get_item(uid)?;
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
if !is_remote {
logging!(
info,
Type::Config,
"[订阅更新] {uid} 不是远程订阅,跳过更新"
);
Ok(None)
} else if item.url.is_none() {
logging!(
warn,
Type::Config,
"Warning: [订阅更新] {uid} 缺少URL,无法更新"
);
bail!("failed to get the profile item url");
} else if !ignore_auto_update
&& !item
.option
.as_ref()
.and_then(|o| o.allow_auto_update)
.unwrap_or(true)
{
logging!(
info,
Type::Config,
"[订阅更新] {} 禁止自动更新,跳过更新",
uid
);
Ok(None)
} else {
logging!(
info,
Type::Config,
"[订阅更新] {} 是远程订阅,URL: {}",
uid,
item.url
.clone()
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?
);
Ok(Some((
item.url
.clone()
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?,
item.option.clone(),
)))
}
}
async fn perform_profile_update(
uid: &String,
url: &String,
opt: Option<&PrfOption>,
option: Option<&PrfOption>,
) -> Result<bool> {
logging!(info, Type::Config, "[订阅更新] 开始下载新的订阅内容");
let mut merged_opt = PrfOption::merge(opt, option);
let is_current = {
let profiles = Config::profiles().await;
profiles.latest_arc().is_current_profile_index(uid)
};
let profiles = Config::profiles().await;
let profiles_arc = profiles.latest_arc();
let profile_name = profiles_arc
.get_name_by_uid(uid)
.cloned()
.unwrap_or_else(|| String::from("UnKown Profile"));
let mut last_err;
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
Ok(mut item) => {
logging!(info, Type::Config, "[订阅更新] 更新订阅配置成功");
profiles_draft_update_item_safe(uid, &mut item).await?;
return Ok(is_current);
}
Err(err) => {
logging!(
warn,
Type::Config,
"Warning: [订阅更新] 正常更新失败: {err},尝试使用Clash代理更新"
);
last_err = err;
}
}
merged_opt.get_or_insert_with(PrfOption::default).self_proxy = Some(true);
merged_opt.get_or_insert_with(PrfOption::default).with_proxy = Some(false);
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
Ok(mut item) => {
logging!(
info,
Type::Config,
"[订阅更新] 使用 Clash代理 更新订阅配置成功"
);
profiles_draft_update_item_safe(uid, &mut item).await?;
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
drop(last_err);
return Ok(is_current);
}
Err(err) => {
logging!(
warn,
Type::Config,
"Warning: [订阅更新] 正常更新失败: {err},尝试使用Clash代理更新"
);
last_err = err;
}
}
merged_opt.get_or_insert_with(PrfOption::default).self_proxy = Some(false);
merged_opt.get_or_insert_with(PrfOption::default).with_proxy = Some(true);
match PrfItem::from_url(url, None, None, merged_opt.as_ref()).await {
Ok(mut item) => {
logging!(
info,
Type::Config,
"[订阅更新] 使用 系统代理 更新订阅配置成功"
);
profiles_draft_update_item_safe(uid, &mut item).await?;
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
drop(last_err);
return Ok(is_current);
}
Err(err) => {
logging!(
warn,
Type::Config,
"Warning: [订阅更新] 正常更新失败: {err},尝试使用系统代理更新"
);
last_err = err;
}
}
handle::Handle::notice_message(
"update_failed_even_with_clash",
format!("{profile_name} - {last_err}"),
);
Ok(is_current)
}
pub async fn update_profile(
uid: &String,
option: Option<&PrfOption>,
auto_refresh: bool,
ignore_auto_update: bool,
) -> Result<()> {
logging!(info, Type::Config, "[订阅更新] 开始更新订阅 {}", uid);
let url_opt = should_update_profile(uid, ignore_auto_update).await?;
let should_refresh = match url_opt {
Some((url, opt)) => {
perform_profile_update(uid, &url, opt.as_ref(), option).await? && auto_refresh
}
None => auto_refresh,
};
if should_refresh {
logging!(info, Type::Config, "[订阅更新] 更新内核配置");
match CoreManager::global().update_config().await {
Ok(_) => {
logging!(info, Type::Config, "[订阅更新] 更新成功");
handle::Handle::refresh_clash();
}
Err(err) => {
logging!(error, Type::Config, "[订阅更新] 更新失败: {}", err);
handle::Handle::notice_message("update_failed", format!("{err}"));
logging!(error, Type::Config, "{err}");
}
}
}
Ok(())
}
/// 增强配置
pub async fn enhance_profiles() -> Result<(bool, String)> {
crate::core::CoreManager::global().update_config().await
}