feat(clippy): cognitive-complexity rule (#5215)

* feat(config): enhance configuration initialization and validation process

* refactor(profile): streamline profile update logic and enhance error handling

* refactor(config): simplify profile item checks and streamline update flag processing

* refactor(disney_plus): add cognitive complexity allowance for check_disney_plus function

* refactor(enhance): restructure configuration and profile item handling for improved clarity and maintainability

* refactor(tray): add cognitive complexity allowance for create_tray_menu function

* refactor(config): add cognitive complexity allowance for patch_config function

* refactor(profiles): simplify item removal logic by introducing take_item_file_by_uid helper function

* refactor(profile): add new validation logic for profile configuration syntax

* refactor(profiles): improve formatting and readability of take_item_file_by_uid function

* refactor(cargo): change cognitive complexity level from warn to deny

* refactor(cargo): ensure cognitive complexity is denied in Cargo.toml

* refactor(i18n): clean up imports and improve code readability
refactor(proxy): simplify system proxy toggle logic
refactor(service): remove unnecessary `as_str()` conversion in error handling
refactor(tray): modularize tray menu creation for better maintainability

* refactor(tray): update menu item text handling to use references for improved performance
This commit is contained in:
Tunglies
2025-10-27 20:55:51 +08:00
committed by GitHub
parent 6df1e137f3
commit c736796380
14 changed files with 1130 additions and 984 deletions

View File

@@ -1 +1,2 @@
avoid-breaking-exported-api = true
avoid-breaking-exported-api = true
cognitive-complexity-threshold = 25

View File

@@ -232,3 +232,4 @@ needless_raw_string_hashes = "deny" # Too many in existing code
#restriction = { level = "allow", priority = -1 }
or_fun_call = "deny"
cognitive_complexity = "deny"

View File

@@ -6,6 +6,7 @@ use crate::{logging, utils::logging::Type};
use super::UnlockItem;
use super::utils::{country_code_to_emoji, get_local_date_string};
#[allow(clippy::cognitive_complexity)]
pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
let device_api_url = "https://disney.api.edge.bamgrid.com/devices";
let auth_header =

View File

@@ -216,6 +216,257 @@ pub async fn delete_profile(index: String) -> CmdResult {
Ok(())
}
/// 验证新配置文件的语法
async fn validate_new_profile(new_profile: &String) -> Result<(), ()> {
logging!(info, Type::Cmd, "正在切换到新配置: {}", new_profile);
// 获取目标配置文件路径
let config_file_result = {
let profiles_config = Config::profiles().await;
let profiles_data = profiles_config.latest_ref();
match profiles_data.get_item(new_profile) {
Ok(item) => {
if let Some(file) = &item.file {
let path = dirs::app_profiles_dir().map(|dir| dir.join(file.as_str()));
path.ok()
} else {
None
}
}
Err(e) => {
logging!(error, Type::Cmd, "获取目标配置信息失败: {}", e);
None
}
}
};
// 如果获取到文件路径检查YAML语法
if let Some(file_path) = config_file_result {
if !file_path.exists() {
logging!(
error,
Type::Cmd,
"目标配置文件不存在: {}",
file_path.display()
);
handle::Handle::notice_message(
"config_validate::file_not_found",
format!("{}", file_path.display()),
);
return Err(());
}
// 超时保护
let file_read_result = tokio::time::timeout(
Duration::from_secs(5),
tokio::fs::read_to_string(&file_path),
)
.await;
match file_read_result {
Ok(Ok(content)) => {
let yaml_parse_result = AsyncHandler::spawn_blocking(move || {
serde_yaml_ng::from_str::<serde_yaml_ng::Value>(&content)
})
.await;
match yaml_parse_result {
Ok(Ok(_)) => {
logging!(info, Type::Cmd, "目标配置文件语法正确");
Ok(())
}
Ok(Err(err)) => {
let error_msg = format!(" {err}");
logging!(
error,
Type::Cmd,
"目标配置文件存在YAML语法错误:{}",
error_msg
);
handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
error_msg.clone(),
);
Err(())
}
Err(join_err) => {
let error_msg = format!("YAML解析任务失败: {join_err}");
logging!(error, Type::Cmd, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::yaml_parse_error",
error_msg.clone(),
);
Err(())
}
}
}
Ok(Err(err)) => {
let error_msg = format!("无法读取目标配置文件: {err}");
logging!(error, Type::Cmd, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_error",
error_msg.clone(),
);
Err(())
}
Err(_) => {
let error_msg = "读取配置文件超时(5秒)".to_string();
logging!(error, Type::Cmd, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_timeout",
error_msg.clone(),
);
Err(())
}
}
} else {
Ok(())
}
}
/// 执行配置更新并处理结果
async fn restore_previous_profile(prev_profile: String) -> CmdResult<()> {
logging!(info, Type::Cmd, "尝试恢复到之前的配置: {}", prev_profile);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
};
Config::profiles()
.await
.draft_mut()
.patch_config(restore_profiles)
.stringify_err()?;
Config::profiles().await.apply();
crate::process::AsyncHandler::spawn(|| async move {
if let Err(e) = profiles_save_file_safe().await {
log::warn!(target: "app", "异步保存恢复配置文件失败: {e}");
}
});
logging!(info, Type::Cmd, "成功恢复到之前的配置");
Ok(())
}
async fn handle_success(current_sequence: u64, current_value: Option<String>) -> CmdResult<bool> {
let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst);
if current_sequence < latest_sequence {
logging!(
info,
Type::Cmd,
"内核操作后发现更新的请求 (序列号: {} < {}),忽略当前结果",
current_sequence,
latest_sequence
);
Config::profiles().await.discard();
return Ok(false);
}
logging!(
info,
Type::Cmd,
"配置更新成功,序列号: {}",
current_sequence
);
Config::profiles().await.apply();
handle::Handle::refresh_clash();
if let Err(e) = Tray::global().update_tooltip().await {
log::warn!(target: "app", "异步更新托盘提示失败: {e}");
}
if let Err(e) = Tray::global().update_menu().await {
log::warn!(target: "app", "异步更新托盘菜单失败: {e}");
}
if let Err(e) = profiles_save_file_safe().await {
log::warn!(target: "app", "异步保存配置文件失败: {e}");
}
if let Some(current) = &current_value {
logging!(
info,
Type::Cmd,
"向前端发送配置变更事件: {}, 序列号: {}",
current,
current_sequence
);
handle::Handle::notify_profile_changed(current.clone());
}
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(true)
}
async fn handle_validation_failure(
error_msg: String,
current_profile: Option<String>,
) -> CmdResult<bool> {
logging!(warn, Type::Cmd, "配置验证失败: {}", error_msg);
Config::profiles().await.discard();
if let Some(prev_profile) = current_profile {
restore_previous_profile(prev_profile).await?;
}
handle::Handle::notice_message("config_validate::error", error_msg);
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(false)
}
async fn handle_update_error<E: std::fmt::Display>(e: E, current_sequence: u64) -> CmdResult<bool> {
logging!(
warn,
Type::Cmd,
"更新过程发生错误: {}, 序列号: {}",
e,
current_sequence
);
Config::profiles().await.discard();
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(false)
}
async fn handle_timeout(current_profile: Option<String>, current_sequence: u64) -> CmdResult<bool> {
let timeout_msg = "配置更新超时(30秒),可能是配置验证或核心通信阻塞";
logging!(
error,
Type::Cmd,
"{}, 序列号: {}",
timeout_msg,
current_sequence
);
Config::profiles().await.discard();
if let Some(prev_profile) = current_profile {
restore_previous_profile(prev_profile).await?;
}
handle::Handle::notice_message("config_validate::timeout", timeout_msg);
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(false)
}
async fn perform_config_update(
current_sequence: u64,
current_value: Option<String>,
current_profile: Option<String>,
) -> CmdResult<bool> {
logging!(
info,
Type::Cmd,
"开始内核配置更新,序列号: {}",
current_sequence
);
let update_result = tokio::time::timeout(
Duration::from_secs(30),
CoreManager::global().update_config(),
)
.await;
match update_result {
Ok(Ok((true, _))) => handle_success(current_sequence, current_value).await,
Ok(Ok((false, error_msg))) => handle_validation_failure(error_msg, current_profile).await,
Ok(Err(e)) => handle_update_error(e, current_sequence).await,
Err(_) => handle_timeout(current_profile, current_sequence).await,
}
}
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
@@ -256,108 +507,10 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
// 如果要切换配置,先检查目标配置文件是否有语法错误
if let Some(new_profile) = profiles.current.as_ref()
&& current_profile.as_ref() != Some(new_profile)
&& validate_new_profile(new_profile).await.is_err()
{
logging!(info, Type::Cmd, "正在切换到新配置: {}", new_profile);
// 获取目标配置文件路径
let config_file_result = {
let profiles_config = Config::profiles().await;
let profiles_data = profiles_config.latest_ref();
match profiles_data.get_item(new_profile) {
Ok(item) => {
if let Some(file) = &item.file {
let path = dirs::app_profiles_dir().map(|dir| dir.join(file.as_str()));
path.ok()
} else {
None
}
}
Err(e) => {
logging!(error, Type::Cmd, "获取目标配置信息失败: {}", e);
None
}
}
};
// 如果获取到文件路径检查YAML语法
if let Some(file_path) = config_file_result {
if !file_path.exists() {
logging!(
error,
Type::Cmd,
"目标配置文件不存在: {}",
file_path.display()
);
handle::Handle::notice_message(
"config_validate::file_not_found",
format!("{}", file_path.display()),
);
return Ok(false);
}
// 超时保护
let file_read_result = tokio::time::timeout(
Duration::from_secs(5),
tokio::fs::read_to_string(&file_path),
)
.await;
match file_read_result {
Ok(Ok(content)) => {
let yaml_parse_result = AsyncHandler::spawn_blocking(move || {
serde_yaml_ng::from_str::<serde_yaml_ng::Value>(&content)
})
.await;
match yaml_parse_result {
Ok(Ok(_)) => {
logging!(info, Type::Cmd, "目标配置文件语法正确");
}
Ok(Err(err)) => {
let error_msg = format!(" {err}");
logging!(
error,
Type::Cmd,
"目标配置文件存在YAML语法错误:{}",
error_msg
);
handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
error_msg.clone(),
);
return Ok(false);
}
Err(join_err) => {
let error_msg = format!("YAML解析任务失败: {join_err}");
logging!(error, Type::Cmd, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::yaml_parse_error",
error_msg.clone(),
);
return Ok(false);
}
}
}
Ok(Err(err)) => {
let error_msg = format!("无法读取目标配置文件: {err}");
logging!(error, Type::Cmd, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_error",
error_msg.clone(),
);
return Ok(false);
}
Err(_) => {
let error_msg = "读取配置文件超时(5秒)".to_string();
logging!(error, Type::Cmd, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_timeout",
error_msg.clone(),
);
return Ok(false);
}
}
}
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
return Ok(false);
}
// 检查请求有效性
@@ -399,163 +552,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
return Ok(false);
}
// 为配置更新添加超时保护
logging!(
info,
Type::Cmd,
"开始内核配置更新,序列号: {}",
current_sequence
);
let update_result = tokio::time::timeout(
Duration::from_secs(30), // 30秒超时
CoreManager::global().update_config(),
)
.await;
// 更新配置并进行验证
match update_result {
Ok(Ok((true, _))) => {
// 内核操作完成后再次检查请求有效性
let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst);
if current_sequence < latest_sequence {
logging!(
info,
Type::Cmd,
"内核操作后发现更新的请求 (序列号: {} < {}),忽略当前结果",
current_sequence,
latest_sequence
);
Config::profiles().await.discard();
return Ok(false);
}
logging!(
info,
Type::Cmd,
"配置更新成功,序列号: {}",
current_sequence
);
Config::profiles().await.apply();
handle::Handle::refresh_clash();
// 强制刷新代理缓存确保profile切换后立即获取最新节点数据
// crate::process::AsyncHandler::spawn(|| async move {
// if let Err(e) = super::proxy::force_refresh_proxies().await {
// log::warn!(target: "app", "强制刷新代理缓存失败: {e}");
// }
// });
if let Err(e) = Tray::global().update_tooltip().await {
log::warn!(target: "app", "异步更新托盘提示失败: {e}");
}
if let Err(e) = Tray::global().update_menu().await {
log::warn!(target: "app", "异步更新托盘菜单失败: {e}");
}
// 保存配置文件
if let Err(e) = profiles_save_file_safe().await {
log::warn!(target: "app", "异步保存配置文件失败: {e}");
}
// 立即通知前端配置变更
if let Some(current) = &current_value {
logging!(
info,
Type::Cmd,
"向前端发送配置变更事件: {}, 序列号: {}",
current,
current_sequence
);
handle::Handle::notify_profile_changed(current.clone());
}
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(true)
}
Ok(Ok((false, error_msg))) => {
logging!(warn, Type::Cmd, "配置验证失败: {}", error_msg);
Config::profiles().await.discard();
// 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile {
logging!(info, Type::Cmd, "尝试恢复到之前的配置: {}", prev_profile);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
};
// 静默恢复,不触发验证
Config::profiles()
.await
.draft_mut()
.patch_config(restore_profiles)
.stringify_err()?;
Config::profiles().await.apply();
crate::process::AsyncHandler::spawn(|| async move {
if let Err(e) = profiles_save_file_safe().await {
log::warn!(target: "app", "异步保存恢复配置文件失败: {e}");
}
});
logging!(info, Type::Cmd, "成功恢复到之前的配置");
}
// 发送验证错误通知
handle::Handle::notice_message("config_validate::error", error_msg.to_string());
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(false)
}
Ok(Err(e)) => {
logging!(
warn,
Type::Cmd,
"更新过程发生错误: {}, 序列号: {}",
e,
current_sequence
);
Config::profiles().await.discard();
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(false)
}
Err(_) => {
// 超时处理
let timeout_msg = "配置更新超时(30秒),可能是配置验证或核心通信阻塞";
logging!(
error,
Type::Cmd,
"{}, 序列号: {}",
timeout_msg,
current_sequence
);
Config::profiles().await.discard();
if let Some(prev_profile) = current_profile {
logging!(
info,
Type::Cmd,
"超时后尝试恢复到之前的配置: {}, 序列号: {}",
prev_profile,
current_sequence
);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
};
Config::profiles()
.await
.draft_mut()
.patch_config(restore_profiles)
.stringify_err()?;
Config::profiles().await.apply();
}
handle::Handle::notice_message("config_validate::timeout", timeout_msg);
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
Ok(false)
}
}
perform_config_update(current_sequence, current_value, current_profile).await
}
/// 根据profile name修改profiles

