341 lines
10 KiB
Rust
341 lines
10 KiB
Rust
//! 代理模块 Tauri 命令层。
|
|
|
|
use tauri::{AppHandle, State};
|
|
|
|
use super::system_proxy::{clear_system_proxy_windows, get_system_proxy_windows, set_system_proxy_windows};
|
|
use super::{
|
|
KernelInfo, KernelUpdateInfo, MihomoManager, ProfileMeta, ProxySettings, ProxyStatus, TrafficSnapshot,
|
|
};
|
|
|
|
use crate::process_manager::{ProcessInfo, ProcessManager, ProcessStatus};
|
|
|
|
/// 判断 mihomo 进程是否处于运行状态
|
|
fn mihomo_running(pm: &ProcessManager) -> bool {
|
|
matches!(
|
|
pm.get_status("proxy").map(|p| p.status),
|
|
Some(ProcessStatus::Running)
|
|
)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_get_settings(state: State<'_, MihomoManager>) -> ProxySettings {
|
|
state.load_settings()
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_save_settings(
|
|
state: State<'_, MihomoManager>,
|
|
settings: ProxySettings,
|
|
) -> Result<(), String> {
|
|
state.save_settings(&settings)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_kernel_info(
|
|
state: State<'_, MihomoManager>,
|
|
app: AppHandle,
|
|
) -> Result<KernelInfo, String> {
|
|
state.prepare_kernel(&app)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_check_kernel_update(
|
|
state: State<'_, MihomoManager>,
|
|
) -> Result<KernelUpdateInfo, String> {
|
|
state.check_kernel_update().await
|
|
}
|
|
|
|
/// 取消内核下载/安装(设置取消标志,下载循环轮询后中止)
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_cancel_kernel_install(state: State<'_, MihomoManager>) -> Result<(), String> {
|
|
state.cancel_kernel_install();
|
|
Ok(())
|
|
}
|
|
|
|
/// 前端确认 mihomo 已停止,唤醒等待中的安装流程继续解压替换。
|
|
/// (下载阶段允许 mihomo 运行以便走系统代理,解压替换前必须停止 mihomo,否则 exe 被占用)
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_confirm_install(state: State<'_, MihomoManager>) -> Result<(), String> {
|
|
state.confirm_install();
|
|
Ok(())
|
|
}
|
|
|
|
/// 应用内核更新:下载阶段已由下载模块完成,本方法仅做 need_stop → 解压 → 替换。
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_apply_kernel_update(
|
|
state: State<'_, MihomoManager>,
|
|
app: AppHandle,
|
|
zip_path: String,
|
|
) -> Result<KernelInfo, String> {
|
|
let path = std::path::PathBuf::from(zip_path);
|
|
state.apply_kernel_update(&app, path).await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_status(pm: State<'_, ProcessManager>) -> ProxyStatus {
|
|
match pm.get_status("proxy") {
|
|
Some(p) => ProxyStatus {
|
|
running: matches!(p.status, crate::process_manager::ProcessStatus::Running),
|
|
pid: p.pid,
|
|
restart_count: p.restart_count,
|
|
},
|
|
None => ProxyStatus {
|
|
running: false,
|
|
pid: None,
|
|
restart_count: 0,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_start(
|
|
state: State<'_, MihomoManager>,
|
|
pm: State<'_, ProcessManager>,
|
|
app: AppHandle,
|
|
) -> Result<ProcessInfo, String> {
|
|
let params = state.prepare_for_start(&app)?;
|
|
let info = pm.start(params)?;
|
|
// 手动启动也遵循「启动时自动开启系统代理」设置
|
|
state.apply_auto_system_proxy();
|
|
Ok(info)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_stop(state: State<'_, MihomoManager>, pm: State<'_, ProcessManager>) -> Result<(), String> {
|
|
// 关闭 mihomo 时同步关闭系统代理(若开启),避免系统代理指向已停止的端口导致断网
|
|
if get_system_proxy_windows() {
|
|
let _ = state.disable_system_proxy();
|
|
}
|
|
pm.stop("proxy")
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_restart(
|
|
state: State<'_, MihomoManager>,
|
|
pm: State<'_, ProcessManager>,
|
|
app: AppHandle,
|
|
) -> Result<ProcessInfo, String> {
|
|
let _ = pm.stop("proxy");
|
|
// 等待 TCP 端口释放(Windows 上 kill 后端口释放有延迟),在阻塞线程池中 sleep 避免阻塞主线程
|
|
tauri::async_runtime::spawn_blocking(|| {
|
|
std::thread::sleep(std::time::Duration::from_millis(800));
|
|
})
|
|
.await
|
|
.map_err(|e| format!("sleep 失败: {}", e))?;
|
|
|
|
// 启动并等待 API 就绪,带有限重试(共 3 次):
|
|
// - 冷加载大订阅(首次解析 + geo 下载)可能远超过前端 waitForApi 的 10s 预算,
|
|
// 这里在命令内等满就绪,避免重启成功后仍被前端误判为「重启失败」导致节点不刷新。
|
|
// - 偶发的端口未及时释放 / 启动瞬间退出,通过重试自愈。
|
|
let mut last_err = "mihomo 启动失败".to_string();
|
|
for _ in 0..3 {
|
|
match start_and_wait(state.inner(), pm.inner(), &app).await {
|
|
Ok(info) => return Ok(info),
|
|
Err(e) => {
|
|
last_err = e;
|
|
// 上个实例刚退出,多等一会儿释放端口再重试
|
|
tauri::async_runtime::spawn_blocking(|| {
|
|
std::thread::sleep(std::time::Duration::from_millis(1200));
|
|
})
|
|
.await
|
|
.map_err(|x| format!("sleep 失败: {}", x))?;
|
|
}
|
|
}
|
|
}
|
|
// 所有启动尝试均失败:mihomo 已停止,同步关闭系统代理(若开启),避免代理指向已停止端口导致断网
|
|
if get_system_proxy_windows() {
|
|
let _ = state.disable_system_proxy();
|
|
}
|
|
Err(last_err)
|
|
}
|
|
|
|
/// 启动 mihomo 并等待其 HTTP API 就绪(最多 20s)。
|
|
/// - 若启动后进程立即退出,快速返回(不白等满预算),便于外层尽早重试。
|
|
/// - 若 pm.start 返回「已在运行中」,说明崩溃监控已抢先拉起进程,同样等待其 API 就绪即可。
|
|
async fn start_and_wait(
|
|
state: &MihomoManager,
|
|
pm: &ProcessManager,
|
|
app: &AppHandle,
|
|
) -> Result<ProcessInfo, String> {
|
|
let params = state.prepare_for_start(app)?;
|
|
if let Err(e) = pm.start(params) {
|
|
// "已在运行中" = 崩溃监控已拉起,不算失败;其余为上抛
|
|
if !e.contains("运行中") {
|
|
return Err(e);
|
|
}
|
|
}
|
|
|
|
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(20);
|
|
loop {
|
|
// 进程已退出且 API 未就绪 → 启动失败(快速失败,交外层重试)
|
|
if let Some(st) = pm.get_status("proxy") {
|
|
if !matches!(st.status, ProcessStatus::Running) {
|
|
return Err("mihomo 启动后立即退出".to_string());
|
|
}
|
|
}
|
|
if state.get_version().await.is_ok() {
|
|
return pm.get_status("proxy").ok_or_else(|| "mihomo 进程不存在".to_string());
|
|
}
|
|
if tokio::time::Instant::now() >= deadline {
|
|
return Err("mihomo 启动超时,API 无响应".to_string());
|
|
}
|
|
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn proxy_version(state: State<'_, MihomoManager>) -> Result<serde_json::Value, String> {
|
|
state.get_version().await
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn proxy_get_proxies(state: State<'_, MihomoManager>) -> Result<serde_json::Value, String> {
|
|
state.get_proxies().await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_select_proxy(
|
|
state: State<'_, MihomoManager>,
|
|
group: String,
|
|
name: String,
|
|
) -> Result<(), String> {
|
|
state.select_proxy(&group, &name).await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_test_delay(
|
|
state: State<'_, MihomoManager>,
|
|
name: String,
|
|
url: Option<String>,
|
|
timeout: Option<u32>,
|
|
) -> Result<u32, String> {
|
|
state
|
|
.test_delay(
|
|
&name,
|
|
url.as_deref().unwrap_or("https://www.gstatic.com/generate_204"),
|
|
timeout.unwrap_or(5000),
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn proxy_get_connections(
|
|
state: State<'_, MihomoManager>,
|
|
) -> Result<serde_json::Value, String> {
|
|
state.get_connections().await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_traffic(
|
|
state: State<'_, MihomoManager>,
|
|
) -> Result<TrafficSnapshot, String> {
|
|
state.traffic_snapshot().await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_close_connection(
|
|
state: State<'_, MihomoManager>,
|
|
id: String,
|
|
) -> Result<(), String> {
|
|
state.close_connection(&id).await
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn proxy_patch_configs(
|
|
state: State<'_, MihomoManager>,
|
|
body: serde_json::Value,
|
|
) -> Result<(), String> {
|
|
state.patch_configs(body).await
|
|
}
|
|
|
|
// ---------- 订阅 ----------
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_import_profile(
|
|
state: State<'_, MihomoManager>,
|
|
url: String,
|
|
name: String,
|
|
) -> Result<ProfileMeta, String> {
|
|
state.import_profile(&url, &name).await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub async fn proxy_update_profile(
|
|
state: State<'_, MihomoManager>,
|
|
id: String,
|
|
) -> Result<ProfileMeta, String> {
|
|
state.update_profile(&id).await
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_delete_profile(
|
|
state: State<'_, MihomoManager>,
|
|
id: String,
|
|
) -> Result<(), String> {
|
|
state.delete_profile(&id)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_activate_profile(
|
|
state: State<'_, MihomoManager>,
|
|
id: String,
|
|
) -> Result<(), String> {
|
|
state.activate_profile(&id)
|
|
}
|
|
|
|
// ---------- 系统代理 ----------
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_set_system_proxy(
|
|
state: State<'_, MihomoManager>,
|
|
pm: State<'_, ProcessManager>,
|
|
) -> Result<(), String> {
|
|
// 停机时禁止开启系统代理:否则系统代理指向已停止的端口,会导致所有网络请求失败
|
|
if !mihomo_running(&pm) {
|
|
return Err("mihomo 未运行,无法开启系统代理".into());
|
|
}
|
|
let settings = state.load_settings();
|
|
let addr = format!("127.0.0.1:{}", settings.mixed_port);
|
|
set_system_proxy_windows(&addr)?;
|
|
let mut settings = settings;
|
|
settings.system_proxy = true;
|
|
state.save_settings(&settings)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_clear_system_proxy(
|
|
state: State<'_, MihomoManager>,
|
|
) -> Result<(), String> {
|
|
clear_system_proxy_windows()?;
|
|
let mut settings = state.load_settings();
|
|
settings.system_proxy = false;
|
|
state.save_settings(&settings)
|
|
}
|
|
|
|
#[tauri::command]
|
|
#[specta::specta]
|
|
pub fn proxy_get_system_proxy() -> bool {
|
|
get_system_proxy_windows()
|
|
}
|