refactor: wip

This commit is contained in:
GyDi 2022-04-20 01:44:47 +08:00
parent 3076fd19c1
commit b8ad328cde
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
13 changed files with 419 additions and 939 deletions

View File

@ -1,12 +1,11 @@
use crate::{
core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, VergeConfig},
utils::{dirs, sysopt::SysProxyConfig},
utils::{dirs, help, sysopt::SysProxyConfig},
};
use crate::{log_if_err, ret_err, wrap_err};
use anyhow::Result;
use serde_yaml::Mapping;
use std::process::Command;
use tauri::{api, Manager, State};
use tauri::{api, State};
type CmdResult<T = ()> = Result<T, String>;
@ -17,11 +16,10 @@ pub fn get_profiles(core: State<'_, Core>) -> CmdResult<Profiles> {
Ok(profiles.clone())
}
/// synchronize data irregularly
/// manually exec enhanced profile
#[tauri::command]
pub fn sync_profiles(core: State<'_, Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
wrap_err!(profiles.sync_file())
pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
wrap_err!(core.activate_enhanced(false))
}
/// import the profile from url
@ -102,23 +100,18 @@ pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult {
drop(profiles);
log_if_err!(core.activate_enhanced(false));
Ok(())
wrap_err!(core.activate_enhanced(false))
}
/// change the profile chain
#[tauri::command]
pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -> CmdResult {
dbg!("change profile chain");
let mut profiles = core.profiles.lock();
profiles.put_chain(chain);
dbg!("change profile chain finish");
drop(profiles);
log_if_err!(core.activate_enhanced(false));
Ok(())
wrap_err!(core.activate_enhanced(false))
}
/// change the profile valid fields
@ -126,18 +119,10 @@ pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -
pub fn change_profile_valid(valid: Option<Vec<String>>, core: State<Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
profiles.put_valid(valid);
drop(profiles);
log_if_err!(core.activate_enhanced(false));
Ok(())
}
/// manually exec enhanced profile
#[tauri::command]
pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
log_if_err!(core.activate_enhanced(false));
Ok(())
wrap_err!(core.activate_enhanced(false))
}
/// delete profile item
@ -147,7 +132,7 @@ pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult {
if wrap_err!(profiles.delete_item(index))? {
drop(profiles);
// std::mem::drop(profiles);
log_if_err!(core.activate_enhanced(false));
}
@ -178,32 +163,7 @@ pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult {
ret_err!("the file not found");
}
// use vscode first
if let Ok(code) = which::which("code") {
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
if let Err(err) = Command::new(code)
.creation_flags(0x08000000)
.arg(path)
.spawn()
{
log::error!("failed to open file by VScode for {err}");
return Err("failed to open file by VScode".into());
}
}
#[cfg(not(target_os = "windows"))]
if let Err(err) = Command::new(code).arg(path).spawn() {
log::error!("failed to open file by VScode for {err}");
return Err("failed to open file by VScode".into());
}
return Ok(());
}
wrap_err!(open::that(path))
wrap_err!(help::open_file(path))
}
/// read the profile item file data
@ -233,12 +193,6 @@ pub fn save_profile_file(
wrap_err!(item.save_file(file_data.unwrap()))
}
/// restart the sidecar
#[tauri::command]
pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult {
wrap_err!(core.restart_clash())
}
/// get the clash core info from the state
/// the caller can also get the infomation by clash's api
#[tauri::command]
@ -255,29 +209,11 @@ pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult
wrap_err!(core.patch_clash(payload))
}
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
wrap_err!(SysProxyConfig::get_sys())
}
/// get the current proxy config
/// which may not the same as system proxy
#[tauri::command]
pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult<Option<SysProxyConfig>> {
let verge = core.verge.lock();
Ok(verge.cur_sysproxy.clone())
}
/// get the verge config
#[tauri::command]
pub fn get_verge_config(core: State<'_, Core>) -> CmdResult<VergeConfig> {
let verge = core.verge.lock();
let mut config = verge.config.clone();
if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass)
}
let config = verge.config.clone();
Ok(config)
}
@ -290,41 +226,35 @@ pub fn patch_verge_config(
app_handle: tauri::AppHandle,
core: State<'_, Core>,
) -> Result<(), String> {
let tun_mode = payload.enable_tun_mode.clone();
let system_proxy = payload.enable_system_proxy.clone();
wrap_err!(core.patch_verge(payload, &app_handle))
}
let mut verge = core.verge.lock();
wrap_err!(verge.patch_config(payload))?;
// change system tray
if system_proxy.is_some() || tun_mode.is_some() {
verge.update_systray(&app_handle).unwrap();
}
// change tun mode
if tun_mode.is_some() {
#[cfg(target_os = "windows")]
if *tun_mode.as_ref().unwrap() {
let wintun_dll = dirs::app_home_dir().join("wintun.dll");
if !wintun_dll.exists() {
log::error!("failed to enable TUN for missing `wintun.dll`");
return Err("failed to enable TUN for missing `wintun.dll`".into());
}
}
std::mem::drop(verge);
log_if_err!(core.activate_enhanced(false));
}
Ok(())
/// restart the sidecar
#[tauri::command]
pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult {
wrap_err!(core.restart_clash())
}
/// kill all sidecars when update app
#[tauri::command]
pub fn kill_sidecars() {
pub fn kill_sidecar() {
api::process::kill_children();
}
/// get the system proxy
#[tauri::command]
pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
wrap_err!(SysProxyConfig::get_sys())
}
/// get the current proxy config
/// which may not the same as system proxy
#[tauri::command]
pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult<Option<SysProxyConfig>> {
let sysopt = core.sysopt.lock();
wrap_err!(sysopt.get_sysproxy())
}
/// open app config dir
#[tauri::command]
pub fn open_app_dir() -> Result<(), String> {

View File

@ -1,520 +0,0 @@
use super::{PrfEnhancedResult, Profiles, Verge, VergeConfig};
use crate::log_if_err;
use crate::utils::{config, dirs, help};
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value};
use std::{collections::HashMap, time::Duration};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use tauri::Window;
use tokio::time::sleep;
#[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>,
}
pub struct Clash {
/// maintain the clash config
pub config: Mapping,
/// some info
pub info: ClashInfo,
/// clash sidecar
pub sidecar: Option<CommandChild>,
/// save the main window
pub window: Option<Window>,
}
impl Clash {
pub fn new() -> Clash {
let config = Clash::read_config();
let info = Clash::get_info(&config);
Clash {
config,
info,
sidecar: None,
window: None,
}
}
/// get clash config
fn read_config() -> Mapping {
config::read_yaml::<Mapping>(dirs::clash_path())
}
/// save the clash config
fn save_config(&self) -> Result<()> {
config::save_yaml(
dirs::clash_path(),
&self.config,
Some("# Default Config For Clash Core\n\n"),
)
}
/// parse the clash's config.yaml
/// get some information
fn get_info(clash_config: &Mapping) -> ClashInfo {
let key_port_1 = Value::from("port");
let key_port_2 = Value::from("mixed-port");
let key_server = Value::from("external-controller");
let key_secret = Value::from("secret");
let port = match clash_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()),
_ => None,
},
_ => None,
};
let port = match port {
Some(_) => port,
None => match clash_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()),
_ => None,
},
_ => None,
},
};
let server = match clash_config.get(&key_server) {
Some(value) => match value {
Value::String(val_str) => {
// `external-controller` could be
// "127.0.0.1:9090" or ":9090"
// Todo: maybe it could support single port
let server = val_str.clone();
let server = match server.starts_with(":") {
true => format!("127.0.0.1{server}"),
false => server,
};
Some(server)
}
_ => None,
},
_ => None,
};
let secret = match clash_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: "init".into(),
port,
server,
secret,
}
}
/// save the main window
pub fn set_window(&mut self, win: Option<Window>) {
self.window = win;
}
/// run clash sidecar
pub fn run_sidecar(&mut self, profiles: &Profiles, delay: bool) -> Result<()> {
let app_dir = dirs::app_home_dir();
let app_dir = app_dir.as_os_str().to_str().unwrap();
let cmd = Command::new_sidecar("clash")?;
let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
self.sidecar = Some(cmd_child);
// clash log
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
_ => {}
}
}
});
// activate profile
log_if_err!(self.activate(&profiles));
log_if_err!(self.activate_enhanced(&profiles, delay, true));
Ok(())
}
/// drop clash sidecar
pub fn drop_sidecar(&mut self) -> Result<()> {
if let Some(sidecar) = self.sidecar.take() {
sidecar.kill()?;
}
Ok(())
}
/// restart clash sidecar
/// should reactivate profile after restart
pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
self.update_config();
self.drop_sidecar()?;
self.run_sidecar(profiles, false)
}
/// update the clash info
pub fn update_config(&mut self) {
self.config = Clash::read_config();
self.info = Clash::get_info(&self.config);
}
/// patch update the clash config
pub fn patch_config(
&mut self,
patch: Mapping,
verge: &mut Verge,
profiles: &mut Profiles,
) -> Result<()> {
let mix_port_key = Value::from("mixed-port");
let mut port = None;
for (key, value) in patch.into_iter() {
let value = value.clone();
// check whether the mix_port is changed
if key == mix_port_key {
if value.is_number() {
port = value.as_i64().as_ref().map(|n| n.to_string());
} else {
port = value.as_str().as_ref().map(|s| s.to_string());
}
}
self.config.insert(key.clone(), value);
}
self.save_config()?;
if let Some(port) = port {
self.restart_sidecar(profiles)?;
verge.init_sysproxy(Some(port));
}
Ok(())
}
/// revise the `tun` and `dns` config
fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping {
macro_rules! revise {
($map: expr, $key: expr, $val: expr) => {
let ret_key = Value::String($key.into());
$map.insert(ret_key, Value::from($val));
};
}
// if key not exists then append value
macro_rules! append {
($map: expr, $key: expr, $val: expr) => {
let ret_key = Value::String($key.into());
if !$map.contains_key(&ret_key) {
$map.insert(ret_key, Value::from($val));
}
};
}
// tun config
let tun_val = config.get(&Value::from("tun"));
let mut new_tun = Mapping::new();
if tun_val.is_some() && tun_val.as_ref().unwrap().is_mapping() {
new_tun = tun_val.as_ref().unwrap().as_mapping().unwrap().clone();
}
revise!(new_tun, "enable", enable);
if enable {
append!(new_tun, "stack", "gvisor");
append!(new_tun, "dns-hijack", vec!["198.18.0.2:53"]);
append!(new_tun, "auto-route", true);
append!(new_tun, "auto-detect-interface", true);
}
revise!(config, "tun", new_tun);
// dns config
let dns_val = config.get(&Value::from("dns"));
let mut new_dns = Mapping::new();
if dns_val.is_some() && dns_val.as_ref().unwrap().is_mapping() {
new_dns = dns_val.as_ref().unwrap().as_mapping().unwrap().clone();
}
// 借鉴cfw的默认配置
revise!(new_dns, "enable", enable);
if enable {
append!(new_dns, "enhanced-mode", "fake-ip");
append!(
new_dns,
"nameserver",
vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"]
);
append!(new_dns, "fallback", vec![] as Vec<&str>);
#[cfg(target_os = "windows")]
append!(
new_dns,
"fake-ip-filter",
vec![
"dns.msftncsi.com",
"www.msftncsi.com",
"www.msftconnecttest.com"
]
);
}
revise!(config, "dns", new_dns);
config
}
/// activate the profile
/// generate a new profile to the temp_dir
/// then put the path to the clash core
fn _activate(info: ClashInfo, config: Mapping, window: Option<Window>) -> Result<()> {
let verge_config = VergeConfig::new();
let tun_enable = verge_config.enable_tun_mode.unwrap_or(false);
let config = Clash::_tun_mode(config, tun_enable);
let temp_path = dirs::profiles_temp_path();
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
tauri::async_runtime::spawn(async move {
if info.server.is_none() {
return;
}
let server = info.server.unwrap();
let server = format!("http://{server}/configs");
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse().unwrap());
if let Some(secret) = info.secret.as_ref() {
let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
headers.insert("Authorization", secret);
}
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!("failed to activate clash for status \"{}\"", resp.status());
}
// emit the window to update something
if let Some(window) = window {
window.emit("verge://refresh-clash-config", "yes").unwrap();
}
// do not retry
break;
}
Err(err) => log::error!("failed to activate for `{err}`"),
}
}
Err(err) => log::error!("failed to activate for `{err}`"),
}
sleep(Duration::from_millis(500)).await;
}
});
Ok(())
}
/// enhanced profiles mode
/// - (sync) refresh config if enhance chain is null
/// - (async) enhanced config
pub fn activate_enhanced(&self, profiles: &Profiles, delay: bool, skip: bool) -> Result<()> {
if self.window.is_none() {
bail!("failed to get the main window");
}
let event_name = help::get_uid("e");
let event_name = format!("enhanced-cb-{event_name}");
// generate the payload
let payload = profiles.gen_enhanced(event_name.clone())?;
let info = self.info.clone();
// do not run enhanced
if payload.chain.len() == 0 {
if skip {
return Ok(());
}
let mut config = self.config.clone();
let filter_data = Clash::strict_filter(payload.current);
for (key, value) in filter_data.into_iter() {
config.insert(key, value);
}
return Clash::_activate(info, config, self.window.clone());
}
let window = self.window.clone().unwrap();
let window_move = self.window.clone();
window.once(&event_name, move |event| {
if let Some(result) = event.payload() {
let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
if let Some(data) = result.data {
let mut config = Clash::read_config();
let filter_data = Clash::loose_filter(data); // loose filter
for (key, value) in filter_data.into_iter() {
config.insert(key, value);
}
log_if_err!(Clash::_activate(info, config, window_move));
log::info!("profile enhanced status {}", result.status);
}
result.error.map(|err| log::error!("{err}"));
}
});
tauri::async_runtime::spawn(async move {
// wait the window setup during resolve app
if delay {
sleep(Duration::from_secs(2)).await;
}
window.emit("script-handler", payload).unwrap();
});
Ok(())
}
/// activate the profile
/// auto activate enhanced profile
pub fn activate(&self, profiles: &Profiles) -> Result<()> {
let data = profiles.gen_activate()?;
let data = Clash::strict_filter(data);
let info = self.info.clone();
let mut config = self.config.clone();
for (key, value) in data.into_iter() {
config.insert(key, value);
}
Clash::_activate(info, config, self.window.clone())
}
/// only 5 default fields available (clash config fields)
/// convert to lowercase
fn strict_filter(config: Mapping) -> Mapping {
// Only the following fields are allowed:
// proxies/proxy-providers/proxy-groups/rule-providers/rules
let valid_keys = vec![
"proxies",
"proxy-providers",
"proxy-groups",
"rules",
"rule-providers",
];
let mut new_config = Mapping::new();
for (key, value) in config.into_iter() {
key.as_str().map(|key_str| {
// change to lowercase
let mut key_str = String::from(key_str);
key_str.make_ascii_lowercase();
// filter
if valid_keys.contains(&&*key_str) {
new_config.insert(Value::String(key_str), value);
}
});
}
new_config
}
/// more clash config fields available
/// convert to lowercase
fn loose_filter(config: Mapping) -> Mapping {
// all of these can not be revised by script or merge
// http/https/socks port should be under control
let not_allow = vec![
"port",
"socks-port",
"mixed-port",
"allow-lan",
"mode",
"external-controller",
"secret",
"log-level",
];
let mut new_config = Mapping::new();
for (key, value) in config.into_iter() {
key.as_str().map(|key_str| {
// change to lowercase
let mut key_str = String::from(key_str);
key_str.make_ascii_lowercase();
// filter
if !not_allow.contains(&&*key_str) {
new_config.insert(Value::String(key_str), value);
}
});
}
new_config
}
}
impl Default for Clash {
fn default() -> Self {
Clash::new()
}
}
impl Drop for Clash {
fn drop(&mut self) {
if let Err(err) = self.drop_sidecar() {
log::error!("{err}");
}
}
}

