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:
@@ -1 +1,2 @@
|
|||||||
avoid-breaking-exported-api = true
|
avoid-breaking-exported-api = true
|
||||||
|
cognitive-complexity-threshold = 25
|
||||||
@@ -232,3 +232,4 @@ needless_raw_string_hashes = "deny" # Too many in existing code
|
|||||||
#restriction = { level = "allow", priority = -1 }
|
#restriction = { level = "allow", priority = -1 }
|
||||||
|
|
||||||
or_fun_call = "deny"
|
or_fun_call = "deny"
|
||||||
|
cognitive_complexity = "deny"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::{logging, utils::logging::Type};
|
|||||||
use super::UnlockItem;
|
use super::UnlockItem;
|
||||||
use super::utils::{country_code_to_emoji, get_local_date_string};
|
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 {
|
pub(super) async fn check_disney_plus(client: &Client) -> UnlockItem {
|
||||||
let device_api_url = "https://disney.api.edge.bamgrid.com/devices";
|
let device_api_url = "https://disney.api.edge.bamgrid.com/devices";
|
||||||
let auth_header =
|
let auth_header =
|
||||||
|
|||||||
@@ -216,6 +216,257 @@ pub async fn delete_profile(index: String) -> CmdResult {
|
|||||||
Ok(())
|
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) = ¤t_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的配置
|
/// 修改profiles的配置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
||||||
@@ -256,110 +507,12 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
|||||||
// 如果要切换配置,先检查目标配置文件是否有语法错误
|
// 如果要切换配置,先检查目标配置文件是否有语法错误
|
||||||
if let Some(new_profile) = profiles.current.as_ref()
|
if let Some(new_profile) = profiles.current.as_ref()
|
||||||
&& current_profile.as_ref() != Some(new_profile)
|
&& current_profile.as_ref() != Some(new_profile)
|
||||||
|
&& validate_new_profile(new_profile).await.is_err()
|
||||||
{
|
{
|
||||||
logging!(info, Type::Cmd, "正在切换到新配置: {}", new_profile);
|
CURRENT_SWITCHING_PROFILE.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
// 获取目标配置文件路径
|
|
||||||
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);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查请求有效性
|
// 检查请求有效性
|
||||||
let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst);
|
let latest_sequence = CURRENT_REQUEST_SEQUENCE.load(Ordering::SeqCst);
|
||||||
if current_sequence < latest_sequence {
|
if current_sequence < latest_sequence {
|
||||||
@@ -399,163 +552,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为配置更新添加超时保护
|
perform_config_update(current_sequence, current_value, current_profile).await
|
||||||
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) = ¤t_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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 根据profile name修改profiles
|
/// 根据profile name修改profiles
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) ->
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let emsg = format!("{} Service failed: {}", op_type, e);
|
let emsg = format!("{} Service failed: {}", op_type, e);
|
||||||
return Err(t(emsg.as_str()).await.into());
|
return Err(t(emsg.as_str()).await);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,24 +53,33 @@ impl Config {
|
|||||||
|
|
||||||
/// 初始化订阅
|
/// 初始化订阅
|
||||||
pub async fn init_config() -> Result<()> {
|
pub async fn init_config() -> Result<()> {
|
||||||
if Self::profiles()
|
Self::ensure_default_profile_items().await?;
|
||||||
.await
|
|
||||||
.latest_ref()
|
let validation_result = Self::generate_and_validate().await?;
|
||||||
.get_item(&"Merge".into())
|
|
||||||
.is_err()
|
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()))?;
|
let merge_item = PrfItem::from_merge(Some("Merge".into()))?;
|
||||||
profiles_append_item_safe(merge_item.clone()).await?;
|
profiles_append_item_safe(merge_item.clone()).await?;
|
||||||
}
|
}
|
||||||
if Self::profiles()
|
if profiles.latest_ref().get_item(&"Script".into()).is_err() {
|
||||||
.await
|
|
||||||
.latest_ref()
|
|
||||||
.get_item(&"Script".into())
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
let script_item = PrfItem::from_script(Some("Script".into()))?;
|
let script_item = PrfItem::from_script(Some("Script".into()))?;
|
||||||
profiles_append_item_safe(script_item.clone()).await?;
|
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 {
|
if let Err(err) = Self::generate().await {
|
||||||
logging!(error, Type::Config, "生成运行时配置失败: {}", err);
|
logging!(error, Type::Config, "生成运行时配置失败: {}", err);
|
||||||
@@ -81,7 +90,7 @@ impl Config {
|
|||||||
// 生成运行时配置文件并验证
|
// 生成运行时配置文件并验证
|
||||||
let config_result = Self::generate_file(ConfigType::Run).await;
|
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, "开始验证配置");
|
logging!(info, Type::Config, "开始验证配置");
|
||||||
|
|
||||||
@@ -97,12 +106,12 @@ impl Config {
|
|||||||
CoreManager::global()
|
CoreManager::global()
|
||||||
.use_default_config("config_validate::boot_error", &error_msg)
|
.use_default_config("config_validate::boot_error", &error_msg)
|
||||||
.await?;
|
.await?;
|
||||||
Some(("config_validate::boot_error", error_msg))
|
Ok(Some(("config_validate::boot_error", error_msg)))
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Config, "配置验证成功");
|
logging!(info, Type::Config, "配置验证成功");
|
||||||
// 前端没有必要知道验证成功的消息,也没有事件驱动
|
// 前端没有必要知道验证成功的消息,也没有事件驱动
|
||||||
// Some(("config_validate::success", String::new()))
|
// Some(("config_validate::success", String::new()))
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -110,7 +119,7 @@ impl Config {
|
|||||||
CoreManager::global()
|
CoreManager::global()
|
||||||
.use_default_config("config_validate::process_terminated", "")
|
.use_default_config("config_validate::process_terminated", "")
|
||||||
.await?;
|
.await?;
|
||||||
Some(("config_validate::process_terminated", String::new()))
|
Ok(Some(("config_validate::process_terminated", String::new())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -118,15 +127,8 @@ impl Config {
|
|||||||
CoreManager::global()
|
CoreManager::global()
|
||||||
.use_default_config("config_validate::error", "")
|
.use_default_config("config_validate::error", "")
|
||||||
.await?;
|
.await?;
|
||||||
Some(("config_validate::error", String::new()))
|
Ok(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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_file(typ: ConfigType) -> Result<PathBuf> {
|
pub async fn generate_file(typ: ConfigType) -> Result<PathBuf> {
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ macro_rules! patch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IProfiles {
|
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 {
|
pub async fn new() -> Self {
|
||||||
match dirs::profiles_path() {
|
match dirs::profiles_path() {
|
||||||
Ok(path) => match help::read_yaml::<Self>(&path).await {
|
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 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 groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
|
||||||
let mut items = self.items.take().unwrap_or_default();
|
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
|
// remove the main item (if exists) and delete its file
|
||||||
for (i, _) in items.iter().enumerate() {
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.clone())) {
|
||||||
if items[i].uid == Some(uid.clone()) {
|
|
||||||
index = Some(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(index) = index
|
|
||||||
&& let Some(file) = items.remove(index).file
|
|
||||||
{
|
|
||||||
let _ = dirs::app_profiles_dir()?
|
let _ = dirs::app_profiles_dir()?
|
||||||
.join(file.as_str())
|
.join(file.as_str())
|
||||||
.remove_if_exists()
|
.remove_if_exists()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
// get the merge index
|
|
||||||
for (i, _) in items.iter().enumerate() {
|
// remove related extension items (merge, script, rules, proxies, groups)
|
||||||
if items[i].uid == merge_uid {
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, merge_uid.clone()) {
|
||||||
merge_index = Some(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(index) = merge_index
|
|
||||||
&& let Some(file) = items.remove(index).file
|
|
||||||
{
|
|
||||||
let _ = dirs::app_profiles_dir()?
|
let _ = dirs::app_profiles_dir()?
|
||||||
.join(file.as_str())
|
.join(file.as_str())
|
||||||
.remove_if_exists()
|
.remove_if_exists()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
// get the script index
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, script_uid.clone()) {
|
||||||
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
|
|
||||||
{
|
|
||||||
let _ = dirs::app_profiles_dir()?
|
let _ = dirs::app_profiles_dir()?
|
||||||
.join(file.as_str())
|
.join(file.as_str())
|
||||||
.remove_if_exists()
|
.remove_if_exists()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
// get the rules index
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, rules_uid.clone()) {
|
||||||
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
|
|
||||||
{
|
|
||||||
let _ = dirs::app_profiles_dir()?
|
let _ = dirs::app_profiles_dir()?
|
||||||
.join(file.as_str())
|
.join(file.as_str())
|
||||||
.remove_if_exists()
|
.remove_if_exists()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
// get the proxies index
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, proxies_uid.clone()) {
|
||||||
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
|
|
||||||
{
|
|
||||||
let _ = dirs::app_profiles_dir()?
|
let _ = dirs::app_profiles_dir()?
|
||||||
.join(file.as_str())
|
.join(file.as_str())
|
||||||
.remove_if_exists()
|
.remove_if_exists()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
// get the groups index
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, groups_uid.clone()) {
|
||||||
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
|
|
||||||
{
|
|
||||||
let _ = dirs::app_profiles_dir()?
|
let _ = dirs::app_profiles_dir()?
|
||||||
.join(file.as_str())
|
.join(file.as_str())
|
||||||
.remove_if_exists()
|
.remove_if_exists()
|
||||||
|
|||||||
@@ -438,6 +438,7 @@ impl IVerge {
|
|||||||
|
|
||||||
/// patch verge config
|
/// patch verge config
|
||||||
/// only save to file
|
/// only save to file
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
pub fn patch_config(&mut self, patch: IVerge) {
|
pub fn patch_config(&mut self, patch: IVerge) {
|
||||||
macro_rules! patch {
|
macro_rules! patch {
|
||||||
($key: tt) => {
|
($key: tt) => {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
use tauri::tray::TrayIconBuilder;
|
use tauri::tray::TrayIconBuilder;
|
||||||
|
use tauri_plugin_mihomo::models::Proxies;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod speed_rate;
|
pub mod speed_rate;
|
||||||
|
use crate::config::PrfSelected;
|
||||||
use crate::module::lightweight;
|
use crate::module::lightweight;
|
||||||
use crate::process::AsyncHandler;
|
use crate::process::AsyncHandler;
|
||||||
use crate::utils::window_manager::WindowManager;
|
use crate::utils::window_manager::WindowManager;
|
||||||
@@ -34,6 +36,56 @@ use tauri::{
|
|||||||
|
|
||||||
// TODO: 是否需要将可变菜单抽离存储起来,后续直接更新对应菜单实例,无需重新创建菜单(待考虑)
|
// 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)]
|
#[derive(Clone)]
|
||||||
struct TrayState {}
|
struct TrayState {}
|
||||||
|
|
||||||
@@ -606,70 +658,8 @@ impl Tray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_tray_menu(
|
fn create_hotkeys(hotkeys: &Option<Vec<String>>) -> HashMap<String, String> {
|
||||||
app_handle: &AppHandle,
|
hotkeys
|
||||||
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
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|h| {
|
.map(|h| {
|
||||||
h.iter()
|
h.iter()
|
||||||
@@ -689,9 +679,13 @@ async fn create_tray_menu(
|
|||||||
})
|
})
|
||||||
.collect::<std::collections::HashMap<String, String>>()
|
.collect::<std::collections::HashMap<String, String>>()
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
let profile_menu_items: Vec<CheckMenuItem<Wry>> = {
|
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
|
let futures = profile_uid_and_name
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(profile_uid, profile_name)| {
|
.map(|(profile_uid, profile_name)| {
|
||||||
@@ -701,7 +695,7 @@ async fn create_tray_menu(
|
|||||||
async move {
|
async move {
|
||||||
let is_current_profile = Config::profiles()
|
let is_current_profile = Config::profiles()
|
||||||
.await
|
.await
|
||||||
.data_mut()
|
.latest_ref()
|
||||||
.is_current_profile_index(profile_uid.clone());
|
.is_current_profile_index(profile_uid.clone());
|
||||||
CheckMenuItem::with_id(
|
CheckMenuItem::with_id(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
@@ -714,10 +708,16 @@ async fn create_tray_menu(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let results = join_all(futures).await;
|
let results = join_all(futures).await;
|
||||||
results.into_iter().collect::<Result<Vec<_>, _>>()?
|
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 proxy_submenus: Vec<Submenu<Wry>> = {
|
||||||
let mut submenus: Vec<(String, usize, Submenu<Wry>)> = Vec::new();
|
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 {
|
if let Ok(proxy_nodes_data) = proxy_nodes_data {
|
||||||
for (group_name, group_data) in proxy_nodes_data.proxies.iter() {
|
for (group_name, group_data) in proxy_nodes_data.proxies.iter() {
|
||||||
// Filter groups based on mode
|
// Filter groups based on mode
|
||||||
let should_show = match mode {
|
let should_show = match proxy_mode {
|
||||||
"global" => group_name == "GLOBAL",
|
"global" => group_name == "GLOBAL",
|
||||||
_ => group_name != "GLOBAL",
|
_ => group_name != "GLOBAL",
|
||||||
} &&
|
} &&
|
||||||
@@ -781,7 +781,7 @@ async fn create_tray_menu(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine if group is active
|
// 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(),
|
"global" => group_name == "GLOBAL" && !now_proxy.is_empty(),
|
||||||
"direct" => false,
|
"direct" => false,
|
||||||
_ => {
|
_ => {
|
||||||
@@ -837,86 +837,22 @@ async fn create_tray_menu(
|
|||||||
.map(|(_, _, submenu)| submenu)
|
.map(|(_, _, submenu)| submenu)
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
Ok(proxy_submenus)
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-fetch all localized strings
|
fn create_proxy_menu_item(
|
||||||
let dashboard_text = t("Dashboard").await;
|
app_handle: &AppHandle,
|
||||||
let rule_mode_text = t("Rule Mode").await;
|
show_proxy_groups_inline: bool,
|
||||||
let global_mode_text = t("Global Mode").await;
|
proxy_submenus: Vec<Submenu<Wry>>,
|
||||||
let direct_mode_text = t("Direct Mode").await;
|
proxies_text: &String,
|
||||||
let profiles_text = t("Profiles").await;
|
) -> Result<ProxyMenuItem> {
|
||||||
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;
|
|
||||||
|
|
||||||
// Convert to references only when needed
|
|
||||||
let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
|
|
||||||
.iter()
|
|
||||||
.map(|item| item as &dyn IsMenuItem<Wry>)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let open_window = &MenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"open_window",
|
|
||||||
dashboard_text,
|
|
||||||
true,
|
|
||||||
hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let rule_mode = &CheckMenuItem::with_id(
|
|
||||||
app_handle,
|
|
||||||
"rule_mode",
|
|
||||||
rule_mode_text,
|
|
||||||
true,
|
|
||||||
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,
|
|
||||||
true,
|
|
||||||
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,
|
|
||||||
true,
|
|
||||||
mode == "direct",
|
|
||||||
hotkeys.get("clash_mode_direct").map(|s| s.as_str()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let profiles = &Submenu::with_id_and_items(
|
|
||||||
app_handle,
|
|
||||||
"profiles",
|
|
||||||
profiles_text,
|
|
||||||
true,
|
|
||||||
&profile_menu_items_refs,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 创建代理主菜单
|
// 创建代理主菜单
|
||||||
let (proxies_submenu, inline_proxy_items): (Option<Submenu<Wry>>, Vec<&dyn IsMenuItem<Wry>>) =
|
let (proxies_submenu, inline_proxy_items) = if show_proxy_groups_inline {
|
||||||
if show_proxy_groups_inline {
|
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
proxy_submenus
|
proxy_submenus
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|submenu| submenu as &dyn IsMenuItem<Wry>)
|
.map(|submenu| Box::new(submenu) as Box<dyn IsMenuItem<Wry>>)
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
} else if !proxy_submenus.is_empty() {
|
} else if !proxy_submenus.is_empty() {
|
||||||
@@ -938,11 +874,149 @@ async fn create_tray_menu(
|
|||||||
} else {
|
} else {
|
||||||
(None, Vec::new())
|
(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 texts = &fetch_menu_texts().await;
|
||||||
|
|
||||||
|
// Convert to references only when needed
|
||||||
|
let profile_menu_items_refs: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
|
||||||
|
.iter()
|
||||||
|
.map(|item| item as &dyn IsMenuItem<Wry>)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let open_window = &MenuItem::with_id(
|
||||||
|
app_handle,
|
||||||
|
"open_window",
|
||||||
|
&texts.dashboard,
|
||||||
|
true,
|
||||||
|
hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let rule_mode = &CheckMenuItem::with_id(
|
||||||
|
app_handle,
|
||||||
|
"rule_mode",
|
||||||
|
&texts.rule_mode,
|
||||||
|
true,
|
||||||
|
current_proxy_mode == "rule",
|
||||||
|
hotkeys.get("clash_mode_rule").map(|s| s.as_str()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let global_mode = &CheckMenuItem::with_id(
|
||||||
|
app_handle,
|
||||||
|
"global_mode",
|
||||||
|
&texts.global_mode,
|
||||||
|
true,
|
||||||
|
current_proxy_mode == "global",
|
||||||
|
hotkeys.get("clash_mode_global").map(|s| s.as_str()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let direct_mode = &CheckMenuItem::with_id(
|
||||||
|
app_handle,
|
||||||
|
"direct_mode",
|
||||||
|
&texts.direct_mode,
|
||||||
|
true,
|
||||||
|
current_proxy_mode == "direct",
|
||||||
|
hotkeys.get("clash_mode_direct").map(|s| s.as_str()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let profiles = &Submenu::with_id_and_items(
|
||||||
|
app_handle,
|
||||||
|
"profiles",
|
||||||
|
&texts.profiles,
|
||||||
|
true,
|
||||||
|
&profile_menu_items_refs,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let proxy_sub_menus = create_subcreate_proxy_menu_item(
|
||||||
|
app_handle,
|
||||||
|
current_proxy_mode,
|
||||||
|
¤t_profile_selected,
|
||||||
|
proxy_group_order_map,
|
||||||
|
proxy_nodes_data.map_err(anyhow::Error::from),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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(
|
let system_proxy = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"system_proxy",
|
"system_proxy",
|
||||||
system_proxy_text,
|
&texts.system_proxy,
|
||||||
true,
|
true,
|
||||||
system_proxy_enabled,
|
system_proxy_enabled,
|
||||||
hotkeys.get("toggle_system_proxy").map(|s| s.as_str()),
|
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(
|
let tun_mode = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"tun_mode",
|
"tun_mode",
|
||||||
tun_mode_text,
|
&texts.tun_mode,
|
||||||
true,
|
true,
|
||||||
tun_mode_enabled,
|
tun_mode_enabled,
|
||||||
hotkeys.get("toggle_tun_mode").map(|s| s.as_str()),
|
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(
|
let close_all_connections = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"close_all_connections",
|
"close_all_connections",
|
||||||
close_all_connections_text,
|
&texts.close_all_connections,
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -968,18 +1042,18 @@ async fn create_tray_menu(
|
|||||||
let lighteweight_mode = &CheckMenuItem::with_id(
|
let lighteweight_mode = &CheckMenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"entry_lightweight_mode",
|
"entry_lightweight_mode",
|
||||||
lightweight_mode_text,
|
&texts.lightweight_mode,
|
||||||
true,
|
true,
|
||||||
is_lightweight_mode,
|
is_lightweight_mode,
|
||||||
hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()),
|
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(
|
let open_app_dir = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_app_dir",
|
"open_app_dir",
|
||||||
conf_dir_text,
|
&texts.conf_dir,
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -987,7 +1061,7 @@ async fn create_tray_menu(
|
|||||||
let open_core_dir = &MenuItem::with_id(
|
let open_core_dir = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_core_dir",
|
"open_core_dir",
|
||||||
core_dir_text,
|
&texts.core_dir,
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -995,7 +1069,7 @@ async fn create_tray_menu(
|
|||||||
let open_logs_dir = &MenuItem::with_id(
|
let open_logs_dir = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_logs_dir",
|
"open_logs_dir",
|
||||||
logs_dir_text,
|
&texts.logs_dir,
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -1003,7 +1077,7 @@ async fn create_tray_menu(
|
|||||||
let open_dir = &Submenu::with_id_and_items(
|
let open_dir = &Submenu::with_id_and_items(
|
||||||
app_handle,
|
app_handle,
|
||||||
"open_dir",
|
"open_dir",
|
||||||
open_dir_text,
|
&texts.open_dir,
|
||||||
true,
|
true,
|
||||||
&[open_app_dir, open_core_dir, open_logs_dir],
|
&[open_app_dir, open_core_dir, open_logs_dir],
|
||||||
)?;
|
)?;
|
||||||
@@ -1011,7 +1085,7 @@ async fn create_tray_menu(
|
|||||||
let restart_clash = &MenuItem::with_id(
|
let restart_clash = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"restart_clash",
|
"restart_clash",
|
||||||
restart_clash_text,
|
&texts.restart_clash,
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -1019,7 +1093,7 @@ async fn create_tray_menu(
|
|||||||
let restart_app = &MenuItem::with_id(
|
let restart_app = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"restart_app",
|
"restart_app",
|
||||||
restart_app_text,
|
&texts.restart_app,
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -1027,7 +1101,7 @@ async fn create_tray_menu(
|
|||||||
let app_version = &MenuItem::with_id(
|
let app_version = &MenuItem::with_id(
|
||||||
app_handle,
|
app_handle,
|
||||||
"app_version",
|
"app_version",
|
||||||
format!("{} {version}", verge_version_text),
|
format!("{} {version}", &texts.verge_version),
|
||||||
true,
|
true,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)?;
|
)?;
|
||||||
@@ -1035,7 +1109,7 @@ async fn create_tray_menu(
|
|||||||
let more = &Submenu::with_id_and_items(
|
let more = &Submenu::with_id_and_items(
|
||||||
app_handle,
|
app_handle,
|
||||||
"more",
|
"more",
|
||||||
more_text,
|
&texts.more,
|
||||||
true,
|
true,
|
||||||
&[
|
&[
|
||||||
close_all_connections,
|
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)?;
|
let separator = &PredefinedMenuItem::separator(app_handle)?;
|
||||||
|
|
||||||
@@ -1063,9 +1143,9 @@ async fn create_tray_menu(
|
|||||||
// 如果有代理节点,添加代理节点菜单
|
// 如果有代理节点,添加代理节点菜单
|
||||||
if show_proxy_groups_inline {
|
if show_proxy_groups_inline {
|
||||||
if !inline_proxy_items.is_empty() {
|
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);
|
menu_items.push(proxies_menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,35 @@ use smartstring::alias::String;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
type ResultLog = Vec<(String, String)>;
|
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
|
#[derive(Debug)]
|
||||||
/// 返回最终订阅、该订阅包含的键、和script执行的结果
|
struct ProfileItems {
|
||||||
pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
config: Mapping,
|
||||||
// config.yaml 的订阅
|
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_config = { Config::clash().await.latest_ref().0.clone() };
|
||||||
|
|
||||||
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = {
|
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),
|
verge.enable_dns_settings.unwrap_or(false),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let redir_enabled = {
|
let redir_enabled = {
|
||||||
let verge = Config::verge().await;
|
let verge = Config::verge().await;
|
||||||
let verge = verge.latest_ref();
|
let verge = verge.latest_ref();
|
||||||
verge.verge_redir_enabled.unwrap_or(false)
|
verge.verge_redir_enabled.unwrap_or(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let tproxy_enabled = {
|
let tproxy_enabled = {
|
||||||
let verge = Config::verge().await;
|
let verge = Config::verge().await;
|
||||||
@@ -44,19 +70,23 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
verge.verge_tproxy_enabled.unwrap_or(false)
|
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里拿东西 - 先收集需要的数据,然后释放锁
|
// 从profiles里拿东西 - 先收集需要的数据,然后释放锁
|
||||||
let (
|
|
||||||
mut config,
|
|
||||||
merge_item,
|
|
||||||
script_item,
|
|
||||||
rules_item,
|
|
||||||
proxies_item,
|
|
||||||
groups_item,
|
|
||||||
global_merge,
|
|
||||||
global_script,
|
|
||||||
profile_name,
|
|
||||||
) = {
|
|
||||||
// 收集所有需要的数据,然后释放profiles锁
|
|
||||||
let (
|
let (
|
||||||
current,
|
current,
|
||||||
merge_uid,
|
merge_uid,
|
||||||
@@ -67,14 +97,12 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
_current_profile_uid,
|
_current_profile_uid,
|
||||||
name,
|
name,
|
||||||
) = {
|
) = {
|
||||||
// 分离async调用和数据获取,避免借用检查问题
|
|
||||||
let current = {
|
let current = {
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles_clone = profiles.latest_ref().clone();
|
let profiles_clone = profiles.latest_ref().clone();
|
||||||
profiles_clone.current_mapping().await.unwrap_or_default()
|
profiles_clone.current_mapping().await.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重新获取锁进行其他操作
|
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles_ref = profiles.latest_ref();
|
let profiles_ref = profiles.latest_ref();
|
||||||
|
|
||||||
@@ -104,7 +132,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 现在获取具体的items,此时profiles锁已经释放
|
// 现在获取具体的items,此时profiles锁已经释放
|
||||||
let merge = {
|
let merge_item = {
|
||||||
let item = {
|
let item = {
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles = profiles.latest_ref();
|
let profiles = profiles.latest_ref();
|
||||||
@@ -121,7 +149,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
data: ChainType::Merge(Mapping::new()),
|
data: ChainType::Merge(Mapping::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let script = {
|
let script_item = {
|
||||||
let item = {
|
let item = {
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles = profiles.latest_ref();
|
let profiles = profiles.latest_ref();
|
||||||
@@ -138,7 +166,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let rules = {
|
let rules_item = {
|
||||||
let item = {
|
let item = {
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles = profiles.latest_ref();
|
let profiles = profiles.latest_ref();
|
||||||
@@ -155,7 +183,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
data: ChainType::Rules(SeqMap::default()),
|
data: ChainType::Rules(SeqMap::default()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let proxies = {
|
let proxies_item = {
|
||||||
let item = {
|
let item = {
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles = profiles.latest_ref();
|
let profiles = profiles.latest_ref();
|
||||||
@@ -172,7 +200,7 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
data: ChainType::Proxies(SeqMap::default()),
|
data: ChainType::Proxies(SeqMap::default()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let groups = {
|
let groups_item = {
|
||||||
let item = {
|
let item = {
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles = profiles.latest_ref();
|
let profiles = profiles.latest_ref();
|
||||||
@@ -223,23 +251,28 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
(
|
ProfileItems {
|
||||||
current,
|
config: current,
|
||||||
merge,
|
merge_item,
|
||||||
script,
|
script_item,
|
||||||
rules,
|
rules_item,
|
||||||
proxies,
|
proxies_item,
|
||||||
groups,
|
groups_item,
|
||||||
global_merge,
|
global_merge,
|
||||||
global_script,
|
global_script,
|
||||||
name,
|
profile_name: name,
|
||||||
)
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut result_map = HashMap::new(); // 保存脚本日志
|
fn process_global_items(
|
||||||
let mut exists_keys = use_keys(&config); // 保存出现过的keys
|
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);
|
||||||
|
|
||||||
// 全局Merge和Script
|
|
||||||
if let ChainType::Merge(merge) = global_merge.data {
|
if let ChainType::Merge(merge) = global_merge.data {
|
||||||
exists_keys.extend(use_keys(&merge));
|
exists_keys.extend(use_keys(&merge));
|
||||||
config = use_merge(merge, config.to_owned());
|
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 {
|
if let ChainType::Script(script) = global_script.data {
|
||||||
let mut logs = vec![];
|
let mut logs = vec![];
|
||||||
|
|
||||||
match use_script(script, config.to_owned(), profile_name.to_owned()) {
|
match use_script(script, config.to_owned(), profile_name.to_owned()) {
|
||||||
Ok((res_config, res_logs)) => {
|
Ok((res_config, res_logs)) => {
|
||||||
exists_keys.extend(use_keys(&res_config));
|
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())),
|
Err(err) => logs.push(("exception".into(), err.to_string().into())),
|
||||||
}
|
}
|
||||||
|
|
||||||
result_map.insert(global_script.uid, logs);
|
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 {
|
if let ChainType::Rules(rules) = rules_item.data {
|
||||||
config = use_seq(rules, config.to_owned(), "rules");
|
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 {
|
if let ChainType::Script(script) = script_item.data {
|
||||||
let mut logs = vec![];
|
let mut logs = vec![];
|
||||||
|
|
||||||
match use_script(script, config.to_owned(), profile_name) {
|
match use_script(script, config.to_owned(), profile_name) {
|
||||||
Ok((res_config, res_logs)) => {
|
Ok((res_config, res_logs)) => {
|
||||||
exists_keys.extend(use_keys(&res_config));
|
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())),
|
Err(err) => logs.push(("exception".into(), err.to_string().into())),
|
||||||
}
|
}
|
||||||
|
|
||||||
result_map.insert(script_item.uid, logs);
|
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() {
|
for (key, value) in clash_config.into_iter() {
|
||||||
if key.as_str() == Some("tun") {
|
if key.as_str() == Some("tun") {
|
||||||
let mut tun = config.get_mut("tun").map_or_else(Mapping::new, |val| {
|
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 {
|
if enable_builtin {
|
||||||
ChainItem::builtin()
|
ChainItem::builtin()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -374,10 +434,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
config = use_tun(config, enable_tun);
|
config
|
||||||
config = use_sort(config);
|
}
|
||||||
|
|
||||||
// 应用独立的DNS配置(如果启用)
|
fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping {
|
||||||
if enable_dns_settings {
|
if enable_dns_settings {
|
||||||
use crate::utils::dirs;
|
use crate::utils::dirs;
|
||||||
use std::fs;
|
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_yaml) = fs::read_to_string(&dns_path)
|
||||||
&& let Ok(dns_config) = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml)
|
&& 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")
|
if let Some(hosts_value) = dns_config.get("hosts")
|
||||||
&& hosts_value.is_mapping()
|
&& 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();
|
let mut exists_set = HashSet::new();
|
||||||
exists_set.extend(exists_keys);
|
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)
|
(config, exists_keys, result_map)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,23 +63,19 @@ enum UpdateFlags {
|
|||||||
LighteWeight = 1 << 10,
|
LighteWeight = 1 << 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Patch Verge configuration
|
fn determine_update_flags(patch: &IVerge) -> i32 {
|
||||||
pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
let mut update_flags: i32 = UpdateFlags::None as i32;
|
||||||
Config::verge()
|
|
||||||
.await
|
|
||||||
.draft_mut()
|
|
||||||
.patch_config(patch.clone());
|
|
||||||
|
|
||||||
let tun_mode = patch.enable_tun_mode;
|
let tun_mode = patch.enable_tun_mode;
|
||||||
let auto_launch = patch.enable_auto_launch;
|
let auto_launch = patch.enable_auto_launch;
|
||||||
let system_proxy = patch.enable_system_proxy;
|
let system_proxy = patch.enable_system_proxy;
|
||||||
let pac = patch.proxy_auto_config;
|
let pac = patch.proxy_auto_config;
|
||||||
let pac_content = patch.pac_file_content;
|
let pac_content = &patch.pac_file_content;
|
||||||
let proxy_bypass = patch.system_proxy_bypass;
|
let proxy_bypass = &patch.system_proxy_bypass;
|
||||||
let language = patch.language;
|
let language = &patch.language;
|
||||||
let mixed_port = patch.verge_mixed_port;
|
let mixed_port = patch.verge_mixed_port;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let tray_icon = patch.tray_icon;
|
let tray_icon = &patch.tray_icon;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let tray_icon: Option<String> = None;
|
let tray_icon: Option<String> = None;
|
||||||
let common_tray_icon = patch.common_tray_icon;
|
let common_tray_icon = patch.common_tray_icon;
|
||||||
@@ -100,13 +96,10 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
let enable_tray_speed = patch.enable_tray_speed;
|
let enable_tray_speed = patch.enable_tray_speed;
|
||||||
let enable_tray_icon = patch.enable_tray_icon;
|
let enable_tray_icon = patch.enable_tray_icon;
|
||||||
let enable_global_hotkey = patch.enable_global_hotkey;
|
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 home_cards = patch.home_cards.clone();
|
||||||
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
|
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
|
||||||
let enable_external_controller = patch.enable_external_controller;
|
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() {
|
if tun_mode.is_some() {
|
||||||
update_flags |= UpdateFlags::ClashConfig as i32;
|
update_flags |= UpdateFlags::ClashConfig as i32;
|
||||||
@@ -174,11 +167,14 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
update_flags |= UpdateFlags::LighteWeight as i32;
|
update_flags |= UpdateFlags::LighteWeight as i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理 external-controller 的开关
|
|
||||||
if enable_external_controller.is_some() {
|
if enable_external_controller.is_some() {
|
||||||
update_flags |= UpdateFlags::RestartCore as i32;
|
update_flags |= UpdateFlags::RestartCore as i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_flags
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_terminated_flags(update_flags: i32, patch: &IVerge) -> Result<()> {
|
||||||
// Process updates based on flags
|
// Process updates based on flags
|
||||||
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
|
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
|
||||||
Config::generate().await?;
|
Config::generate().await?;
|
||||||
@@ -189,7 +185,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
|
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
|
||||||
Config::verge().await.draft_mut().enable_global_hotkey = enable_global_hotkey;
|
Config::verge().await.draft_mut().enable_global_hotkey = patch.enable_global_hotkey;
|
||||||
handle::Handle::refresh_verge();
|
handle::Handle::refresh_verge();
|
||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
|
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
|
||||||
@@ -199,9 +195,9 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
sysopt::Sysopt::global().update_sysproxy().await?;
|
sysopt::Sysopt::global().update_sysproxy().await?;
|
||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0
|
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0
|
||||||
&& let Some(hotkeys) = patch.hotkeys
|
&& let Some(hotkeys) = &patch.hotkeys
|
||||||
{
|
{
|
||||||
hotkey::Hotkey::global().update(hotkeys).await?;
|
hotkey::Hotkey::global().update(hotkeys.to_owned()).await?;
|
||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
|
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
|
||||||
tray::Tray::global().update_menu().await?;
|
tray::Tray::global().update_menu().await?;
|
||||||
@@ -216,29 +212,36 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
tray::Tray::global().update_click_behavior().await?;
|
tray::Tray::global().update_click_behavior().await?;
|
||||||
}
|
}
|
||||||
if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 {
|
if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 {
|
||||||
if enable_auto_light_weight.unwrap_or(false) {
|
if patch.enable_auto_light_weight_mode.unwrap_or(false) {
|
||||||
lightweight::enable_auto_light_weight_mode().await;
|
lightweight::enable_auto_light_weight_mode().await;
|
||||||
} else {
|
} else {
|
||||||
lightweight::disable_auto_light_weight_mode();
|
lightweight::disable_auto_light_weight_mode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
<Result<()>>::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(())
|
||||||
};
|
};
|
||||||
match res {
|
|
||||||
Ok(()) => {
|
if let Err(err) = process_flag_result {
|
||||||
|
Config::verge().await.discard();
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
Config::verge().await.apply();
|
Config::verge().await.apply();
|
||||||
if !not_save_file {
|
if !not_save_file {
|
||||||
// 分离数据获取和异步调用
|
// 分离数据获取和异步调用
|
||||||
let verge_data = Config::verge().await.data_mut().clone();
|
let verge_data = Config::verge().await.data_mut().clone();
|
||||||
verge_data.save_file().await?;
|
verge_data.save_file().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
Config::verge().await.discard();
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,18 +23,7 @@ pub async fn toggle_proxy_profile(profile_index: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update a profile
|
async fn should_update_profile(uid: String) -> Result<Option<(String, Option<PrfOption>)>> {
|
||||||
/// If updating current profile, activate it
|
|
||||||
/// auto_refresh: 是否自动更新配置和刷新前端
|
|
||||||
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 url_opt = {
|
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
let profiles = profiles.latest_ref();
|
let profiles = profiles.latest_ref();
|
||||||
let item = profiles.get_item(&uid)?;
|
let item = profiles.get_item(&uid)?;
|
||||||
@@ -42,7 +31,7 @@ pub async fn update_profile(
|
|||||||
|
|
||||||
if !is_remote {
|
if !is_remote {
|
||||||
log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新");
|
log::info!(target: "app", "[订阅更新] {uid} 不是远程订阅,跳过更新");
|
||||||
None // 非远程订阅直接更新
|
Ok(None)
|
||||||
} else if item.url.is_none() {
|
} else if item.url.is_none() {
|
||||||
log::warn!(target: "app", "[订阅更新] {uid} 缺少URL,无法更新");
|
log::warn!(target: "app", "[订阅更新] {uid} 缺少URL,无法更新");
|
||||||
bail!("failed to get the profile item url");
|
bail!("failed to get the profile item url");
|
||||||
@@ -53,83 +42,69 @@ pub async fn update_profile(
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
log::info!(target: "app", "[订阅更新] {} 禁止自动更新,跳过更新", uid);
|
log::info!(target: "app", "[订阅更新] {} 禁止自动更新,跳过更新", uid);
|
||||||
None
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
log::info!(target: "app",
|
log::info!(target: "app",
|
||||||
"[订阅更新] {} 是远程订阅,URL: {}",
|
"[订阅更新] {} 是远程订阅,URL: {}",
|
||||||
uid,
|
uid,
|
||||||
item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?
|
item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?
|
||||||
);
|
);
|
||||||
Some((
|
Ok(Some((
|
||||||
item.url
|
item.url
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?,
|
.ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?,
|
||||||
item.option.clone(),
|
item.option.clone(),
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let should_update = match url_opt {
|
async fn perform_profile_update(
|
||||||
Some((url, opt)) => {
|
uid: String,
|
||||||
|
url: String,
|
||||||
|
opt: Option<PrfOption>,
|
||||||
|
option: Option<PrfOption>,
|
||||||
|
) -> Result<bool> {
|
||||||
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
|
log::info!(target: "app", "[订阅更新] 开始下载新的订阅内容");
|
||||||
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
|
let merged_opt = PrfOption::merge(opt.clone(), option.clone());
|
||||||
|
|
||||||
// 尝试使用正常设置更新
|
|
||||||
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
|
match PrfItem::from_url(&url, None, None, merged_opt.clone()).await {
|
||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
|
log::info!(target: "app", "[订阅更新] 更新订阅配置成功");
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
|
profiles_draft_update_item_safe(uid.clone(), item).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();
|
let is_current = Some(uid.clone()) == profiles.latest_ref().get_current();
|
||||||
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
|
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
|
||||||
is_current && auto_refresh
|
Ok(is_current)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 首次更新失败,尝试使用Clash代理
|
|
||||||
log::warn!(target: "app", "[订阅更新] 正常更新失败: {err},尝试使用Clash代理更新");
|
log::warn!(target: "app", "[订阅更新] 正常更新失败: {err},尝试使用Clash代理更新");
|
||||||
|
|
||||||
// 发送通知
|
|
||||||
handle::Handle::notice_message("update_retry_with_clash", uid.clone());
|
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_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 original_self_proxy = merged_opt.as_ref().and_then(|o| o.self_proxy);
|
||||||
|
|
||||||
// 创建使用Clash代理的选项
|
|
||||||
let mut fallback_opt = merged_opt.unwrap_or_default();
|
let mut fallback_opt = merged_opt.unwrap_or_default();
|
||||||
fallback_opt.with_proxy = Some(false);
|
fallback_opt.with_proxy = Some(false);
|
||||||
fallback_opt.self_proxy = Some(true);
|
fallback_opt.self_proxy = Some(true);
|
||||||
|
|
||||||
// 使用Clash代理重试
|
|
||||||
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
|
match PrfItem::from_url(&url, None, None, Some(fallback_opt)).await {
|
||||||
Ok(mut item) => {
|
Ok(mut item) => {
|
||||||
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
|
log::info!(target: "app", "[订阅更新] 使用Clash代理更新成功");
|
||||||
|
|
||||||
// 恢复原始代理设置到item
|
|
||||||
if let Some(option) = item.option.as_mut() {
|
if let Some(option) = item.option.as_mut() {
|
||||||
option.with_proxy = original_with_proxy;
|
option.with_proxy = original_with_proxy;
|
||||||
option.self_proxy = original_self_proxy;
|
option.self_proxy = original_self_proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新到配置
|
|
||||||
let profiles = Config::profiles().await;
|
let profiles = Config::profiles().await;
|
||||||
|
|
||||||
// 使用 Send-safe 方法进行数据操作
|
|
||||||
profiles_draft_update_item_safe(uid.clone(), item.clone()).await?;
|
profiles_draft_update_item_safe(uid.clone(), item.clone()).await?;
|
||||||
|
|
||||||
// 获取配置名称用于通知
|
|
||||||
let profile_name = item.name.clone().unwrap_or_else(|| uid.clone());
|
let profile_name = item.name.clone().unwrap_or_else(|| uid.clone());
|
||||||
|
|
||||||
// 发送通知告知用户自动更新使用了回退机制
|
|
||||||
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
handle::Handle::notice_message("update_with_clash_proxy", profile_name);
|
||||||
|
|
||||||
let is_current = Some(uid.clone()) == profiles.data_ref().get_current();
|
let is_current = Some(uid.clone()) == profiles.data_ref().get_current();
|
||||||
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
|
log::info!(target: "app", "[订阅更新] 是否为当前使用的订阅: {is_current}");
|
||||||
is_current && auto_refresh
|
Ok(is_current)
|
||||||
}
|
}
|
||||||
Err(retry_err) => {
|
Err(retry_err) => {
|
||||||
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}");
|
log::error!(target: "app", "[订阅更新] 使用Clash代理更新仍然失败: {retry_err}");
|
||||||
@@ -137,30 +112,36 @@ pub async fn update_profile(
|
|||||||
"update_failed_even_with_clash",
|
"update_failed_even_with_clash",
|
||||||
format!("{retry_err}"),
|
format!("{retry_err}"),
|
||||||
);
|
);
|
||||||
return Err(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);
|
||||||
|
|
||||||
|
let url_opt = should_update_profile(uid.clone()).await?;
|
||||||
|
|
||||||
|
let should_refresh = match url_opt {
|
||||||
|
Some((url, opt)) => {
|
||||||
|
perform_profile_update(uid.clone(), url, opt, option).await? && auto_refresh
|
||||||
}
|
}
|
||||||
None => auto_refresh,
|
None => auto_refresh,
|
||||||
};
|
};
|
||||||
|
|
||||||
if should_update {
|
if should_refresh {
|
||||||
logging!(info, Type::Config, "[订阅更新] 更新内核配置");
|
logging!(info, Type::Config, "[订阅更新] 更新内核配置");
|
||||||
match CoreManager::global().update_config().await {
|
match CoreManager::global().update_config().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Config, "[订阅更新] 更新成功");
|
logging!(info, Type::Config, "[订阅更新] 更新成功");
|
||||||
handle::Handle::refresh_clash();
|
handle::Handle::refresh_clash();
|
||||||
// if let Err(err) = cmd::proxy::force_refresh_proxies().await {
|
|
||||||
// logging!(
|
|
||||||
// error,
|
|
||||||
// Type::Config,
|
|
||||||
// true,
|
|
||||||
// "[订阅更新] 代理组刷新失败: {}",
|
|
||||||
// err
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(error, Type::Config, "[订阅更新] 更新失败: {}", err);
|
logging!(error, Type::Config, "[订阅更新] 更新失败: {}", err);
|
||||||
|
|||||||
@@ -7,18 +7,9 @@ use tauri_plugin_clipboard_manager::ClipboardExt;
|
|||||||
|
|
||||||
/// Toggle system proxy on/off
|
/// Toggle system proxy on/off
|
||||||
pub async fn toggle_system_proxy() {
|
pub async fn toggle_system_proxy() {
|
||||||
// 获取当前系统代理状态
|
|
||||||
let enable = {
|
|
||||||
let verge = Config::verge().await;
|
let verge = Config::verge().await;
|
||||||
|
let enable = verge.latest_ref().enable_system_proxy.unwrap_or(false);
|
||||||
verge.latest_ref().enable_system_proxy.unwrap_or(false)
|
let auto_close_connection = verge.latest_ref().auto_close_connection.unwrap_or(false);
|
||||||
};
|
|
||||||
// 获取自动关闭连接设置
|
|
||||||
let auto_close_connection = {
|
|
||||||
let verge = Config::verge().await;
|
|
||||||
|
|
||||||
verge.latest_ref().auto_close_connection.unwrap_or(false)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果当前系统代理即将关闭,且自动关闭连接设置为true,则关闭所有连接
|
// 如果当前系统代理即将关闭,且自动关闭连接设置为true,则关闭所有连接
|
||||||
if enable
|
if enable
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{config::Config, utils::dirs};
|
use crate::{config::Config, utils::dirs};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use smartstring::alias::String;
|
||||||
use std::{fs, path::PathBuf, sync::RwLock};
|
use std::{fs, path::PathBuf, sync::RwLock};
|
||||||
use sys_locale;
|
use sys_locale;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user