View File

@@ -12,7 +12,7 @@ async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) ->
.await
{
let emsg = format!("{} Service failed: {}", op_type, e);
return Err(t(emsg.as_str()).await.into());
return Err(t(emsg.as_str()).await);
}
Ok(())
}

View File

@@ -53,24 +53,33 @@ impl Config {
/// 初始化订阅
pub async fn init_config() -> Result<()> {
if Self::profiles()
.await
.latest_ref()
.get_item(&"Merge".into())
.is_err()
{
Self::ensure_default_profile_items().await?;
let validation_result = Self::generate_and_validate().await?;
if let Some((msg_type, msg_content)) = validation_result {
sleep(timing::STARTUP_ERROR_DELAY).await;
handle::Handle::notice_message(msg_type, msg_content);
}
Ok(())
}
// Ensure "Merge" and "Script" profile items exist, adding them if missing.
async fn ensure_default_profile_items() -> Result<()> {
let profiles = Self::profiles().await;
if profiles.latest_ref().get_item(&"Merge".into()).is_err() {
let merge_item = PrfItem::from_merge(Some("Merge".into()))?;
profiles_append_item_safe(merge_item.clone()).await?;
}
if Self::profiles()
.await
.latest_ref()
.get_item(&"Script".into())
.is_err()
{
if profiles.latest_ref().get_item(&"Script".into()).is_err() {
let script_item = PrfItem::from_script(Some("Script".into()))?;
profiles_append_item_safe(script_item.clone()).await?;
}
Ok(())
}
async fn generate_and_validate() -> Result<Option<(&'static str, String)>> {
// 生成运行时配置
if let Err(err) = Self::generate().await {
logging!(error, Type::Config, "生成运行时配置失败: {}", err);
@@ -81,7 +90,7 @@ impl Config {
// 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run).await;
let validation_result = if config_result.is_ok() {
if config_result.is_ok() {
// 验证配置文件
logging!(info, Type::Config, "开始验证配置");
@@ -97,12 +106,12 @@ impl Config {
CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg)
.await?;
Some(("config_validate::boot_error", error_msg))
Ok(Some(("config_validate::boot_error", error_msg)))
} else {
logging!(info, Type::Config, "配置验证成功");
// 前端没有必要知道验证成功的消息,也没有事件驱动
// Some(("config_validate::success", String::new()))
None
Ok(None)
}
}
Err(err) => {
@@ -110,7 +119,7 @@ impl Config {
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.await?;
Some(("config_validate::process_terminated", String::new()))
Ok(Some(("config_validate::process_terminated", String::new())))
}
}
} else {
@@ -118,15 +127,8 @@ impl Config {
CoreManager::global()
.use_default_config("config_validate::error", "")
.await?;
Some(("config_validate::error", String::new()))
};
if let Some((msg_type, msg_content)) = validation_result {
sleep(timing::STARTUP_ERROR_DELAY).await;
handle::Handle::notice_message(msg_type, msg_content);
Ok(Some(("config_validate::error", String::new())))
}
Ok(())
}
pub async fn generate_file(typ: ConfigType) -> Result<PathBuf> {

View File

@@ -37,6 +37,18 @@ macro_rules! patch {
}
impl IProfiles {
// Helper to find and remove an item by uid from the items vec, returning its file name (if any).
fn take_item_file_by_uid(
items: &mut Vec<PrfItem>,
target_uid: Option<String>,
) -> Option<String> {
for (i, _) in items.iter().enumerate() {
if items[i].uid == target_uid {
return items.remove(i).file;
}
}
None
}
pub async fn new() -> Self {
match dirs::profiles_path() {
Ok(path) => match help::read_yaml::<Self>(&path).await {
@@ -277,98 +289,41 @@ impl IProfiles {
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
let mut items = self.items.take().unwrap_or_default();
let mut index = None;
let mut merge_index = None;
let mut script_index = None;
let mut rules_index = None;
let mut proxies_index = None;
let mut groups_index = None;
// get the index
for (i, _) in items.iter().enumerate() {
if items[i].uid == Some(uid.clone()) {
index = Some(i);
break;
}
}
if let Some(index) = index
&& let Some(file) = items.remove(index).file
{
// remove the main item (if exists) and delete its file
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.clone())) {
let _ = dirs::app_profiles_dir()?
.join(file.as_str())
.remove_if_exists()
.await;
}
// get the merge index
for (i, _) in items.iter().enumerate() {
if items[i].uid == merge_uid {
merge_index = Some(i);
break;
}
}
if let Some(index) = merge_index
&& let Some(file) = items.remove(index).file
{
// remove related extension items (merge, script, rules, proxies, groups)
if let Some(file) = Self::take_item_file_by_uid(&mut items, merge_uid.clone()) {
let _ = dirs::app_profiles_dir()?
.join(file.as_str())
.remove_if_exists()
.await;
}
// get the script index
for (i, _) in items.iter().enumerate() {
if items[i].uid == script_uid {
script_index = Some(i);
break;
}
}
if let Some(index) = script_index
&& let Some(file) = items.remove(index).file
{
if let Some(file) = Self::take_item_file_by_uid(&mut items, script_uid.clone()) {
let _ = dirs::app_profiles_dir()?
.join(file.as_str())
.remove_if_exists()
.await;
}
// get the rules index
for (i, _) in items.iter().enumerate() {
if items[i].uid == rules_uid {
rules_index = Some(i);
break;
}
}
if let Some(index) = rules_index
&& let Some(file) = items.remove(index).file
{
if let Some(file) = Self::take_item_file_by_uid(&mut items, rules_uid.clone()) {
let _ = dirs::app_profiles_dir()?
.join(file.as_str())
.remove_if_exists()
.await;
}
// get the proxies index
for (i, _) in items.iter().enumerate() {
if items[i].uid == proxies_uid {
proxies_index = Some(i);
break;
}
}
if let Some(index) = proxies_index
&& let Some(file) = items.remove(index).file
{
if let Some(file) = Self::take_item_file_by_uid(&mut items, proxies_uid.clone()) {
let _ = dirs::app_profiles_dir()?
.join(file.as_str())
.remove_if_exists()
.await;
}
// get the groups index
for (i, _) in items.iter().enumerate() {
if items[i].uid == groups_uid {
groups_index = Some(i);
break;
}
}
if let Some(index) = groups_index
&& let Some(file) = items.remove(index).file
{
if let Some(file) = Self::take_item_file_by_uid(&mut items, groups_uid.clone()) {
let _ = dirs::app_profiles_dir()?
.join(file.as_str())
.remove_if_exists()

View File

@@ -438,6 +438,7 @@ impl IVerge {
/// patch verge config
/// only save to file
#[allow(clippy::cognitive_complexity)]
pub fn patch_config(&mut self, patch: IVerge) {
macro_rules! patch {
($key: tt) => {

View File

@@ -1,8 +1,10 @@
use once_cell::sync::OnceCell;
use tauri::Emitter;
use tauri::tray::TrayIconBuilder;
use tauri_plugin_mihomo::models::Proxies;
#[cfg(target_os = "macos")]
pub mod speed_rate;
use crate::config::PrfSelected;
use crate::module::lightweight;
use crate::process::AsyncHandler;
use crate::utils::window_manager::WindowManager;
@@ -34,6 +36,56 @@ use tauri::{
// TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑)
type ProxyMenuItem = (Option<Submenu<Wry>>, Vec<Box<dyn IsMenuItem<Wry>>>);
struct MenuTexts {
dashboard: String,
rule_mode: String,
global_mode: String,
direct_mode: String,
profiles: String,
proxies: String,
system_proxy: String,
tun_mode: String,
close_all_connections: String,
lightweight_mode: String,
copy_env: String,
conf_dir: String,
core_dir: String,
logs_dir: String,
open_dir: String,
restart_clash: String,
restart_app: String,
verge_version: String,
more: String,
exit: String,
}
async fn fetch_menu_texts() -> MenuTexts {
MenuTexts {
dashboard: t("Dashboard").await,
rule_mode: t("Rule Mode").await,
global_mode: t("Global Mode").await,
direct_mode: t("Direct Mode").await,
profiles: t("Profiles").await,
proxies: t("Proxies").await,
system_proxy: t("System Proxy").await,
tun_mode: t("TUN Mode").await,
close_all_connections: t("Close All Connections").await,
lightweight_mode: t("LightWeight Mode").await,
copy_env: t("Copy Env").await,
conf_dir: t("Conf Dir").await,
core_dir: t("Core Dir").await,
logs_dir: t("Logs Dir").await,
open_dir: t("Open Dir").await,
restart_clash: t("Restart Clash Core").await,
restart_app: t("Restart App").await,
verge_version: t("Verge Version").await,
more: t("More").await,
exit: t("Exit").await,
}
}
#[derive(Clone)]
struct TrayState {}
@@ -606,70 +658,8 @@ impl Tray {
}
}
async fn create_tray_menu(
app_handle: &AppHandle,
mode: Option<&str>,
system_proxy_enabled: bool,
tun_mode_enabled: bool,
profile_uid_and_name: Vec<(String, String)>,
is_lightweight_mode: bool,
) -> Result<tauri::menu::Menu<Wry>> {
let mode = mode.unwrap_or("");
// 获取当前配置文件的选中代理组信息
let current_profile_selected = {
let profiles_config = Config::profiles().await;
let profiles_ref = profiles_config.latest_ref();
profiles_ref
.get_current()
.and_then(|uid| profiles_ref.get_item(&uid).ok())
.and_then(|profile| profile.selected.clone())
.unwrap_or_default()
};
let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await;
let runtime_proxy_groups_order = cmd::get_runtime_config()
.await
.map_err(|e| {
logging!(
error,
Type::Cmd,
"Failed to fetch runtime proxy groups for tray menu: {e}"
);
})
.ok()
.flatten()
.map(|config| {
config
.get("proxy-groups")
.and_then(|groups| groups.as_sequence())
.map(|groups| {
groups
.iter()
.filter_map(|group| group.get("name"))
.filter_map(|name| name.as_str())
.map(|name| name.into())
.collect::<Vec<String>>()
})
.unwrap_or_default()
});
let proxy_group_order_map = runtime_proxy_groups_order.as_ref().map(|group_names| {
group_names
.iter()
.enumerate()
.map(|(index, name)| (name.clone(), index))
.collect::<HashMap<String, usize>>()
});
let verge_settings = Config::verge().await.latest_ref().clone();
let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false);
let version = env!("CARGO_PKG_VERSION");
let hotkeys = verge_settings
.hotkeys
fn create_hotkeys(hotkeys: &Option<Vec<String>>) -> HashMap<String, String> {
hotkeys
.as_ref()
.map(|h| {
h.iter()
@@ -689,35 +679,45 @@ async fn create_tray_menu(
})
.collect::<std::collections::HashMap<String, String>>()
})
.unwrap_or_default();
.unwrap_or_default()
}
let profile_menu_items: Vec<CheckMenuItem<Wry>> = {
let futures = profile_uid_and_name
.iter()
.map(|(profile_uid, profile_name)| {
let app_handle = app_handle.clone();
let profile_uid = profile_uid.clone();
let profile_name = profile_name.clone();
async move {
let is_current_profile = Config::profiles()
.await
.data_mut()
.is_current_profile_index(profile_uid.clone());
CheckMenuItem::with_id(
&app_handle,
format!("profiles_{profile_uid}"),
t(&profile_name).await,
true,
is_current_profile,
None::<&str>,
)
}
});
let results = join_all(futures).await;
results.into_iter().collect::<Result<Vec<_>, _>>()?
};
async fn create_profile_menu_item(
app_handle: &AppHandle,
profile_uid_and_name: Vec<(String, String)>,
) -> Result<Vec<CheckMenuItem<Wry>>> {
let futures = profile_uid_and_name
.iter()
.map(|(profile_uid, profile_name)| {
let app_handle = app_handle.clone();
let profile_uid = profile_uid.clone();
let profile_name = profile_name.clone();
async move {
let is_current_profile = Config::profiles()
.await
.latest_ref()
.is_current_profile_index(profile_uid.clone());
CheckMenuItem::with_id(
&app_handle,
format!("profiles_{profile_uid}"),
t(&profile_name).await,
true,
is_current_profile,
None::<&str>,
)
}
});
let results = join_all(futures).await;
Ok(results.into_iter().collect::<Result<Vec<_>, _>>()?)
}
// 代理组子菜单
fn create_subcreate_proxy_menu_item(
app_handle: &AppHandle,
proxy_mode: &str,
current_profile_selected: &[PrfSelected],
proxy_group_order_map: Option<HashMap<String, usize>>,
proxy_nodes_data: Result<Proxies>,
) -> Result<Vec<Submenu<Wry>>> {
let proxy_submenus: Vec<Submenu<Wry>> = {
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = Vec::new();
@@ -725,7 +725,7 @@ async fn create_tray_menu(
if let Ok(proxy_nodes_data) = proxy_nodes_data {
for (group_name, group_data) in proxy_nodes_data.proxies.iter() {
// Filter groups based on mode
let should_show = match mode {
let should_show = match proxy_mode {
"global" => group_name == "GLOBAL",
_ => group_name != "GLOBAL",
} &&
@@ -781,7 +781,7 @@ async fn create_tray_menu(
}
// Determine if group is active
let is_group_active = match mode {
let is_group_active = match proxy_mode {
"global" => group_name == "GLOBAL" && !now_proxy.is_empty(),
"direct" => false,
_ => {
@@ -837,28 +837,117 @@ async fn create_tray_menu(
.map(|(_, _, submenu)| submenu)
.collect()
};
Ok(proxy_submenus)
}
fn create_proxy_menu_item(
app_handle: &AppHandle,
show_proxy_groups_inline: bool,
proxy_submenus: Vec<Submenu<Wry>>,
proxies_text: &String,
) -> Result<ProxyMenuItem> {
// 创建代理主菜单
let (proxies_submenu, inline_proxy_items) = if show_proxy_groups_inline {
(
None,
proxy_submenus
.into_iter()
.map(|submenu| Box::new(submenu) as Box<dyn IsMenuItem<Wry>>)
.collect(),
)
} else if !proxy_submenus.is_empty() {
let proxy_submenu_refs: Vec<&dyn IsMenuItem<Wry>> = proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.collect();
(
Some(Submenu::with_id_and_items(
app_handle,
"proxies",
proxies_text,
true,
&proxy_submenu_refs,
)?),
Vec::new(),
)
} else {
(None, Vec::new())
};
Ok((proxies_submenu, inline_proxy_items))
}
async fn create_tray_menu(
app_handle: &AppHandle,
mode: Option<&str>,
system_proxy_enabled: bool,
tun_mode_enabled: bool,
profile_uid_and_name: Vec<(String, String)>,
is_lightweight_mode: bool,
) -> Result<tauri::menu::Menu<Wry>> {
let current_proxy_mode = mode.unwrap_or("");
// 获取当前配置文件的选中代理组信息
let current_profile_selected = {
let profiles_config = Config::profiles().await;
let profiles_ref = profiles_config.latest_ref();
profiles_ref
.get_current()
.and_then(|uid| profiles_ref.get_item(&uid).ok())
.and_then(|profile| profile.selected.clone())
.unwrap_or_default()
};
let proxy_nodes_data = handle::Handle::mihomo().await.get_proxies().await;
let runtime_proxy_groups_order = cmd::get_runtime_config()
.await
.map_err(|e| {
logging!(
error,
Type::Cmd,
"Failed to fetch runtime proxy groups for tray menu: {e}"
);
})
.ok()
.flatten()
.map(|config| {
config
.get("proxy-groups")
.and_then(|groups| groups.as_sequence())
.map(|groups| {
groups
.iter()
.filter_map(|group| group.get("name"))
.filter_map(|name| name.as_str())
.map(|name| name.into())
.collect::<Vec<String>>()
})
.unwrap_or_default()
});
let proxy_group_order_map: Option<
HashMap<smartstring::SmartString<smartstring::LazyCompact>, usize>,
> = runtime_proxy_groups_order.as_ref().map(|group_names| {
group_names
.iter()
.enumerate()
.map(|(index, name)| (name.clone(), index))
.collect::<HashMap<String, usize>>()
});
let verge_settings = Config::verge().await.latest_ref().clone();
let show_proxy_groups_inline = verge_settings.tray_inline_proxy_groups.unwrap_or(false);
let version = env!("CARGO_PKG_VERSION");
let hotkeys = create_hotkeys(&verge_settings.hotkeys);
let profile_menu_items: Vec<CheckMenuItem<Wry>> =
create_profile_menu_item(app_handle, profile_uid_and_name).await?;
// Pre-fetch all localized strings
let dashboard_text = t("Dashboard").await;
let rule_mode_text = t("Rule Mode").await;
let global_mode_text = t("Global Mode").await;
let direct_mode_text = t("Direct Mode").await;
let profiles_text = t("Profiles").await;
let proxies_text = t("Proxies").await;
let system_proxy_text = t("System Proxy").await;
let tun_mode_text = t("TUN Mode").await;
let close_all_connections_text = t("Close All Connections").await;
let lightweight_mode_text = t("LightWeight Mode").await;
let copy_env_text = t("Copy Env").await;
let conf_dir_text = t("Conf Dir").await;
let core_dir_text = t("Core Dir").await;
let logs_dir_text = t("Logs Dir").await;
let open_dir_text = t("Open Dir").await;
let restart_clash_text = t("Restart Clash Core").await;
let restart_app_text = t("Restart App").await;
let verge_version_text = t("Verge Version").await;
let more_text = t("More").await;
let exit_text = t("Exit").await;
let texts = &fetch_menu_texts().await;
// Convert to references only when needed
let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
@@ -869,7 +958,7 @@ async fn create_tray_menu(
let open_window = &MenuItem::with_id(
app_handle,
"open_window",
dashboard_text,
&texts.dashboard,
true,
hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()),
)?;
@@ -877,72 +966,57 @@ async fn create_tray_menu(
let rule_mode = &CheckMenuItem::with_id(
app_handle,
"rule_mode",
rule_mode_text,
&texts.rule_mode,
true,
mode == "rule",
current_proxy_mode == "rule",
hotkeys.get("clash_mode_rule").map(|s| s.as_str()),
)?;
let global_mode = &CheckMenuItem::with_id(
app_handle,
"global_mode",
global_mode_text,
&texts.global_mode,
true,
mode == "global",
current_proxy_mode == "global",
hotkeys.get("clash_mode_global").map(|s| s.as_str()),
)?;
let direct_mode = &CheckMenuItem::with_id(
app_handle,
"direct_mode",
direct_mode_text,
&texts.direct_mode,
true,
mode == "direct",
current_proxy_mode == "direct",
hotkeys.get("clash_mode_direct").map(|s| s.as_str()),
)?;
let profiles = &Submenu::with_id_and_items(
app_handle,
"profiles",
profiles_text,
&texts.profiles,
true,
&profile_menu_items_refs,
)?;
// 创建代理主菜单
let (proxies_submenu, inline_proxy_items): (Option<Submenu<Wry>>, Vec<&dyn IsMenuItem<Wry>>) =
if show_proxy_groups_inline {
(
None,
proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.collect(),
)
} else if !proxy_submenus.is_empty() {
let proxy_submenu_refs: Vec<&dyn IsMenuItem<Wry>> = proxy_submenus
.iter()
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
.collect();
let proxy_sub_menus = create_subcreate_proxy_menu_item(
app_handle,
current_proxy_mode,
&current_profile_selected,
proxy_group_order_map,
proxy_nodes_data.map_err(anyhow::Error::from),
)?;
(
Some(Submenu::with_id_and_items(
app_handle,
"proxies",
proxies_text,
true,
&proxy_submenu_refs,
)?),
Vec::new(),
)
} else {
(None, Vec::new())
};
let (proxies_menu, inline_proxy_items) = create_proxy_menu_item(
app_handle,
show_proxy_groups_inline,
proxy_sub_menus,
&texts.proxies,
)?;
let system_proxy = &CheckMenuItem::with_id(
app_handle,
"system_proxy",
system_proxy_text,
&texts.system_proxy,
true,
system_proxy_enabled,
hotkeys.get("toggle_system_proxy").map(|s| s.as_str()),
@@ -951,7 +1025,7 @@ async fn create_tray_menu(
let tun_mode = &CheckMenuItem::with_id(
app_handle,
"tun_mode",
tun_mode_text,
&texts.tun_mode,
true,
tun_mode_enabled,
hotkeys.get("toggle_tun_mode").map(|s| s.as_str()),
@@ -960,7 +1034,7 @@ async fn create_tray_menu(
let close_all_connections = &MenuItem::with_id(
app_handle,
"close_all_connections",
close_all_connections_text,
&texts.close_all_connections,
true,
None::<&str>,
)?;
@@ -968,18 +1042,18 @@ async fn create_tray_menu(
let lighteweight_mode = &CheckMenuItem::with_id(
app_handle,
"entry_lightweight_mode",
lightweight_mode_text,
&texts.lightweight_mode,
true,
is_lightweight_mode,
hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()),
)?;
let copy_env = &MenuItem::with_id(app_handle, "copy_env", copy_env_text, true, None::<&str>)?;
let copy_env = &MenuItem::with_id(app_handle, "copy_env", &texts.copy_env, true, None::<&str>)?;
let open_app_dir = &MenuItem::with_id(
app_handle,
"open_app_dir",
conf_dir_text,
&texts.conf_dir,
true,
None::<&str>,
)?;
@@ -987,7 +1061,7 @@ async fn create_tray_menu(
let open_core_dir = &MenuItem::with_id(
app_handle,
"open_core_dir",
core_dir_text,
&texts.core_dir,
true,
None::<&str>,
)?;
@@ -995,7 +1069,7 @@ async fn create_tray_menu(
let open_logs_dir = &MenuItem::with_id(
app_handle,
"open_logs_dir",
logs_dir_text,
&texts.logs_dir,
true,
None::<&str>,
)?;
@@ -1003,7 +1077,7 @@ async fn create_tray_menu(
let open_dir = &Submenu::with_id_and_items(
app_handle,
"open_dir",
open_dir_text,
&texts.open_dir,
true,
&[open_app_dir, open_core_dir, open_logs_dir],
)?;
@@ -1011,7 +1085,7 @@ async fn create_tray_menu(
let restart_clash = &MenuItem::with_id(
app_handle,
"restart_clash",
restart_clash_text,
&texts.restart_clash,
true,
None::<&str>,
)?;
@@ -1019,7 +1093,7 @@ async fn create_tray_menu(
let restart_app = &MenuItem::with_id(
app_handle,
"restart_app",
restart_app_text,
&texts.restart_app,
true,
None::<&str>,
)?;
@@ -1027,7 +1101,7 @@ async fn create_tray_menu(
let app_version = &MenuItem::with_id(
app_handle,
"app_version",
format!("{} {version}", verge_version_text),
format!("{} {version}", &texts.verge_version),
true,
None::<&str>,
)?;
@@ -1035,7 +1109,7 @@ async fn create_tray_menu(
let more = &Submenu::with_id_and_items(
app_handle,
"more",
more_text,
&texts.more,
true,
&[
close_all_connections,
@@ -1045,7 +1119,13 @@ async fn create_tray_menu(
],
)?;
let quit = &MenuItem::with_id(app_handle, "quit", exit_text, true, Some("CmdOrControl+Q"))?;
let quit = &MenuItem::with_id(
app_handle,
"quit",
&texts.exit,
true,
Some("CmdOrControl+Q"),
)?;
let separator = &PredefinedMenuItem::separator(app_handle)?;
@@ -1063,9 +1143,9 @@ async fn create_tray_menu(
// 如果有代理节点,添加代理节点菜单
if show_proxy_groups_inline {
if !inline_proxy_items.is_empty() {
menu_items.extend_from_slice(&inline_proxy_items);
menu_items.extend(inline_proxy_items.iter().map(|item| item.as_ref()));
}
} else if let Some(ref proxies_menu) = proxies_submenu {
} else if let Some(ref proxies_menu) = proxies_menu {
menu_items.push(proxies_menu);
}

View File

@@ -12,11 +12,35 @@ use smartstring::alias::String;
use std::collections::{HashMap, HashSet};
type ResultLog = Vec<(String, String)>;
#[derive(Debug)]
struct ConfigValues {
clash_config: Mapping,
clash_core: Option<String>,
enable_tun: bool,
enable_builtin: bool,
socks_enabled: bool,
http_enabled: bool,
enable_dns_settings: bool,
#[cfg(not(target_os = "windows"))]
redir_enabled: bool,
#[cfg(target_os = "linux")]
tproxy_enabled: bool,
}
/// Enhance mode
/// 返回最终订阅、该订阅包含的键、和script执行的结果
pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// config.yaml 的订阅
#[derive(Debug)]
struct ProfileItems {
config: Mapping,
merge_item: ChainItem,
script_item: ChainItem,
rules_item: ChainItem,
proxies_item: ChainItem,
groups_item: ChainItem,
global_merge: ChainItem,
global_script: ChainItem,
profile_name: String,
}
async fn get_config_values() -> ConfigValues {
let clash_config = { Config::clash().await.latest_ref().0.clone() };
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = {
@@ -31,12 +55,14 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
verge.enable_dns_settings.unwrap_or(false),
)
};
#[cfg(not(target_os = "windows"))]
let redir_enabled = {
let verge = Config::verge().await;
let verge = verge.latest_ref();
verge.verge_redir_enabled.unwrap_or(false)
};
#[cfg(target_os = "linux")]
let tproxy_enabled = {
let verge = Config::verge().await;
@@ -44,9 +70,189 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
verge.verge_tproxy_enabled.unwrap_or(false)
};
ConfigValues {
clash_config,
clash_core,
enable_tun,
enable_builtin,
socks_enabled,
http_enabled,
enable_dns_settings,
#[cfg(not(target_os = "windows"))]
redir_enabled,
#[cfg(target_os = "linux")]
tproxy_enabled,
}
}
async fn collect_profile_items() -> ProfileItems {
// 从profiles里拿东西 - 先收集需要的数据,然后释放锁
let (
mut config,
current,
merge_uid,
script_uid,
rules_uid,
proxies_uid,
groups_uid,
_current_profile_uid,
name,
) = {
let current = {
let profiles = Config::profiles().await;
let profiles_clone = profiles.latest_ref().clone();
profiles_clone.current_mapping().await.unwrap_or_default()
};
let profiles = Config::profiles().await;
let profiles_ref = profiles.latest_ref();
let merge_uid = profiles_ref.current_merge().unwrap_or_default();
let script_uid = profiles_ref.current_script().unwrap_or_default();
let rules_uid = profiles_ref.current_rules().unwrap_or_default();
let proxies_uid = profiles_ref.current_proxies().unwrap_or_default();
let groups_uid = profiles_ref.current_groups().unwrap_or_default();
let current_profile_uid = profiles_ref.get_current().unwrap_or_default();
let name = profiles_ref
.get_item(&current_profile_uid)
.ok()
.and_then(|item| item.name.clone())
.unwrap_or_default();
(
current,
merge_uid,
script_uid,
rules_uid,
proxies_uid,
groups_uid,
current_profile_uid,
name,
)
};
// 现在获取具体的items此时profiles锁已经释放
let merge_item = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&merge_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Merge(Mapping::new()),
});
let script_item = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&script_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
let rules_item = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&rules_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Rules(SeqMap::default()),
});
let proxies_item = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&proxies_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Proxies(SeqMap::default()),
});
let groups_item = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&groups_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Groups(SeqMap::default()),
});
let global_merge = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&"Merge".into()).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "Merge".into(),
data: ChainType::Merge(Mapping::new()),
});
let global_script = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&"Script".into()).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "Script".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
ProfileItems {
config: current,
merge_item,
script_item,
rules_item,
@@ -54,192 +260,19 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
groups_item,
global_merge,
global_script,
profile_name,
) = {
// 收集所有需要的数据然后释放profiles锁
let (
current,
merge_uid,
script_uid,
rules_uid,
proxies_uid,
groups_uid,
_current_profile_uid,
name,
) = {
// 分离async调用和数据获取避免借用检查问题
let current = {
let profiles = Config::profiles().await;
let profiles_clone = profiles.latest_ref().clone();
profiles_clone.current_mapping().await.unwrap_or_default()
};
profile_name: name,
}
}
// 重新获取锁进行其他操作
let profiles = Config::profiles().await;
let profiles_ref = profiles.latest_ref();
fn process_global_items(
mut config: Mapping,
global_merge: ChainItem,
global_script: ChainItem,
profile_name: String,
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
let mut result_map = HashMap::new();
let mut exists_keys = use_keys(&config);
let merge_uid = profiles_ref.current_merge().unwrap_or_default();
let script_uid = profiles_ref.current_script().unwrap_or_default();
let rules_uid = profiles_ref.current_rules().unwrap_or_default();
let proxies_uid = profiles_ref.current_proxies().unwrap_or_default();
let groups_uid = profiles_ref.current_groups().unwrap_or_default();
let current_profile_uid = profiles_ref.get_current().unwrap_or_default();
let name = profiles_ref
.get_item(&current_profile_uid)
.ok()
.and_then(|item| item.name.clone())
.unwrap_or_default();
(
current,
merge_uid,
script_uid,
rules_uid,
proxies_uid,
groups_uid,
current_profile_uid,
name,
)
};
// 现在获取具体的items此时profiles锁已经释放
let merge = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&merge_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Merge(Mapping::new()),
});
let script = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&script_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
let rules = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&rules_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Rules(SeqMap::default()),
});
let proxies = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&proxies_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Proxies(SeqMap::default()),
});
let groups = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&groups_uid).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Groups(SeqMap::default()),
});
let global_merge = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&"Merge".into()).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "Merge".into(),
data: ChainType::Merge(Mapping::new()),
});
let global_script = {
let item = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
profiles.get_item(&"Script".into()).ok().cloned()
};
if let Some(item) = item {
<Option<ChainItem>>::from_async(&item).await
} else {
None
}
}
.unwrap_or_else(|| ChainItem {
uid: "Script".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
(
current,
merge,
script,
rules,
proxies,
groups,
global_merge,
global_script,
name,
)
};
let mut result_map = HashMap::new(); // 保存脚本日志
let mut exists_keys = use_keys(&config); // 保存出现过的keys
// 全局Merge和Script
if let ChainType::Merge(merge) = global_merge.data {
exists_keys.extend(use_keys(&merge));
config = use_merge(merge, config.to_owned());
@@ -247,7 +280,6 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if let ChainType::Script(script) = global_script.data {
let mut logs = vec![];
match use_script(script, config.to_owned(), profile_name.to_owned()) {
Ok((res_config, res_logs)) => {
exists_keys.extend(use_keys(&res_config));
@@ -256,11 +288,24 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
}
Err(err) => logs.push(("exception".into(), err.to_string().into())),
}
result_map.insert(global_script.uid, logs);
}
// 订阅关联的Merge、Script、Rules、Proxies、Groups
(config, exists_keys, result_map)
}
#[allow(clippy::too_many_arguments)]
fn process_profile_items(
mut config: Mapping,
mut exists_keys: Vec<String>,
mut result_map: HashMap<String, ResultLog>,
rules_item: ChainItem,
proxies_item: ChainItem,
groups_item: ChainItem,
merge_item: ChainItem,
script_item: ChainItem,
profile_name: String,
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if let ChainType::Rules(rules) = rules_item.data {
config = use_seq(rules, config.to_owned(), "rules");
}
@@ -280,7 +325,6 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if let ChainType::Script(script) = script_item.data {
let mut logs = vec![];
match use_script(script, config.to_owned(), profile_name) {
Ok((res_config, res_logs)) => {
exists_keys.extend(use_keys(&res_config));
@@ -289,11 +333,20 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
}
Err(err) => logs.push(("exception".into(), err.to_string().into())),
}
result_map.insert(script_item.uid, logs);
}
// 合并默认的config
(config, exists_keys, result_map)
}
async fn merge_default_config(
mut config: Mapping,
clash_config: Mapping,
socks_enabled: bool,
http_enabled: bool,
#[cfg(not(target_os = "windows"))] redir_enabled: bool,
#[cfg(target_os = "linux")] tproxy_enabled: bool,
) -> Mapping {
for (key, value) in clash_config.into_iter() {
if key.as_str() == Some("tun") {
let mut tun = config.get_mut("tun").map_or_else(Mapping::new, |val| {
@@ -353,7 +406,14 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
}
}
// 内建脚本最后跑
config
}
fn apply_builtin_scripts(
mut config: Mapping,
clash_core: Option<String>,
enable_builtin: bool,
) -> Mapping {
if enable_builtin {
ChainItem::builtin()
.into_iter()
@@ -374,10 +434,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
});
}
config = use_tun(config, enable_tun);
config = use_sort(config);
config
}
// 应用独立的DNS配置如果启用
fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping {
if enable_dns_settings {
use crate::utils::dirs;
use std::fs;
@@ -389,7 +449,6 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
&& let Ok(dns_yaml) = fs::read_to_string(&dns_path)
&& let Ok(dns_config) = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml)
{
// 处理hosts配置
if let Some(hosts_value) = dns_config.get("hosts")
&& hosts_value.is_mapping()
{
@@ -410,9 +469,82 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
}
}
config
}
/// Enhance mode
/// 返回最终订阅、该订阅包含的键、和script执行的结果
pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// gather config values
let cfg_vals = get_config_values().await;
let ConfigValues {
clash_config,
clash_core,
enable_tun,
enable_builtin,
socks_enabled,
http_enabled,
enable_dns_settings,
#[cfg(not(target_os = "windows"))]
redir_enabled,
#[cfg(target_os = "linux")]
tproxy_enabled,
} = cfg_vals;
// collect profile items
let profile = collect_profile_items().await;
let config = profile.config;
let merge_item = profile.merge_item;
let script_item = profile.script_item;
let rules_item = profile.rules_item;
let proxies_item = profile.proxies_item;
let groups_item = profile.groups_item;
let global_merge = profile.global_merge;
let global_script = profile.global_script;
let profile_name = profile.profile_name;
// process globals
let (config, exists_keys, result_map) =
process_global_items(config, global_merge, global_script, profile_name.clone());
// process profile-specific items
let (config, exists_keys, result_map) = process_profile_items(
config,
exists_keys,
result_map,
rules_item,
proxies_item,
groups_item,
merge_item,
script_item,
profile_name,
);
// merge default clash config
let config = merge_default_config(
config,
clash_config,
socks_enabled,
http_enabled,
#[cfg(not(target_os = "windows"))]
redir_enabled,
#[cfg(target_os = "linux")]
tproxy_enabled,
)
.await;
// builtin scripts
let mut config = apply_builtin_scripts(config, clash_core, enable_builtin);
config = use_tun(config, enable_tun);
config = use_sort(config);
// dns settings
config = apply_dns_settings(config, enable_dns_settings);
let mut exists_set = HashSet::new();
exists_set.extend(exists_keys);
exists_keys = exists_set.into_iter().collect();
let exists_keys: Vec<String> = exists_set.into_iter().collect();
(config, exists_keys, result_map)
}

View File

@@ -63,23 +63,19 @@ enum UpdateFlags {
LighteWeight = 1 << 10,
}
/// Patch Verge configuration
pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
Config::verge()
.await
.draft_mut()
.patch_config(patch.clone());
fn determine_update_flags(patch: &IVerge) -> i32 {
let mut update_flags: i32 = UpdateFlags::None as i32;
let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy;
let pac = patch.proxy_auto_config;
let pac_content = patch.pac_file_content;
let proxy_bypass = patch.system_proxy_bypass;
let language = patch.language;
let pac_content = &patch.pac_file_content;
let proxy_bypass = &patch.system_proxy_bypass;
let language = &patch.language;
let mixed_port = patch.verge_mixed_port;
#[cfg(target_os = "macos")]
let tray_icon = patch.tray_icon;
let tray_icon = &patch.tray_icon;
#[cfg(not(target_os = "macos"))]
let tray_icon: Option<String> = None;
let common_tray_icon = patch.common_tray_icon;
@@ -100,145 +96,152 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let enable_tray_speed = patch.enable_tray_speed;
let enable_tray_icon = patch.enable_tray_icon;
let enable_global_hotkey = patch.enable_global_hotkey;
let tray_event = patch.tray_event;
let tray_event = &patch.tray_event;
let home_cards = patch.home_cards.clone();
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
let enable_external_controller = patch.enable_external_controller;
let res: std::result::Result<(), anyhow::Error> = {
// Initialize with no flags set
let mut update_flags: i32 = UpdateFlags::None as i32;
if tun_mode.is_some() {
update_flags |= UpdateFlags::ClashConfig as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
}
if enable_global_hotkey.is_some() || home_cards.is_some() {
update_flags |= UpdateFlags::VergeConfig as i32;
}
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
if socks_enabled.is_some()
|| http_enabled.is_some()
|| socks_port.is_some()
|| http_port.is_some()
|| mixed_port.is_some()
{
update_flags |= UpdateFlags::RestartCore as i32;
}
if auto_launch.is_some() {
update_flags |= UpdateFlags::Launch as i32;
}
if tun_mode.is_some() {
update_flags |= UpdateFlags::ClashConfig as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
}
if enable_global_hotkey.is_some() || home_cards.is_some() {
update_flags |= UpdateFlags::VergeConfig as i32;
}
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
if socks_enabled.is_some()
|| http_enabled.is_some()
|| socks_port.is_some()
|| http_port.is_some()
|| mixed_port.is_some()
{
update_flags |= UpdateFlags::RestartCore as i32;
}
if auto_launch.is_some() {
update_flags |= UpdateFlags::Launch as i32;
}
if system_proxy.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
}
if system_proxy.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
}
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
}
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
}
if language.is_some() {
update_flags |= UpdateFlags::SystrayMenu as i32;
}
if common_tray_icon.is_some()
|| sysproxy_tray_icon.is_some()
|| tun_tray_icon.is_some()
|| tray_icon.is_some()
|| enable_tray_speed.is_some()
|| enable_tray_icon.is_some()
{
update_flags |= UpdateFlags::SystrayIcon as i32;
}
if language.is_some() {
update_flags |= UpdateFlags::SystrayMenu as i32;
}
if common_tray_icon.is_some()
|| sysproxy_tray_icon.is_some()
|| tun_tray_icon.is_some()
|| tray_icon.is_some()
|| enable_tray_speed.is_some()
|| enable_tray_icon.is_some()
{
update_flags |= UpdateFlags::SystrayIcon as i32;
}
if patch.hotkeys.is_some() {
update_flags |= UpdateFlags::Hotkey as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
}
if patch.hotkeys.is_some() {
update_flags |= UpdateFlags::Hotkey as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
}
if tray_event.is_some() {
update_flags |= UpdateFlags::SystrayClickBehavior as i32;
}
if tray_event.is_some() {
update_flags |= UpdateFlags::SystrayClickBehavior as i32;
}
if enable_auto_light_weight.is_some() {
update_flags |= UpdateFlags::LighteWeight as i32;
}
if enable_auto_light_weight.is_some() {
update_flags |= UpdateFlags::LighteWeight as i32;
}
// 处理 external-controller 的开关
if enable_external_controller.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
if enable_external_controller.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
}
// Process updates based on flags
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
Config::generate().await?;
CoreManager::global().restart_core().await?;
}
if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 {
CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
}
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
Config::verge().await.draft_mut().enable_global_hotkey = enable_global_hotkey;
handle::Handle::refresh_verge();
}
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
sysopt::Sysopt::global().update_launch().await?;
}
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
sysopt::Sysopt::global().update_sysproxy().await?;
}
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0
&& let Some(hotkeys) = patch.hotkeys
{
hotkey::Hotkey::global().update(hotkeys).await?;
}
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
tray::Tray::global().update_menu().await?;
}
if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 {
tray::Tray::global().update_icon().await?;
}
if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 {
tray::Tray::global().update_tooltip().await?;
}
if (update_flags & (UpdateFlags::SystrayClickBehavior as i32)) != 0 {
tray::Tray::global().update_click_behavior().await?;
}
if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 {
if enable_auto_light_weight.unwrap_or(false) {
lightweight::enable_auto_light_weight_mode().await;
} else {
lightweight::disable_auto_light_weight_mode();
}
}
update_flags
}
<Result<()>>::Ok(())
};
match res {
Ok(()) => {
Config::verge().await.apply();
if !not_save_file {
// 分离数据获取和异步调用
let verge_data = Config::verge().await.data_mut().clone();
verge_data.save_file().await?;
}
Ok(())
}
Err(err) => {
Config::verge().await.discard();
Err(err)
async fn process_terminated_flags(update_flags: i32, patch: &IVerge) -> Result<()> {
// Process updates based on flags
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
Config::generate().await?;
CoreManager::global().restart_core().await?;
}
if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 {
CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
}
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
Config::verge().await.draft_mut().enable_global_hotkey = patch.enable_global_hotkey;
handle::Handle::refresh_verge();
}
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
sysopt::Sysopt::global().update_launch().await?;
}
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
sysopt::Sysopt::global().update_sysproxy().await?;
}
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0
&& let Some(hotkeys) = &patch.hotkeys
{
hotkey::Hotkey::global().update(hotkeys.to_owned()).await?;
}
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
tray::Tray::global().update_menu().await?;
}
if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 {
tray::Tray::global().update_icon().await?;
}
if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 {
tray::Tray::global().update_tooltip().await?;
}
if (update_flags & (UpdateFlags::SystrayClickBehavior as i32)) != 0 {
tray::Tray::global().update_click_behavior().await?;
}
if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 {
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(())
}
pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
Config::verge()
.await
.draft_mut()
.patch_config(patch.clone());
let update_flags = determine_update_flags(&patch);
let process_flag_result: std::result::Result<(), anyhow::Error> = {
process_terminated_flags(update_flags, &patch).await?;
Ok(())
};
if let Err(err) = process_flag_result {
Config::verge().await.discard();
return Err(err);
}
Config::verge().await.apply();
if !not_save_file {
// 分离数据获取和异步调用
let verge_data = Config::verge().await.data_mut().clone();
verge_data.save_file().await?;
}
Ok(())
}

View File

@@ -23,144 +23,125 @@ pub async fn toggle_proxy_profile(profile_index: String) {
}
}
/// Update a profile
/// If updating current profile, activate it
/// auto_refresh: 是否自动更新配置和刷新前端
async fn should_update_profile(uid: String) -> Result<Option<(String, Option<PrfOption>)>> {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
if !is_remote {
log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新");
Ok(None)
} else if item.url.is_none() {
log::warn!(target: "app", "[订阅更新] {uid} 缺少URL无法更新");
bail!("failed to get the profile item url");
} else if !item
.option
.as_ref()
.and_then(|o| o.allow_auto_update)
.unwrap_or(true)
{
log::info!(target: "app", "[订阅更新] {} 禁止自动更新,跳过更新", uid);
Ok(None)
} else {
log::info!(target: "app",
"[订阅更新] {} 是远程订阅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> {
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
Ok(item) => {
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
let profiles = Config::profiles().await;
profiles_draft_update_item_safe(uid.clone(), item).await?;
let is_current = Some(uid.clone()) == profiles.latest_ref().get_current();
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
Ok(is_current)
}
Err(err) => {
log::warn!(target: "app", "[订阅更新] 正常更新失败: {err}尝试使用Clash代理更新");
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
let original_with_proxy = merged_opt.as_ref().and_then(|o| o.with_proxy);
let original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy);
let mut fallback_opt = merged_opt.unwrap_or_default();
fallback_opt.with_proxy = Some(false);
fallback_opt.self_proxy = Some(true);
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
Ok(mut item) => {
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
if let Some(option) = item.option.as_mut() {
option.with_proxy = original_with_proxy;
option.self_proxy = original_self_proxy;
}
let profiles = Config::profiles().await;
profiles_draft_update_item_safe(uid.clone(), item.clone()).await?;
let profile_name = item.name.clone().unwrap_or_else(|| uid.clone());
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
let is_current = Some(uid.clone()) == profiles.data_ref().get_current();
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
Ok(is_current)
}
Err(retry_err) => {
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}");
handle::Handle::notice_message(
"update_failed_even_with_clash",
format!("{retry_err}"),
);
Err(retry_err)
}
}
}
}
}
pub async fn update_profile(
uid: String,
option: Option<PrfOption>,
auto_refresh: Option<bool>,
) -> Result<()> {
logging!(info, Type::Config, "[订阅更新] 开始更新订阅 {}", uid);
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true保持兼容性
let auto_refresh = auto_refresh.unwrap_or(true);
let url_opt = {
let profiles = Config::profiles().await;
let profiles = profiles.latest_ref();
let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
let url_opt = should_update_profile(uid.clone()).await?;
if !is_remote {
log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新");
None // 非远程订阅直接更新
} else if item.url.is_none() {
log::warn!(target: "app", "[订阅更新] {uid} 缺少URL无法更新");
bail!("failed to get the profile item url");
} else if !item
.option
.as_ref()
.and_then(|o| o.allow_auto_update)
.unwrap_or(true)
{
log::info!(target: "app", "[订阅更新] {} 禁止自动更新,跳过更新", uid);
None
} else {
log::info!(target: "app",
"[订阅更新] {} 是远程订阅URL: {}",
uid,
item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?
);
Some((
item.url
.clone()
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?,
item.option.clone(),
))
}
};
let should_update = match url_opt {
let should_refresh = match url_opt {
Some((url, opt)) => {
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
// 尝试使用正常设置更新
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
Ok(item) => {
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
let profiles = Config::profiles().await;
// 使用Send-safe helper函数
let result = profiles_draft_update_item_safe(uid.clone(), item).await;
result?;
let is_current = Some(uid.clone()) == profiles.latest_ref().get_current();
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
is_current && auto_refresh
}
Err(err) => {
// 首次更新失败尝试使用Clash代理
log::warn!(target: "app", "[订阅更新] 正常更新失败: {err}尝试使用Clash代理更新");
// 发送通知
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
// 保存原始代理设置
let original_with_proxy = merged_opt.as_ref().and_then(|o| o.with_proxy);
let original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy);
// 创建使用Clash代理的选项
let mut fallback_opt = merged_opt.unwrap_or_default();
fallback_opt.with_proxy = Some(false);
fallback_opt.self_proxy = Some(true);
// 使用Clash代理重试
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
Ok(mut item) => {
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
// 恢复原始代理设置到item
if let Some(option) = item.option.as_mut() {
option.with_proxy = original_with_proxy;
option.self_proxy = original_self_proxy;
}
// 更新到配置
let profiles = Config::profiles().await;
// 使用 Send-safe 方法进行数据操作
profiles_draft_update_item_safe(uid.clone(), item.clone()).await?;
// 获取配置名称用于通知
let profile_name = item.name.clone().unwrap_or_else(|| uid.clone());
// 发送通知告知用户自动更新使用了回退机制
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
let is_current = Some(uid.clone()) == profiles.data_ref().get_current();
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
is_current && auto_refresh
}
Err(retry_err) => {
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}");
handle::Handle::notice_message(
"update_failed_even_with_clash",
format!("{retry_err}"),
);
return Err(retry_err);
}
}
}
}
perform_profile_update(uid.clone(), url, opt, option).await? && auto_refresh
}
None => auto_refresh,
};
if should_update {
if should_refresh {
logging!(info, Type::Config, "[订阅更新] 更新内核配置");
match CoreManager::global().update_config().await {
Ok(_) => {
logging!(info, Type::Config, "[订阅更新] 更新成功");
handle::Handle::refresh_clash();
// if let Err(err) = cmd::proxy::force_refresh_proxies().await {
// logging!(
// error,
// Type::Config,
// true,
// "[订阅更新] 代理组刷新失败: {}",
// err
// );
// }
}
Err(err) => {
logging!(error, Type::Config, "[订阅更新] 更新失败: {}", err);

View File

@@ -7,18 +7,9 @@ use tauri_plugin_clipboard_manager::ClipboardExt;
/// Toggle system proxy on/off
pub async fn toggle_system_proxy() {
// 获取当前系统代理状态
let enable = {
let verge = Config::verge().await;
verge.latest_ref().enable_system_proxy.unwrap_or(false)
};
// 获取自动关闭连接设置
let auto_close_connection = {
let verge = Config::verge().await;
verge.latest_ref().auto_close_connection.unwrap_or(false)
};
let verge = Config::verge().await;
let enable = verge.latest_ref().enable_system_proxy.unwrap_or(false);
let auto_close_connection = verge.latest_ref().auto_close_connection.unwrap_or(false);
// 如果当前系统代理即将关闭且自动关闭连接设置为true则关闭所有连接
if enable

View File

@@ -1,6 +1,7 @@
use crate::{config::Config, utils::dirs};
use once_cell::sync::Lazy;
use serde_json::Value;
use smartstring::alias::String;
use std::{fs, path::PathBuf, sync::RwLock};
use sys_locale;