mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 17:15:38 +08:00
refactor: optimize
This commit is contained in:
@@ -1,166 +0,0 @@
|
||||
use crate::utils::{config, dirs};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClashInfo {
|
||||
/// clash sidecar status
|
||||
pub status: String,
|
||||
|
||||
/// clash core port
|
||||
pub port: Option<String>,
|
||||
|
||||
/// same as `external-controller`
|
||||
pub server: Option<String>,
|
||||
|
||||
/// clash secret
|
||||
pub secret: Option<String>,
|
||||
}
|
||||
|
||||
impl ClashInfo {
|
||||
/// parse the clash's config.yaml
|
||||
/// get some information
|
||||
pub fn from(config: &Mapping) -> ClashInfo {
|
||||
let key_port_1 = Value::from("mixed-port");
|
||||
let key_port_2 = Value::from("port");
|
||||
let key_server = Value::from("external-controller");
|
||||
let key_secret = Value::from("secret");
|
||||
|
||||
let mut status: u32 = 0;
|
||||
|
||||
let port = match config.get(&key_port_1) {
|
||||
Some(value) => match value {
|
||||
Value::String(val_str) => Some(val_str.clone()),
|
||||
Value::Number(val_num) => Some(val_num.to_string()),
|
||||
_ => {
|
||||
status |= 0b1;
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
status |= 0b10;
|
||||
None
|
||||
}
|
||||
};
|
||||
let port = match port {
|
||||
Some(_) => port,
|
||||
None => match config.get(&key_port_2) {
|
||||
Some(value) => match value {
|
||||
Value::String(val_str) => Some(val_str.clone()),
|
||||
Value::Number(val_num) => Some(val_num.to_string()),
|
||||
_ => {
|
||||
status |= 0b100;
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
status |= 0b1000;
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// `external-controller` could be
|
||||
// "127.0.0.1:9090" or ":9090"
|
||||
let server = match config.get(&key_server) {
|
||||
Some(value) => match value.as_str() {
|
||||
Some(val_str) => {
|
||||
if val_str.starts_with(":") {
|
||||
Some(format!("127.0.0.1{val_str}"))
|
||||
} else if val_str.starts_with("0.0.0.0:") {
|
||||
Some(format!("127.0.0.1:{}", &val_str[8..]))
|
||||
} else if val_str.starts_with("[::]:") {
|
||||
Some(format!("127.0.0.1:{}", &val_str[5..]))
|
||||
} else {
|
||||
Some(val_str.into())
|
||||
}
|
||||
}
|
||||
None => {
|
||||
status |= 0b10000;
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
status |= 0b100000;
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let secret = match config.get(&key_secret) {
|
||||
Some(value) => match value {
|
||||
Value::String(val_str) => Some(val_str.clone()),
|
||||
Value::Bool(val_bool) => Some(val_bool.to_string()),
|
||||
Value::Number(val_num) => Some(val_num.to_string()),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
ClashInfo {
|
||||
status: format!("{status}"),
|
||||
port,
|
||||
server,
|
||||
secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Clash {
|
||||
/// maintain the clash config
|
||||
pub config: Mapping,
|
||||
|
||||
/// some info
|
||||
pub info: ClashInfo,
|
||||
}
|
||||
|
||||
impl Clash {
|
||||
pub fn new() -> Clash {
|
||||
let config = Clash::read_config();
|
||||
let info = ClashInfo::from(&config);
|
||||
|
||||
Clash { config, info }
|
||||
}
|
||||
|
||||
/// get clash config
|
||||
pub fn read_config() -> Mapping {
|
||||
config::read_yaml::<Mapping>(dirs::clash_path())
|
||||
}
|
||||
|
||||
/// save the clash config
|
||||
pub fn save_config(&self) -> Result<()> {
|
||||
config::save_yaml(
|
||||
dirs::clash_path(),
|
||||
&self.config,
|
||||
Some("# Default Config For Clash Core\n\n"),
|
||||
)
|
||||
}
|
||||
|
||||
/// patch update the clash config
|
||||
/// if the port is changed then return true
|
||||
pub fn patch_config(&mut self, patch: Mapping) -> Result<()> {
|
||||
let port_key = Value::from("mixed-port");
|
||||
let server_key = Value::from("external-controller");
|
||||
let secret_key = Value::from("secret");
|
||||
|
||||
let change_info = patch.contains_key(&port_key)
|
||||
|| patch.contains_key(&server_key)
|
||||
|| patch.contains_key(&secret_key);
|
||||
|
||||
for (key, value) in patch.into_iter() {
|
||||
self.config.insert(key, value);
|
||||
}
|
||||
|
||||
if change_info {
|
||||
self.info = ClashInfo::from(&self.config);
|
||||
}
|
||||
|
||||
self.save_config()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Clash {
|
||||
fn default() -> Self {
|
||||
Clash::new()
|
||||
}
|
||||
}
|
||||
101
src-tauri/src/core/handle.rs
Normal file
101
src-tauri/src/core/handle.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use crate::data::*;
|
||||
use crate::log_if_err;
|
||||
use anyhow::{bail, Result};
|
||||
use serde_yaml::Value;
|
||||
use tauri::{AppHandle, Manager, Window};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Handle {
|
||||
pub app_handle: Option<AppHandle>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
pub fn from(app_handle: Option<AppHandle>) -> Handle {
|
||||
Handle { app_handle }
|
||||
}
|
||||
|
||||
pub fn get_window(&self) -> Option<Window> {
|
||||
self
|
||||
.app_handle
|
||||
.as_ref()
|
||||
.map_or(None, |a| a.get_window("main"))
|
||||
}
|
||||
|
||||
pub fn refresh_clash(&self) {
|
||||
if let Some(window) = self.get_window() {
|
||||
log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_verge(&self) {
|
||||
if let Some(window) = self.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() {
|
||||
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
// update system tray state (clash config)
|
||||
pub fn update_systray_clash(&self) -> Result<()> {
|
||||
if self.app_handle.is_none() {
|
||||
bail!("unhandle error");
|
||||
}
|
||||
|
||||
let app_handle = self.app_handle.as_ref().unwrap();
|
||||
|
||||
let global = Data::global();
|
||||
let clash = global.clash.lock();
|
||||
let mode = clash
|
||||
.config
|
||||
.get(&Value::from("mode"))
|
||||
.map(|val| val.as_str().unwrap_or("rule"))
|
||||
.unwrap_or("rule");
|
||||
|
||||
let tray = app_handle.tray_handle();
|
||||
|
||||
tray.get_item("rule_mode").set_selected(mode == "rule")?;
|
||||
tray
|
||||
.get_item("global_mode")
|
||||
.set_selected(mode == "global")?;
|
||||
tray
|
||||
.get_item("direct_mode")
|
||||
.set_selected(mode == "direct")?;
|
||||
tray
|
||||
.get_item("script_mode")
|
||||
.set_selected(mode == "script")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update the system tray state (verge config)
|
||||
pub fn update_systray(&self) -> Result<()> {
|
||||
if self.app_handle.is_none() {
|
||||
bail!("unhandle error");
|
||||
}
|
||||
|
||||
let app_handle = self.app_handle.as_ref().unwrap();
|
||||
let tray = app_handle.tray_handle();
|
||||
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref();
|
||||
let tun_mode = verge.enable_tun_mode.as_ref();
|
||||
|
||||
tray
|
||||
.get_item("system_proxy")
|
||||
.set_selected(*system_proxy.unwrap_or(&false))?;
|
||||
tray
|
||||
.get_item("tun_mode")
|
||||
.set_selected(*tun_mode.unwrap_or(&false))?;
|
||||
|
||||
// update verge config
|
||||
self.refresh_verge();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,108 +1,71 @@
|
||||
use self::notice::Notice;
|
||||
use self::handle::Handle;
|
||||
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::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use serde_yaml::Mapping;
|
||||
use serde_yaml::Value;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Manager, Window};
|
||||
|
||||
mod clash;
|
||||
mod notice;
|
||||
mod prfitem;
|
||||
mod profiles;
|
||||
mod handle;
|
||||
mod service;
|
||||
mod sysopt;
|
||||
mod timer;
|
||||
mod verge;
|
||||
|
||||
pub use self::clash::*;
|
||||
pub use self::prfitem::*;
|
||||
pub use self::profiles::*;
|
||||
pub use self::service::*;
|
||||
pub use self::verge::*;
|
||||
|
||||
static CORE: Lazy<Core> = Lazy::new(|| Core {
|
||||
service: Arc::new(Mutex::new(Service::new())),
|
||||
sysopt: Arc::new(Mutex::new(Sysopt::new())),
|
||||
timer: Arc::new(Mutex::new(Timer::new())),
|
||||
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
|
||||
handle: Handle::default(),
|
||||
});
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Core {
|
||||
pub clash: Arc<Mutex<Clash>>,
|
||||
pub verge: Arc<Mutex<Verge>>,
|
||||
pub profiles: Arc<Mutex<Profiles>>,
|
||||
pub service: Arc<Mutex<Service>>,
|
||||
pub sysopt: Arc<Mutex<Sysopt>>,
|
||||
pub timer: Arc<Mutex<Timer>>,
|
||||
pub runtime: Arc<Mutex<RuntimeResult>>,
|
||||
pub window: Arc<Mutex<Option<Window>>>,
|
||||
pub handle: Handle,
|
||||
}
|
||||
|
||||
impl Core {
|
||||
pub fn new() -> Core {
|
||||
Core {
|
||||
clash: Arc::new(Mutex::new(Clash::new())),
|
||||
verge: Arc::new(Mutex::new(Verge::new())),
|
||||
profiles: Arc::new(Mutex::new(Profiles::new())),
|
||||
service: Arc::new(Mutex::new(Service::new())),
|
||||
sysopt: Arc::new(Mutex::new(Sysopt::new())),
|
||||
timer: Arc::new(Mutex::new(Timer::new())),
|
||||
runtime: Arc::new(Mutex::new(RuntimeResult::default())),
|
||||
window: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
pub fn global() -> Core {
|
||||
CORE.clone()
|
||||
}
|
||||
|
||||
/// initialize the core state
|
||||
pub fn init(&self, app_handle: tauri::AppHandle) {
|
||||
pub fn init(&mut self, app_handle: tauri::AppHandle) {
|
||||
// kill old clash process
|
||||
Service::kill_old_clash();
|
||||
self.handle = Handle::from(Some(app_handle));
|
||||
|
||||
let verge = self.verge.lock();
|
||||
let clash_core = verge.clash_core.clone();
|
||||
|
||||
let mut service = self.service.lock();
|
||||
service.set_core(clash_core);
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let enable = verge.enable_service_mode.clone();
|
||||
service.set_mode(enable.unwrap_or(false));
|
||||
let mut service = self.service.lock();
|
||||
log_if_err!(service.start());
|
||||
}
|
||||
|
||||
log_if_err!(service.start());
|
||||
drop(verge);
|
||||
drop(service);
|
||||
|
||||
log_if_err!(self.activate());
|
||||
|
||||
let clash = self.clash.lock();
|
||||
let verge = self.verge.lock();
|
||||
{
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
log_if_err!(sysopt.init_launch());
|
||||
log_if_err!(sysopt.init_sysproxy());
|
||||
}
|
||||
|
||||
// let silent_start = verge.enable_silent_start.clone();
|
||||
let auto_launch = verge.enable_auto_launch.clone();
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
|
||||
sysopt.init_sysproxy(clash.info.port.clone(), &verge);
|
||||
|
||||
drop(clash);
|
||||
drop(verge);
|
||||
|
||||
log_if_err!(sysopt.init_launch(auto_launch));
|
||||
|
||||
log_if_err!(self.update_systray(&app_handle));
|
||||
log_if_err!(self.update_systray_clash(&app_handle));
|
||||
log_if_err!(self.handle.update_systray());
|
||||
log_if_err!(self.handle.update_systray_clash());
|
||||
|
||||
// timer initialize
|
||||
let mut timer = self.timer.lock();
|
||||
timer.set_core(self.clone());
|
||||
log_if_err!(timer.restore());
|
||||
}
|
||||
|
||||
/// save the window instance
|
||||
pub fn set_win(&self, win: Option<Window>) {
|
||||
let mut window = self.window.lock();
|
||||
*window = win;
|
||||
}
|
||||
|
||||
/// restart the clash sidecar
|
||||
pub fn restart_clash(&self) -> Result<()> {
|
||||
let mut service = self.service.lock();
|
||||
@@ -119,7 +82,8 @@ impl Core {
|
||||
bail!("invalid clash core name \"{clash_core}\"");
|
||||
}
|
||||
|
||||
let mut verge = self.verge.lock();
|
||||
let global = Data::global();
|
||||
let mut verge = global.verge.lock();
|
||||
verge.patch_config(Verge {
|
||||
clash_core: Some(clash_core.clone()),
|
||||
..Verge::default()
|
||||
@@ -127,10 +91,8 @@ impl Core {
|
||||
drop(verge);
|
||||
|
||||
let mut service = self.service.lock();
|
||||
service.stop()?;
|
||||
service.set_core(Some(clash_core));
|
||||
service.clear_logs();
|
||||
service.start()?;
|
||||
service.restart()?;
|
||||
drop(service);
|
||||
|
||||
self.activate()
|
||||
@@ -138,12 +100,13 @@ impl Core {
|
||||
|
||||
/// Patch Clash
|
||||
/// handle the clash config changed
|
||||
pub fn patch_clash(&self, patch: Mapping, app_handle: &AppHandle) -> Result<()> {
|
||||
pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
|
||||
let has_port = patch.contains_key(&Value::from("mixed-port"));
|
||||
let has_mode = patch.contains_key(&Value::from("mode"));
|
||||
|
||||
let port = {
|
||||
let mut clash = self.clash.lock();
|
||||
let global = Data::global();
|
||||
let mut clash = global.clash.lock();
|
||||
clash.patch_config(patch)?;
|
||||
clash.info.port.clone()
|
||||
};
|
||||
@@ -157,175 +120,121 @@ impl Core {
|
||||
self.activate()?;
|
||||
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
let verge = self.verge.lock();
|
||||
sysopt.init_sysproxy(port, &verge);
|
||||
sysopt.init_sysproxy()?;
|
||||
}
|
||||
|
||||
if has_mode {
|
||||
self.update_systray_clash(app_handle)?;
|
||||
self.handle.update_systray_clash()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Patch Verge
|
||||
pub fn patch_verge(&self, patch: Verge, app_handle: &AppHandle) -> Result<()> {
|
||||
let tun_mode = patch.enable_tun_mode.clone();
|
||||
let auto_launch = patch.enable_auto_launch.clone();
|
||||
let system_proxy = patch.enable_system_proxy.clone();
|
||||
let proxy_bypass = patch.system_proxy_bypass.clone();
|
||||
let proxy_guard = patch.enable_proxy_guard.clone();
|
||||
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);
|
||||
|
||||
#[cfg(windows)]
|
||||
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;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let service_mode = patch.enable_service_mode.clone();
|
||||
let service_mode = patch.enable_service_mode;
|
||||
|
||||
// 重启服务
|
||||
if service_mode.is_some() {
|
||||
let service_mode = service_mode.unwrap();
|
||||
|
||||
let mut service = self.service.lock();
|
||||
service.stop()?;
|
||||
service.set_mode(service_mode);
|
||||
service.start()?;
|
||||
service.restart()?;
|
||||
drop(service);
|
||||
}
|
||||
|
||||
// self.activate_enhanced(false)?;
|
||||
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 auto_launch.is_some() {
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
sysopt.update_launch(auto_launch)?;
|
||||
}
|
||||
|
||||
if system_proxy.is_some() || proxy_bypass.is_some() {
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
sysopt.update_sysproxy(system_proxy.clone(), proxy_bypass)?;
|
||||
sysopt.guard_proxy();
|
||||
}
|
||||
|
||||
if proxy_guard.unwrap_or(false) {
|
||||
let sysopt = self.sysopt.lock();
|
||||
sysopt.guard_proxy();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
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`");
|
||||
}
|
||||
}
|
||||
|
||||
// save the patch
|
||||
let mut verge = self.verge.lock();
|
||||
verge.patch_config(patch)?;
|
||||
drop(verge);
|
||||
|
||||
if system_proxy.is_some() || tun_mode.is_some() {
|
||||
self.update_systray(app_handle)?;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if tun_mode.is_some() {
|
||||
self.activate()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
let mut sysopt = self.sysopt.lock();
|
||||
|
||||
// update system tray state (clash config)
|
||||
pub fn update_systray_clash(&self, app_handle: &AppHandle) -> Result<()> {
|
||||
let clash = self.clash.lock();
|
||||
let mode = clash
|
||||
.config
|
||||
.get(&Value::from("mode"))
|
||||
.map(|val| val.as_str().unwrap_or("rule"))
|
||||
.unwrap_or("rule");
|
||||
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();
|
||||
}
|
||||
|
||||
let tray = app_handle.tray_handle();
|
||||
|
||||
tray.get_item("rule_mode").set_selected(mode == "rule")?;
|
||||
tray
|
||||
.get_item("global_mode")
|
||||
.set_selected(mode == "global")?;
|
||||
tray
|
||||
.get_item("direct_mode")
|
||||
.set_selected(mode == "direct")?;
|
||||
tray
|
||||
.get_item("script_mode")
|
||||
.set_selected(mode == "script")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update the system tray state (verge config)
|
||||
pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> {
|
||||
let verge = self.verge.lock();
|
||||
let tray = app_handle.tray_handle();
|
||||
|
||||
let system_proxy = verge.enable_system_proxy.as_ref();
|
||||
let tun_mode = verge.enable_tun_mode.as_ref();
|
||||
|
||||
tray
|
||||
.get_item("system_proxy")
|
||||
.set_selected(*system_proxy.unwrap_or(&false))?;
|
||||
tray
|
||||
.get_item("tun_mode")
|
||||
.set_selected(*tun_mode.unwrap_or(&false))?;
|
||||
|
||||
// update verge config
|
||||
let window = app_handle.get_window("main");
|
||||
let notice = Notice::from(window);
|
||||
notice.refresh_verge();
|
||||
if system_proxy.is_some() || tun_mode.is_some() {
|
||||
self.handle.update_systray()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// update rule/global/direct/script mode
|
||||
pub fn update_mode(&self, app_handle: &AppHandle, mode: &str) -> Result<()> {
|
||||
pub fn update_mode(&self, mode: &str) -> Result<()> {
|
||||
// save config to file
|
||||
let mut clash = self.clash.lock();
|
||||
clash.config.insert(Value::from("mode"), Value::from(mode));
|
||||
clash.save_config()?;
|
||||
|
||||
let info = clash.info.clone();
|
||||
drop(clash);
|
||||
|
||||
let notice = {
|
||||
let window = self.window.lock();
|
||||
Notice::from(window.clone())
|
||||
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 service = self.service.lock();
|
||||
service.patch_config(info, mapping, notice)?;
|
||||
tauri::async_runtime::spawn(async move {
|
||||
log_if_err!(Service::patch_config(info, mapping.to_owned()).await);
|
||||
});
|
||||
|
||||
// update tray
|
||||
self.update_systray_clash(app_handle)?;
|
||||
self.handle.update_systray_clash()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// activate the profile
|
||||
/// auto activate enhanced profile
|
||||
/// 触发clash配置更新
|
||||
pub fn activate(&self) -> Result<()> {
|
||||
let profile_activate = {
|
||||
let profiles = self.profiles.lock();
|
||||
profiles.gen_activate()?
|
||||
};
|
||||
let global = Data::global();
|
||||
|
||||
let (clash_config, clash_info) = {
|
||||
let clash = self.clash.lock();
|
||||
(clash.config.clone(), clash.info.clone())
|
||||
};
|
||||
let verge = global.verge.lock();
|
||||
let clash = global.clash.lock();
|
||||
let profiles = global.profiles.lock();
|
||||
|
||||
let tun_mode = {
|
||||
let verge = self.verge.lock();
|
||||
verge.enable_tun_mode.unwrap_or(false)
|
||||
};
|
||||
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();
|
||||
|
||||
drop(clash);
|
||||
drop(verge);
|
||||
drop(profiles);
|
||||
|
||||
let (config, exists_keys, logs) = enhance_config(
|
||||
clash_config,
|
||||
@@ -336,25 +245,36 @@ impl Core {
|
||||
);
|
||||
|
||||
let mut runtime = self.runtime.lock();
|
||||
runtime.config = Some(config.clone());
|
||||
runtime.config_yaml = Some(serde_yaml::to_string(&config).unwrap_or("".into()));
|
||||
runtime.exists_keys = exists_keys;
|
||||
runtime.chain_logs = logs;
|
||||
|
||||
let notice = {
|
||||
let window = self.window.lock();
|
||||
Notice::from(window.clone())
|
||||
*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 service = self.service.lock();
|
||||
service.set_config(clash_info, config, notice)
|
||||
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(_) => handle.refresh_clash(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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 = self.profiles.lock();
|
||||
let profiles = global.profiles.lock();
|
||||
let item = profiles.get_item(&uid)?;
|
||||
|
||||
if let Some(typ) = item.itype.as_ref() {
|
||||
@@ -363,7 +283,7 @@ impl Core {
|
||||
// reactivate the config
|
||||
if Some(uid) == profiles.get_current() {
|
||||
drop(profiles);
|
||||
return self.activate();
|
||||
self.activate()?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -377,7 +297,7 @@ impl Core {
|
||||
let merged_opt = PrfOption::merge(opt, option);
|
||||
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
|
||||
|
||||
let mut profiles = self.profiles.lock();
|
||||
let mut profiles = global.profiles.lock();
|
||||
profiles.update_item(uid.clone(), item)?;
|
||||
|
||||
// reactivate the profile
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
use crate::log_if_err;
|
||||
use tauri::Window;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Notice {
|
||||
win: Option<Window>,
|
||||
}
|
||||
|
||||
impl Notice {
|
||||
pub fn from(win: Option<Window>) -> Notice {
|
||||
Notice { win }
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn set_win(&mut self, win: Option<Window>) {
|
||||
self.win = win;
|
||||
}
|
||||
|
||||
pub fn refresh_clash(&self) {
|
||||
if let Some(window) = self.win.as_ref() {
|
||||
log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_verge(&self) {
|
||||
if let Some(window) = self.win.as_ref() {
|
||||
log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn refresh_profiles(&self) {
|
||||
if let Some(window) = self.win.as_ref() {
|
||||
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
use crate::utils::{config, dirs, help, tmpl};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PrfItem {
|
||||
pub uid: Option<String>,
|
||||
|
||||
/// profile item type
|
||||
/// enum value: remote | local | script | merge
|
||||
#[serde(rename = "type")]
|
||||
pub itype: Option<String>,
|
||||
|
||||
/// profile name
|
||||
pub name: Option<String>,
|
||||
|
||||
/// profile file
|
||||
pub file: Option<String>,
|
||||
|
||||
/// profile description
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub desc: Option<String>,
|
||||
|
||||
/// source url
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
|
||||
/// selected infomation
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected: Option<Vec<PrfSelected>>,
|
||||
|
||||
/// subscription user info
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra: Option<PrfExtra>,
|
||||
|
||||
/// updated time
|
||||
pub updated: Option<usize>,
|
||||
|
||||
/// some options of the item
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub option: Option<PrfOption>,
|
||||
|
||||
/// the file data
|
||||
#[serde(skip)]
|
||||
pub file_data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PrfSelected {
|
||||
pub name: Option<String>,
|
||||
pub now: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||
pub struct PrfExtra {
|
||||
pub upload: usize,
|
||||
pub download: usize,
|
||||
pub total: usize,
|
||||
pub expire: usize,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct PrfOption {
|
||||
/// for `remote` profile's http request
|
||||
/// see issue #13
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub user_agent: Option<String>,
|
||||
|
||||
/// for `remote` profile
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub with_proxy: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub update_interval: Option<u64>,
|
||||
}
|
||||
|
||||
impl PrfOption {
|
||||
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
|
||||
if one.is_none() {
|
||||
return other;
|
||||
}
|
||||
|
||||
if one.is_some() && other.is_some() {
|
||||
let mut one = one.unwrap();
|
||||
let other = other.unwrap();
|
||||
|
||||
if let Some(val) = other.user_agent {
|
||||
one.user_agent = Some(val);
|
||||
}
|
||||
|
||||
if let Some(val) = other.with_proxy {
|
||||
one.with_proxy = Some(val);
|
||||
}
|
||||
|
||||
if let Some(val) = other.update_interval {
|
||||
one.update_interval = Some(val);
|
||||
}
|
||||
|
||||
return Some(one);
|
||||
}
|
||||
|
||||
return one;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PrfItem {
|
||||
fn default() -> Self {
|
||||
PrfItem {
|
||||
uid: None,
|
||||
itype: None,
|
||||
name: None,
|
||||
desc: None,
|
||||
file: None,
|
||||
url: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
updated: None,
|
||||
option: None,
|
||||
file_data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrfItem {
|
||||
/// From partial item
|
||||
/// must contain `itype`
|
||||
pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
|
||||
if item.itype.is_none() {
|
||||
bail!("type should not be null");
|
||||
}
|
||||
|
||||
match item.itype.unwrap().as_str() {
|
||||
"remote" => {
|
||||
if item.url.is_none() {
|
||||
bail!("url should not be null");
|
||||
}
|
||||
let url = item.url.as_ref().unwrap().as_str();
|
||||
let name = item.name;
|
||||
let desc = item.desc;
|
||||
PrfItem::from_url(url, name, desc, item.option).await
|
||||
}
|
||||
"local" => {
|
||||
let name = item.name.unwrap_or("Local File".into());
|
||||
let desc = item.desc.unwrap_or("".into());
|
||||
PrfItem::from_local(name, desc, file_data)
|
||||
}
|
||||
"merge" => {
|
||||
let name = item.name.unwrap_or("Merge".into());
|
||||
let desc = item.desc.unwrap_or("".into());
|
||||
PrfItem::from_merge(name, desc)
|
||||
}
|
||||
"script" => {
|
||||
let name = item.name.unwrap_or("Script".into());
|
||||
let desc = item.desc.unwrap_or("".into());
|
||||
PrfItem::from_script(name, desc)
|
||||
}
|
||||
typ @ _ => bail!("invalid profile item type \"{typ}\""),
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Local type
|
||||
/// create a new item from name/desc
|
||||
pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
|
||||
let uid = help::get_uid("l");
|
||||
let file = format!("{uid}.yaml");
|
||||
|
||||
Ok(PrfItem {
|
||||
uid: Some(uid),
|
||||
itype: Some("local".into()),
|
||||
name: Some(name),
|
||||
desc: Some(desc),
|
||||
file: Some(file),
|
||||
url: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
updated: Some(help::get_now()),
|
||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||
})
|
||||
}
|
||||
|
||||
/// ## Remote type
|
||||
/// create a new item from url
|
||||
pub async fn from_url(
|
||||
url: &str,
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
option: Option<PrfOption>,
|
||||
) -> Result<PrfItem> {
|
||||
let with_proxy = match option.as_ref() {
|
||||
Some(opt) => opt.with_proxy.unwrap_or(false),
|
||||
None => false,
|
||||
};
|
||||
let user_agent = match option.as_ref() {
|
||||
Some(opt) => opt.user_agent.clone(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut builder = reqwest::ClientBuilder::new();
|
||||
|
||||
if !with_proxy {
|
||||
builder = builder.no_proxy();
|
||||
}
|
||||
|
||||
builder = builder.user_agent(user_agent.unwrap_or("clash-verge/v1.0.0".into()));
|
||||
|
||||
let resp = builder.build()?.get(url).send().await?;
|
||||
let header = resp.headers();
|
||||
|
||||
// parse the Subscription Userinfo
|
||||
let extra = match header.get("Subscription-Userinfo") {
|
||||
Some(value) => {
|
||||
let sub_info = value.to_str().unwrap_or("");
|
||||
|
||||
Some(PrfExtra {
|
||||
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
|
||||
download: help::parse_str(sub_info, "download=").unwrap_or(0),
|
||||
total: help::parse_str(sub_info, "total=").unwrap_or(0),
|
||||
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
// parse the Content-Disposition
|
||||
let filename = match header.get("Content-Disposition") {
|
||||
Some(value) => {
|
||||
let filename = value.to_str().unwrap_or("");
|
||||
help::parse_str::<String>(filename, "filename=")
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
// parse the profile-update-interval
|
||||
let option = match header.get("profile-update-interval") {
|
||||
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
||||
Ok(val) => Some(PrfOption {
|
||||
update_interval: Some(val * 60), // hour -> min
|
||||
..PrfOption::default()
|
||||
}),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let uid = help::get_uid("r");
|
||||
let file = format!("{uid}.yaml");
|
||||
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||
let data = resp.text_with_charset("utf-8").await?;
|
||||
|
||||
// check the data whether the valid yaml format
|
||||
if !serde_yaml::from_str::<Mapping>(&data).is_ok() {
|
||||
bail!("the remote profile data is invalid yaml");
|
||||
}
|
||||
|
||||
Ok(PrfItem {
|
||||
uid: Some(uid),
|
||||
itype: Some("remote".into()),
|
||||
name: Some(name),
|
||||
desc,
|
||||
file: Some(file),
|
||||
url: Some(url.into()),
|
||||
selected: None,
|
||||
extra,
|
||||
option,
|
||||
updated: Some(help::get_now()),
|
||||
file_data: Some(data),
|
||||
})
|
||||
}
|
||||
|
||||
/// ## Merge type (enhance)
|
||||
/// create the enhanced item by using `merge` rule
|
||||
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
|
||||
let uid = help::get_uid("m");
|
||||
let file = format!("{uid}.yaml");
|
||||
|
||||
Ok(PrfItem {
|
||||
uid: Some(uid),
|
||||
itype: Some("merge".into()),
|
||||
name: Some(name),
|
||||
desc: Some(desc),
|
||||
file: Some(file),
|
||||
url: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
updated: Some(help::get_now()),
|
||||
file_data: Some(tmpl::ITEM_MERGE.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// ## Script type (enhance)
|
||||
/// create the enhanced item by using javascript(browserjs)
|
||||
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
|
||||
let uid = help::get_uid("s");
|
||||
let file = format!("{uid}.js"); // js ext
|
||||
|
||||
Ok(PrfItem {
|
||||
uid: Some(uid),
|
||||
itype: Some("script".into()),
|
||||
name: Some(name),
|
||||
desc: Some(desc),
|
||||
file: Some(file),
|
||||
url: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
updated: Some(help::get_now()),
|
||||
file_data: Some(tmpl::ITEM_SCRIPT.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// get the file data
|
||||
pub fn read_file(&self) -> Result<String> {
|
||||
if self.file.is_none() {
|
||||
bail!("could not find the file");
|
||||
}
|
||||
|
||||
let file = self.file.clone().unwrap();
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
fs::read_to_string(path).context("failed to read the file")
|
||||
}
|
||||
|
||||
/// save the file data
|
||||
pub fn save_file(&self, data: String) -> Result<()> {
|
||||
if self.file.is_none() {
|
||||
bail!("could not find the file");
|
||||
}
|
||||
|
||||
let file = self.file.clone().unwrap();
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
fs::write(path, data.as_bytes()).context("failed to save the file")
|
||||
}
|
||||
|
||||
/// get the data for enhanced mode
|
||||
pub fn to_enhance(&self) -> Option<ChainItem> {
|
||||
let itype = self.itype.as_ref()?.as_str();
|
||||
let file = self.file.clone()?;
|
||||
let uid = self.uid.clone().unwrap_or("".into());
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match itype {
|
||||
"script" => Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())),
|
||||
}),
|
||||
"merge" => Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Merge(config::read_yaml::<Mapping>(path)),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChainItem {
|
||||
pub uid: String,
|
||||
pub data: ChainType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ChainType {
|
||||
Merge(Mapping),
|
||||
Script(String),
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
use super::prfitem::PrfItem;
|
||||
use super::ChainItem;
|
||||
use crate::utils::{config, dirs, help};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
use std::{fs, io::Write};
|
||||
|
||||
///
|
||||
/// ## Profiles Config
|
||||
///
|
||||
/// Define the `profiles.yaml` schema
|
||||
///
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Profiles {
|
||||
/// same as PrfConfig.current
|
||||
current: Option<String>,
|
||||
|
||||
/// same as PrfConfig.chain
|
||||
chain: Option<Vec<String>>,
|
||||
|
||||
/// record valid fields for clash
|
||||
valid: Option<Vec<String>>,
|
||||
|
||||
/// profile list
|
||||
items: Option<Vec<PrfItem>>,
|
||||
}
|
||||
|
||||
macro_rules! patch {
|
||||
($lv: expr, $rv: expr, $key: tt) => {
|
||||
if ($rv.$key).is_some() {
|
||||
$lv.$key = $rv.$key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Profiles {
|
||||
pub fn new() -> Self {
|
||||
Profiles::read_file()
|
||||
}
|
||||
|
||||
/// read the config from the file
|
||||
pub fn read_file() -> Self {
|
||||
let mut profiles = config::read_yaml::<Self>(dirs::profiles_path());
|
||||
|
||||
if profiles.items.is_none() {
|
||||
profiles.items = Some(vec![]);
|
||||
}
|
||||
|
||||
// compatiable with the old old old version
|
||||
profiles.items.as_mut().map(|items| {
|
||||
for mut item in items.iter_mut() {
|
||||
if item.uid.is_none() {
|
||||
item.uid = Some(help::get_uid("d"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
profiles
|
||||
}
|
||||
|
||||
/// save the config to the file
|
||||
pub fn save_file(&self) -> Result<()> {
|
||||
config::save_yaml(
|
||||
dirs::profiles_path(),
|
||||
self,
|
||||
Some("# Profiles Config for Clash Verge\n\n"),
|
||||
)
|
||||
}
|
||||
|
||||
/// get the current uid
|
||||
pub fn get_current(&self) -> Option<String> {
|
||||
self.current.clone()
|
||||
}
|
||||
|
||||
/// only change the main to the target id
|
||||
pub fn put_current(&mut self, uid: String) -> Result<()> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
let items = self.items.as_ref().unwrap();
|
||||
let some_uid = Some(uid.clone());
|
||||
|
||||
if items.iter().find(|&each| each.uid == some_uid).is_some() {
|
||||
self.current = some_uid;
|
||||
return self.save_file();
|
||||
}
|
||||
|
||||
bail!("invalid uid \"{uid}\"");
|
||||
}
|
||||
|
||||
/// just change the `chain`
|
||||
pub fn put_chain(&mut self, chain: Option<Vec<String>>) -> Result<()> {
|
||||
self.chain = chain;
|
||||
self.save_file()
|
||||
}
|
||||
|
||||
/// just change the `field`
|
||||
pub fn put_valid(&mut self, valid: Option<Vec<String>>) -> Result<()> {
|
||||
self.valid = valid;
|
||||
self.save_file()
|
||||
}
|
||||
|
||||
/// get items ref
|
||||
pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
|
||||
self.items.as_ref()
|
||||
}
|
||||
|
||||
/// find the item by the uid
|
||||
pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
|
||||
if self.items.is_some() {
|
||||
let items = self.items.as_ref().unwrap();
|
||||
let some_uid = Some(uid.clone());
|
||||
|
||||
for each in items.iter() {
|
||||
if each.uid == some_uid {
|
||||
return Ok(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("failed to get the profile item \"uid:{uid}\"");
|
||||
}
|
||||
|
||||
/// append new item
|
||||
/// if the file_data is some
|
||||
/// then should save the data to file
|
||||
pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
|
||||
if item.uid.is_none() {
|
||||
bail!("the uid should not be null");
|
||||
}
|
||||
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
if item.file.is_none() {
|
||||
bail!("the file should not be null");
|
||||
}
|
||||
|
||||
let file = item.file.clone().unwrap();
|
||||
let path = dirs::app_profiles_dir().join(&file);
|
||||
|
||||
fs::File::create(path)
|
||||
.context(format!("failed to create file \"{}\"", file))?
|
||||
.write(file_data.as_bytes())
|
||||
.context(format!("failed to write to file \"{}\"", file))?;
|
||||
}
|
||||
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
self.items.as_mut().map(|items| items.push(item));
|
||||
self.save_file()
|
||||
}
|
||||
|
||||
/// update the item value
|
||||
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
|
||||
let mut items = self.items.take().unwrap_or(vec![]);
|
||||
|
||||
for mut each in items.iter_mut() {
|
||||
if each.uid == Some(uid.clone()) {
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
patch!(each, item, file);
|
||||
patch!(each, item, url);
|
||||
patch!(each, item, selected);
|
||||
patch!(each, item, extra);
|
||||
patch!(each, item, updated);
|
||||
patch!(each, item, option);
|
||||
|
||||
self.items = Some(items);
|
||||
return self.save_file();
|
||||
}
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
bail!("failed to find the profile item \"uid:{uid}\"")
|
||||
}
|
||||
|
||||
/// be used to update the remote item
|
||||
/// only patch `updated` `extra` `file_data`
|
||||
pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
|
||||
// find the item
|
||||
let _ = self.get_item(&uid)?;
|
||||
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
let some_uid = Some(uid.clone());
|
||||
|
||||
for mut each in items.iter_mut() {
|
||||
if each.uid == some_uid {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
let file = each.file.take();
|
||||
let file = file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
|
||||
|
||||
// the file must exists
|
||||
each.file = Some(file.clone());
|
||||
|
||||
let path = dirs::app_profiles_dir().join(&file);
|
||||
|
||||
fs::File::create(path)
|
||||
.context(format!("failed to create file \"{}\"", file))?
|
||||
.write(file_data.as_bytes())
|
||||
.context(format!("failed to write to file \"{}\"", file))?;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.save_file()
|
||||
}
|
||||
|
||||
/// delete item
|
||||
/// if delete the current then return true
|
||||
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
|
||||
let current = self.current.as_ref().unwrap_or(&uid);
|
||||
let current = current.clone();
|
||||
|
||||
let mut items = self.items.take().unwrap_or(vec![]);
|
||||
let mut index = None;
|
||||
|
||||
// get the index
|
||||
for i in 0..items.len() {
|
||||
if items[i].uid == Some(uid.clone()) {
|
||||
index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(index) = index {
|
||||
items.remove(index).file.map(|file| {
|
||||
let path = dirs::app_profiles_dir().join(file);
|
||||
if path.exists() {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// delete the original uid
|
||||
if current == uid {
|
||||
self.current = match items.len() > 0 {
|
||||
true => items[0].uid.clone(),
|
||||
false => None,
|
||||
};
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
self.save_file()?;
|
||||
Ok(current == uid)
|
||||
}
|
||||
|
||||
/// generate the current Mapping data
|
||||
fn gen_current(&self) -> Result<Mapping> {
|
||||
let config = Mapping::new();
|
||||
|
||||
if self.current.is_none() || self.items.is_none() {
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
let current = self.current.clone().unwrap();
|
||||
for item in self.items.as_ref().unwrap().iter() {
|
||||
if item.uid == Some(current.clone()) {
|
||||
let file_path = match item.file.clone() {
|
||||
Some(file) => dirs::app_profiles_dir().join(file),
|
||||
None => bail!("failed to get the file field"),
|
||||
};
|
||||
|
||||
if !file_path.exists() {
|
||||
bail!("failed to read the file \"{}\"", file_path.display());
|
||||
}
|
||||
|
||||
return Ok(config::read_yaml::<Mapping>(file_path.clone()));
|
||||
}
|
||||
}
|
||||
bail!("failed to find current profile \"uid:{current}\"");
|
||||
}
|
||||
|
||||
/// generate the data for activate clash config
|
||||
pub fn gen_activate(&self) -> Result<PrfActivate> {
|
||||
let current = self.gen_current()?;
|
||||
let chain = match self.chain.as_ref() {
|
||||
Some(chain) => chain
|
||||
.iter()
|
||||
.filter_map(|uid| self.get_item(uid).ok())
|
||||
.filter_map(|item| item.to_enhance())
|
||||
.collect::<Vec<ChainItem>>(),
|
||||
None => vec![],
|
||||
};
|
||||
let valid = self.valid.clone().unwrap_or(vec![]);
|
||||
|
||||
Ok(PrfActivate {
|
||||
current,
|
||||
chain,
|
||||
valid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PrfActivate {
|
||||
pub current: Mapping,
|
||||
pub chain: Vec<ChainItem>,
|
||||
pub valid: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RuntimeResult {
|
||||
pub config: Option<Mapping>,
|
||||
pub config_yaml: Option<String>,
|
||||
// 记录在配置中(包括merge和script生成的)出现过的keys
|
||||
// 这些keys不一定都生效
|
||||
pub exists_keys: Vec<String>,
|
||||
pub chain_logs: HashMap<String, Vec<(String, String)>>,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{notice::Notice, ClashInfo};
|
||||
use crate::data::{ClashInfo, Data};
|
||||
use crate::log_if_err;
|
||||
use crate::utils::{config, dirs};
|
||||
use anyhow::{bail, Result};
|
||||
@@ -15,7 +15,6 @@ use std::{
|
||||
use tauri::api::process::{Command, CommandChild, CommandEvent};
|
||||
use tokio::time::sleep;
|
||||
|
||||
static mut CLASH_CORE: &str = "clash";
|
||||
const LOGS_QUEUE_LEN: usize = 100;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -23,39 +22,31 @@ pub struct Service {
|
||||
sidecar: Option<CommandChild>,
|
||||
|
||||
logs: Arc<RwLock<VecDeque<String>>>,
|
||||
|
||||
#[allow(unused)]
|
||||
service_mode: bool,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn new() -> Service {
|
||||
let queue = VecDeque::with_capacity(LOGS_QUEUE_LEN + 10);
|
||||
|
||||
Service {
|
||||
sidecar: None,
|
||||
logs: Arc::new(RwLock::new(queue)),
|
||||
service_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_core(&mut self, clash_core: Option<String>) {
|
||||
unsafe {
|
||||
CLASH_CORE = Box::leak(clash_core.unwrap_or("clash".into()).into_boxed_str());
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn set_mode(&mut self, enable: bool) {
|
||||
self.service_mode = enable;
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
self.start_clash_by_sidecar()?;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if !self.service_mode {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -76,18 +67,24 @@ impl Service {
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
self.stop_clash_by_sidecar()?;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if !self.service_mode {
|
||||
return self.stop_clash_by_sidecar();
|
||||
}
|
||||
let _ = self.stop_clash_by_sidecar();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
log_if_err!(Self::stop_clash_by_service().await);
|
||||
});
|
||||
let enable = {
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
verge.enable_service_mode.clone().unwrap_or(false)
|
||||
};
|
||||
|
||||
if enable {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
log_if_err!(Self::stop_clash_by_service().await);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -119,13 +116,19 @@ impl Service {
|
||||
/// start the clash sidecar
|
||||
fn start_clash_by_sidecar(&mut self) -> Result<()> {
|
||||
if self.sidecar.is_some() {
|
||||
bail!("could not run clash sidecar twice");
|
||||
let sidecar = self.sidecar.take().unwrap();
|
||||
let _ = sidecar.kill();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
let clash_core = unsafe { CLASH_CORE };
|
||||
let cmd = Command::new_sidecar(clash_core)?;
|
||||
let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
|
||||
|
||||
@@ -178,70 +181,70 @@ impl Service {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update clash config
|
||||
/// using PUT methods
|
||||
pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> {
|
||||
if !self.service_mode && self.sidecar.is_none() {
|
||||
bail!("did not start sidecar");
|
||||
pub fn check_start(&mut self) -> Result<()> {
|
||||
let global = Data::global();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
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"))?;
|
||||
|
||||
let (server, headers) = Self::clash_client_info(info)?;
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
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());
|
||||
|
||||
// retry 5 times
|
||||
for _ in 0..5 {
|
||||
match reqwest::ClientBuilder::new().no_proxy().build() {
|
||||
Ok(client) => {
|
||||
let builder = client.put(&server).headers(headers.clone()).json(&data);
|
||||
|
||||
match builder.send().await {
|
||||
Ok(resp) => {
|
||||
if resp.status() != 204 {
|
||||
log::error!(target: "app", "failed to activate clash with status \"{}\"", resp.status());
|
||||
}
|
||||
|
||||
notice.refresh_clash();
|
||||
|
||||
// do not retry
|
||||
break;
|
||||
// retry 5 times
|
||||
for _ 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 @ _ => {
|
||||
log::error!(target: "app", "failed to activate clash with status \"{status}\"");
|
||||
}
|
||||
Err(err) => log::error!(target: "app", "failed to activate for `{err}`"),
|
||||
}
|
||||
},
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
Err(err) => log::error!(target: "app", "failed to activate for `{err}`"),
|
||||
}
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
});
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// patch clash config
|
||||
pub fn patch_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> {
|
||||
if !self.service_mode && self.sidecar.is_none() {
|
||||
bail!("did not start sidecar");
|
||||
}
|
||||
|
||||
pub async fn patch_config(info: ClashInfo, config: Mapping) -> Result<()> {
|
||||
let (server, headers) = Self::clash_client_info(info)?;
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Ok(client) = reqwest::ClientBuilder::new().no_proxy().build() {
|
||||
let builder = client.patch(&server).headers(headers.clone()).json(&config);
|
||||
|
||||
match builder.send().await {
|
||||
Ok(_) => notice.refresh_clash(),
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let client = reqwest::ClientBuilder::new().no_proxy().build()?;
|
||||
let builder = client.patch(&server).headers(headers.clone()).json(&config);
|
||||
builder.send().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -296,7 +299,7 @@ impl Drop for Service {
|
||||
|
||||
/// ### Service Mode
|
||||
///
|
||||
#[cfg(windows)]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod win_service {
|
||||
use super::*;
|
||||
use anyhow::Context;
|
||||
@@ -453,7 +456,12 @@ pub mod win_service {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
let clash_core = unsafe { CLASH_CORE };
|
||||
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();
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use super::{Clash, Verge};
|
||||
use crate::{log_if_err, utils::sysopt::SysProxyConfig};
|
||||
use crate::{data::*, log_if_err};
|
||||
use anyhow::{bail, Result};
|
||||
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
|
||||
use std::sync::Arc;
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
|
||||
|
||||
pub struct Sysopt {
|
||||
/// current system proxy setting
|
||||
cur_sysproxy: Option<SysProxyConfig>,
|
||||
cur_sysproxy: Option<Sysproxy>,
|
||||
|
||||
/// record the original system proxy
|
||||
/// recover it when exit
|
||||
old_sysproxy: Option<SysProxyConfig>,
|
||||
old_sysproxy: Option<Sysproxy>,
|
||||
|
||||
/// helps to auto launch the app
|
||||
auto_launch: Option<AutoLaunch>,
|
||||
@@ -20,6 +20,13 @@ pub struct Sysopt {
|
||||
guard_state: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;<local>";
|
||||
#[cfg(target_os = "linux")]
|
||||
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1";
|
||||
#[cfg(target_os = "macos")]
|
||||
static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,<local>";
|
||||
|
||||
impl Sysopt {
|
||||
pub fn new() -> Sysopt {
|
||||
Sysopt {
|
||||
@@ -31,84 +38,105 @@ impl Sysopt {
|
||||
}
|
||||
|
||||
/// init the sysproxy
|
||||
pub fn init_sysproxy(&mut self, port: Option<String>, verge: &Verge) {
|
||||
if let Some(port) = port {
|
||||
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
||||
pub fn init_sysproxy(&mut self) -> Result<()> {
|
||||
let data = Data::global();
|
||||
let clash = data.clash.lock();
|
||||
let port = clash.info.port.clone();
|
||||
|
||||
self.old_sysproxy = match SysProxyConfig::get_sys() {
|
||||
Ok(proxy) => Some(proxy),
|
||||
Err(_) => None,
|
||||
};
|
||||
if port.is_none() {
|
||||
bail!("clash port is none");
|
||||
}
|
||||
|
||||
let bypass = verge.system_proxy_bypass.clone();
|
||||
let sysproxy = SysProxyConfig::new(enable, port, bypass);
|
||||
let verge = data.verge.lock();
|
||||
|
||||
if enable {
|
||||
if let Err(err) = sysproxy.set_sys() {
|
||||
log::error!(target: "app", "failed to set system proxy for `{err}`");
|
||||
}
|
||||
}
|
||||
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());
|
||||
|
||||
self.cur_sysproxy = Some(sysproxy);
|
||||
let port = port.unwrap().parse::<u16>()?;
|
||||
let host = String::from("127.0.0.1");
|
||||
|
||||
self.cur_sysproxy = Some(Sysproxy {
|
||||
enable,
|
||||
host,
|
||||
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()?;
|
||||
}
|
||||
|
||||
// launchs the system proxy guard
|
||||
self.guard_proxy();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update the system proxy
|
||||
/// when the verge config is changed
|
||||
pub fn update_sysproxy(&mut self, enable: Option<bool>, bypass: Option<String>) -> Result<()> {
|
||||
let sysproxy = self.cur_sysproxy.take();
|
||||
|
||||
if sysproxy.is_none() {
|
||||
bail!("unhandle error for sysproxy is none");
|
||||
pub fn update_sysproxy(&mut self) -> Result<()> {
|
||||
if self.cur_sysproxy.is_none() {
|
||||
return self.init_sysproxy();
|
||||
}
|
||||
|
||||
let mut sysproxy = sysproxy.unwrap();
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
|
||||
if let Some(enable) = enable {
|
||||
sysproxy.enable = enable;
|
||||
}
|
||||
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());
|
||||
|
||||
if let Some(bypass) = bypass {
|
||||
sysproxy.bypass = bypass;
|
||||
}
|
||||
let mut sysproxy = self.cur_sysproxy.take().unwrap();
|
||||
|
||||
sysproxy.enable = enable;
|
||||
sysproxy.bypass = bypass;
|
||||
|
||||
self.cur_sysproxy = Some(sysproxy);
|
||||
|
||||
if self.cur_sysproxy.as_ref().unwrap().set_sys().is_err() {
|
||||
bail!("failed to set system proxy");
|
||||
}
|
||||
self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// reset the sysproxy
|
||||
pub fn reset_sysproxy(&mut self) {
|
||||
if let Some(sysproxy) = self.old_sysproxy.take() {
|
||||
// 如果原代理设置是开启的,且域名端口设置和当前的一致,就不恢复原设置
|
||||
// https://github.com/zzzgydi/clash-verge/issues/157
|
||||
if let Some(cur) = self.cur_sysproxy.as_ref() {
|
||||
if sysproxy.enable && cur.server == sysproxy.server {
|
||||
return;
|
||||
pub fn reset_sysproxy(&mut self) -> Result<()> {
|
||||
if self.cur_sysproxy.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cur = self.cur_sysproxy.take().unwrap();
|
||||
|
||||
match self.old_sysproxy.take() {
|
||||
Some(old) => {
|
||||
// 如果原代理设置和当前的设置是一样的,需要关闭
|
||||
// 否则就恢复原代理设置
|
||||
if old.enable && old.host == cur.host && old.port == cur.port {
|
||||
cur.enable = false;
|
||||
cur.set_system_proxy()?;
|
||||
} else {
|
||||
old.set_system_proxy()?;
|
||||
}
|
||||
}
|
||||
|
||||
match sysproxy.set_sys() {
|
||||
Ok(_) => self.cur_sysproxy = None,
|
||||
Err(_) => log::error!(target: "app", "failed to reset proxy"),
|
||||
None => {
|
||||
if cur.enable {
|
||||
cur.enable = false;
|
||||
cur.set_system_proxy()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// get current proxy
|
||||
pub fn get_sysproxy(&self) -> Result<Option<SysProxyConfig>> {
|
||||
Ok(self.cur_sysproxy.clone())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// init the auto launch
|
||||
pub fn init_launch(&mut self, enable: Option<bool>) -> Result<()> {
|
||||
pub fn init_launch(&mut self) -> Result<()> {
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
|
||||
|
||||
if !enable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let app_exe = current_exe().unwrap();
|
||||
let app_exe = dunce::canonicalize(app_exe).unwrap();
|
||||
let app_name = app_exe.file_stem().unwrap().to_str().unwrap();
|
||||
@@ -125,25 +153,23 @@ impl Sysopt {
|
||||
.set_app_path(app_path)
|
||||
.build()?;
|
||||
|
||||
if let Some(enable) = enable {
|
||||
// fix issue #26
|
||||
if enable {
|
||||
auto.enable()?;
|
||||
}
|
||||
}
|
||||
|
||||
// fix issue #26
|
||||
auto.enable()?;
|
||||
self.auto_launch = Some(auto);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// update the startup
|
||||
pub fn update_launch(&mut self, enable: Option<bool>) -> Result<()> {
|
||||
if enable.is_none() {
|
||||
return Ok(());
|
||||
pub fn update_launch(&mut self) -> Result<()> {
|
||||
if self.auto_launch.is_none() {
|
||||
return self.init_launch();
|
||||
}
|
||||
|
||||
let enable = enable.unwrap();
|
||||
let data = Data::global();
|
||||
let verge = data.verge.lock();
|
||||
let enable = verge.enable_auto_launch.clone().unwrap_or(false);
|
||||
|
||||
let auto_launch = self.auto_launch.as_ref().unwrap();
|
||||
|
||||
match enable {
|
||||
@@ -176,35 +202,42 @@ impl Sysopt {
|
||||
loop {
|
||||
sleep(Duration::from_secs(wait_secs)).await;
|
||||
|
||||
let verge = Verge::new();
|
||||
let global = Data::global();
|
||||
let verge = global.verge.lock();
|
||||
|
||||
let enable_proxy = verge.enable_system_proxy.unwrap_or(false);
|
||||
let enable_guard = verge.enable_proxy_guard.unwrap_or(false);
|
||||
let guard_duration = verge.proxy_guard_duration.unwrap_or(10);
|
||||
let enable = verge.enable_system_proxy.clone().unwrap_or(false);
|
||||
let guard = verge.enable_proxy_guard.clone().unwrap_or(false);
|
||||
let guard_duration = verge.proxy_guard_duration.clone().unwrap_or(10);
|
||||
let bypass = verge.system_proxy_bypass.clone();
|
||||
drop(verge);
|
||||
|
||||
// stop loop
|
||||
if !enable || !guard {
|
||||
break;
|
||||
}
|
||||
|
||||
// update duration
|
||||
wait_secs = guard_duration;
|
||||
|
||||
// stop loop
|
||||
if !enable_guard || !enable_proxy {
|
||||
break;
|
||||
}
|
||||
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");
|
||||
|
||||
let clash = Clash::new();
|
||||
match port {
|
||||
Ok(port) => {
|
||||
let sysproxy = Sysproxy {
|
||||
enable: true,
|
||||
host: "127.0.0.1".into(),
|
||||
port,
|
||||
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
|
||||
};
|
||||
|
||||
match &clash.info.port {
|
||||
Some(port) => {
|
||||
let bypass = verge.system_proxy_bypass.clone();
|
||||
let sysproxy = SysProxyConfig::new(true, port.clone(), bypass);
|
||||
|
||||
log_if_err!(sysproxy.set_sys());
|
||||
}
|
||||
None => {
|
||||
let status = &clash.info.status;
|
||||
log::error!(target: "app", "failed to parse clash port with status {status}")
|
||||
log_if_err!(sysproxy.set_system_proxy());
|
||||
}
|
||||
Err(_) => log::error!(target: "app", "failed to parse clash port"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::Core;
|
||||
use crate::log_if_err;
|
||||
use crate::utils::help::get_now;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use crate::{data::Data, log_if_err};
|
||||
use anyhow::{Context, Result};
|
||||
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -16,9 +16,6 @@ pub struct Timer {
|
||||
|
||||
/// increment id
|
||||
timer_count: TaskID,
|
||||
|
||||
/// save the instance of the app
|
||||
core: Option<Core>,
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
@@ -27,20 +24,11 @@ impl Timer {
|
||||
delay_timer: DelayTimerBuilder::default().build(),
|
||||
timer_map: HashMap::new(),
|
||||
timer_count: 1,
|
||||
core: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_core(&mut self, core: Core) {
|
||||
self.core = Some(core);
|
||||
}
|
||||
|
||||
/// Correctly update all cron tasks
|
||||
pub fn refresh(&mut self) -> Result<()> {
|
||||
if self.core.is_none() {
|
||||
bail!("unhandle error for core is none");
|
||||
}
|
||||
|
||||
let diff_map = self.gen_diff();
|
||||
|
||||
for (uid, diff) in diff_map.into_iter() {
|
||||
@@ -69,7 +57,9 @@ impl Timer {
|
||||
self.refresh()?;
|
||||
|
||||
let cur_timestamp = get_now(); // seconds
|
||||
let profiles = self.core.as_ref().unwrap().profiles.lock();
|
||||
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
|
||||
profiles
|
||||
.get_items()
|
||||
@@ -94,7 +84,8 @@ impl Timer {
|
||||
|
||||
/// generate a uid -> update_interval map
|
||||
fn gen_map(&self) -> HashMap<String, u64> {
|
||||
let profiles = self.core.as_ref().unwrap().profiles.lock();
|
||||
let global = Data::global();
|
||||
let profiles = global.profiles.lock();
|
||||
|
||||
let mut new_map = HashMap::new();
|
||||
|
||||
@@ -148,14 +139,14 @@ impl Timer {
|
||||
|
||||
/// add a cron task
|
||||
fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> {
|
||||
let core = self.core.clone().unwrap();
|
||||
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.clone(), uid.clone()))
|
||||
.spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned()))
|
||||
.context("failed to create timer task")?;
|
||||
|
||||
self
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
use crate::utils::{config, dirs};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// ### `verge.yaml` schema
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Verge {
|
||||
/// app listening port
|
||||
/// for app singleton
|
||||
pub app_singleton_port: Option<u16>,
|
||||
|
||||
// i18n
|
||||
pub language: Option<String>,
|
||||
|
||||
/// `light` or `dark`
|
||||
pub theme_mode: Option<String>,
|
||||
|
||||
/// enable blur mode
|
||||
/// maybe be able to set the alpha
|
||||
pub theme_blur: Option<bool>,
|
||||
|
||||
/// enable traffic graph default is true
|
||||
pub traffic_graph: Option<bool>,
|
||||
|
||||
/// clash tun mode
|
||||
pub enable_tun_mode: Option<bool>,
|
||||
|
||||
/// windows service mode
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enable_service_mode: Option<bool>,
|
||||
|
||||
/// can the app auto startup
|
||||
pub enable_auto_launch: Option<bool>,
|
||||
|
||||
/// not show the window on launch
|
||||
pub enable_silent_start: Option<bool>,
|
||||
|
||||
/// set system proxy
|
||||
pub enable_system_proxy: Option<bool>,
|
||||
|
||||
/// enable proxy guard
|
||||
pub enable_proxy_guard: Option<bool>,
|
||||
|
||||
/// set system proxy bypass
|
||||
pub system_proxy_bypass: Option<String>,
|
||||
|
||||
/// proxy guard duration
|
||||
pub proxy_guard_duration: Option<u64>,
|
||||
|
||||
/// theme setting
|
||||
pub theme_setting: Option<VergeTheme>,
|
||||
|
||||
/// web ui list
|
||||
pub web_ui_list: Option<Vec<String>>,
|
||||
|
||||
/// clash core path
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub clash_core: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct VergeTheme {
|
||||
pub primary_color: Option<String>,
|
||||
pub secondary_color: Option<String>,
|
||||
pub primary_text: Option<String>,
|
||||
pub secondary_text: Option<String>,
|
||||
|
||||
pub info_color: Option<String>,
|
||||
pub error_color: Option<String>,
|
||||
pub warning_color: Option<String>,
|
||||
pub success_color: Option<String>,
|
||||
|
||||
pub font_family: Option<String>,
|
||||
pub css_injection: Option<String>,
|
||||
}
|
||||
|
||||
impl Verge {
|
||||
pub fn new() -> Self {
|
||||
config::read_yaml::<Verge>(dirs::verge_path())
|
||||
}
|
||||
|
||||
/// Save Verge App Config
|
||||
pub fn save_file(&self) -> Result<()> {
|
||||
config::save_yaml(
|
||||
dirs::verge_path(),
|
||||
self,
|
||||
Some("# The Config for Clash Verge App\n\n"),
|
||||
)
|
||||
}
|
||||
|
||||
/// patch verge config
|
||||
/// only save to file
|
||||
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
|
||||
macro_rules! patch {
|
||||
($key: tt) => {
|
||||
if patch.$key.is_some() {
|
||||
self.$key = patch.$key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
patch!(language);
|
||||
patch!(theme_mode);
|
||||
patch!(theme_blur);
|
||||
patch!(traffic_graph);
|
||||
|
||||
patch!(enable_tun_mode);
|
||||
patch!(enable_service_mode);
|
||||
patch!(enable_auto_launch);
|
||||
patch!(enable_silent_start);
|
||||
patch!(enable_system_proxy);
|
||||
patch!(enable_proxy_guard);
|
||||
patch!(system_proxy_bypass);
|
||||
patch!(proxy_guard_duration);
|
||||
|
||||
patch!(theme_setting);
|
||||
patch!(web_ui_list);
|
||||
patch!(clash_core);
|
||||
|
||||
self.save_file()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user