mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 03:03:46 +08:00
refactor: streamline clash mode handling and improve API interactions
This commit is contained in:
parent
4cb6ad7736
commit
15e54df67c
@ -1,9 +1,7 @@
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml::Mapping;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub struct Rate {
|
pub struct Rate {
|
||||||
@ -11,40 +9,6 @@ pub struct Rate {
|
|||||||
pub down: u64,
|
pub down: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PUT /configs
|
|
||||||
/// path 是绝对路径
|
|
||||||
pub async fn put_configs(path: &str) -> Result<()> {
|
|
||||||
let (url, headers) = clash_client_info()?;
|
|
||||||
let url = format!("{url}/configs?force=true");
|
|
||||||
|
|
||||||
let mut data = HashMap::new();
|
|
||||||
data.insert("path", path);
|
|
||||||
|
|
||||||
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
|
||||||
let builder = client.put(&url).headers(headers).json(&data);
|
|
||||||
let response = builder.send().await?;
|
|
||||||
|
|
||||||
match response.status().as_u16() {
|
|
||||||
204 => Ok(()),
|
|
||||||
status => {
|
|
||||||
let body = response.text().await?;
|
|
||||||
// print!("failed to put configs with status \"{}\"\n{}\n{}", status, url, body);
|
|
||||||
bail!("failed to put configs with status \"{status}\"\n{url}\n{body}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PATCH /configs
|
|
||||||
pub async fn patch_configs(config: &Mapping) -> Result<()> {
|
|
||||||
let (url, headers) = clash_client_info()?;
|
|
||||||
let url = format!("{url}/configs");
|
|
||||||
|
|
||||||
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
|
||||||
let builder = client.patch(&url).headers(headers.clone()).json(config);
|
|
||||||
builder.send().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct DelayRes {
|
pub struct DelayRes {
|
||||||
delay: u64,
|
delay: u64,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::core::{clash_api, handle, service};
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::core::tray::Tray;
|
use crate::core::tray::Tray;
|
||||||
|
use crate::core::{handle, service};
|
||||||
use crate::log_err;
|
use crate::log_err;
|
||||||
|
use crate::module::mihomo::MihomoManager;
|
||||||
use crate::utils::{dirs, help};
|
use crate::utils::{dirs, help};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use serde_yaml::Mapping;
|
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||||
use std::{sync::Arc, time::Duration, path::PathBuf};
|
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
@ -54,12 +54,14 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 关闭tun模式
|
// 关闭tun模式
|
||||||
let mut disable = Mapping::new();
|
// Create a JSON object to disable TUN mode
|
||||||
let mut tun = Mapping::new();
|
let disable = serde_json::json!({
|
||||||
tun.insert("enable".into(), false.into());
|
"tun": {
|
||||||
disable.insert("tun".into(), tun.into());
|
"enable": false
|
||||||
|
}
|
||||||
|
});
|
||||||
log::debug!(target: "app", "disable tun mode");
|
log::debug!(target: "app", "disable tun mode");
|
||||||
log_err!(clash_api::patch_configs(&disable).await);
|
log_err!(MihomoManager::global().patch_configs(disable).await);
|
||||||
|
|
||||||
// 服务模式
|
// 服务模式
|
||||||
if service::check_service().await.is_ok() {
|
if service::check_service().await.is_ok() {
|
||||||
@ -78,7 +80,7 @@ impl CoreManager {
|
|||||||
// 如果没有使用服务,尝试停止sidecar进程
|
// 如果没有使用服务,尝试停止sidecar进程
|
||||||
self.stop_sidecar_process();
|
self.stop_sidecar_process();
|
||||||
}
|
}
|
||||||
|
|
||||||
*running = false;
|
*running = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -111,7 +113,7 @@ impl CoreManager {
|
|||||||
match service::run_core_by_service(&config_path).await {
|
match service::run_core_by_service(&config_path).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!(target: "app", "core started successfully in service mode");
|
log::info!(target: "app", "core started successfully in service mode");
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 服务启动失败,尝试sidecar模式
|
// 服务启动失败,尝试sidecar模式
|
||||||
log::warn!(target: "app", "failed to start core in service mode: {}", err);
|
log::warn!(target: "app", "failed to start core in service mode: {}", err);
|
||||||
@ -138,28 +140,30 @@ impl CoreManager {
|
|||||||
async fn run_core_by_sidecar(&self, config_path: &PathBuf) -> Result<()> {
|
async fn run_core_by_sidecar(&self, config_path: &PathBuf) -> Result<()> {
|
||||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||||
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
||||||
|
|
||||||
log::info!(target: "app", "starting core {} in sidecar mode", clash_core);
|
log::info!(target: "app", "starting core {} in sidecar mode", clash_core);
|
||||||
|
|
||||||
let app_handle = handle::Handle::global().app_handle().ok_or(anyhow::anyhow!("failed to get app handle"))?;
|
let app_handle = handle::Handle::global()
|
||||||
|
.app_handle()
|
||||||
|
.ok_or(anyhow::anyhow!("failed to get app handle"))?;
|
||||||
|
|
||||||
// 获取配置目录
|
// 获取配置目录
|
||||||
let config_dir = dirs::app_home_dir()?;
|
let config_dir = dirs::app_home_dir()?;
|
||||||
let config_path_str = dirs::path_to_str(config_path)?;
|
let config_path_str = dirs::path_to_str(config_path)?;
|
||||||
|
|
||||||
// 启动核心进程并转入后台运行
|
// 启动核心进程并转入后台运行
|
||||||
let (_, child) = app_handle
|
let (_, child) = app_handle
|
||||||
.shell()
|
.shell()
|
||||||
.sidecar(clash_core)?
|
.sidecar(clash_core)?
|
||||||
.args(["-d", dirs::path_to_str(&config_dir)?, "-f", config_path_str])
|
.args(["-d", dirs::path_to_str(&config_dir)?, "-f", config_path_str])
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
// 保存进程ID以便后续管理
|
// 保存进程ID以便后续管理
|
||||||
handle::Handle::global().set_core_process(child);
|
handle::Handle::global().set_core_process(child);
|
||||||
|
|
||||||
// 等待短暂时间确保启动成功
|
// 等待短暂时间确保启动成功
|
||||||
sleep(Duration::from_millis(300)).await;
|
sleep(Duration::from_millis(300)).await;
|
||||||
|
|
||||||
log::info!(target: "app", "core started in sidecar mode");
|
log::info!(target: "app", "core started in sidecar mode");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -201,10 +205,10 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log::info!(target: "app", "change core to `{clash_core}`");
|
log::info!(target: "app", "change core to `{clash_core}`");
|
||||||
|
|
||||||
// 1. 先更新内核配置(但不应用)
|
// 1. 先更新内核配置(但不应用)
|
||||||
Config::verge().draft().clash_core = Some(clash_core);
|
Config::verge().draft().clash_core = Some(clash_core);
|
||||||
|
|
||||||
// 2. 使用新内核验证配置
|
// 2. 使用新内核验证配置
|
||||||
println!("[切换内核] 使用新内核验证配置");
|
println!("[切换内核] 使用新内核验证配置");
|
||||||
match self.validate_config().await {
|
match self.validate_config().await {
|
||||||
@ -213,7 +217,7 @@ impl CoreManager {
|
|||||||
// 3. 验证通过后,应用内核配置并重启
|
// 3. 验证通过后,应用内核配置并重启
|
||||||
Config::verge().apply();
|
Config::verge().apply();
|
||||||
log_err!(Config::verge().latest().save_file());
|
log_err!(Config::verge().latest().save_file());
|
||||||
|
|
||||||
match self.restart_core().await {
|
match self.restart_core().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("[切换内核] 内核切换成功");
|
println!("[切换内核] 内核切换成功");
|
||||||
@ -233,10 +237,11 @@ impl CoreManager {
|
|||||||
Ok((false, error_msg)) => {
|
Ok((false, error_msg)) => {
|
||||||
println!("[切换内核] 配置验证失败: {}", error_msg);
|
println!("[切换内核] 配置验证失败: {}", error_msg);
|
||||||
// 使用默认配置并继续切换内核
|
// 使用默认配置并继续切换内核
|
||||||
self.use_default_config("config_validate::core_change", &error_msg).await?;
|
self.use_default_config("config_validate::core_change", &error_msg)
|
||||||
|
.await?;
|
||||||
Config::verge().apply();
|
Config::verge().apply();
|
||||||
log_err!(Config::verge().latest().save_file());
|
log_err!(Config::verge().latest().save_file());
|
||||||
|
|
||||||
match self.restart_core().await {
|
match self.restart_core().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("[切换内核] 内核切换成功(使用默认配置)");
|
println!("[切换内核] 内核切换成功(使用默认配置)");
|
||||||
@ -266,13 +271,13 @@ impl CoreManager {
|
|||||||
println!("[core配置验证] 应用正在退出,跳过验证");
|
println!("[core配置验证] 应用正在退出,跳过验证");
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[core配置验证] 开始验证配置文件: {}", config_path);
|
println!("[core配置验证] 开始验证配置文件: {}", config_path);
|
||||||
|
|
||||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||||
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
|
||||||
println!("[core配置验证] 使用内核: {}", clash_core);
|
println!("[core配置验证] 使用内核: {}", clash_core);
|
||||||
|
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
let test_dir = dirs::app_home_dir()?.join("test");
|
let test_dir = dirs::app_home_dir()?.join("test");
|
||||||
let test_dir = dirs::path_to_str(&test_dir)?;
|
let test_dir = dirs::path_to_str(&test_dir)?;
|
||||||
@ -289,14 +294,15 @@ impl CoreManager {
|
|||||||
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
// 检查进程退出状态和错误输出
|
// 检查进程退出状态和错误输出
|
||||||
let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"];
|
let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"];
|
||||||
let has_error = !output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
|
let has_error =
|
||||||
|
!output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw));
|
||||||
|
|
||||||
println!("\n[core配置验证] -------- 验证结果 --------");
|
println!("\n[core配置验证] -------- 验证结果 --------");
|
||||||
println!("[core配置验证] 进程退出状态: {:?}", output.status);
|
println!("[core配置验证] 进程退出状态: {:?}", output.status);
|
||||||
|
|
||||||
if !stderr.is_empty() {
|
if !stderr.is_empty() {
|
||||||
println!("[core配置验证] stderr输出:\n{}", stderr);
|
println!("[core配置验证] stderr输出:\n{}", stderr);
|
||||||
}
|
}
|
||||||
@ -317,7 +323,7 @@ impl CoreManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
println!("[core配置验证] -------- 验证结束 --------\n");
|
println!("[core配置验证] -------- 验证结束 --------\n");
|
||||||
Ok((false, error_msg)) // 返回错误消息给调用者处理
|
Ok((false, error_msg)) // 返回错误消息给调用者处理
|
||||||
} else {
|
} else {
|
||||||
println!("[core配置验证] 验证成功");
|
println!("[core配置验证] 验证成功");
|
||||||
println!("[core配置验证] -------- 验证结束 --------\n");
|
println!("[core配置验证] -------- 验证结束 --------\n");
|
||||||
@ -333,26 +339,33 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 验证指定的配置文件
|
/// 验证指定的配置文件
|
||||||
pub async fn validate_config_file(&self, config_path: &str, is_merge_file: Option<bool>) -> Result<(bool, String)> {
|
pub async fn validate_config_file(
|
||||||
|
&self,
|
||||||
|
config_path: &str,
|
||||||
|
is_merge_file: Option<bool>,
|
||||||
|
) -> Result<(bool, String)> {
|
||||||
// 检查程序是否正在退出,如果是则跳过验证
|
// 检查程序是否正在退出,如果是则跳过验证
|
||||||
if handle::Handle::global().is_exiting() {
|
if handle::Handle::global().is_exiting() {
|
||||||
println!("[core配置验证] 应用正在退出,跳过验证");
|
println!("[core配置验证] 应用正在退出,跳过验证");
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查文件是否存在
|
// 检查文件是否存在
|
||||||
if !std::path::Path::new(config_path).exists() {
|
if !std::path::Path::new(config_path).exists() {
|
||||||
let error_msg = format!("File not found: {}", config_path);
|
let error_msg = format!("File not found: {}", config_path);
|
||||||
//handle::Handle::notice_message("config_validate::file_not_found", &error_msg);
|
//handle::Handle::notice_message("config_validate::file_not_found", &error_msg);
|
||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
|
// 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证
|
||||||
if is_merge_file.unwrap_or(false) {
|
if is_merge_file.unwrap_or(false) {
|
||||||
println!("[core配置验证] 检测到Merge文件,仅进行语法检查: {}", config_path);
|
println!(
|
||||||
|
"[core配置验证] 检测到Merge文件,仅进行语法检查: {}",
|
||||||
|
config_path
|
||||||
|
);
|
||||||
return self.validate_file_syntax(config_path).await;
|
return self.validate_file_syntax(config_path).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否为脚本文件
|
// 检查是否为脚本文件
|
||||||
let is_script = if config_path.ends_with(".js") {
|
let is_script = if config_path.ends_with(".js") {
|
||||||
true
|
true
|
||||||
@ -366,12 +379,12 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_script {
|
if is_script {
|
||||||
log::info!(target: "app", "检测到脚本文件,使用JavaScript验证: {}", config_path);
|
log::info!(target: "app", "检测到脚本文件,使用JavaScript验证: {}", config_path);
|
||||||
return self.validate_script_file(config_path).await;
|
return self.validate_script_file(config_path).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对YAML配置文件使用Clash内核验证
|
// 对YAML配置文件使用Clash内核验证
|
||||||
log::info!(target: "app", "使用Clash内核验证配置文件: {}", config_path);
|
log::info!(target: "app", "使用Clash内核验证配置文件: {}", config_path);
|
||||||
self.validate_config_internal(config_path).await
|
self.validate_config_internal(config_path).await
|
||||||
@ -385,33 +398,36 @@ impl CoreManager {
|
|||||||
} else if path.ends_with(".js") {
|
} else if path.ends_with(".js") {
|
||||||
return Ok(true); // JS文件是脚本文件
|
return Ok(true); // JS文件是脚本文件
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 读取文件内容
|
// 2. 读取文件内容
|
||||||
let content = match std::fs::read_to_string(path) {
|
let content = match std::fs::read_to_string(path) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!(target: "app", "无法读取文件以检测类型: {}, 错误: {}", path, err);
|
log::warn!(target: "app", "无法读取文件以检测类型: {}, 错误: {}", path, err);
|
||||||
return Err(anyhow::anyhow!("Failed to read file to detect type: {}", err));
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to read file to detect type: {}",
|
||||||
|
err
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. 检查是否存在明显的YAML特征
|
// 3. 检查是否存在明显的YAML特征
|
||||||
let has_yaml_features = content.contains(": ") ||
|
let has_yaml_features = content.contains(": ")
|
||||||
content.contains("#") ||
|
|| content.contains("#")
|
||||||
content.contains("---") ||
|
|| content.contains("---")
|
||||||
content.lines().any(|line| line.trim().starts_with("- "));
|
|| content.lines().any(|line| line.trim().starts_with("- "));
|
||||||
|
|
||||||
// 4. 检查是否存在明显的JS特征
|
// 4. 检查是否存在明显的JS特征
|
||||||
let has_js_features = content.contains("function ") ||
|
let has_js_features = content.contains("function ")
|
||||||
content.contains("const ") ||
|
|| content.contains("const ")
|
||||||
content.contains("let ") ||
|
|| content.contains("let ")
|
||||||
content.contains("var ") ||
|
|| content.contains("var ")
|
||||||
content.contains("//") ||
|
|| content.contains("//")
|
||||||
content.contains("/*") ||
|
|| content.contains("/*")
|
||||||
content.contains("*/") ||
|
|| content.contains("*/")
|
||||||
content.contains("export ") ||
|
|| content.contains("export ")
|
||||||
content.contains("import ");
|
|| content.contains("import ");
|
||||||
|
|
||||||
// 5. 决策逻辑
|
// 5. 决策逻辑
|
||||||
if has_yaml_features && !has_js_features {
|
if has_yaml_features && !has_js_features {
|
||||||
// 只有YAML特征,没有JS特征
|
// 只有YAML特征,没有JS特征
|
||||||
@ -422,22 +438,21 @@ impl CoreManager {
|
|||||||
} else if has_yaml_features && has_js_features {
|
} else if has_yaml_features && has_js_features {
|
||||||
// 两种特征都有,需要更精细判断
|
// 两种特征都有,需要更精细判断
|
||||||
// 优先检查是否有明确的JS结构特征
|
// 优先检查是否有明确的JS结构特征
|
||||||
if content.contains("function main") ||
|
if content.contains("function main")
|
||||||
content.contains("module.exports") ||
|
|| content.contains("module.exports")
|
||||||
content.contains("export default") {
|
|| content.contains("export default")
|
||||||
|
{
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查冒号后是否有空格(YAML的典型特征)
|
// 检查冒号后是否有空格(YAML的典型特征)
|
||||||
let yaml_pattern_count = content.lines()
|
let yaml_pattern_count = content.lines().filter(|line| line.contains(": ")).count();
|
||||||
.filter(|line| line.contains(": "))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
if yaml_pattern_count > 2 {
|
if yaml_pattern_count > 2 {
|
||||||
return Ok(false); // 多个键值对格式,更可能是YAML
|
return Ok(false); // 多个键值对格式,更可能是YAML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认情况:无法确定时,假设为非脚本文件(更安全)
|
// 默认情况:无法确定时,假设为非脚本文件(更安全)
|
||||||
log::debug!(target: "app", "无法确定文件类型,默认当作YAML处理: {}", path);
|
log::debug!(target: "app", "无法确定文件类型,默认当作YAML处理: {}", path);
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@ -455,29 +470,32 @@ impl CoreManager {
|
|||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!(target: "app", "验证脚本文件: {}", path);
|
log::debug!(target: "app", "验证脚本文件: {}", path);
|
||||||
|
|
||||||
// 使用boa引擎进行基本语法检查
|
// 使用boa引擎进行基本语法检查
|
||||||
use boa_engine::{Context, Source};
|
use boa_engine::{Context, Source};
|
||||||
|
|
||||||
let mut context = Context::default();
|
let mut context = Context::default();
|
||||||
let result = context.eval(Source::from_bytes(&content));
|
let result = context.eval(Source::from_bytes(&content));
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::debug!(target: "app", "脚本语法验证通过: {}", path);
|
log::debug!(target: "app", "脚本语法验证通过: {}", path);
|
||||||
|
|
||||||
// 检查脚本是否包含main函数
|
// 检查脚本是否包含main函数
|
||||||
if !content.contains("function main") && !content.contains("const main") && !content.contains("let main") {
|
if !content.contains("function main")
|
||||||
|
&& !content.contains("const main")
|
||||||
|
&& !content.contains("let main")
|
||||||
|
{
|
||||||
let error_msg = "Script must contain a main function";
|
let error_msg = "Script must contain a main function";
|
||||||
log::warn!(target: "app", "脚本缺少main函数: {}", path);
|
log::warn!(target: "app", "脚本缺少main函数: {}", path);
|
||||||
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
|
//handle::Handle::notice_message("config_validate::script_missing_main", error_msg);
|
||||||
return Ok((false, error_msg.to_string()));
|
return Ok((false, error_msg.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((true, String::new()))
|
Ok((true, String::new()))
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Script syntax error: {}", err);
|
let error_msg = format!("Script syntax error: {}", err);
|
||||||
log::warn!(target: "app", "脚本语法错误: {}", err);
|
log::warn!(target: "app", "脚本语法错误: {}", err);
|
||||||
@ -494,13 +512,13 @@ impl CoreManager {
|
|||||||
println!("[core配置更新] 应用正在退出,跳过验证");
|
println!("[core配置更新] 应用正在退出,跳过验证");
|
||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[core配置更新] 开始更新配置");
|
println!("[core配置更新] 开始更新配置");
|
||||||
|
|
||||||
// 1. 先生成新的配置内容
|
// 1. 先生成新的配置内容
|
||||||
println!("[core配置更新] 生成新的配置内容");
|
println!("[core配置更新] 生成新的配置内容");
|
||||||
Config::generate().await?;
|
Config::generate().await?;
|
||||||
|
|
||||||
// 2. 生成临时文件并进行验证
|
// 2. 生成临时文件并进行验证
|
||||||
println!("[core配置更新] 生成临时配置文件用于验证");
|
println!("[core配置更新] 生成临时配置文件用于验证");
|
||||||
let temp_config = Config::generate_file(ConfigType::Check)?;
|
let temp_config = Config::generate_file(ConfigType::Check)?;
|
||||||
@ -519,7 +537,7 @@ impl CoreManager {
|
|||||||
// 5. 应用新配置
|
// 5. 应用新配置
|
||||||
println!("[core配置更新] 应用新配置");
|
println!("[core配置更新] 应用新配置");
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
match clash_api::put_configs(run_path).await {
|
match MihomoManager::global().put_configs_force(run_path).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("[core配置更新] 配置应用成功");
|
println!("[core配置更新] 配置应用成功");
|
||||||
Config::runtime().apply();
|
Config::runtime().apply();
|
||||||
@ -556,7 +574,7 @@ impl CoreManager {
|
|||||||
/// 只进行文件语法检查,不进行完整验证
|
/// 只进行文件语法检查,不进行完整验证
|
||||||
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
|
async fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> {
|
||||||
println!("[core配置语法检查] 开始检查文件: {}", config_path);
|
println!("[core配置语法检查] 开始检查文件: {}", config_path);
|
||||||
|
|
||||||
// 读取文件内容
|
// 读取文件内容
|
||||||
let content = match std::fs::read_to_string(config_path) {
|
let content = match std::fs::read_to_string(config_path) {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
@ -566,14 +584,14 @@ impl CoreManager {
|
|||||||
return Ok((false, error_msg));
|
return Ok((false, error_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 对YAML文件尝试解析,只检查语法正确性
|
// 对YAML文件尝试解析,只检查语法正确性
|
||||||
println!("[core配置语法检查] 进行YAML语法检查");
|
println!("[core配置语法检查] 进行YAML语法检查");
|
||||||
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
match serde_yaml::from_str::<serde_yaml::Value>(&content) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("[core配置语法检查] YAML语法检查通过");
|
println!("[core配置语法检查] YAML语法检查通过");
|
||||||
Ok((true, String::new()))
|
Ok((true, String::new()))
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 使用标准化的前缀,以便错误处理函数能正确识别
|
// 使用标准化的前缀,以便错误处理函数能正确识别
|
||||||
let error_msg = format!("YAML syntax error: {}", err);
|
let error_msg = format!("YAML syntax error: {}", err);
|
||||||
@ -589,7 +607,7 @@ impl CoreManager {
|
|||||||
if !*running {
|
if !*running {
|
||||||
return RunningMode::NotRunning;
|
return RunningMode::NotRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查服务状态
|
// 检查服务状态
|
||||||
match service::check_service().await {
|
match service::check_service().await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@ -605,7 +623,7 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// 服务不可用,检查是否有sidecar进程
|
// 服务不可用,检查是否有sidecar进程
|
||||||
if handle::Handle::global().has_core_process() {
|
if handle::Handle::global().has_core_process() {
|
||||||
@ -616,4 +634,4 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
use serde_json::json;
|
||||||
use std::{
|
use std::{
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
@ -42,43 +43,80 @@ impl MihomoManager {
|
|||||||
data.providers_proxies.clone()
|
data.providers_proxies.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh_proxies(&self) -> Result<&Self, String> {
|
async fn send_request(
|
||||||
let url = format!("{}/proxies", self.mihomo_server);
|
&self,
|
||||||
|
method: &str,
|
||||||
|
url: String,
|
||||||
|
data: Option<serde_json::Value>,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
let response = reqwest::ClientBuilder::new()
|
let response = reqwest::ClientBuilder::new()
|
||||||
.default_headers(self.headers.clone())
|
.default_headers(self.headers.clone())
|
||||||
.no_proxy()
|
.no_proxy()
|
||||||
.timeout(Duration::from_secs(3))
|
.timeout(Duration::from_secs(2))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.get(url)
|
.request(
|
||||||
|
match method {
|
||||||
|
"GET" => reqwest::Method::GET,
|
||||||
|
"PUT" => reqwest::Method::PUT,
|
||||||
|
"POST" => reqwest::Method::POST,
|
||||||
|
"PATCH" => reqwest::Method::PATCH,
|
||||||
|
_ => reqwest::Method::GET,
|
||||||
|
},
|
||||||
|
&url,
|
||||||
|
)
|
||||||
|
.json(&data.unwrap_or(json!({})))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.json::<serde_json::Value>()
|
.json::<serde_json::Value>()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let proxies = response;
|
return Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn refresh_proxies(&self) -> Result<&Self, String> {
|
||||||
|
let url = format!("{}/proxies", self.mihomo_server);
|
||||||
|
let proxies = self.send_request("GET", url, None).await?;
|
||||||
self.update_proxies(proxies);
|
self.update_proxies(proxies);
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh_providers_proxies(&self) -> Result<&Self, String> {
|
pub async fn refresh_providers_proxies(&self) -> Result<&Self, String> {
|
||||||
let url = format!("{}/providers/proxies", self.mihomo_server);
|
let url = format!("{}/providers/proxies", self.mihomo_server);
|
||||||
let response = reqwest::ClientBuilder::new()
|
let providers_proxies = self.send_request("GET", url, None).await?;
|
||||||
.default_headers(self.headers.clone())
|
self.update_providers_proxies(providers_proxies);
|
||||||
.no_proxy()
|
|
||||||
.timeout(Duration::from_secs(3))
|
|
||||||
.build()
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.get(url)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?
|
|
||||||
.json::<serde_json::Value>()
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
let proxies = response;
|
|
||||||
self.update_providers_proxies(proxies);
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MihomoManager {
|
||||||
|
pub async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), String> {
|
||||||
|
let url = format!("{}/configs?force=true", self.mihomo_server);
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"path": clash_config_path,
|
||||||
|
});
|
||||||
|
let response = self.send_request("PUT", url, Some(payload)).await.unwrap();
|
||||||
|
if response["code"] == 204 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(response["message"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("unknown error")
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn patch_configs(&self, config: serde_json::Value) -> Result<(), String> {
|
||||||
|
let url = format!("{}/configs", self.mihomo_server);
|
||||||
|
let response = self.send_request("PATCH", url, Some(config)).await.unwrap();
|
||||||
|
if response["code"] == 204 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(response["message"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("unknown error")
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::core::{clash_api, handle, tray, CoreManager};
|
use crate::core::{handle, tray, CoreManager};
|
||||||
use crate::log_err;
|
use crate::log_err;
|
||||||
|
use crate::module::mihomo::MihomoManager;
|
||||||
use crate::utils::resolve;
|
use crate::utils::resolve;
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
@ -38,10 +39,14 @@ pub fn restart_app() {
|
|||||||
pub fn change_clash_mode(mode: String) {
|
pub fn change_clash_mode(mode: String) {
|
||||||
let mut mapping = Mapping::new();
|
let mut mapping = Mapping::new();
|
||||||
mapping.insert(Value::from("mode"), mode.clone().into());
|
mapping.insert(Value::from("mode"), mode.clone().into());
|
||||||
|
// Convert YAML mapping to JSON Value
|
||||||
|
let json_value = serde_json::json!({
|
||||||
|
"mode": mode
|
||||||
|
});
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
log::debug!(target: "app", "change clash mode to {mode}");
|
log::debug!(target: "app", "change clash mode to {mode}");
|
||||||
|
|
||||||
match clash_api::patch_configs(&mapping).await {
|
match MihomoManager::global().patch_configs(json_value).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// 更新订阅
|
// 更新订阅
|
||||||
Config::clash().data().patch_config(mapping);
|
Config::clash().data().patch_config(mapping);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::utils::resolve;
|
|
||||||
use crate::core::handle;
|
|
||||||
use crate::core::{sysopt, CoreManager, clash_api};
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use tauri::Manager;
|
use crate::core::handle;
|
||||||
|
use crate::core::{sysopt, CoreManager};
|
||||||
|
use crate::module::mihomo::MihomoManager;
|
||||||
|
use crate::utils::resolve;
|
||||||
use futures;
|
use futures;
|
||||||
|
use tauri::Manager;
|
||||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||||
|
|
||||||
/// Open or close the dashboard window
|
/// Open or close the dashboard window
|
||||||
@ -52,7 +53,7 @@ pub fn open_or_close_dashboard() {
|
|||||||
pub fn setup_window_state_monitor(app_handle: &tauri::AppHandle) {
|
pub fn setup_window_state_monitor(app_handle: &tauri::AppHandle) {
|
||||||
let window = app_handle.get_webview_window("main").unwrap();
|
let window = app_handle.get_webview_window("main").unwrap();
|
||||||
let app_handle_clone = app_handle.clone();
|
let app_handle_clone = app_handle.clone();
|
||||||
|
|
||||||
// 监听窗口移动事件
|
// 监听窗口移动事件
|
||||||
let app_handle_move = app_handle_clone.clone();
|
let app_handle_move = app_handle_clone.clone();
|
||||||
window.on_window_event(move |event| {
|
window.on_window_event(move |event| {
|
||||||
@ -60,19 +61,19 @@ pub fn setup_window_state_monitor(app_handle: &tauri::AppHandle) {
|
|||||||
// 窗口移动时保存状态
|
// 窗口移动时保存状态
|
||||||
tauri::WindowEvent::Moved(_) => {
|
tauri::WindowEvent::Moved(_) => {
|
||||||
let _ = app_handle_move.save_window_state(StateFlags::all());
|
let _ = app_handle_move.save_window_state(StateFlags::all());
|
||||||
},
|
}
|
||||||
// 窗口调整大小时保存状态
|
// 窗口调整大小时保存状态
|
||||||
tauri::WindowEvent::Resized(_) => {
|
tauri::WindowEvent::Resized(_) => {
|
||||||
let _ = app_handle_move.save_window_state(StateFlags::all());
|
let _ = app_handle_move.save_window_state(StateFlags::all());
|
||||||
},
|
}
|
||||||
// 其他可能改变窗口状态的事件
|
// 其他可能改变窗口状态的事件
|
||||||
tauri::WindowEvent::ScaleFactorChanged { .. } => {
|
tauri::WindowEvent::ScaleFactorChanged { .. } => {
|
||||||
let _ = app_handle_move.save_window_state(StateFlags::all());
|
let _ = app_handle_move.save_window_state(StateFlags::all());
|
||||||
},
|
}
|
||||||
// 窗口关闭时保存
|
// 窗口关闭时保存
|
||||||
tauri::WindowEvent::CloseRequested { .. } => {
|
tauri::WindowEvent::CloseRequested { .. } => {
|
||||||
let _ = app_handle_move.save_window_state(StateFlags::all());
|
let _ = app_handle_move.save_window_state(StateFlags::all());
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -81,16 +82,16 @@ pub fn setup_window_state_monitor(app_handle: &tauri::AppHandle) {
|
|||||||
/// 优化的应用退出函数
|
/// 优化的应用退出函数
|
||||||
pub fn quit(code: Option<i32>) {
|
pub fn quit(code: Option<i32>) {
|
||||||
log::debug!(target: "app", "启动退出流程");
|
log::debug!(target: "app", "启动退出流程");
|
||||||
|
|
||||||
// 获取应用句柄并设置退出标志
|
// 获取应用句柄并设置退出标志
|
||||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||||
handle::Handle::global().set_is_exiting();
|
handle::Handle::global().set_is_exiting();
|
||||||
|
|
||||||
// 优先关闭窗口,提供立即反馈
|
// 优先关闭窗口,提供立即反馈
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
if let Some(window) = handle::Handle::global().get_window() {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在单独线程中处理资源清理,避免阻塞主线程
|
// 在单独线程中处理资源清理,避免阻塞主线程
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// 使用tokio运行时执行异步清理任务
|
// 使用tokio运行时执行异步清理任务
|
||||||
@ -100,34 +101,38 @@ pub fn quit(code: Option<i32>) {
|
|||||||
|
|
||||||
// 1. 直接关闭TUN模式 (优先处理,通常最容易卡住)
|
// 1. 直接关闭TUN模式 (优先处理,通常最容易卡住)
|
||||||
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
if Config::verge().data().enable_tun_mode.unwrap_or(false) {
|
||||||
let mut disable = serde_yaml::Mapping::new();
|
let disable = serde_json::json!({
|
||||||
let mut tun = serde_yaml::Mapping::new();
|
"tun": {
|
||||||
tun.insert("enable".into(), false.into());
|
"enable": false
|
||||||
disable.insert("tun".into(), tun.into());
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 设置1秒超时
|
// 设置1秒超时
|
||||||
let _ = timeout(Duration::from_secs(1),
|
let _ = timeout(
|
||||||
clash_api::patch_configs(&disable)).await;
|
Duration::from_secs(1),
|
||||||
|
MihomoManager::global().patch_configs(disable),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 并行处理系统代理和核心进程清理
|
// 2. 并行处理系统代理和核心进程清理
|
||||||
let proxy_future = timeout(Duration::from_secs(1),
|
let proxy_future = timeout(
|
||||||
sysopt::Sysopt::global().reset_sysproxy());
|
Duration::from_secs(1),
|
||||||
|
sysopt::Sysopt::global().reset_sysproxy(),
|
||||||
let core_future = timeout(Duration::from_secs(1),
|
);
|
||||||
CoreManager::global().stop_core());
|
|
||||||
|
let core_future = timeout(Duration::from_secs(1), CoreManager::global().stop_core());
|
||||||
|
|
||||||
// 同时等待两个任务完成
|
// 同时等待两个任务完成
|
||||||
let _ = futures::join!(proxy_future, core_future);
|
let _ = futures::join!(proxy_future, core_future);
|
||||||
|
|
||||||
// 3. 处理macOS特定清理
|
// 3. 处理macOS特定清理
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let _ = timeout(Duration::from_millis(500),
|
let _ = timeout(Duration::from_millis(500), resolve::restore_public_dns()).await;
|
||||||
resolve::restore_public_dns()).await;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 无论清理结果如何,确保应用退出
|
// 无论清理结果如何,确保应用退出
|
||||||
app_handle.exit(code.unwrap_or(0));
|
app_handle.exit(code.unwrap_or(0));
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user