View File

@ -1,14 +1,15 @@
use self::notice::Notice;
use self::service::Service;
use self::sysopt::Sysopt;
use crate::core::enhance::PrfEnhancedResult;
use crate::log_if_err;
use crate::utils::help;
use crate::utils::{dirs, help};
use anyhow::{bail, Result};
use parking_lot::Mutex;
use serde_yaml::Mapping;
use std::sync::Arc;
use std::time::Duration;
use tauri::Window;
use tauri::{AppHandle, Manager, Window};
use tokio::time::sleep;
mod clash;
@ -17,6 +18,7 @@ mod notice;
mod prfitem;
mod profiles;
mod service;
mod sysopt;
mod timer;
mod verge;
@ -35,6 +37,8 @@ pub struct Core {
pub service: Arc<Mutex<Service>>,
pub sysopt: Arc<Mutex<Sysopt>>,
pub window: Arc<Mutex<Option<Window>>>,
}
@ -50,10 +54,12 @@ impl Core {
verge: Arc::new(Mutex::new(verge)),
profiles: Arc::new(Mutex::new(profiles)),
service: Arc::new(Mutex::new(service)),
sysopt: Arc::new(Mutex::new(Sysopt::new())),
window: Arc::new(Mutex::new(None)),
}
}
/// initialize the core state
pub fn init(&self, app_handle: tauri::AppHandle) {
let mut service = self.service.lock();
log_if_err!(service.start());
@ -62,26 +68,30 @@ impl Core {
log_if_err!(self.activate());
let clash = self.clash.lock();
let mut verge = self.verge.lock();
let verge = self.verge.lock();
let hide = verge.config.enable_silent_start.clone().unwrap_or(false);
let silent_start = verge.config.enable_silent_start.clone();
let auto_launch = verge.config.enable_auto_launch.clone();
// silent start
if hide {
if silent_start.unwrap_or(false) {
let window = self.window.lock();
window.as_ref().map(|win| {
win.hide().unwrap();
});
}
verge.init_sysproxy(clash.info.port.clone());
let mut sysopt = self.sysopt.lock();
log_if_err!(verge.init_launch());
log_if_err!(verge.update_systray(&app_handle));
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));
// wait the window setup during resolve app
let core = self.clone();
tauri::async_runtime::spawn(async move {
@ -106,6 +116,7 @@ impl Core {
self.activate_enhanced(true)
}
/// Patch Clash
/// handle the clash config changed
pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
let (changed, port) = {
@ -123,13 +134,85 @@ impl Core {
self.activate()?;
self.activate_enhanced(true)?;
let mut verge = self.verge.lock();
verge.init_sysproxy(port);
let mut sysopt = self.sysopt.lock();
let verge = self.verge.lock();
sysopt.init_sysproxy(port, &verge);
}
Ok(())
}
/// Patch Verge
pub fn patch_verge(&self, patch: VergeConfig, 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();
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 = 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)?;
}
if tun_mode.is_some() {
self.activate_enhanced(false)?;
}
Ok(())
}
/// update the system tray state
pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> {
let verge = self.verge.lock();
let tray = app_handle.tray_handle();
let system_proxy = verge.config.enable_system_proxy.as_ref();
let tun_mode = verge.config.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();
Ok(())
}
/// activate the profile
/// auto activate enhanced profile
pub fn activate(&self) -> Result<()> {
@ -165,6 +248,7 @@ impl Core {
service.set_config(info, config, notice)
}
/// Enhanced
/// enhanced profiles mode
pub fn activate_enhanced(&self, skip: bool) -> Result<()> {
let window = self.window.lock();
@ -235,10 +319,6 @@ impl Core {
result.error.map(|err| log::error!("{err}"));
});
// if delay {
// sleep(Duration::from_secs(2)).await;
// }
window.emit("script-handler", payload).unwrap();
Ok(())

View File

@ -60,7 +60,7 @@ pub struct PrfExtra {
pub expire: usize,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct PrfOption {
/// for `remote` profile's http request
/// see issue #13

View File

@ -47,6 +47,7 @@ impl Profiles {
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() {
@ -67,19 +68,6 @@ impl Profiles {
)
}
/// sync the config between file and memory
pub fn sync_file(&mut self) -> Result<()> {
let data = Self::read_file();
if data.current.is_none() && data.items.is_none() {
bail!("failed to read profiles.yaml");
}
self.current = data.current;
self.chain = data.chain;
self.items = data.items;
Ok(())
}
/// get the current uid
pub fn get_current(&self) -> Option<String> {
self.current.clone()
@ -94,11 +82,9 @@ impl Profiles {
let items = self.items.as_ref().unwrap();
let some_uid = Some(uid.clone());
for each in items.iter() {
if each.uid == some_uid {
self.current = some_uid;
return self.save_file();
}
if items.iter().find(|&each| each.uid == some_uid).is_some() {
self.current = some_uid;
return self.save_file();
}
bail!("invalid uid \"{uid}\"");
@ -162,7 +148,7 @@ impl Profiles {
self.save_file()
}
/// update the item's value
/// update the item value
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]);
@ -197,7 +183,7 @@ impl Profiles {
// find the item
let _ = self.get_item(&uid)?;
self.items.as_mut().map(|items| {
if let Some(items) = self.items.as_mut() {
let some_uid = Some(uid.clone());
for mut each in items.iter_mut() {
@ -217,15 +203,15 @@ impl Profiles {
let path = dirs::app_profiles_dir().join(&file);
fs::File::create(path)
.unwrap()
.context(format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.unwrap();
.context(format!("failed to write to file \"{}\"", file))?;
}
break;
}
}
});
}
self.save_file()
}

View File

@ -57,6 +57,8 @@ impl Service {
self.start()
}
/// update clash config
/// using PUT methods
pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> {
if self.sidecar.is_none() {
bail!("did not start sidecar");

View File

@ -0,0 +1,207 @@
use super::{Clash, Verge};
use crate::{log_if_err, utils::sysopt::SysProxyConfig};
use anyhow::{bail, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
// use parking_lot::Mutex;
use std::sync::Arc;
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
pub struct Sysopt {
/// current system proxy setting
cur_sysproxy: Option<SysProxyConfig>,
/// record the original system proxy
/// recover it when exit
old_sysproxy: Option<SysProxyConfig>,
/// helps to auto launch the app
auto_launch: Option<AutoLaunch>,
/// record whether the guard async is running or not
guard_state: Arc<Mutex<bool>>,
}
impl Sysopt {
pub fn new() -> Sysopt {
Sysopt {
cur_sysproxy: None,
old_sysproxy: None,
auto_launch: None,
guard_state: Arc::new(Mutex::new(false)),
}
}
/// init the sysproxy
pub fn init_sysproxy(&mut self, port: Option<String>, verge: &Verge) {
if let Some(port) = port {
let enable = verge.config.enable_system_proxy.clone().unwrap_or(false);
self.old_sysproxy = match SysProxyConfig::get_sys() {
Ok(proxy) => Some(proxy),
Err(_) => None,
};
let bypass = verge.config.system_proxy_bypass.clone();
let sysproxy = SysProxyConfig::new(enable, port, bypass);
if enable {
if let Err(err) = sysproxy.set_sys() {
log::error!("failed to set system proxy for `{err}`");
}
}
self.cur_sysproxy = Some(sysproxy);
}
// launchs the system proxy guard
self.guard_proxy();
}
/// 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");
}
let mut sysproxy = sysproxy.unwrap();
if let Some(enable) = enable {
sysproxy.enable = enable;
}
if let Some(bypass) = bypass {
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");
}
Ok(())
}
/// reset the sysproxy
pub fn reset_sysproxy(&mut self) {
if let Some(sysproxy) = self.old_sysproxy.take() {
match sysproxy.set_sys() {
Ok(_) => self.cur_sysproxy = None,
Err(_) => log::error!("failed to reset proxy"),
}
}
}
/// get current proxy
pub fn get_sysproxy(&self) -> Result<Option<SysProxyConfig>> {
Ok(self.cur_sysproxy.clone())
}
/// init the auto launch
pub fn init_launch(&mut self, enable: Option<bool>) -> Result<()> {
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();
let app_path = app_exe.as_os_str().to_str().unwrap();
// fix issue #26
#[cfg(target_os = "windows")]
let app_path = format!("\"{app_path}\"");
#[cfg(target_os = "windows")]
let app_path = app_path.as_str();
let auto = AutoLaunchBuilder::new()
.set_app_name(app_name)
.set_app_path(app_path)
.build();
if let Some(enable) = enable {
// fix issue #26
if enable {
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(());
}
let enable = enable.unwrap();
let auto_launch = self.auto_launch.as_ref().unwrap();
match enable {
true => auto_launch.enable()?,
false => auto_launch.disable()?,
};
Ok(())
}
/// launch a system proxy guard
/// read config from file directly
pub fn guard_proxy(&self) {
use tokio::time::{sleep, Duration};
let guard_state = self.guard_state.clone();
tauri::async_runtime::spawn(async move {
// if it is running, exit
let mut state = guard_state.lock().await;
if *state {
return;
}
*state = true;
drop(state);
// default duration is 10s
let mut wait_secs = 10u64;
loop {
sleep(Duration::from_secs(wait_secs)).await;
log::debug!("guard heartbeat detection");
let verge = Verge::new();
let enable_proxy = verge.config.enable_system_proxy.unwrap_or(false);
let enable_guard = verge.config.enable_proxy_guard.unwrap_or(false);
let guard_duration = verge.config.proxy_guard_duration.unwrap_or(10);
// update duration
wait_secs = guard_duration;
// stop loop
if !enable_guard || !enable_proxy {
break;
}
log::info!("try to guard proxy");
let clash = Clash::new();
match &clash.info.port {
Some(port) => {
let bypass = verge.config.system_proxy_bypass.clone();
let sysproxy = SysProxyConfig::new(true, port.clone(), bypass);
log_if_err!(sysproxy.set_sys());
}
None => log::error!("failed to parse clash port"),
}
}
let mut state = guard_state.lock().await;
*state = false;
});
}
}

View File

@ -1,14 +1,7 @@
use crate::log_if_err;
use crate::{
core::Clash,
utils::{config, dirs, sysopt::SysProxyConfig},
};
use crate::utils::{config, dirs};
use anyhow::{bail, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tauri::{async_runtime::Mutex, utils::platform::current_exe};
use tauri::{AppHandle, Manager};
/// ### `verge.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -87,19 +80,6 @@ impl VergeConfig {
pub struct Verge {
/// manage the verge config
pub config: VergeConfig,
/// current system proxy setting
pub cur_sysproxy: Option<SysProxyConfig>,
/// record the original system proxy
/// recover it when exit
old_sysproxy: Option<SysProxyConfig>,
/// helps to auto launch the app
auto_launch: Option<AutoLaunch>,
/// record whether the guard async is running or not
guard_state: Arc<Mutex<bool>>,
}
impl Default for Verge {
@ -112,100 +92,11 @@ impl Verge {
pub fn new() -> Self {
Verge {
config: VergeConfig::new(),
old_sysproxy: None,
cur_sysproxy: None,
auto_launch: None,
guard_state: Arc::new(Mutex::new(false)),
}
}
/// init the sysproxy
pub fn init_sysproxy(&mut self, port: Option<String>) {
if let Some(port) = port {
let enable = self.config.enable_system_proxy.clone().unwrap_or(false);
self.old_sysproxy = match SysProxyConfig::get_sys() {
Ok(proxy) => Some(proxy),
Err(_) => None,
};
let bypass = self.config.system_proxy_bypass.clone();
let sysproxy = SysProxyConfig::new(enable, port, bypass);
if enable {
if sysproxy.set_sys().is_err() {
log::error!("failed to set system proxy");
}
}
self.cur_sysproxy = Some(sysproxy);
}
// launchs the system proxy guard
Verge::guard_proxy(self.guard_state.clone());
}
/// reset the sysproxy
pub fn reset_sysproxy(&mut self) {
if let Some(sysproxy) = self.old_sysproxy.take() {
match sysproxy.set_sys() {
Ok(_) => self.cur_sysproxy = None,
Err(_) => log::error!("failed to reset proxy"),
}
}
}
/// init the auto launch
pub fn init_launch(&mut self) -> Result<()> {
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();
let app_path = app_exe.as_os_str().to_str().unwrap();
// fix issue #26
#[cfg(target_os = "windows")]
let app_path = format!("\"{app_path}\"");
#[cfg(target_os = "windows")]
let app_path = app_path.as_str();
let auto = AutoLaunchBuilder::new()
.set_app_name(app_name)
.set_app_path(app_path)
.build();
if let Some(enable) = self.config.enable_auto_launch.as_ref() {
// fix issue #26
if *enable {
auto.enable()?;
}
}
self.auto_launch = Some(auto);
Ok(())
}
/// update the startup
fn update_launch(&mut self, enable: bool) -> Result<()> {
let conf_enable = self.config.enable_auto_launch.clone().unwrap_or(false);
if enable == conf_enable {
return Ok(());
}
let auto_launch = self.auto_launch.clone().unwrap();
match enable {
true => auto_launch.enable()?,
false => auto_launch.disable()?,
};
Ok(())
}
/// patch verge config
/// There should be only one update at a time here
/// so call the save_file at the end is savely
/// only save to file
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
// only change it
if patch.language.is_some() {
@ -217,62 +108,28 @@ impl Verge {
if patch.theme_blur.is_some() {
self.config.theme_blur = patch.theme_blur;
}
if patch.traffic_graph.is_some() {
self.config.traffic_graph = patch.traffic_graph;
}
if patch.enable_silent_start.is_some() {
self.config.enable_silent_start = patch.enable_silent_start;
}
if patch.theme_setting.is_some() {
self.config.theme_setting = patch.theme_setting;
}
if patch.traffic_graph.is_some() {
self.config.traffic_graph = patch.traffic_graph;
}
// should update system startup
// system setting
if patch.enable_silent_start.is_some() {
self.config.enable_silent_start = patch.enable_silent_start;
}
if patch.enable_auto_launch.is_some() {
let enable = patch.enable_auto_launch.unwrap();
self.update_launch(enable)?;
self.config.enable_auto_launch = Some(enable);
self.config.enable_auto_launch = patch.enable_auto_launch;
}
// should update system proxy
// proxy
if patch.enable_system_proxy.is_some() {
let enable = patch.enable_system_proxy.unwrap();
if let Some(mut sysproxy) = self.cur_sysproxy.take() {
sysproxy.enable = enable;
if sysproxy.set_sys().is_err() {
self.cur_sysproxy = Some(sysproxy);
bail!("failed to set system proxy");
}
self.cur_sysproxy = Some(sysproxy);
}
self.config.enable_system_proxy = Some(enable);
self.config.enable_system_proxy = patch.enable_system_proxy;
}
// should update system proxy too
if patch.system_proxy_bypass.is_some() {
let bypass = patch.system_proxy_bypass.unwrap();
if let Some(mut sysproxy) = self.cur_sysproxy.take() {
if sysproxy.enable {
sysproxy.bypass = bypass.clone();
if sysproxy.set_sys().is_err() {
self.cur_sysproxy = Some(sysproxy);
bail!("failed to set system proxy");
}
}
self.cur_sysproxy = Some(sysproxy);
}
self.config.system_proxy_bypass = Some(bypass);
self.config.system_proxy_bypass = patch.system_proxy_bypass;
}
// proxy guard
// only change it
if patch.enable_proxy_guard.is_some() {
self.config.enable_proxy_guard = patch.enable_proxy_guard;
}
@ -280,103 +137,11 @@ impl Verge {
self.config.proxy_guard_duration = patch.proxy_guard_duration;
}
// relaunch the guard
if patch.enable_system_proxy.is_some() || patch.enable_proxy_guard.is_some() {
Verge::guard_proxy(self.guard_state.clone());
}
// handle the tun mode
// tun mode
if patch.enable_tun_mode.is_some() {
self.config.enable_tun_mode = patch.enable_tun_mode;
}
self.config.save_file()
}
/// update the system tray state
pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> {
// system proxy
let system_proxy = self.config.enable_system_proxy.as_ref();
system_proxy.map(|system_proxy| {
app_handle
.tray_handle()
.get_item("system_proxy")
.set_selected(*system_proxy)
.unwrap();
});
// tun mode
let tun_mode = self.config.enable_tun_mode.as_ref();
tun_mode.map(|tun_mode| {
app_handle
.tray_handle()
.get_item("tun_mode")
.set_selected(*tun_mode)
.unwrap();
});
// update verge config
let window = app_handle.get_window("main").unwrap();
window.emit("verge://refresh-verge-config", "yes").unwrap();
Ok(())
}
}
impl Verge {
/// launch a system proxy guard
/// read config from file directly
pub fn guard_proxy(guard_state: Arc<Mutex<bool>>) {
use tokio::time::{sleep, Duration};
tauri::async_runtime::spawn(async move {
// if it is running, exit
let mut state = guard_state.lock().await;
if *state {
return;
}
*state = true;
std::mem::drop(state);
// default duration is 10s
let mut wait_secs = 10u64;
loop {
sleep(Duration::from_secs(wait_secs)).await;
log::debug!("guard heartbeat detection");
let verge = Verge::new();
let enable_proxy = verge.config.enable_system_proxy.unwrap_or(false);
let enable_guard = verge.config.enable_proxy_guard.unwrap_or(false);
let guard_duration = verge.config.proxy_guard_duration.unwrap_or(10);
// update duration
wait_secs = guard_duration;
// stop loop
if !enable_guard || !enable_proxy {
break;
}
log::info!("try to guard proxy");
let clash = Clash::new();
match &clash.info.port {
Some(port) => {
let bypass = verge.config.system_proxy_bypass.clone();
let sysproxy = SysProxyConfig::new(true, port.clone(), bypass);
log_if_err!(sysproxy.set_sys());
}
None => log::error!("fail to parse clash port"),
}
}
let mut state = guard_state.lock().await;
*state = false;
});
}
}

View File

@ -51,33 +51,33 @@ fn main() -> std::io::Result<()> {
}
"system_proxy" => {
let core = app_handle.state::<core::Core>();
let mut verge = core.verge.lock();
let old_value = verge.config.enable_system_proxy.clone().unwrap_or(false);
let new_value = !old_value;
let new_value = {
let verge = core.verge.lock();
!verge.config.enable_system_proxy.clone().unwrap_or(false)
};
match verge.patch_config(VergeConfig {
let patch = VergeConfig {
enable_system_proxy: Some(new_value),
..VergeConfig::default()
}) {
Ok(_) => verge.update_systray(app_handle).unwrap(),
Err(err) => log::error!("{err}"),
}
};
crate::log_if_err!(core.patch_verge(patch, app_handle));
}
"tun_mode" => {
let core = app_handle.state::<core::Core>();
let mut verge = core.verge.lock();
let old_value = verge.config.enable_tun_mode.clone().unwrap_or(false);
let new_value = !old_value;
let new_value = {
let verge = core.verge.lock();
!verge.config.enable_tun_mode.clone().unwrap_or(false)
};
match verge.patch_config(VergeConfig {
let patch = VergeConfig {
enable_tun_mode: Some(new_value),
..VergeConfig::default()
}) {
Ok(_) => verge.update_systray(app_handle).unwrap(),
Err(err) => log::error!("{err}"),
}
};
crate::log_if_err!(core.patch_verge(patch, app_handle));
}
"restart_clash" => {
let core = app_handle.state::<core::Core>();
@ -104,7 +104,7 @@ fn main() -> std::io::Result<()> {
cmds::restart_sidecar,
cmds::get_sys_proxy,
cmds::get_cur_proxy,
cmds::kill_sidecars,
cmds::kill_sidecar,
cmds::open_app_dir,
cmds::open_logs_dir,
// clash
@ -122,7 +122,6 @@ fn main() -> std::io::Result<()> {
cmds::delete_profile,
cmds::select_profile,
cmds::get_profiles,
cmds::sync_profiles,
cmds::enhance_profiles,
cmds::change_profile_chain,
cmds::change_profile_valid,

View File

@ -1,4 +1,7 @@
use anyhow::{bail, Result};
use nanoid::nanoid;
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
@ -41,6 +44,38 @@ pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
}
}
/// open file
/// use vscode by default
pub fn open_file(path: PathBuf) -> Result<()> {
// use vscode first
if let Ok(code) = which::which("code") {
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
if let Err(err) = Command::new(code)
.creation_flags(0x08000000)
.arg(path)
.spawn()
{
bail!(format!("failed to open file by VScode for `{err}`"));
}
}
#[cfg(not(target_os = "windows"))]
if let Err(err) = Command::new(code).arg(path).spawn() {
bail!(format!("failed to open file by VScode for `{err}`"));
}
return Ok(());
}
match open::that(path) {
Ok(_) => Ok(()),
Err(err) => bail!(format!("failed to open file for `{err}`")),
}
}
#[macro_export]
macro_rules! log_if_err {
($result: expr) => {

View File

@ -21,9 +21,9 @@ pub fn resolve_setup(app: &App) {
/// reset system proxy
pub fn resolve_reset(app_handle: &AppHandle) {
let core = app_handle.state::<Core>();
let mut verge = core.verge.lock();
let mut sysopt = core.sysopt.lock();
verge.reset_sysproxy();
sysopt.reset_sysproxy();
}
/// customize the window theme

View File

@ -13,7 +13,7 @@ import {
} from "@mui/material";
import { relaunch } from "@tauri-apps/api/process";
import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
import { killSidecars, restartSidecar } from "../../services/cmds";
import { killSidecar, restartSidecar } from "../../services/cmds";
import { atomUpdateState } from "../../services/states";
import Notice from "../base/base-notice";
@ -41,7 +41,7 @@ const UpdateDialog = (props: Props) => {
setUpdateState(true);
try {
await killSidecars();
await killSidecar();
await installUpdate();
await relaunch();
} catch (err: any) {

View File

@ -6,10 +6,6 @@ export async function getProfiles() {
return invoke<CmdType.ProfilesConfig>("get_profiles");
}
export async function syncProfiles() {
return invoke<void>("sync_profiles");
}
export async function enhanceProfiles() {
return invoke<void>("enhance_profiles");
}
@ -94,8 +90,8 @@ export async function restartSidecar() {
return invoke<void>("restart_sidecar");
}
export async function killSidecars() {
return invoke<any>("kill_sidecars");
export async function killSidecar() {
return invoke<any>("kill_sidecar");
}
export async function openAppDir() {