refactor: wip

This commit is contained in:
GyDi
2022-11-14 01:26:33 +08:00
parent afc37c71a6
commit 837422fbb8
32 changed files with 2704 additions and 880 deletions

View File

@@ -0,0 +1,66 @@
use crate::{config, utils::dirs};
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde_yaml::Mapping;
use std::collections::HashMap;
/// PUT /configs
pub async fn put_configs() -> Result<()> {
let (url, headers) = clash_client_info()?;
let url = format!("{url}/configs");
let runtime_yaml = dirs::clash_runtime_yaml();
let runtime_yaml = dirs::path_to_str(&runtime_yaml)?;
let mut data = HashMap::new();
data.insert("path", runtime_yaml);
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 @ _ => {
bail!("failed to put configs with status \"{status}\"")
}
}
}
/// 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(())
}
/// 根据clash info获取clash服务地址和请求头
fn clash_client_info() -> Result<(String, HeaderMap)> {
let info = { config::ClashN::global().info.lock().clone() };
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 server = info.server.unwrap();
let server = format!("http://{server}");
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?);
if let Some(secret) = info.secret.as_ref() {
let secret = format!("Bearer {}", secret.clone()).parse()?;
headers.insert("Authorization", secret);
}
Ok((server, headers))
}

262
src-tauri/src/core/core.rs Normal file
View File

@@ -0,0 +1,262 @@
use super::{clash_api, logger::Logger};
use crate::{
config::*,
enhance,
utils::{self, dirs},
};
use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{fs, io::Write, sync::Arc, time::Duration};
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep;
#[derive(Debug)]
pub struct CoreManager {
clash_core: Arc<Mutex<String>>,
sidecar: Arc<Mutex<Option<CommandChild>>>,
#[allow(unused)]
use_service_mode: Arc<Mutex<bool>>,
pub runtime_config: Arc<Mutex<RuntimeResult>>,
}
impl CoreManager {
pub fn global() -> &'static CoreManager {
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
CORE_MANAGER.get_or_init(|| CoreManager {
clash_core: Arc::new(Mutex::new("clash".into())),
sidecar: Arc::new(Mutex::new(None)),
runtime_config: Arc::new(Mutex::new(RuntimeResult::default())),
use_service_mode: Arc::new(Mutex::new(false)),
})
}
pub fn init(&self) -> Result<()> {
// kill old clash process
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();
system.process(Pid::from_u32(pid)).map(|proc| {
if proc.name().contains("clash") {
proc.kill();
}
});
}
}
// 使用配置的核心
let verge = VergeN::global().config.lock();
if let Some(verge_core) = verge.clash_core.as_ref() {
if verge_core == "clash" || verge_core == "clash-meta" {
let mut clash_core = self.clash_core.lock();
*clash_core = verge_core.clone();
}
}
// 启动clash
self.run_core()?;
// 更新配置
tauri::async_runtime::spawn(async {
sleep(Duration::from_millis(100)).await;
crate::log_err!(Self::global().activate_config().await);
});
Ok(())
}
/// 检查配置是否正确
pub fn check_config(&self) -> Result<()> {
let config_path = dirs::clash_runtime_yaml();
let config_path = dirs::path_to_str(&config_path)?;
let clash_core = { self.clash_core.lock().clone() };
let output = Command::new_sidecar(clash_core)?
.args(["-t", config_path])
.output()?;
if !output.status.success() {
Logger::global().set_log(output.stderr.clone());
bail!("{}", output.stderr); // 过滤掉终端颜色值
}
Ok(())
}
/// 启动核心
pub fn run_core(&self) -> Result<()> {
// 先纠正重要的配置字段
self.correct_config()?;
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
let _ = child.kill();
}
let app_dir = dirs::app_home_dir();
let app_dir = dirs::path_to_str(&app_dir)?;
let clash_core = { self.clash_core.lock().clone() };
// 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写入文件中
crate::log_err!({
let pid = cmd_child.pid();
let path = dirs::clash_pid_path();
fs::File::create(path)
.context("failed to create the pid file")?
.write(format!("{pid}").as_bytes())
.context("failed to write pid to the file")?;
<Result<()>>::Ok(())
});
*sidecar = Some(cmd_child);
tauri::async_runtime::spawn(async move {
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);
Logger::global().set_log(line);
}
CommandEvent::Stderr(err) => {
log::error!(target: "app" ,"[clash error]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Error(err) => {
log::error!(target: "app" ,"[clash error]: {err}");
Logger::global().set_log(err);
}
CommandEvent::Terminated(_) => {
log::info!(target: "app" ,"clash core Terminated");
break;
}
_ => {}
}
}
});
Ok(())
}
/// 停止核心运行
pub fn stop_core(&self) -> Result<()> {
let mut sidecar = self.sidecar.lock();
if let Some(child) = sidecar.take() {
let _ = child.kill();
}
Ok(())
}
/// 切换核心
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
if &clash_core != "clash" && &clash_core != "clash-meta" {
bail!("invalid clash core name \"{clash_core}\"");
}
// 清掉旧日志
Logger::global().clear_log();
let mut self_core = self.clash_core.lock();
let old_core = self_core.clone(); // 保存一下旧值
*self_core = clash_core.clone();
drop(self_core);
match self.run_core() {
Ok(_) => {
// 更新到配置文件
let mut verge = VergeN::global().config.lock();
verge.clash_core = Some(clash_core);
drop(verge);
let _ = VergeN::global().save_file();
sleep(Duration::from_millis(100)).await; // 等一会儿再更新配置
self.activate_config().await?;
Ok(())
}
Err(err) => {
// 恢复旧的值
let mut self_core = self.clash_core.lock();
*self_core = old_core;
Err(err)
}
}
}
/// 纠正一下配置
/// 将mixed-port和external-controller都改为配置的内容
pub fn correct_config(&self) -> Result<()> {
// todo!()
Ok(())
}
/// 激活一个配置
pub async fn activate_config(&self) -> Result<()> {
let clash_config = { ClashN::global().config.lock().clone() };
let tun_mode = { VergeN::global().config.lock().enable_tun_mode.clone() };
let tun_mode = tun_mode.unwrap_or(false);
let pa = { ProfilesN::global().config.lock().gen_activate()? };
let (config, exists_keys, logs) =
enhance::enhance_config(clash_config, pa.current, pa.chain, pa.valid, tun_mode);
// 保存到文件中
let runtime_path = dirs::clash_runtime_yaml();
utils::config::save_yaml(runtime_path, &config, Some("# Clash Verge Runtime Config"))?;
// 检查配置是否正常
self.check_config()?;
// todo 是否需要检查核心是否运行
// 发送请求 发送5次
for i in 0..5 {
match clash_api::put_configs().await {
Ok(_) => break,
Err(err) => {
if i < 4 {
log::error!(target: "app", "{err}");
} else {
bail!(err);
}
}
}
sleep(Duration::from_millis(250)).await;
}
// 保存结果
let mut runtime = self.runtime_config.lock();
let config_yaml = Some(serde_yaml::to_string(&config).unwrap_or("".into()));
*runtime = RuntimeResult {
config: Some(config),
config_yaml,
exists_keys,
chain_logs: logs,
};
Ok(())
}
}

