|
@@ -9,8 +9,8 @@ use std::fs;
|
|
|
use std::io::Write;
|
|
|
use std::sync::Arc;
|
|
|
use std::{
|
|
|
- collections::{HashMap, VecDeque},
|
|
|
- time::Duration,
|
|
|
+ collections::{HashMap, VecDeque},
|
|
|
+ time::Duration,
|
|
|
};
|
|
|
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
|
|
use tokio::time::sleep;
|
|
@@ -19,209 +19,209 @@ const LOGS_QUEUE_LEN: usize = 100;
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
pub struct Service {
|
|
|
- sidecar: Option<CommandChild>,
|
|
|
+ sidecar: Option<CommandChild>,
|
|
|
|
|
|
- logs: Arc<RwLock<VecDeque<String>>>,
|
|
|
+ logs: Arc<RwLock<VecDeque<String>>>,
|
|
|
|
|
|
- #[allow(unused)]
|
|
|
- use_service_mode: bool,
|
|
|
+ #[allow(unused)]
|
|
|
+ use_service_mode: bool,
|
|
|
}
|
|
|
|
|
|
impl Service {
|
|
|
- pub fn new() -> Service {
|
|
|
- let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
|
|
|
+ pub fn new() -> Service {
|
|
|
+ let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
|
|
|
|
|
|
- Service {
|
|
|
- sidecar: None,
|
|
|
- logs: Arc::new(RwLock::new(queue)),
|
|
|
- use_service_mode: false,
|
|
|
+ Service {
|
|
|
+ sidecar: None,
|
|
|
+ logs: Arc::new(RwLock::new(queue)),
|
|
|
+ use_service_mode: false,
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- pub fn start(&mut self) -> Result<()> {
|
|
|
- #[cfg(not(target_os = "windows"))]
|
|
|
- self.start_clash_by_sidecar()?;
|
|
|
-
|
|
|
- #[cfg(target_os = "windows")]
|
|
|
- {
|
|
|
- let enable = {
|
|
|
- let data = Data::global();
|
|
|
- let verge = data.verge.lock();
|
|
|
- verge.enable_service_mode.clone().unwrap_or(false)
|
|
|
- };
|
|
|
|
|
|
- self.use_service_mode = enable;
|
|
|
+ pub fn start(&mut self) -> Result<()> {
|
|
|
+ #[cfg(not(target_os = "windows"))]
|
|
|
+ self.start_clash_by_sidecar()?;
|
|
|
+
|
|
|
+ #[cfg(target_os = "windows")]
|
|
|
+ {
|
|
|
+ let enable = {
|
|
|
+ let data = Data::global();
|
|
|
+ let verge = data.verge.lock();
|
|
|
+ verge.enable_service_mode.clone().unwrap_or(false)
|
|
|
+ };
|
|
|
|
|
|
- if !enable {
|
|
|
- return self.start_clash_by_sidecar();
|
|
|
- }
|
|
|
+ self.use_service_mode = enable;
|
|
|
|
|
|
- tauri::async_runtime::spawn(async move {
|
|
|
- match Self::check_service().await {
|
|
|
- Ok(status) => {
|
|
|
- // 未启动clash
|
|
|
- if status.code != 0 {
|
|
|
- log_if_err!(Self::start_clash_by_service().await);
|
|
|
+ if !enable {
|
|
|
+ return self.start_clash_by_sidecar();
|
|
|
}
|
|
|
- }
|
|
|
- Err(err) => log::error!(target: "app", "{err}"),
|
|
|
+
|
|
|
+ tauri::async_runtime::spawn(async move {
|
|
|
+ match Self::check_service().await {
|
|
|
+ Ok(status) => {
|
|
|
+ // 未启动clash
|
|
|
+ if status.code != 0 {
|
|
|
+ log_if_err!(Self::start_clash_by_service().await);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(err) => log::error!(target: "app", "{err}"),
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
- });
|
|
|
+
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
+ pub fn stop(&mut self) -> Result<()> {
|
|
|
+ #[cfg(not(target_os = "windows"))]
|
|
|
+ self.stop_clash_by_sidecar()?;
|
|
|
|
|
|
- pub fn stop(&mut self) -> Result<()> {
|
|
|
- #[cfg(not(target_os = "windows"))]
|
|
|
- self.stop_clash_by_sidecar()?;
|
|
|
+ #[cfg(target_os = "windows")]
|
|
|
+ {
|
|
|
+ let _ = self.stop_clash_by_sidecar();
|
|
|
|
|
|
- #[cfg(target_os = "windows")]
|
|
|
- {
|
|
|
- let _ = self.stop_clash_by_sidecar();
|
|
|
+ if self.use_service_mode {
|
|
|
+ tauri::async_runtime::block_on(async move {
|
|
|
+ log_if_err!(Self::stop_clash_by_service().await);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if self.use_service_mode {
|
|
|
- tauri::async_runtime::block_on(async move {
|
|
|
- log_if_err!(Self::stop_clash_by_service().await);
|
|
|
- });
|
|
|
- }
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- pub fn restart(&mut self) -> Result<()> {
|
|
|
- self.stop()?;
|
|
|
- self.start()
|
|
|
- }
|
|
|
-
|
|
|
- pub fn get_logs(&self) -> VecDeque<String> {
|
|
|
- self.logs.read().clone()
|
|
|
- }
|
|
|
-
|
|
|
- #[allow(unused)]
|
|
|
- pub fn set_logs(&self, text: String) {
|
|
|
- let mut logs = self.logs.write();
|
|
|
- if logs.len() > LOGS_QUEUE_LEN {
|
|
|
- (*logs).pop_front();
|
|
|
+ pub fn restart(&mut self) -> Result<()> {
|
|
|
+ self.stop()?;
|
|
|
+ self.start()
|
|
|
}
|
|
|
- (*logs).push_back(text);
|
|
|
- }
|
|
|
-
|
|
|
- pub fn clear_logs(&self) {
|
|
|
- let mut logs = self.logs.write();
|
|
|
- (*logs).clear();
|
|
|
- }
|
|
|
-
|
|
|
- /// start the clash sidecar
|
|
|
- fn start_clash_by_sidecar(&mut self) -> Result<()> {
|
|
|
- if self.sidecar.is_some() {
|
|
|
- let sidecar = self.sidecar.take().unwrap();
|
|
|
- let _ = sidecar.kill();
|
|
|
+
|
|
|
+ pub fn get_logs(&self) -> VecDeque<String> {
|
|
|
+ self.logs.read().clone()
|
|
|
}
|
|
|
|
|
|
- let clash_core: String = {
|
|
|
- let global = Data::global();
|
|
|
- let verge = global.verge.lock();
|
|
|
- verge.clash_core.clone().unwrap_or("clash".into())
|
|
|
- };
|
|
|
-
|
|
|
- let app_dir = dirs::app_home_dir();
|
|
|
- let app_dir = app_dir.as_os_str().to_str().unwrap();
|
|
|
-
|
|
|
- // fix #212
|
|
|
- let args = match clash_core.as_str() {
|
|
|
- "clash-meta" => vec!["-m", "-d", app_dir],
|
|
|
- _ => vec!["-d", app_dir],
|
|
|
- };
|
|
|
-
|
|
|
- let cmd = Command::new_sidecar(clash_core)?;
|
|
|
-
|
|
|
- let (mut rx, cmd_child) = cmd.args(args).spawn()?;
|
|
|
-
|
|
|
- // 将pid写入文件中
|
|
|
- let pid = cmd_child.pid();
|
|
|
- log_if_err!(|| -> Result<()> {
|
|
|
- let path = dirs::clash_pid_path();
|
|
|
- fs::File::create(path)?.write(format!("{pid}").as_bytes())?;
|
|
|
- Ok(())
|
|
|
- }());
|
|
|
-
|
|
|
- self.sidecar = Some(cmd_child);
|
|
|
-
|
|
|
- // clash log
|
|
|
- let logs = self.logs.clone();
|
|
|
- tauri::async_runtime::spawn(async move {
|
|
|
- let write_log = |text: String| {
|
|
|
- let mut logs = logs.write();
|
|
|
- if logs.len() >= LOGS_QUEUE_LEN {
|
|
|
- (*logs).pop_front();
|
|
|
+ #[allow(unused)]
|
|
|
+ pub fn set_logs(&self, text: String) {
|
|
|
+ let mut logs = self.logs.write();
|
|
|
+ if logs.len() > LOGS_QUEUE_LEN {
|
|
|
+ (*logs).pop_front();
|
|
|
}
|
|
|
(*logs).push_back(text);
|
|
|
- };
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn clear_logs(&self) {
|
|
|
+ let mut logs = self.logs.write();
|
|
|
+ (*logs).clear();
|
|
|
+ }
|
|
|
|
|
|
- while let Some(event) = rx.recv().await {
|
|
|
- match event {
|
|
|
- CommandEvent::Stdout(line) => {
|
|
|
- let can_short = line.starts_with("time=") && line.len() > 33;
|
|
|
- let stdout = if can_short { &line[33..] } else { &line };
|
|
|
- log::info!(target: "app" ,"[clash]: {}", stdout);
|
|
|
- write_log(line);
|
|
|
- }
|
|
|
- CommandEvent::Stderr(err) => {
|
|
|
- log::error!(target: "app" ,"[clash error]: {}", err);
|
|
|
- write_log(err);
|
|
|
- }
|
|
|
- CommandEvent::Error(err) => log::error!(target: "app" ,"{err}"),
|
|
|
- CommandEvent::Terminated(_) => break,
|
|
|
- _ => {}
|
|
|
+ /// start the clash sidecar
|
|
|
+ fn start_clash_by_sidecar(&mut self) -> Result<()> {
|
|
|
+ if self.sidecar.is_some() {
|
|
|
+ let sidecar = self.sidecar.take().unwrap();
|
|
|
+ let _ = sidecar.kill();
|
|
|
}
|
|
|
- }
|
|
|
- });
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
+ let clash_core: String = {
|
|
|
+ let global = Data::global();
|
|
|
+ let verge = global.verge.lock();
|
|
|
+ verge.clash_core.clone().unwrap_or("clash".into())
|
|
|
+ };
|
|
|
+
|
|
|
+ let app_dir = dirs::app_home_dir();
|
|
|
+ let app_dir = app_dir.as_os_str().to_str().unwrap();
|
|
|
+
|
|
|
+ // fix #212
|
|
|
+ let args = match clash_core.as_str() {
|
|
|
+ "clash-meta" => vec!["-m", "-d", app_dir],
|
|
|
+ _ => vec!["-d", app_dir],
|
|
|
+ };
|
|
|
+
|
|
|
+ let cmd = Command::new_sidecar(clash_core)?;
|
|
|
+
|
|
|
+ let (mut rx, cmd_child) = cmd.args(args).spawn()?;
|
|
|
+
|
|
|
+ // 将pid写入文件中
|
|
|
+ let pid = cmd_child.pid();
|
|
|
+ log_if_err!(|| -> Result<()> {
|
|
|
+ let path = dirs::clash_pid_path();
|
|
|
+ fs::File::create(path)?.write(format!("{pid}").as_bytes())?;
|
|
|
+ Ok(())
|
|
|
+ }());
|
|
|
+
|
|
|
+ self.sidecar = Some(cmd_child);
|
|
|
+
|
|
|
+ // clash log
|
|
|
+ let logs = self.logs.clone();
|
|
|
+ tauri::async_runtime::spawn(async move {
|
|
|
+ let write_log = |text: String| {
|
|
|
+ let mut logs = logs.write();
|
|
|
+ if logs.len() >= LOGS_QUEUE_LEN {
|
|
|
+ (*logs).pop_front();
|
|
|
+ }
|
|
|
+ (*logs).push_back(text);
|
|
|
+ };
|
|
|
+
|
|
|
+ while let Some(event) = rx.recv().await {
|
|
|
+ match event {
|
|
|
+ CommandEvent::Stdout(line) => {
|
|
|
+ let can_short = line.starts_with("time=") && line.len() > 33;
|
|
|
+ let stdout = if can_short { &line[33..] } else { &line };
|
|
|
+ log::info!(target: "app" ,"[clash]: {}", stdout);
|
|
|
+ write_log(line);
|
|
|
+ }
|
|
|
+ CommandEvent::Stderr(err) => {
|
|
|
+ log::error!(target: "app" ,"[clash error]: {}", err);
|
|
|
+ write_log(err);
|
|
|
+ }
|
|
|
+ CommandEvent::Error(err) => log::error!(target: "app" ,"{err}"),
|
|
|
+ CommandEvent::Terminated(_) => break,
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- /// stop the clash sidecar
|
|
|
- fn stop_clash_by_sidecar(&mut self) -> Result<()> {
|
|
|
- if let Some(sidecar) = self.sidecar.take() {
|
|
|
- sidecar.kill()?;
|
|
|
- }
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- pub fn check_start(&mut self) -> Result<()> {
|
|
|
- #[cfg(target_os = "windows")]
|
|
|
- {
|
|
|
- let global = Data::global();
|
|
|
- let verge = global.verge.lock();
|
|
|
- let service_mode = verge.enable_service_mode.unwrap_or(false);
|
|
|
-
|
|
|
- if !service_mode && self.sidecar.is_none() {
|
|
|
- self.start()?;
|
|
|
- }
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- #[cfg(not(target_os = "windows"))]
|
|
|
- if self.sidecar.is_none() {
|
|
|
- self.start()?;
|
|
|
+ /// stop the clash sidecar
|
|
|
+ fn stop_clash_by_sidecar(&mut self) -> Result<()> {
|
|
|
+ if let Some(sidecar) = self.sidecar.take() {
|
|
|
+ sidecar.kill()?;
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
+ pub fn check_start(&mut self) -> Result<()> {
|
|
|
+ #[cfg(target_os = "windows")]
|
|
|
+ {
|
|
|
+ let global = Data::global();
|
|
|
+ let verge = global.verge.lock();
|
|
|
+ let service_mode = verge.enable_service_mode.unwrap_or(false);
|
|
|
+
|
|
|
+ if !service_mode && self.sidecar.is_none() {
|
|
|
+ self.start()?;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[cfg(not(target_os = "windows"))]
|
|
|
+ if self.sidecar.is_none() {
|
|
|
+ self.start()?;
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
|
|
|
- /// update clash config
|
|
|
- /// using PUT methods
|
|
|
- pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
|
|
- let temp_path = dirs::profiles_temp_path();
|
|
|
- config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
|
|
|
+ /// update clash config
|
|
|
+ /// using PUT methods
|
|
|
+ pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
|
|
+ let temp_path = dirs::profiles_temp_path();
|
|
|
+ config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
|
|
|
|
|
|
- let (server, headers) = Self::clash_client_info(info)?;
|
|
|
+ let (server, headers) = Self::clash_client_info(info)?;
|
|
|
|
|
|
- let mut data = HashMap::new();
|
|
|
- data.insert("path", temp_path.as_os_str().to_str().unwrap());
|
|
|
+ let mut data = HashMap::new();
|
|
|
+ data.insert("path", temp_path.as_os_str().to_str().unwrap());
|
|
|
|
|
|
- macro_rules! report_err {
|
|
|
+ macro_rules! report_err {
|
|
|
($i: expr, $e: expr) => {
|
|
|
match $i {
|
|
|
4 => bail!($e),
|
|
@@ -230,306 +230,308 @@ impl Service {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // retry 5 times
|
|
|
- for i in 0..5 {
|
|
|
- let headers = headers.clone();
|
|
|
- match reqwest::ClientBuilder::new().no_proxy().build() {
|
|
|
- Ok(client) => {
|
|
|
- let builder = client.put(&server).headers(headers).json(&data);
|
|
|
- match builder.send().await {
|
|
|
- Ok(resp) => match resp.status().as_u16() {
|
|
|
- 204 => break,
|
|
|
- // 配置有问题不重试
|
|
|
- 400 => bail!("failed to update clash config with status 400"),
|
|
|
- status @ _ => report_err!(i, "failed to activate clash with status \"{status}\""),
|
|
|
- },
|
|
|
- Err(err) => report_err!(i, "{err}"),
|
|
|
- }
|
|
|
+ // retry 5 times
|
|
|
+ for i in 0..5 {
|
|
|
+ let headers = headers.clone();
|
|
|
+ match reqwest::ClientBuilder::new().no_proxy().build() {
|
|
|
+ Ok(client) => {
|
|
|
+ let builder = client.put(&server).headers(headers).json(&data);
|
|
|
+ match builder.send().await {
|
|
|
+ Ok(resp) => match resp.status().as_u16() {
|
|
|
+ 204 => break,
|
|
|
+ // 配置有问题不重试
|
|
|
+ 400 => bail!("failed to update clash config with status 400"),
|
|
|
+ status @ _ => {
|
|
|
+ report_err!(i, "failed to activate clash with status \"{status}\"")
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Err(err) => report_err!(i, "{err}"),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(err) => report_err!(i, "{err}"),
|
|
|
+ }
|
|
|
+ sleep(Duration::from_millis(500)).await;
|
|
|
}
|
|
|
- Err(err) => report_err!(i, "{err}"),
|
|
|
- }
|
|
|
- sleep(Duration::from_millis(500)).await;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /// patch clash config
|
|
|
- pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
|
|
- let (server, headers) = Self::clash_client_info(info)?;
|
|
|
-
|
|
|
- let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
|
|
- let builder = client.patch(&server).headers(headers.clone()).json(&config);
|
|
|
- builder.send().await?;
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /// get clash client url and headers from clash info
|
|
|
- fn clash_client_info(info: ClashInfo) -> Result<(String, HeaderMap)> {
|
|
|
- if info.server.is_none() {
|
|
|
- let status = &info.status;
|
|
|
- if info.port.is_none() {
|
|
|
- bail!("failed to parse config.yaml file with status {status}");
|
|
|
- } else {
|
|
|
- bail!("failed to parse the server with status {status}");
|
|
|
- }
|
|
|
+ /// patch clash config
|
|
|
+ pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
|
|
+ let (server, headers) = Self::clash_client_info(info)?;
|
|
|
+
|
|
|
+ let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
|
|
+ let builder = client.patch(&server).headers(headers.clone()).json(&config);
|
|
|
+ builder.send().await?;
|
|
|
+ Ok(())
|
|
|
}
|
|
|
|
|
|
- let server = info.server.unwrap();
|
|
|
- let server = format!("http://{server}/configs");
|
|
|
+ /// get clash client url and headers from clash info
|
|
|
+ fn clash_client_info(info: ClashInfo) -> Result<(String, HeaderMap)> {
|
|
|
+ if info.server.is_none() {
|
|
|
+ let status = &info.status;
|
|
|
+ if info.port.is_none() {
|
|
|
+ bail!("failed to parse config.yaml file with status {status}");
|
|
|
+ } else {
|
|
|
+ bail!("failed to parse the server with status {status}");
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- let mut headers = HeaderMap::new();
|
|
|
- headers.insert("Content-Type", "application/json".parse().unwrap());
|
|
|
+ let server = info.server.unwrap();
|
|
|
+ let server = format!("http://{server}/configs");
|
|
|
|
|
|
- if let Some(secret) = info.secret.as_ref() {
|
|
|
- let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
|
|
|
- headers.insert("Authorization", secret);
|
|
|
- }
|
|
|
+ let mut headers = HeaderMap::new();
|
|
|
+ headers.insert("Content-Type", "application/json".parse().unwrap());
|
|
|
|
|
|
- Ok((server, headers))
|
|
|
- }
|
|
|
+ if let Some(secret) = info.secret.as_ref() {
|
|
|
+ let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
|
|
|
+ headers.insert("Authorization", secret);
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok((server, headers))
|
|
|
+ }
|
|
|
|
|
|
- /// kill old clash process
|
|
|
- pub fn kill_old_clash() {
|
|
|
- use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
|
|
+ /// kill old clash process
|
|
|
+ pub fn kill_old_clash() {
|
|
|
+ use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
|
|
|
|
|
- if let Ok(pid) = fs::read(dirs::clash_pid_path()) {
|
|
|
- if let Ok(pid) = String::from_utf8_lossy(&pid).parse() {
|
|
|
- let mut system = System::new();
|
|
|
- system.refresh_all();
|
|
|
+ if let Ok(pid) = fs::read(dirs::clash_pid_path()) {
|
|
|
+ if let Ok(pid) = String::from_utf8_lossy(&pid).parse() {
|
|
|
+ let mut system = System::new();
|
|
|
+ system.refresh_all();
|
|
|
|
|
|
- let proc = system.process(Pid::from_u32(pid));
|
|
|
- if let Some(proc) = proc {
|
|
|
- proc.kill();
|
|
|
+ let proc = system.process(Pid::from_u32(pid));
|
|
|
+ if let Some(proc) = proc {
|
|
|
+ proc.kill();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
}
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
impl Drop for Service {
|
|
|
- fn drop(&mut self) {
|
|
|
- log_if_err!(self.stop());
|
|
|
- }
|
|
|
+ fn drop(&mut self) {
|
|
|
+ log_if_err!(self.stop());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// ### Service Mode
|
|
|
///
|
|
|
#[cfg(target_os = "windows")]
|
|
|
pub mod win_service {
|
|
|
- use super::*;
|
|
|
- use anyhow::Context;
|
|
|
- use deelevate::{PrivilegeLevel, Token};
|
|
|
- use runas::Command as RunasCommand;
|
|
|
- use serde::{Deserialize, Serialize};
|
|
|
- use std::os::windows::process::CommandExt;
|
|
|
- use std::{env::current_exe, process::Command as StdCommand};
|
|
|
-
|
|
|
- const SERVICE_NAME: &str = "clash_verge_service";
|
|
|
-
|
|
|
- const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
|
|
-
|
|
|
- #[derive(Debug, Deserialize, Serialize, Clone)]
|
|
|
- pub struct ResponseBody {
|
|
|
- pub bin_path: String,
|
|
|
- pub config_dir: String,
|
|
|
- pub log_file: String,
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Debug, Deserialize, Serialize, Clone)]
|
|
|
- pub struct JsonResponse {
|
|
|
- pub code: u64,
|
|
|
- pub msg: String,
|
|
|
- pub data: Option<ResponseBody>,
|
|
|
- }
|
|
|
-
|
|
|
- impl Service {
|
|
|
- /// Install the Clash Verge Service
|
|
|
- /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
|
|
- pub async fn install_service() -> Result<()> {
|
|
|
- let binary_path = dirs::service_path();
|
|
|
- let install_path = binary_path.with_file_name("install-service.exe");
|
|
|
-
|
|
|
- if !install_path.exists() {
|
|
|
- bail!("installer exe not found");
|
|
|
- }
|
|
|
-
|
|
|
- let token = Token::with_current_process()?;
|
|
|
- let level = token.privilege_level()?;
|
|
|
-
|
|
|
- let status = match level {
|
|
|
- PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
|
|
|
- _ => StdCommand::new(install_path)
|
|
|
- .creation_flags(0x08000000)
|
|
|
- .status()?,
|
|
|
- };
|
|
|
-
|
|
|
- if !status.success() {
|
|
|
- bail!(
|
|
|
- "failed to install service with status {}",
|
|
|
- status.code().unwrap()
|
|
|
- );
|
|
|
- }
|
|
|
+ use super::*;
|
|
|
+ use anyhow::Context;
|
|
|
+ use deelevate::{PrivilegeLevel, Token};
|
|
|
+ use runas::Command as RunasCommand;
|
|
|
+ use serde::{Deserialize, Serialize};
|
|
|
+ use std::os::windows::process::CommandExt;
|
|
|
+ use std::{env::current_exe, process::Command as StdCommand};
|
|
|
+
|
|
|
+ const SERVICE_NAME: &str = "clash_verge_service";
|
|
|
+
|
|
|
+ const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
|
|
+
|
|
|
+ #[derive(Debug, Deserialize, Serialize, Clone)]
|
|
|
+ pub struct ResponseBody {
|
|
|
+ pub bin_path: String,
|
|
|
+ pub config_dir: String,
|
|
|
+ pub log_file: String,
|
|
|
+ }
|
|
|
|
|
|
- Ok(())
|
|
|
+ #[derive(Debug, Deserialize, Serialize, Clone)]
|
|
|
+ pub struct JsonResponse {
|
|
|
+ pub code: u64,
|
|
|
+ pub msg: String,
|
|
|
+ pub data: Option<ResponseBody>,
|
|
|
}
|
|
|
|
|
|
- /// Uninstall the Clash Verge Service
|
|
|
- /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
|
|
- pub async fn uninstall_service() -> Result<()> {
|
|
|
- let binary_path = dirs::service_path();
|
|
|
- let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
|
|
+ impl Service {
|
|
|
+ /// Install the Clash Verge Service
|
|
|
+ /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
|
|
+ pub async fn install_service() -> Result<()> {
|
|
|
+ let binary_path = dirs::service_path();
|
|
|
+ let install_path = binary_path.with_file_name("install-service.exe");
|
|
|
|
|
|
- if !uninstall_path.exists() {
|
|
|
- bail!("uninstaller exe not found");
|
|
|
- }
|
|
|
+ if !install_path.exists() {
|
|
|
+ bail!("installer exe not found");
|
|
|
+ }
|
|
|
|
|
|
- let token = Token::with_current_process()?;
|
|
|
- let level = token.privilege_level()?;
|
|
|
+ let token = Token::with_current_process()?;
|
|
|
+ let level = token.privilege_level()?;
|
|
|
+
|
|
|
+ let status = match level {
|
|
|
+ PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
|
|
|
+ _ => StdCommand::new(install_path)
|
|
|
+ .creation_flags(0x08000000)
|
|
|
+ .status()?,
|
|
|
+ };
|
|
|
+
|
|
|
+ if !status.success() {
|
|
|
+ bail!(
|
|
|
+ "failed to install service with status {}",
|
|
|
+ status.code().unwrap()
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- let status = match level {
|
|
|
- PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?,
|
|
|
- _ => StdCommand::new(uninstall_path)
|
|
|
- .creation_flags(0x08000000)
|
|
|
- .status()?,
|
|
|
- };
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
|
|
|
- if !status.success() {
|
|
|
- bail!(
|
|
|
- "failed to uninstall service with status {}",
|
|
|
- status.code().unwrap()
|
|
|
- );
|
|
|
- }
|
|
|
+ /// Uninstall the Clash Verge Service
|
|
|
+ /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
|
|
+ pub async fn uninstall_service() -> Result<()> {
|
|
|
+ let binary_path = dirs::service_path();
|
|
|
+ let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
|
|
|
|
|
|
- Ok(())
|
|
|
- }
|
|
|
+ if !uninstall_path.exists() {
|
|
|
+ bail!("uninstaller exe not found");
|
|
|
+ }
|
|
|
|
|
|
- /// [deprecated]
|
|
|
- /// start service
|
|
|
- /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
|
|
- pub async fn start_service() -> Result<()> {
|
|
|
- let token = Token::with_current_process()?;
|
|
|
- let level = token.privilege_level()?;
|
|
|
+ let token = Token::with_current_process()?;
|
|
|
+ let level = token.privilege_level()?;
|
|
|
+
|
|
|
+ let status = match level {
|
|
|
+ PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?,
|
|
|
+ _ => StdCommand::new(uninstall_path)
|
|
|
+ .creation_flags(0x08000000)
|
|
|
+ .status()?,
|
|
|
+ };
|
|
|
+
|
|
|
+ if !status.success() {
|
|
|
+ bail!(
|
|
|
+ "failed to uninstall service with status {}",
|
|
|
+ status.code().unwrap()
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- let args = ["start", SERVICE_NAME];
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
|
|
|
- let status = match level {
|
|
|
- PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?,
|
|
|
- _ => StdCommand::new("sc").args(&args).status()?,
|
|
|
- };
|
|
|
+ /// [deprecated]
|
|
|
+ /// start service
|
|
|
+ /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
|
|
+ pub async fn start_service() -> Result<()> {
|
|
|
+ let token = Token::with_current_process()?;
|
|
|
+ let level = token.privilege_level()?;
|
|
|
+
|
|
|
+ let args = ["start", SERVICE_NAME];
|
|
|
+
|
|
|
+ let status = match level {
|
|
|
+ PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?,
|
|
|
+ _ => StdCommand::new("sc").args(&args).status()?,
|
|
|
+ };
|
|
|
+
|
|
|
+ match status.success() {
|
|
|
+ true => Ok(()),
|
|
|
+ false => bail!(
|
|
|
+ "failed to start service with status {}",
|
|
|
+ status.code().unwrap()
|
|
|
+ ),
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- match status.success() {
|
|
|
- true => Ok(()),
|
|
|
- false => bail!(
|
|
|
- "failed to start service with status {}",
|
|
|
- status.code().unwrap()
|
|
|
- ),
|
|
|
- }
|
|
|
- }
|
|
|
+ /// stop service
|
|
|
+ pub async fn stop_service() -> Result<()> {
|
|
|
+ let url = format!("{SERVICE_URL}/stop_service");
|
|
|
+ let res = reqwest::ClientBuilder::new()
|
|
|
+ .no_proxy()
|
|
|
+ .build()?
|
|
|
+ .post(url)
|
|
|
+ .send()
|
|
|
+ .await?
|
|
|
+ .json::<JsonResponse>()
|
|
|
+ .await
|
|
|
+ .context("failed to connect to the Clash Verge Service")?;
|
|
|
+
|
|
|
+ if res.code != 0 {
|
|
|
+ bail!(res.msg);
|
|
|
+ }
|
|
|
|
|
|
- /// stop service
|
|
|
- pub async fn stop_service() -> Result<()> {
|
|
|
- let url = format!("{SERVICE_URL}/stop_service");
|
|
|
- let res = reqwest::ClientBuilder::new()
|
|
|
- .no_proxy()
|
|
|
- .build()?
|
|
|
- .post(url)
|
|
|
- .send()
|
|
|
- .await?
|
|
|
- .json::<JsonResponse>()
|
|
|
- .await
|
|
|
- .context("failed to connect to the Clash Verge Service")?;
|
|
|
-
|
|
|
- if res.code != 0 {
|
|
|
- bail!(res.msg);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
|
|
|
- /// check the windows service status
|
|
|
- pub async fn check_service() -> Result<JsonResponse> {
|
|
|
- let url = format!("{SERVICE_URL}/get_clash");
|
|
|
- let response = reqwest::ClientBuilder::new()
|
|
|
- .no_proxy()
|
|
|
- .build()?
|
|
|
- .get(url)
|
|
|
- .send()
|
|
|
- .await?
|
|
|
- .json::<JsonResponse>()
|
|
|
- .await
|
|
|
- .context("failed to connect to the Clash Verge Service")?;
|
|
|
-
|
|
|
- Ok(response)
|
|
|
- }
|
|
|
+ /// check the windows service status
|
|
|
+ pub async fn check_service() -> Result<JsonResponse> {
|
|
|
+ let url = format!("{SERVICE_URL}/get_clash");
|
|
|
+ let response = reqwest::ClientBuilder::new()
|
|
|
+ .no_proxy()
|
|
|
+ .build()?
|
|
|
+ .get(url)
|
|
|
+ .send()
|
|
|
+ .await?
|
|
|
+ .json::<JsonResponse>()
|
|
|
+ .await
|
|
|
+ .context("failed to connect to the Clash Verge Service")?;
|
|
|
+
|
|
|
+ Ok(response)
|
|
|
+ }
|
|
|
|
|
|
- /// start the clash by service
|
|
|
- pub(super) async fn start_clash_by_service() -> Result<()> {
|
|
|
- let status = Self::check_service().await?;
|
|
|
+ /// start the clash by service
|
|
|
+ pub(super) async fn start_clash_by_service() -> Result<()> {
|
|
|
+ let status = Self::check_service().await?;
|
|
|
|
|
|
- if status.code == 0 {
|
|
|
- Self::stop_clash_by_service().await?;
|
|
|
- sleep(Duration::from_secs(1)).await;
|
|
|
- }
|
|
|
+ if status.code == 0 {
|
|
|
+ Self::stop_clash_by_service().await?;
|
|
|
+ sleep(Duration::from_secs(1)).await;
|
|
|
+ }
|
|
|
|
|
|
- let clash_core = {
|
|
|
- let global = Data::global();
|
|
|
- let verge = global.verge.lock();
|
|
|
- verge.clash_core.clone().unwrap_or("clash".into())
|
|
|
- };
|
|
|
+ let clash_core = {
|
|
|
+ let global = Data::global();
|
|
|
+ let verge = global.verge.lock();
|
|
|
+ verge.clash_core.clone().unwrap_or("clash".into())
|
|
|
+ };
|
|
|
+
|
|
|
+ let clash_bin = format!("{clash_core}.exe");
|
|
|
+ let bin_path = current_exe().unwrap().with_file_name(clash_bin);
|
|
|
+ let bin_path = bin_path.as_os_str().to_str().unwrap();
|
|
|
+
|
|
|
+ let config_dir = dirs::app_home_dir();
|
|
|
+ let config_dir = config_dir.as_os_str().to_str().unwrap();
|
|
|
+
|
|
|
+ let log_path = dirs::service_log_file();
|
|
|
+ let log_path = log_path.as_os_str().to_str().unwrap();
|
|
|
+
|
|
|
+ let mut map = HashMap::new();
|
|
|
+ map.insert("bin_path", bin_path);
|
|
|
+ map.insert("config_dir", config_dir);
|
|
|
+ map.insert("log_file", log_path);
|
|
|
+
|
|
|
+ let url = format!("{SERVICE_URL}/start_clash");
|
|
|
+ let res = reqwest::ClientBuilder::new()
|
|
|
+ .no_proxy()
|
|
|
+ .build()?
|
|
|
+ .post(url)
|
|
|
+ .json(&map)
|
|
|
+ .send()
|
|
|
+ .await?
|
|
|
+ .json::<JsonResponse>()
|
|
|
+ .await
|
|
|
+ .context("failed to connect to the Clash Verge Service")?;
|
|
|
+
|
|
|
+ if res.code != 0 {
|
|
|
+ bail!(res.msg);
|
|
|
+ }
|
|
|
|
|
|
- let clash_bin = format!("{clash_core}.exe");
|
|
|
- let bin_path = current_exe().unwrap().with_file_name(clash_bin);
|
|
|
- let bin_path = bin_path.as_os_str().to_str().unwrap();
|
|
|
-
|
|
|
- let config_dir = dirs::app_home_dir();
|
|
|
- let config_dir = config_dir.as_os_str().to_str().unwrap();
|
|
|
-
|
|
|
- let log_path = dirs::service_log_file();
|
|
|
- let log_path = log_path.as_os_str().to_str().unwrap();
|
|
|
-
|
|
|
- let mut map = HashMap::new();
|
|
|
- map.insert("bin_path", bin_path);
|
|
|
- map.insert("config_dir", config_dir);
|
|
|
- map.insert("log_file", log_path);
|
|
|
-
|
|
|
- let url = format!("{SERVICE_URL}/start_clash");
|
|
|
- let res = reqwest::ClientBuilder::new()
|
|
|
- .no_proxy()
|
|
|
- .build()?
|
|
|
- .post(url)
|
|
|
- .json(&map)
|
|
|
- .send()
|
|
|
- .await?
|
|
|
- .json::<JsonResponse>()
|
|
|
- .await
|
|
|
- .context("failed to connect to the Clash Verge Service")?;
|
|
|
-
|
|
|
- if res.code != 0 {
|
|
|
- bail!(res.msg);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// stop the clash by service
|
|
|
+ pub(super) async fn stop_clash_by_service() -> Result<()> {
|
|
|
+ let url = format!("{SERVICE_URL}/stop_clash");
|
|
|
+ let res = reqwest::ClientBuilder::new()
|
|
|
+ .no_proxy()
|
|
|
+ .build()?
|
|
|
+ .post(url)
|
|
|
+ .send()
|
|
|
+ .await?
|
|
|
+ .json::<JsonResponse>()
|
|
|
+ .await
|
|
|
+ .context("failed to connect to the Clash Verge Service")?;
|
|
|
+
|
|
|
+ if res.code != 0 {
|
|
|
+ bail!(res.msg);
|
|
|
+ }
|
|
|
|
|
|
- /// stop the clash by service
|
|
|
- pub(super) async fn stop_clash_by_service() -> Result<()> {
|
|
|
- let url = format!("{SERVICE_URL}/stop_clash");
|
|
|
- let res = reqwest::ClientBuilder::new()
|
|
|
- .no_proxy()
|
|
|
- .build()?
|
|
|
- .post(url)
|
|
|
- .send()
|
|
|
- .await?
|
|
|
- .json::<JsonResponse>()
|
|
|
- .await
|
|
|
- .context("failed to connect to the Clash Verge Service")?;
|
|
|
-
|
|
|
- if res.code != 0 {
|
|
|
- bail!(res.msg);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
}
|