feat: Add sidecar mode as an alternative to service mode

- Auto-fallback to sidecar mode if service mode fails
This commit is contained in:
wonfen 2025-03-03 03:34:34 +08:00
parent d370868222
commit 9d74b93ee0
3 changed files with 100 additions and 13 deletions

View File

@ -7,7 +7,7 @@ use crate::utils::{dirs, help};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use serde_yaml::Mapping;
use std::{sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration, path::PathBuf};
use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex;
use tokio::time::sleep;
@ -53,12 +53,37 @@ impl CoreManager {
// 服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "stop the core by service");
service::stop_core_by_service().await?;
match service::stop_core_by_service().await {
Ok(_) => {
log::info!(target: "app", "core stopped successfully by service");
}
Err(err) => {
log::warn!(target: "app", "failed to stop core by service: {}", err);
// 服务停止失败尝试停止可能的sidecar进程
self.stop_sidecar_process();
}
}
} else {
// 如果没有使用服务尝试停止sidecar进程
self.stop_sidecar_process();
}
*running = false;
Ok(())
}
/// 停止通过sidecar启动的进程
fn stop_sidecar_process(&self) {
if let Some(process) = handle::Handle::global().take_core_process() {
log::info!(target: "app", "stopping core process in sidecar mode");
if let Err(e) = process.kill() {
log::warn!(target: "app", "failed to kill core process: {}", e);
} else {
log::info!(target: "app", "core process stopped successfully");
}
}
}
/// 启动核心
pub async fn start_core(&self) -> Result<()> {
let mut running = self.running.lock().await;
@ -69,11 +94,26 @@ impl CoreManager {
let config_path = Config::generate_file(ConfigType::Run)?;
// 服务模式
// 先尝试服务模式
if service::check_service().await.is_ok() {
log::info!(target: "app", "try to run core in service mode");
service::run_core_by_service(&config_path).await?;
match service::run_core_by_service(&config_path).await {
Ok(_) => {
log::info!(target: "app", "core started successfully in service mode");
},
Err(err) => {
// 服务启动失败尝试sidecar模式
log::warn!(target: "app", "failed to start core in service mode: {}", err);
log::info!(target: "app", "trying to run core in sidecar mode");
self.run_core_by_sidecar(&config_path).await?;
}
}
} else {
// 服务不可用直接使用sidecar模式
log::info!(target: "app", "service not available, running core in sidecar mode");
self.run_core_by_sidecar(&config_path).await?;
}
// 流量订阅
#[cfg(target_os = "macos")]
log_err!(Tray::global().subscribe_traffic().await);
@ -83,11 +123,43 @@ impl CoreManager {
Ok(())
}
/// 通过sidecar启动内核
async fn run_core_by_sidecar(&self, config_path: &PathBuf) -> Result<()> {
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
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 config_dir = dirs::app_home_dir()?;
let config_path_str = dirs::path_to_str(config_path)?;
// 启动核心进程并转入后台运行
let (_, child) = app_handle
.shell()
.sidecar(clash_core)?
.args(["-d", dirs::path_to_str(&config_dir)?, "-f", config_path_str])
.spawn()?;
// 保存进程ID以便后续管理
handle::Handle::global().set_core_process(child);
// 等待短暂时间确保启动成功
sleep(Duration::from_millis(300)).await;
log::info!(target: "app", "core started in sidecar mode");
Ok(())
}
/// 重启内核
pub async fn restart_core(&self) -> Result<()> {
// 重新启动app
log::info!(target: "app", "restarting core");
self.stop_core().await?;
self.start_core().await?;
log::info!(target: "app", "core restarted successfully");
Ok(())
}
@ -139,9 +211,11 @@ impl CoreManager {
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
Config::verge().discard();
Config::runtime().discard();
Err(err)
// 即使使用服务失败我们也尝试使用sidecar模式启动
log::info!(target: "app", "trying sidecar mode after service failure");
self.start_core().await?;
Config::runtime().apply();
Ok(())
}
}
}
@ -159,8 +233,10 @@ impl CoreManager {
}
Err(err) => {
println!("[切换内核] 内核切换失败: {}", err);
Config::verge().discard();
Err(err)
// 即使使用服务失败我们也尝试使用sidecar模式启动
log::info!(target: "app", "trying sidecar mode after service failure with default config");
self.start_core().await?;
Ok(())
}
}
}

View File

@ -3,11 +3,13 @@ use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager, WebviewWindow};
use tauri_plugin_shell::process::CommandChild;
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
pub is_exiting: Arc<RwLock<bool>>,
pub core_process: Arc<RwLock<Option<CommandChild>>>,
}
impl Handle {
@ -17,6 +19,7 @@ impl Handle {
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(RwLock::new(None)),
is_exiting: Arc::new(RwLock::new(false)),
core_process: Arc::new(RwLock::new(None)),
})
}
@ -68,6 +71,16 @@ impl Handle {
*is_exiting = true;
}
pub fn set_core_process(&self, process: CommandChild) {
let mut core_process = self.core_process.write();
*core_process = Some(process);
}
pub fn take_core_process(&self) -> Option<CommandChild> {
let mut core_process = self.core_process.write();
core_process.take()
}
pub fn is_exiting(&self) -> bool {
*self.is_exiting.read()
}

View File

@ -77,14 +77,12 @@ pub async fn resolve_setup(app: &mut App) {
}
}
if !service_runing {
log::error!(target: "app", "service not runing. exit");
app.app_handle().exit(-2);
log::warn!(target: "app", "service not running, will fallback to user mode");
}
}
}
Err(e) => {
log::error!(target: "app", "{e:?}");
app.app_handle().exit(-1);
log::warn!(target: "app", "failed to install service: {e:?}, will fallback to user mode");
}
}
}