View File

@@ -0,0 +1,217 @@
#![cfg(target_os = "windows")]
use crate::utils::{config, dirs};
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()
);
}
Ok(())
}
/// 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");
if !uninstall_path.exists() {
bail!("uninstaller exe not found");
}
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()
);
}
Ok(())
}
/// [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()
),
}
}
/// 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(())
}
/// 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?;
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_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(())
}
/// 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(())
}
}

View File

@@ -1,65 +1,77 @@
use super::tray::Tray;
use crate::log_if_err;
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::sync::Arc;
use tauri::{AppHandle, Manager, Window};
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Option<AppHandle>,
pub app_handle: Arc<Mutex<Option<AppHandle>>>,
}
impl Handle {
pub fn set_inner(&mut self, app_handle: AppHandle) {
self.app_handle = Some(app_handle);
pub fn global() -> &'static Handle {
static HANDLE: OnceCell<Handle> = OnceCell::new();
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(Mutex::new(None)),
})
}
pub fn init(&self, app_handle: AppHandle) {
*self.app_handle.lock() = Some(app_handle);
}
pub fn get_window(&self) -> Option<Window> {
self.app_handle
.lock()
.as_ref()
.map_or(None, |a| a.get_window("main"))
}
pub fn refresh_clash(&self) {
if let Some(window) = self.get_window() {
pub fn refresh_clash() {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
}
}
pub fn refresh_verge(&self) {
if let Some(window) = self.get_window() {
pub fn refresh_verge() {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
}
}
#[allow(unused)]
pub fn refresh_profiles(&self) {
if let Some(window) = self.get_window() {
pub fn refresh_profiles() {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
}
}
pub fn notice_message(&self, status: String, msg: String) {
if let Some(window) = self.get_window() {
log_if_err!(window.emit("verge://notice-message", (status, msg)));
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
if let Some(window) = Self::global().get_window() {
log_if_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
}
}
pub fn update_systray(&self) -> Result<()> {
if self.app_handle.is_none() {
bail!("update_systray unhandle error");
pub fn update_systray() -> Result<()> {
let app_handle = Self::global().app_handle.lock();
if app_handle.is_none() {
bail!("update_systray unhandled error");
}
let app_handle = self.app_handle.as_ref().unwrap();
Tray::update_systray(app_handle)?;
Tray::update_systray(app_handle.as_ref().unwrap())?;
Ok(())
}
/// update the system tray state
pub fn update_systray_part(&self) -> Result<()> {
if self.app_handle.is_none() {
bail!("update_systray unhandle error");
pub fn update_systray_part() -> Result<()> {
let app_handle = Self::global().app_handle.lock();
if app_handle.is_none() {
bail!("update_systray unhandled error");
}
let app_handle = self.app_handle.as_ref().unwrap();
Tray::update_part(app_handle)?;
Tray::update_part(app_handle.as_ref().unwrap())?;
Ok(())
}
}

View File

@@ -1,25 +1,30 @@
use crate::{data::*, feat, log_if_err};
use crate::{config, feat, log_err};
use anyhow::{bail, Result};
use std::collections::HashMap;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use tauri::{AppHandle, GlobalShortcutManager};
pub struct Hotkey {
current: Vec<String>, // 保存当前的热键设置
manager: Option<AppHandle>,
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
app_handle: Arc<Mutex<Option<AppHandle>>>,
}
impl Hotkey {
pub fn new() -> Hotkey {
Hotkey {
current: Vec::new(),
manager: None,
}
pub fn global() -> &'static Hotkey {
static HOTKEY: OnceCell<Hotkey> = OnceCell::new();
HOTKEY.get_or_init(|| Hotkey {
current: Arc::new(Mutex::new(Vec::new())),
app_handle: Arc::new(Mutex::new(None)),
})
}
pub fn init(&mut self, app_handle: AppHandle) -> Result<()> {
self.manager = Some(app_handle);
let data = Data::global();
let verge = data.verge.lock();
pub fn init(&self, app_handle: AppHandle) -> Result<()> {
*self.app_handle.lock() = Some(app_handle);
let verge = config::VergeN::global().config.lock();
if let Some(hotkeys) = verge.hotkeys.as_ref() {
for hotkey in hotkeys.iter() {
@@ -28,25 +33,26 @@ impl Hotkey {
let key = iter.next();
if func.is_some() && key.is_some() {
log_if_err!(self.register(key.unwrap(), func.unwrap()));
log_err!(self.register(key.unwrap(), func.unwrap()));
} else {
log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None"));
}
}
self.current = hotkeys.clone();
*self.current.lock() = hotkeys.clone();
}
Ok(())
}
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
if self.manager.is_none() {
let app_handle = self.app_handle.lock();
if app_handle.is_none() {
bail!("failed to get hotkey manager");
}
Ok(self.manager.as_ref().unwrap().global_shortcut_manager())
Ok(app_handle.as_ref().unwrap().global_shortcut_manager())
}
fn register(&mut self, hotkey: &str, func: &str) -> Result<()> {
fn register(&self, hotkey: &str, func: &str) -> Result<()> {
let mut manager = self.get_manager()?;
if manager.is_registered(hotkey)? {
@@ -54,16 +60,16 @@ impl Hotkey {
}
let f = match func.trim() {
"clash_mode_rule" => || feat::change_clash_mode("rule"),
"clash_mode_global" => || feat::change_clash_mode("global"),
"clash_mode_direct" => || feat::change_clash_mode("direct"),
"clash_mode_script" => || feat::change_clash_mode("script"),
"toggle_system_proxy" => || feat::toggle_system_proxy(),
"enable_system_proxy" => || feat::enable_system_proxy(),
"disable_system_proxy" => || feat::disable_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(),
"enable_tun_mode" => || feat::enable_tun_mode(),
"disable_tun_mode" => || feat::disable_tun_mode(),
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"clash_mode_script" => || feat::change_clash_mode("script".into()),
"toggle_system_proxy" => || log_err!(feat::toggle_system_proxy()),
"enable_system_proxy" => || log_err!(feat::enable_system_proxy()),
"disable_system_proxy" => || log_err!(feat::disable_system_proxy()),
"toggle_tun_mode" => || log_err!(feat::toggle_tun_mode()),
"enable_tun_mode" => || log_err!(feat::enable_tun_mode()),
"disable_tun_mode" => || log_err!(feat::disable_tun_mode()),
_ => bail!("invalid function \"{func}\""),
};
@@ -73,14 +79,14 @@ impl Hotkey {
Ok(())
}
fn unregister(&mut self, hotkey: &str) -> Result<()> {
fn unregister(&self, hotkey: &str) -> Result<()> {
self.get_manager()?.unregister(&hotkey)?;
log::info!(target: "app", "unregister hotkey {hotkey}");
Ok(())
}
pub fn update(&mut self, new_hotkeys: Vec<String>) -> Result<()> {
let current = self.current.to_owned();
pub fn update(&self, new_hotkeys: Vec<String>) -> Result<()> {
let mut current = self.current.lock();
let old_map = Self::get_map_from_vec(&current);
let new_map = Self::get_map_from_vec(&new_hotkeys);
@@ -91,10 +97,10 @@ impl Hotkey {
});
add.iter().for_each(|(key, func)| {
log_if_err!(self.register(key, func));
log_err!(self.register(key, func));
});
self.current = new_hotkeys;
*current = new_hotkeys;
Ok(())
}

View File

@@ -0,0 +1,36 @@
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::VecDeque, sync::Arc};
const LOGS_QUEUE_LEN: usize = 100;
pub struct Logger {
log_data: Arc<Mutex<VecDeque<String>>>,
}
impl Logger {
pub fn global() -> &'static Logger {
static LOGGER: OnceCell<Logger> = OnceCell::new();
LOGGER.get_or_init(|| Logger {
log_data: Arc::new(Mutex::new(VecDeque::with_capacity(LOGS_QUEUE_LEN + 10))),
})
}
pub fn get_log(&self) -> VecDeque<String> {
self.log_data.lock().clone()
}
pub fn set_log(&self, text: String) {
let mut logs = self.log_data.lock();
if logs.len() > LOGS_QUEUE_LEN {
logs.pop_front();
}
logs.push_back(text);
}
pub fn clear_log(&self) {
let mut logs = self.log_data.lock();
logs.clear();
}
}

View File

@@ -1,349 +1,347 @@
use self::handle::Handle;
use self::hotkey::Hotkey;
use self::sysopt::Sysopt;
use self::timer::Timer;
use crate::config::enhance_config;
use crate::data::*;
use crate::log_if_err;
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use serde_yaml::{Mapping, Value};
use std::sync::Arc;
// use self::handle::Handle;
// use self::hotkey::Hotkey;
// use self::sysopt::Sysopt;
// use self::timer::Timer;
// // use crate::data::*;
// // use crate::enhance::enhance_config;
// use crate::log_if_err;
// use anyhow::{bail, Result};
// use once_cell::sync::OnceCell;
// use parking_lot::Mutex;
// use serde_yaml::{Mapping, Value};
// use std::sync::Arc;
mod handle;
mod hotkey;
mod service;
mod sysopt;
mod timer;
pub mod clash_api;
mod core;
pub mod core_service;
pub mod handle;
pub mod hotkey;
pub mod logger;
// pub mod service;
pub mod sysopt;
pub mod timer;
pub mod tray;
pub use self::service::*;
pub use self::core::*;
// pub use self::service::*;
#[derive(Clone)]
pub struct Core {
pub service: Arc<Mutex<Service>>,
pub sysopt: Arc<Mutex<Sysopt>>,
pub timer: Arc<Mutex<Timer>>,
pub hotkey: Arc<Mutex<Hotkey>>,
pub runtime: Arc<Mutex<RuntimeResult>>,
pub handle: Arc<Mutex<Handle>>,
}
pub struct Core {}
impl Core {
pub fn global() -> &'static Core {
static CORE: OnceCell<Core> = OnceCell::new();
// pub fn global() -> &'static Core {
// static CORE: OnceCell<Core> = OnceCell::new();
CORE.get_or_init(|| Core {
service: Arc::new(Mutex::new(Service::new())),
sysopt: Arc::new(Mutex::new(Sysopt::new())),
timer: Arc::new(Mutex::new(Timer::new())),
hotkey: Arc::new(Mutex::new(Hotkey::new())),
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
handle: Arc::new(Mutex::new(Handle::default())),
})
}
// CORE.get_or_init(|| Core {
// service: Arc::new(Mutex::new(Service::new())),
// sysopt: Arc::new(Mutex::new(Sysopt::new())),
// timer: Arc::new(Mutex::new(Timer::new())),
// hotkey: Arc::new(Mutex::new(Hotkey::new())),
// runtime: Arc::new(Mutex::new(RuntimeResult::default())),
// handle: Arc::new(Mutex::new(Handle::default())),
// })
// }
/// initialize the core state
pub fn init(&self, app_handle: tauri::AppHandle) {
// kill old clash process
Service::kill_old_clash();
// /// initialize the core state
// pub fn init(&self, app_handle: tauri::AppHandle) {
// kill old clash process
// Service::kill_old_clash();
let mut handle = self.handle.lock();
handle.set_inner(app_handle.clone());
drop(handle);
// let mut handle = self.handle.lock();
// handle.set_inner(app_handle.clone());
// drop(handle);
let mut service = self.service.lock();
log_if_err!(service.start());
drop(service);
// let mut service = self.service.lock();
// log_if_err!(service.start());
// drop(service);
log_if_err!(self.activate());
// log_if_err!(self.activate());
let mut sysopt = self.sysopt.lock();
log_if_err!(sysopt.init_launch());
log_if_err!(sysopt.init_sysproxy());
drop(sysopt);
// let mut sysopt = self.sysopt.lock();
// log_if_err!(sysopt.init_launch());
// log_if_err!(sysopt.init_sysproxy());
// drop(sysopt);
let handle = self.handle.lock();
log_if_err!(handle.update_systray_part());
drop(handle);
// let handle = self.handle.lock();
// log_if_err!(handle.update_systray_part());
// drop(handle);
let mut hotkey = self.hotkey.lock();
log_if_err!(hotkey.init(app_handle));
drop(hotkey);
// let mut hotkey = self.hotkey.lock();
// log_if_err!(hotkey.init(app_handle));
// drop(hotkey);
// timer initialize
let mut timer = self.timer.lock();
log_if_err!(timer.restore());
}
// // timer initialize
// let mut timer = self.timer.lock();
// log_if_err!(timer.restore());
// }
/// restart the clash sidecar
pub fn restart_clash(&self) -> Result<()> {
let mut service = self.service.lock();
service.restart()?;
drop(service);
self.activate()
}
// /// restart the clash sidecar
// pub fn restart_clash(&self) -> Result<()> {
// let mut service = self.service.lock();
// service.restart()?;
// drop(service);
// self.activate()
// }
/// change the clash core
pub fn change_core(&self, clash_core: Option<String>) -> Result<()> {
let clash_core = clash_core.unwrap_or("clash".into());
// /// change the clash core
// pub fn change_core(&self, clash_core: Option<String>) -> Result<()> {
// let clash_core = clash_core.unwrap_or("clash".into());
if &clash_core != "clash" && &clash_core != "clash-meta" {
bail!("invalid clash core name \"{clash_core}\"");
}
// if &clash_core != "clash" && &clash_core != "clash-meta" {
// bail!("invalid clash core name \"{clash_core}\"");
// }
let global = Data::global();
let mut verge = global.verge.lock();
verge.patch_config(Verge {
clash_core: Some(clash_core.clone()),
..Verge::default()
})?;
drop(verge);
// let global = Data::global();
// let mut verge = global.verge.lock();
// verge.patch_config(Verge {
// clash_core: Some(clash_core.clone()),
// ..Verge::default()
// })?;
// drop(verge);
let mut service = self.service.lock();
service.clear_logs();
service.restart()?;
drop(service);
// let mut service = self.service.lock();
// service.clear_logs();
// service.restart()?;
// drop(service);
self.activate()
}
// self.activate()
// }
/// Patch Clash
/// handle the clash config changed
pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
let patch_cloned = patch.clone();
let clash_mode = patch.get("mode");
let mixed_port = patch.get("mixed-port");
let external = patch.get("external-controller");
let secret = patch.get("secret");
// /// Patch Clash
// /// handle the clash config changed
// pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
// let patch_cloned = patch.clone();
// let clash_mode = patch.get("mode");
// let mixed_port = patch.get("mixed-port");
// let external = patch.get("external-controller");
// let secret = patch.get("secret");
let valid_port = {
let global = Data::global();
let mut clash = global.clash.lock();
clash.patch_config(patch_cloned)?;
clash.info.port.is_some()
};
// let valid_port = {
// let global = Data::global();
// let mut clash = global.clash.lock();
// clash.patch_config(patch_cloned)?;
// clash.info.port.is_some()
// };
// todo: port check
if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
let mut service = self.service.lock();
service.restart()?;
drop(service);
// // todo: port check
// if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() {
// let mut service = self.service.lock();
// service.restart()?;
// drop(service);
self.activate()?;
// self.activate()?;
let mut sysopt = self.sysopt.lock();
sysopt.init_sysproxy()?;
}
// let mut sysopt = self.sysopt.lock();
// sysopt.init_sysproxy()?;
// }
if clash_mode.is_some() {
let handle = self.handle.lock();
handle.update_systray_part()?;
}
// if clash_mode.is_some() {
// let handle = self.handle.lock();
// handle.update_systray_part()?;
// }
Ok(())
}
// Ok(())
// }
/// Patch Verge
pub fn patch_verge(&self, patch: Verge) -> Result<()> {
// save the patch
let global = Data::global();
let mut verge = global.verge.lock();
verge.patch_config(patch.clone())?;
drop(verge);
// /// Patch Verge
// pub fn patch_verge(&self, patch: Verge) -> Result<()> {
// // save the patch
// let global = Data::global();
// let mut verge = global.verge.lock();
// verge.patch_config(patch.clone())?;
// drop(verge);
let tun_mode = patch.enable_tun_mode;
let auto_launch = patch.enable_auto_launch;
let system_proxy = patch.enable_system_proxy;
let proxy_bypass = patch.system_proxy_bypass;
let proxy_guard = patch.enable_proxy_guard;
let language = patch.language;
// let tun_mode = patch.enable_tun_mode;
// let auto_launch = patch.enable_auto_launch;
// let system_proxy = patch.enable_system_proxy;
// let proxy_bypass = patch.system_proxy_bypass;
// let proxy_guard = patch.enable_proxy_guard;
// let language = patch.language;
#[cfg(target_os = "windows")]
{
let service_mode = patch.enable_service_mode;
// #[cfg(target_os = "windows")]
// {
// let service_mode = patch.enable_service_mode;
// 重启服务
if service_mode.is_some() {
let mut service = self.service.lock();
service.restart()?;
drop(service);
}
// // 重启服务
// if service_mode.is_some() {
// let mut service = self.service.lock();
// service.restart()?;
// drop(service);
// }
if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) {
let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll");
if !wintun_dll.exists() {
bail!("failed to enable TUN for missing `wintun.dll`");
}
}
// if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) {
// let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll");
// if !wintun_dll.exists() {
// bail!("failed to enable TUN for missing `wintun.dll`");
// }
// }
if service_mode.is_some() || tun_mode.is_some() {
self.activate()?;
}
}
// if service_mode.is_some() || tun_mode.is_some() {
// self.activate()?;
// }
// }
#[cfg(not(target_os = "windows"))]
if tun_mode.is_some() {
self.activate()?;
}
// #[cfg(not(target_os = "windows"))]
// if tun_mode.is_some() {
// self.activate()?;
// }
let mut sysopt = self.sysopt.lock();
// let mut sysopt = self.sysopt.lock();
if auto_launch.is_some() {
sysopt.update_launch()?;
}
if system_proxy.is_some() || proxy_bypass.is_some() {
sysopt.update_sysproxy()?;
sysopt.guard_proxy();
}
if proxy_guard.unwrap_or(false) {
sysopt.guard_proxy();
}
// if auto_launch.is_some() {
// sysopt.update_launch()?;
// }
// if system_proxy.is_some() || proxy_bypass.is_some() {
// sysopt.update_sysproxy()?;
// sysopt.guard_proxy();
// }
// if proxy_guard.unwrap_or(false) {
// sysopt.guard_proxy();
// }
// 更新tray
if language.is_some() {
let handle = self.handle.lock();
handle.update_systray()?;
} else if system_proxy.is_some() || tun_mode.is_some() {
let handle = self.handle.lock();
handle.update_systray_part()?;
}
// // 更新tray
// if language.is_some() {
// let handle = self.handle.lock();
// handle.update_systray()?;
// } else if system_proxy.is_some() || tun_mode.is_some() {
// let handle = self.handle.lock();
// handle.update_systray_part()?;
// }
if patch.hotkeys.is_some() {
let mut hotkey = self.hotkey.lock();
hotkey.update(patch.hotkeys.unwrap())?;
}
// if patch.hotkeys.is_some() {
// let mut hotkey = self.hotkey.lock();
// hotkey.update(patch.hotkeys.unwrap())?;
// }
Ok(())
}
// Ok(())
// }
// update rule/global/direct/script mode
pub fn update_mode(&self, mode: &str) -> Result<()> {
// save config to file
let info = {
let global = Data::global();
let mut clash = global.clash.lock();
clash.config.insert(Value::from("mode"), Value::from(mode));
clash.save_config()?;
clash.info.clone()
};
// /// update rule/global/direct/script mode
// pub fn update_mode(&self, mode: &str) -> Result<()> {
// // save config to file
// let info = {
// let global = Data::global();
// let mut clash = global.clash.lock();
// clash.config.insert(Value::from("mode"), Value::from(mode));
// clash.save_config()?;
// clash.info.clone()
// };
let mut mapping = Mapping::new();
mapping.insert(Value::from("mode"), Value::from(mode));
// let mut mapping = Mapping::new();
// mapping.insert(Value::from("mode"), Value::from(mode));
let handle = self.handle.clone();
// let handle = self.handle.clone();
tauri::async_runtime::spawn(async move {
log_if_err!(Service::patch_config(info, mapping.to_owned()).await);
// tauri::async_runtime::spawn(async move {
// log_if_err!(Service::patch_config(info, mapping.to_owned()).await);
// update tray
let handle = handle.lock();
handle.refresh_clash();
log_if_err!(handle.update_systray_part());
});
// // update tray
// let handle = handle.lock();
// handle.refresh_clash();
// log_if_err!(handle.update_systray_part());
// });
Ok(())
}
// Ok(())
// }
/// activate the profile
/// auto activate enhanced profile
/// 触发clash配置更新
pub fn activate(&self) -> Result<()> {
let global = Data::global();
// /// activate the profile
// /// auto activate enhanced profile
// /// 触发clash配置更新
// pub fn activate(&self) -> Result<()> {
// let global = Data::global();
let verge = global.verge.lock();
let clash = global.clash.lock();
let profiles = global.profiles.lock();
// let verge = global.verge.lock();
// let clash = global.clash.lock();
// let profiles = global.profiles.lock();
let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false);
let profile_activate = profiles.gen_activate()?;
// let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false);
// let profile_activate = profiles.gen_activate()?;
let clash_config = clash.config.clone();
let clash_info = clash.info.clone();
// let clash_config = clash.config.clone();
// let clash_info = clash.info.clone();
drop(clash);
drop(verge);
drop(profiles);
// drop(clash);
// drop(verge);
// drop(profiles);
let (config, exists_keys, logs) = enhance_config(
clash_config,
profile_activate.current,
profile_activate.chain,
profile_activate.valid,
tun_mode,
);
// let (config, exists_keys, logs) = enhance_config(
// clash_config,
// profile_activate.current,
// profile_activate.chain,
// profile_activate.valid,
// tun_mode,
// );
let mut runtime = self.runtime.lock();
*runtime = RuntimeResult {
config: Some(config.clone()),
config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())),
exists_keys,
chain_logs: logs,
};
drop(runtime);
// let mut runtime = self.runtime.lock();
// *runtime = RuntimeResult {
// config: Some(config.clone()),
// config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())),
// exists_keys,
// chain_logs: logs,
// };
// drop(runtime);
let mut service = self.service.lock();
service.check_start()?;
drop(service);
// let mut service = self.service.lock();
// service.check_start()?;
// drop(service);
let handle = self.handle.clone();
tauri::async_runtime::spawn(async move {
match Service::set_config(clash_info, config).await {
Ok(_) => {
let handle = handle.lock();
handle.refresh_clash();
handle.notice_message("set_config::ok".into(), "ok".into());
}
Err(err) => {
let handle = handle.lock();
handle.notice_message("set_config::error".into(), format!("{err}"));
log::error!(target: "app", "{err}")
}
}
});
// let handle = self.handle.clone();
// tauri::async_runtime::spawn(async move {
// match Service::set_config(clash_info, config).await {
// Ok(_) => {
// let handle = handle.lock();
// handle.refresh_clash();
// handle.notice_message("set_config::ok".into(), "ok".into());
// }
// Err(err) => {
// let handle = handle.lock();
// handle.notice_message("set_config::error".into(), format!("{err}"));
// log::error!(target: "app", "{err}")
// }
// }
// });
Ok(())
}
// Ok(())
// }
/// Static function
/// update profile item
pub async fn update_profile_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
let global = Data::global();
// /// Static function
// /// update profile item
// pub async fn update_profile_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
// let global = Data::global();
let (url, opt) = {
let profiles = global.profiles.lock();
let item = profiles.get_item(&uid)?;
// let (url, opt) = {
// let profiles = global.profiles.lock();
// let item = profiles.get_item(&uid)?;
if let Some(typ) = item.itype.as_ref() {
// maybe only valid for `local` profile
if *typ != "remote" {
// reactivate the config
if Some(uid) == profiles.get_current() {
drop(profiles);
self.activate()?;
}
return Ok(());
}
}
if item.url.is_none() {
bail!("failed to get the profile item url");
}
(item.url.clone().unwrap(), item.option.clone())
};
// if let Some(typ) = item.itype.as_ref() {
// // maybe only valid for `local` profile
// if *typ != "remote" {
// // reactivate the config
// if Some(uid) == profiles.get_current() {
// drop(profiles);
// self.activate()?;
// }
// return Ok(());
// }
// }
// if item.url.is_none() {
// bail!("failed to get the profile item url");
// }
// (item.url.clone().unwrap(), item.option.clone())
// };
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
// let merged_opt = PrfOption::merge(opt, option);
// let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
let mut profiles = global.profiles.lock();
profiles.update_item(uid.clone(), item)?;
// let mut profiles = global.profiles.lock();
// profiles.update_item(uid.clone(), item)?;
// reactivate the profile
if Some(uid) == profiles.get_current() {
drop(profiles);
self.activate()?;
}
// // reactivate the profile
// if Some(uid) == profiles.get_current() {
// drop(profiles);
// self.activate()?;
// }
Ok(())
}
// Ok(())
// }
}

View File

@@ -213,7 +213,7 @@ impl Service {
/// update clash config
/// using PUT methods
pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> {
let temp_path = dirs::profiles_temp_path();
let temp_path = dirs::clash_runtime_yaml();
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
let (server, headers) = Self::clash_client_info(info)?;
@@ -222,13 +222,13 @@ impl Service {
data.insert("path", temp_path.as_os_str().to_str().unwrap());
macro_rules! report_err {
($i: expr, $e: expr) => {
match $i {
4 => bail!($e),
_ => log::error!(target: "app", $e),
($i: expr, $e: expr) => {
match $i {
4 => bail!($e),
_ => log::error!(target: "app", $e),
}
};
}
};
}
// retry 5 times
for i in 0..5 {

View File

@@ -1,23 +1,25 @@
use crate::{data::*, log_if_err};
use crate::{config, log_err};
use anyhow::{anyhow, bail, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::sync::Arc;
use sysproxy::Sysproxy;
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
pub struct Sysopt {
/// current system proxy setting
cur_sysproxy: Option<Sysproxy>,
cur_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
/// record the original system proxy
/// recover it when exit
old_sysproxy: Option<Sysproxy>,
old_sysproxy: Arc<Mutex<Option<Sysproxy>>>,
/// helps to auto launch the app
auto_launch: Option<AutoLaunch>,
auto_launch: Arc<Mutex<Option<AutoLaunch>>>,
/// record whether the guard async is running or not
guard_state: Arc<Mutex<bool>>,
guard_state: Arc<TokioMutex<bool>>,
}
#[cfg(target_os = "windows")]
@@ -28,44 +30,45 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1";
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
impl Sysopt {
pub fn new() -> Sysopt {
Sysopt {
cur_sysproxy: None,
old_sysproxy: None,
auto_launch: None,
guard_state: Arc::new(Mutex::new(false)),
}
pub fn global() -> &'static Sysopt {
static SYSOPT: OnceCell<Sysopt> = OnceCell::new();
SYSOPT.get_or_init(|| Sysopt {
cur_sysproxy: Arc::new(Mutex::new(None)),
old_sysproxy: Arc::new(Mutex::new(None)),
auto_launch: Arc::new(Mutex::new(None)),
guard_state: Arc::new(TokioMutex::new(false)),
})
}
/// init the sysproxy
pub fn init_sysproxy(&mut self) -> Result<()> {
let data = Data::global();
let clash = data.clash.lock();
let port = clash.info.port.clone();
pub fn init_sysproxy(&self) -> Result<()> {
let port = { config::ClashN::global().info.lock().port.clone() };
if port.is_none() {
bail!("clash port is none");
}
let verge = data.verge.lock();
let port = port.unwrap().parse::<u16>()?;
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
let bypass = verge.system_proxy_bypass.clone();
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
let port = port.unwrap().parse::<u16>()?;
let host = String::from("127.0.0.1");
self.cur_sysproxy = Some(Sysproxy {
let current = Sysproxy {
enable,
host,
host: String::from("127.0.0.1"),
port,
bypass,
});
};
if enable {
self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
current.set_system_proxy()?;
*self.old_sysproxy.lock() = old;
*self.cur_sysproxy.lock() = Some(current);
}
// run the system proxy guard
@@ -74,37 +77,44 @@ impl Sysopt {
}
/// update the system proxy
pub fn update_sysproxy(&mut self) -> Result<()> {
if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() {
pub fn update_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock();
let old_sysproxy = self.old_sysproxy.lock();
if cur_sysproxy.is_none() || old_sysproxy.is_none() {
drop(cur_sysproxy);
drop(old_sysproxy);
return self.init_sysproxy();
}
let data = Data::global();
let verge = data.verge.lock();
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
let bypass = verge.system_proxy_bypass.clone();
let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
let mut sysproxy = self.cur_sysproxy.take().unwrap();
let mut sysproxy = cur_sysproxy.take().unwrap();
sysproxy.enable = enable;
sysproxy.bypass = bypass;
self.cur_sysproxy = Some(sysproxy);
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
sysproxy.set_system_proxy()?;
*cur_sysproxy = Some(sysproxy);
Ok(())
}
/// reset the sysproxy
pub fn reset_sysproxy(&mut self) -> Result<()> {
let cur = self.cur_sysproxy.take();
pub fn reset_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock();
let mut old_sysproxy = self.old_sysproxy.lock();
if let Some(mut old) = self.old_sysproxy.take() {
let cur_sysproxy = cur_sysproxy.take();
if let Some(mut old) = old_sysproxy.take() {
// 如果原代理和当前代理 端口一致就disable关闭否则就恢复原代理设置
// 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了
let port_same = cur.map_or(true, |cur| old.port == cur.port);
let port_same = cur_sysproxy.map_or(true, |cur| old.port == cur.port);
if old.enable && port_same {
old.enable = false;
@@ -114,7 +124,7 @@ impl Sysopt {
}
old.set_system_proxy()?;
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur {
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy {
// 没有原代理就按现在的代理设置disable即可
log::info!(target: "app", "reset proxy by disabling the current proxy");
cur.enable = false;
@@ -127,9 +137,8 @@ impl Sysopt {
}
/// init the auto launch
pub fn init_launch(&mut self) -> Result<()> {
let data = Data::global();
let verge = data.verge.lock();
pub fn init_launch(&self) -> Result<()> {
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
let app_exe = current_exe()?;
@@ -167,51 +176,41 @@ impl Sysopt {
.set_app_path(&app_path)
.build()?;
self.auto_launch = Some(auto);
// 避免在开发时将自启动关了
#[cfg(feature = "verge-dev")]
if !enable {
return Ok(());
}
let auto = self.auto_launch.as_ref().unwrap();
// macos每次启动都更新登录项避免重复设置登录项
#[cfg(target_os = "macos")]
{
let _ = auto.disable();
if enable {
auto.enable()?;
}
}
let _ = auto.disable();
#[cfg(not(target_os = "macos"))]
{
match enable {
true => auto.enable()?,
false => auto.disable()?,
};
if enable {
auto.enable()?;
}
*self.auto_launch.lock() = Some(auto);
Ok(())
}
/// update the startup
pub fn update_launch(&mut self) -> Result<()> {
if self.auto_launch.is_none() {
pub fn update_launch(&self) -> Result<()> {
let auto_launch = self.auto_launch.lock();
if auto_launch.is_none() {
drop(auto_launch);
return self.init_launch();
}
let data = Data::global();
let verge = data.verge.lock();
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
let auto_launch = self.auto_launch.as_ref().unwrap();
let auto_launch = auto_launch.as_ref().unwrap();
match enable {
true => auto_launch.enable()?,
false => crate::log_if_err!(auto_launch.disable()), // 忽略关闭的错误
false => log_err!(auto_launch.disable()), // 忽略关闭的错误
};
Ok(())
@@ -239,8 +238,7 @@ impl Sysopt {
loop {
sleep(Duration::from_secs(wait_secs)).await;
let global = Data::global();
let verge = global.verge.lock();
let verge = config::VergeN::global().config.lock();
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
let guard = verge.enable_proxy_guard.clone().unwrap_or(false);
@@ -256,14 +254,10 @@ impl Sysopt {
// update duration
wait_secs = guard_duration;
let clash = global.clash.lock();
let port = clash.info.port.clone();
let port = port.unwrap_or("".into()).parse::<u16>();
drop(clash);
log::debug!(target: "app", "try to guard the system proxy");
match port {
let port = { config::ClashN::global().info.lock().port.clone() };
match port.unwrap_or("".into()).parse::<u16>() {
Ok(port) => {
let sysproxy = Sysproxy {
enable: true,
@@ -272,14 +266,17 @@ impl Sysopt {
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
};
log_if_err!(sysproxy.set_system_proxy());
log_err!(sysproxy.set_system_proxy());
}
Err(_) => {
log::error!(target: "app", "failed to parse clash port in guard proxy")
}
Err(_) => log::error!(target: "app", "failed to parse clash port"),
}
}
let mut state = guard_state.lock().await;
*state = false;
drop(state);
});
}
}

View File

@@ -1,50 +1,96 @@
use super::Core;
use crate::utils::help::get_now;
use crate::{data::Data, log_if_err};
use crate::config::{self, ProfilesN};
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
type TaskID = u64;
pub struct Timer {
/// cron manager
delay_timer: DelayTimer,
delay_timer: Arc<Mutex<DelayTimer>>,
/// save the current state
timer_map: HashMap<String, (TaskID, u64)>,
timer_map: Arc<Mutex<HashMap<String, (TaskID, u64)>>>,
/// increment id
timer_count: TaskID,
timer_count: Arc<Mutex<TaskID>>,
}
impl Timer {
pub fn new() -> Self {
Timer {
delay_timer: DelayTimerBuilder::default().build(),
timer_map: HashMap::new(),
timer_count: 1,
}
pub fn global() -> &'static Timer {
static TIMER: OnceCell<Timer> = OnceCell::new();
TIMER.get_or_init(|| Timer {
delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(Mutex::new(HashMap::new())),
timer_count: Arc::new(Mutex::new(1)),
})
}
/// restore timer
pub fn init(&self) -> Result<()> {
self.refresh()?;
let cur_timestamp = chrono::Local::now().timestamp();
let profiles = config::ProfilesN::global().config.lock();
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
profiles.get_items().map(|items| {
items
.iter()
// .filter_map(|item| {
// item.uid.is_some() && item.updated.is_some() && item.option.is_some()
// })
.filter_map(|item| {
// mins to seconds
let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60;
let updated = item.updated? as i64;
if interval > 0 && cur_timestamp - updated >= interval {
Some(item)
} else {
None
}
})
.for_each(|item| {
if let Some(uid) = item.uid.as_ref() {
if let Some((task_id, _)) = timer_map.get(uid) {
crate::log_err!(delay_timer.advance_task(*task_id));
}
}
})
});
Ok(())
}
/// Correctly update all cron tasks
pub fn refresh(&mut self) -> Result<()> {
pub fn refresh(&self) -> Result<()> {
let diff_map = self.gen_diff();
let mut timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
for (uid, diff) in diff_map.into_iter() {
match diff {
DiffFlag::Del(tid) => {
let _ = self.timer_map.remove(&uid);
log_if_err!(self.delay_timer.remove_task(tid));
let _ = timer_map.remove(&uid);
crate::log_err!(delay_timer.remove_task(tid));
}
DiffFlag::Add(tid, val) => {
let _ = self.timer_map.insert(uid.clone(), (tid, val));
log_if_err!(self.add_task(uid, tid, val));
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(self.add_task(uid, tid, val));
}
DiffFlag::Mod(tid, val) => {
let _ = self.timer_map.insert(uid.clone(), (tid, val));
log_if_err!(self.delay_timer.remove_task(tid));
log_if_err!(self.add_task(uid, tid, val));
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(delay_timer.remove_task(tid));
crate::log_err!(self.add_task(uid, tid, val));
}
}
}
@@ -52,41 +98,9 @@ impl Timer {
Ok(())
}
/// restore timer
pub fn restore(&mut self) -> Result<()> {
self.refresh()?;
let cur_timestamp = get_now(); // seconds
let global = Data::global();
let profiles = global.profiles.lock();
profiles
.get_items()
.unwrap_or(&vec![])
.iter()
.filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some())
.filter(|item| {
// mins to seconds
let interval =
item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60;
let updated = item.updated.unwrap();
return interval > 0 && cur_timestamp - updated >= interval;
})
.for_each(|item| {
let uid = item.uid.as_ref().unwrap();
if let Some((task_id, _)) = self.timer_map.get(uid) {
log_if_err!(self.delay_timer.advance_task(*task_id));
}
});
Ok(())
}
/// generate a uid -> update_interval map
fn gen_map(&self) -> HashMap<String, u64> {
let global = Data::global();
let profiles = global.profiles.lock();
let profiles = config::ProfilesN::global().config.lock();
let mut new_map = HashMap::new();
@@ -107,11 +121,13 @@ impl Timer {
}
/// generate the diff map for refresh
fn gen_diff(&mut self) -> HashMap<String, DiffFlag> {
fn gen_diff(&self) -> HashMap<String, DiffFlag> {
let mut diff_map = HashMap::new();
let timer_map = self.timer_map.lock();
let new_map = self.gen_map();
let cur_map = &self.timer_map;
let cur_map = &timer_map;
cur_map.iter().for_each(|(uid, (tid, val))| {
let new_val = new_map.get(uid).unwrap_or(&0);
@@ -123,34 +139,31 @@ impl Timer {
}
});
let mut count = self.timer_count;
let mut count = self.timer_count.lock();
new_map.iter().for_each(|(uid, val)| {
if cur_map.get(uid).is_none() {
diff_map.insert(uid.clone(), DiffFlag::Add(count, *val));
diff_map.insert(uid.clone(), DiffFlag::Add(*count, *val));
count += 1;
*count += 1;
}
});
self.timer_count = count;
diff_map
}
/// add a cron task
fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> {
let core = Core::global();
let task = TaskBuilder::default()
.set_task_id(tid)
.set_maximum_parallel_runnable_num(1)
.set_frequency_repeated_by_minutes(minutes)
// .set_frequency_repeated_by_seconds(minutes) // for test
.spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned()))
.spawn_async_routine(move || Self::async_task(uid.to_owned()))
.context("failed to create timer task")?;
self.delay_timer
.lock()
.add_task(task)
.context("failed to add timer task")?;
@@ -158,9 +171,9 @@ impl Timer {
}
/// the task runner
async fn async_task(core: Core, uid: String) {
async fn async_task(uid: String) {
log::info!(target: "app", "running timer task `{uid}`");
log_if_err!(core.update_profile_item(uid, None).await);
crate::log_err!(ProfilesN::global().update_item(uid, None).await);
}
}

View File

@@ -1,5 +1,6 @@
use crate::{data::Data, feat, utils::resolve};
use anyhow::{Ok, Result};
use crate::log_err;
use crate::{config, feat, utils::resolve};
use anyhow::Result;
use tauri::{
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
SystemTraySubmenu,
@@ -9,14 +10,16 @@ pub struct Tray {}
impl Tray {
pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu {
let data = Data::global();
let zh = {
let verge = data.verge.lock();
let verge = config::VergeN::global().config.lock();
verge.language == Some("zh".into())
};
let version = app_handle.package_info().version.to_string();
dbg!(&zh);
dbg!(&version);
if zh {
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("open_window", "打开面板"))
@@ -75,13 +78,14 @@ impl Tray {
}
pub fn update_part(app_handle: &AppHandle) -> Result<()> {
let global = Data::global();
let clash = global.clash.lock();
let mode = clash
.config
.get(&serde_yaml::Value::from("mode"))
.map(|val| val.as_str().unwrap_or("rule"))
.unwrap_or("rule");
let mode = {
let clash = config::ClashN::global().config.lock();
clash
.get("mode")
.map(|val| val.as_str().unwrap_or("rule"))
.unwrap_or("rule")
.to_owned()
};
let tray = app_handle.tray_handle();
@@ -90,7 +94,7 @@ impl Tray {
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
let _ = tray.get_item("script_mode").set_selected(mode == "script");
let verge = global.verge.lock();
let verge = config::VergeN::global().config.lock();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
@@ -105,12 +109,12 @@ impl Tray {
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => {
let mode = &mode[0..mode.len() - 5];
feat::change_clash_mode(mode);
feat::change_clash_mode(mode.into());
}
"open_window" => resolve::create_window(app_handle),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(),
"system_proxy" => log_err!(feat::toggle_system_proxy()),
"tun_mode" => log_err!(feat::toggle_tun_mode()),
"restart_clash" => feat::restart_clash_core(),
"restart_app" => api::process::restart(&app_handle.env()),
"quit" => {
@@ -124,7 +128,9 @@ impl Tray {
SystemTrayEvent::LeftClick { .. } => {
resolve::create_window(app_handle);
}
_ => {}
e @ _ => {
dbg!("trya");
}
}
}
}