feat: refactor and supports cron tasks

This commit is contained in:
GyDi 2022-04-20 20:39:27 +08:00 committed by GitHub
commit e47747dd0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1889 additions and 1285 deletions

329
src-tauri/Cargo.lock generated
View File

@ -14,6 +14,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.6",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
@ -59,6 +70,115 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8"
[[package]]
name = "async-channel"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-fs"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
dependencies = [
"async-lock",
"blocking",
"futures-lite",
]
[[package]]
name = "async-io"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
dependencies = [
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
"socket2",
"waker-fn",
"winapi",
]
[[package]]
name = "async-lock"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
dependencies = [
"event-listener",
]
[[package]]
name = "async-net"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5373304df79b9b4395068fb080369ec7178608827306ce4d081cba51cac551df"
dependencies = [
"async-io",
"blocking",
"futures-lite",
]
[[package]]
name = "async-process"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
dependencies = [
"async-io",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"libc",
"once_cell",
"signal-hook",
"winapi",
]
[[package]]
name = "async-task"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
[[package]]
name = "async-trait"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atk"
version = "0.15.1"
@ -83,6 +203,12 @@ dependencies = [
"system-deps 6.0.2",
]
[[package]]
name = "atomic-waker"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "attohttpc"
version = "0.18.0"
@ -162,6 +288,20 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
dependencies = [
"async-channel",
"async-task",
"atomic-waker",
"fastrand",
"futures-lite",
"once_cell",
]
[[package]]
name = "brotli"
version = "3.3.3"
@ -220,6 +360,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "cairo-rs"
version = "0.15.10"
@ -336,12 +482,14 @@ dependencies = [
"anyhow",
"auto-launch",
"chrono",
"delay_timer",
"dirs",
"dunce",
"log",
"log4rs",
"nanoid",
"open",
"parking_lot 0.12.0",
"port_scanner",
"reqwest",
"serde",
@ -398,6 +546,25 @@ dependencies = [
"memchr",
]
[[package]]
name = "concat-idents"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b6f90860248d75014b7b103db8fee4f291c07bfb41306cdf77a0a5ab7a10d2f"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
"cache-padded",
]
[[package]]
name = "convert_case"
version = "0.4.0"
@ -463,6 +630,17 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "cron_clock"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a8699d8ed16e3db689f8ae04d8dc3c6666a4ba7e724e5a157884b7cc385d16b"
dependencies = [
"chrono",
"nom",
"once_cell",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.4"
@ -606,6 +784,16 @@ dependencies = [
"syn",
]
[[package]]
name = "dashmap"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
dependencies = [
"cfg-if",
"num_cpus",
]
[[package]]
name = "deflate"
version = "0.7.20"
@ -625,6 +813,31 @@ dependencies = [
"adler32",
]
[[package]]
name = "delay_timer"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68db8078186fbb91582bc7ba4f7b90e52cc4618a585748dd6a9fb6f4a25ea3c4"
dependencies = [
"anyhow",
"async-trait",
"autocfg",
"concat-idents",
"cron_clock",
"dashmap",
"event-listener",
"futures",
"log",
"lru",
"once_cell",
"rs-snowflake",
"rustc_version 0.2.3",
"smol",
"thiserror",
"tokio",
"tracing",
]
[[package]]
name = "derivative"
version = "2.2.0"
@ -757,6 +970,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "fastrand"
version = "1.7.0"
@ -1289,6 +1508,9 @@ name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "headers"
@ -1728,6 +1950,15 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "lru"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91"
dependencies = [
"hashbrown",
]
[[package]]
name = "mac"
version = "0.1.1"
@ -1803,6 +2034,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "minisign-verify"
version = "0.2.0"
@ -1961,6 +2198,16 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "ntapi"
version = "0.3.7"
@ -2404,6 +2651,19 @@ dependencies = [
"miniz_oxide 0.5.1",
]
[[package]]
name = "polling"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
]
[[package]]
name = "port_scanner"
version = "0.1.5"
@ -2706,6 +2966,21 @@ dependencies = [
"windows 0.33.0",
]
[[package]]
name = "rs-snowflake"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148be9baabb59959aeaf4b27597bce9f071511a6a74a4a20a2c5681722a21e"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
@ -2816,13 +3091,22 @@ dependencies = [
"thin-slice",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser 0.7.0",
]
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
"semver-parser 0.10.2",
]
[[package]]
@ -2831,6 +3115,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "semver-parser"
version = "0.10.2"
@ -3025,6 +3315,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "signal-hook"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
@ -3052,6 +3352,24 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "smol"
version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
dependencies = [
"async-channel",
"async-executor",
"async-fs",
"async-io",
"async-lock",
"async-net",
"async-process",
"blocking",
"futures-lite",
"once_cell",
]
[[package]]
name = "socket2"
version = "0.4.4"
@ -4090,6 +4408,15 @@ dependencies = [
"windows-bindgen",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
dependencies = [
"cc",
]
[[package]]
name = "which"
version = "4.2.5"

View File

@ -21,6 +21,8 @@ nanoid = "0.4.0"
chrono = "0.4.19"
serde_json = "1.0"
serde_yaml = "0.8"
delay_timer = "0.11.1"
parking_lot = "0.12.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.0-rc.6", features = ["process-all", "shell-all", "system-tray", "updater", "window-all"] }
window-shadows = { git = "https://github.com/tauri-apps/window-shadows" }
@ -39,8 +41,8 @@ port_scanner = "0.1.5"
winreg = { version = "0.10", features = ["transactions"] }
[features]
default = [ "custom-protocol" ]
custom-protocol = [ "tauri/custom-protocol" ]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
verge-dev = []
debug-yml = []

View File

@ -1,26 +1,25 @@
use crate::{
core::{ClashInfo, PrfItem, PrfOption, Profiles, VergeConfig},
states::{ClashState, ProfilesState, VergeState},
utils::{dirs, sysopt::SysProxyConfig},
core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, Verge},
utils::{dirs, help, sysopt::SysProxyConfig},
};
use crate::{ret_err, wrap_err};
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>;
/// get all profiles from `profiles.yaml`
#[tauri::command]
pub fn get_profiles<'a>(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
let profiles = profiles_state.0.lock().unwrap();
pub fn get_profiles(core: State<'_, Core>) -> CmdResult<Profiles> {
let profiles = core.profiles.lock();
Ok(profiles.clone())
}
/// synchronize data irregularly
/// manually exec enhanced profile
#[tauri::command]
pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap();
wrap_err!(profiles.sync_file())
pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
wrap_err!(core.activate_enhanced(false))
}
/// import the profile from url
@ -29,11 +28,11 @@ pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), Str
pub async fn import_profile(
url: String,
option: Option<PrfOption>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
core: State<'_, Core>,
) -> CmdResult {
let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
let mut profiles = profiles_state.0.lock().unwrap();
let mut profiles = core.profiles.lock();
wrap_err!(profiles.append_item(item))
}
@ -44,11 +43,11 @@ pub async fn import_profile(
pub async fn create_profile(
item: PrfItem, // partial
file_data: Option<String>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
core: State<'_, Core>,
) -> CmdResult {
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
let mut profiles = profiles_state.0.lock().unwrap();
let mut profiles = core.profiles.lock();
wrap_err!(profiles.append_item(item))
}
@ -57,118 +56,53 @@ pub async fn create_profile(
pub async fn update_profile(
index: String,
option: Option<PrfOption>,
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let (url, opt) = {
// must release the lock here
let profiles = profiles_state.0.lock().unwrap();
let item = wrap_err!(profiles.get_item(&index))?;
// check the profile type
if let Some(typ) = item.itype.as_ref() {
if *typ != "remote" {
ret_err!(format!("could not update the `{typ}` profile"));
}
}
if item.url.is_none() {
ret_err!("failed to get the item url");
}
(item.url.clone().unwrap(), item.option.clone())
};
let fetch_opt = PrfOption::merge(opt, option);
let item = wrap_err!(PrfItem::from_url(&url, None, None, fetch_opt).await)?;
let mut profiles = profiles_state.0.lock().unwrap();
wrap_err!(profiles.update_item(index.clone(), item))?;
// reactivate the profile
if Some(index) == profiles.get_current() {
let clash = clash_state.0.lock().unwrap();
wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
}
Ok(())
core: State<'_, Core>,
) -> CmdResult {
wrap_err!(Core::update_profile_item(core.inner().clone(), index, option).await)
}
/// change the current profile
#[tauri::command]
pub fn select_profile(
index: String,
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap();
pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
wrap_err!(profiles.put_current(index))?;
let clash = clash_state.0.lock().unwrap();
wrap_err!(clash.activate_enhanced(&profiles, false, false))
drop(profiles);
wrap_err!(core.activate_enhanced(false))
}
/// change the profile chain
#[tauri::command]
pub fn change_profile_chain(
chain: Option<Vec<String>>,
app_handle: tauri::AppHandle,
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut clash = clash_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
profiles.put_chain(chain);
clash.set_window(app_handle.get_window("main"));
wrap_err!(clash.activate_enhanced(&profiles, false, false))
drop(profiles);
wrap_err!(core.activate_enhanced(false))
}
/// change the profile valid fields
#[tauri::command]
pub fn change_profile_valid(
valid: Option<Vec<String>>,
app_handle: tauri::AppHandle,
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut clash = clash_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
pub fn change_profile_valid(valid: Option<Vec<String>>, core: State<Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
profiles.put_valid(valid);
clash.set_window(app_handle.get_window("main"));
wrap_err!(clash.activate_enhanced(&profiles, false, false))
}
drop(profiles);
/// manually exec enhanced profile
#[tauri::command]
pub fn enhance_profiles(
app_handle: tauri::AppHandle,
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut clash = clash_state.0.lock().unwrap();
let profiles = profiles_state.0.lock().unwrap();
clash.set_window(app_handle.get_window("main"));
wrap_err!(clash.activate_enhanced(&profiles, false, false))
wrap_err!(core.activate_enhanced(false))
}
/// delete profile item
#[tauri::command]
pub fn delete_profile(
index: String,
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap();
pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
if wrap_err!(profiles.delete_item(index))? {
let clash = clash_state.0.lock().unwrap();
wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
drop(profiles);
log_if_err!(core.activate_enhanced(false));
}
Ok(())
@ -176,19 +110,16 @@ pub fn delete_profile(
/// patch the profile config
#[tauri::command]
pub fn patch_profile(
index: String,
profile: PrfItem,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap();
pub fn patch_profile(index: String, profile: PrfItem, core: State<'_, Core>) -> CmdResult {
let mut profiles = core.profiles.lock();
wrap_err!(profiles.patch_item(index, profile))
}
/// run vscode command to edit the profile
#[tauri::command]
pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
let profiles = profiles_state.0.lock().unwrap();
pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult {
let profiles = core.profiles.lock();
let item = wrap_err!(profiles.get_item(&index))?;
let file = item.file.clone();
@ -201,41 +132,14 @@ pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) ->
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
#[tauri::command]
pub fn read_profile_file(
index: String,
profiles_state: State<'_, ProfilesState>,
) -> Result<String, String> {
let profiles = profiles_state.0.lock().unwrap();
pub fn read_profile_file(index: String, core: State<'_, Core>) -> CmdResult<String> {
let profiles = core.profiles.lock();
let item = wrap_err!(profiles.get_item(&index))?;
let data = wrap_err!(item.read_file())?;
@ -247,34 +151,22 @@ pub fn read_profile_file(
pub fn save_profile_file(
index: String,
file_data: Option<String>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
core: State<'_, Core>,
) -> CmdResult {
if file_data.is_none() {
return Ok(());
}
let profiles = profiles_state.0.lock().unwrap();
let profiles = core.profiles.lock();
let item = wrap_err!(profiles.get_item(&index))?;
wrap_err!(item.save_file(file_data.unwrap()))
}
/// restart the sidecar
#[tauri::command]
pub fn restart_sidecar(
clash_state: State<'_, ClashState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let mut clash = clash_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
wrap_err!(clash.restart_sidecar(&mut profiles))
}
/// get the clash core info from the state
/// the caller can also get the infomation by clash's api
#[tauri::command]
pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
let clash = clash_state.0.lock().unwrap();
pub fn get_clash_info(core: State<'_, Core>) -> CmdResult<ClashInfo> {
let clash = core.clash.lock();
Ok(clash.info.clone())
}
@ -282,16 +174,38 @@ pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, S
/// after putting the change to the clash core
/// then we should save the latest config
#[tauri::command]
pub fn patch_clash_config(
payload: Mapping,
clash_state: State<'_, ClashState>,
verge_state: State<'_, VergeState>,
profiles_state: State<'_, ProfilesState>,
pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult {
wrap_err!(core.patch_clash(payload))
}
/// get the verge config
#[tauri::command]
pub fn get_verge_config(core: State<'_, Core>) -> CmdResult<Verge> {
let verge = core.verge.lock();
Ok(verge.clone())
}
/// patch the verge config
/// this command only save the config and not responsible for other things
#[tauri::command]
pub fn patch_verge_config(
payload: Verge,
app_handle: tauri::AppHandle,
core: State<'_, Core>,
) -> Result<(), String> {
let mut clash = clash_state.0.lock().unwrap();
let mut verge = verge_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
wrap_err!(core.patch_verge(payload, &app_handle))
}
/// 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_sidecar() {
api::process::kill_children();
}
/// get the system proxy
@ -303,69 +217,9 @@ pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
/// get the current proxy config
/// which may not the same as system proxy
#[tauri::command]
pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
let verge = verge_state.0.lock().unwrap();
Ok(verge.cur_sysproxy.clone())
}
/// get the verge config
#[tauri::command]
pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfig, String> {
let verge = verge_state.0.lock().unwrap();
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)
}
Ok(config)
}
/// patch the verge config
/// this command only save the config and not responsible for other things
#[tauri::command]
pub fn patch_verge_config(
payload: VergeConfig,
app_handle: tauri::AppHandle,
clash_state: State<'_, ClashState>,
verge_state: State<'_, VergeState>,
profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> {
let tun_mode = payload.enable_tun_mode.clone();
let system_proxy = payload.enable_system_proxy.clone();
let mut verge = verge_state.0.lock().unwrap();
wrap_err!(verge.patch_config(payload))?;
// 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());
}
}
let clash = clash_state.0.lock().unwrap();
let profiles = profiles_state.0.lock().unwrap();
wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
}
// change system tray
if system_proxy.is_some() || tun_mode.is_some() {
verge.update_systray(&app_handle).unwrap();
}
Ok(())
}
/// kill all sidecars when update app
#[tauri::command]
pub fn kill_sidecars() {
api::process::kill_children();
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

View File

@ -1,14 +1,7 @@
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 crate::utils::{config, dirs};
use anyhow::Result;
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 {
@ -25,56 +18,16 @@ pub struct ClashInfo {
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"),
)
}
impl ClashInfo {
/// parse the clash's config.yaml
/// get some information
fn get_info(clash_config: &Mapping) -> ClashInfo {
pub fn from(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) {
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()),
@ -84,7 +37,7 @@ impl Clash {
};
let port = match port {
Some(_) => port,
None => match clash_config.get(&key_port_2) {
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()),
@ -94,7 +47,7 @@ impl Clash {
},
};
let server = match clash_config.get(&key_server) {
let server = match config.get(&key_server) {
Some(value) => match value {
Value::String(val_str) => {
// `external-controller` could be
@ -112,7 +65,8 @@ impl Clash {
},
_ => None,
};
let secret = match clash_config.get(&key_secret) {
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()),
@ -129,99 +83,78 @@ impl Clash {
secret,
}
}
}
/// save the main window
pub fn set_window(&mut self, win: Option<Window>) {
self.window = win;
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 }
}
/// 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(())
/// get clash config
pub fn read_config() -> Mapping {
config::read_yaml::<Mapping>(dirs::clash_path())
}
/// 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)
/// 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"),
)
}
/// todo: delete
/// update the clash info
pub fn update_config(&mut self) {
self.config = Clash::read_config();
self.info = Clash::get_info(&self.config);
self.info = ClashInfo::from(&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;
/// if the port is changed then return true
pub fn patch_config(&mut self, patch: Mapping) -> Result<bool> {
let port_key = Value::from("mixed-port");
let server_key = Value::from("external-controller");
let secret_key = Value::from("secret");
let mut change_port = false;
let mut change_info = false;
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());
}
if key == port_key {
change_port = true;
}
self.config.insert(key.clone(), value);
if key == port_key || key == server_key || key == secret_key {
change_info = true;
}
self.config.insert(key, value);
}
if change_info {
self.info = ClashInfo::from(&self.config);
}
self.save_config()?;
if let Some(port) = port {
self.restart_sidecar(profiles)?;
verge.init_sysproxy(Some(port));
}
Ok(())
Ok(change_port)
}
/// revise the `tun` and `dns` config
fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping {
pub 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());
@ -294,154 +227,9 @@ impl Clash {
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 {
pub fn strict_filter(config: Mapping) -> Mapping {
// Only the following fields are allowed:
// proxies/proxy-providers/proxy-groups/rule-providers/rules
let valid_keys = vec![
@ -472,7 +260,7 @@ impl Clash {
/// more clash config fields available
/// convert to lowercase
fn loose_filter(config: Mapping) -> Mapping {
pub 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![
@ -510,11 +298,3 @@ impl Default for Clash {
Clash::new()
}
}
impl Drop for Clash {
fn drop(&mut self) {
if let Err(err) = self.drop_sidecar() {
log::error!("{err}");
}
}
}

View File

@ -0,0 +1,66 @@
use super::prfitem::PrfItem;
use crate::utils::{config, dirs};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::fs;
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PrfEnhanced {
pub current: Mapping,
pub chain: Vec<PrfData>,
pub valid: Vec<String>,
pub callback: String,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PrfEnhancedResult {
pub data: Option<Mapping>,
pub status: String,
pub error: Option<String>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PrfData {
item: PrfItem,
#[serde(skip_serializing_if = "Option::is_none")]
merge: Option<Mapping>,
#[serde(skip_serializing_if = "Option::is_none")]
script: Option<String>,
}
impl PrfData {
pub fn from_item(item: &PrfItem) -> Option<PrfData> {
match item.itype.as_ref() {
Some(itype) => {
let file = item.file.clone()?;
let path = dirs::app_profiles_dir().join(file);
if !path.exists() {
return None;
}
match itype.as_str() {
"script" => Some(PrfData {
item: item.clone(),
script: Some(fs::read_to_string(path).unwrap_or("".into())),
merge: None,
}),
"merge" => Some(PrfData {
item: item.clone(),
merge: Some(config::read_yaml::<Mapping>(path)),
script: None,
}),
_ => None,
}
}
None => None,
}
}
}

View File

@ -1,7 +1,383 @@
use self::notice::Notice;
use self::service::Service;
use self::sysopt::Sysopt;
use self::timer::Timer;
use crate::core::enhance::PrfEnhancedResult;
use crate::log_if_err;
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::{AppHandle, Manager, Window};
use tokio::time::sleep;
mod clash;
mod enhance;
mod notice;
mod prfitem;
mod profiles;
mod service;
mod sysopt;
mod timer;
mod verge;
pub use self::clash::*;
pub use self::prfitem::*;
pub use self::profiles::*;
pub use self::verge::*;
#[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 window: Arc<Mutex<Option<Window>>>,
}
impl Core {
pub fn new() -> Core {
let clash = Clash::new();
let verge = Verge::new();
let profiles = Profiles::new();
let service = Service::new();
Core {
clash: Arc::new(Mutex::new(clash)),
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())),
timer: Arc::new(Mutex::new(Timer::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());
drop(service);
log_if_err!(self.activate());
let clash = self.clash.lock();
let verge = self.verge.lock();
let silent_start = verge.enable_silent_start.clone();
let auto_launch = verge.enable_auto_launch.clone();
// silent start
if silent_start.unwrap_or(false) {
let window = self.window.lock();
window.as_ref().map(|win| {
win.hide().unwrap();
});
}
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));
// wait the window setup during resolve app
let core = self.clone();
tauri::async_runtime::spawn(async move {
sleep(Duration::from_secs(2)).await;
log_if_err!(core.activate_enhanced(true));
});
// timer initialize
let mut timer = self.timer.lock();
timer.set_core(self.clone());
log_if_err!(timer.refresh());
}
/// 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();
service.restart()?;
drop(service);
self.activate()?;
self.activate_enhanced(true)
}
/// Patch Clash
/// handle the clash config changed
pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
let (changed, port) = {
let mut clash = self.clash.lock();
(clash.patch_config(patch)?, clash.info.port.clone())
};
// todo: port check
if changed {
let mut service = self.service.lock();
service.restart()?;
drop(service);
self.activate()?;
self.activate_enhanced(true)?;
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: 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();
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.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();
Ok(())
}
/// activate the profile
/// auto activate enhanced profile
pub fn activate(&self) -> Result<()> {
let data = {
let profiles = self.profiles.lock();
let data = profiles.gen_activate()?;
Clash::strict_filter(data)
};
let (mut config, info) = {
let clash = self.clash.lock();
let config = clash.config.clone();
let info = clash.info.clone();
(config, info)
};
for (key, value) in data.into_iter() {
config.insert(key, value);
}
let config = {
let verge = self.verge.lock();
let tun_mode = verge.enable_tun_mode.unwrap_or(false);
Clash::_tun_mode(config, tun_mode)
};
let notice = {
let window = self.window.lock();
Notice::from(window.clone())
};
let service = self.service.lock();
service.set_config(info, config, notice)
}
/// Enhanced
/// enhanced profiles mode
pub fn activate_enhanced(&self, skip: bool) -> Result<()> {
let window = self.window.lock();
if 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 = {
let profiles = self.profiles.lock();
profiles.gen_enhanced(event_name.clone())?
};
// do not run enhanced
if payload.chain.len() == 0 {
if skip {
return Ok(());
}
drop(window);
return self.activate();
}
let tun_mode = {
let verge = self.verge.lock();
verge.enable_tun_mode.unwrap_or(false)
};
let info = {
let clash = self.clash.lock();
clash.info.clone()
};
let notice = Notice::from(window.clone());
let service = self.service.clone();
let window = window.clone().unwrap();
window.once(&event_name, move |event| {
let result = event.payload();
if result.is_none() {
log::warn!("event payload result is none");
return;
}
let result = result.unwrap();
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);
}
let config = Clash::_tun_mode(config, tun_mode);
let service = service.lock();
log_if_err!(service.set_config(info, config, notice));
log::info!("profile enhanced status {}", result.status);
}
result.error.map(|err| log::error!("{err}"));
});
window.emit("script-handler", payload).unwrap();
Ok(())
}
}
impl Core {
/// Static function
/// update profile item
pub async fn update_profile_item(
core: Core,
uid: String,
option: Option<PrfOption>,
) -> Result<()> {
let (url, opt) = {
let profiles = core.profiles.lock();
let item = profiles.get_item(&uid)?;
if let Some(typ) = item.itype.as_ref() {
// maybe only valid for `local` profile
if *typ != "remote" {
// reactivate the config
if Some(uid) == profiles.get_current() {
drop(profiles);
return core.activate_enhanced(false);
}
return Ok(());
}
}
if item.url.is_none() {
bail!("failed to get the profile item url");
}
(item.url.clone().unwrap(), item.option.clone())
};
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
let mut profiles = core.profiles.lock();
profiles.update_item(uid.clone(), item)?;
// reactivate the profile
if Some(uid) == profiles.get_current() {
drop(profiles);
core.activate_enhanced(false)?;
}
Ok(())
}
}

View File

@ -0,0 +1,35 @@
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 }
}
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"));
}
}
pub fn refresh_profiles(&self) {
if let Some(window) = self.win.as_ref() {
log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
}
}
}

View File

@ -0,0 +1,309 @@
use crate::utils::{dirs, help, tmpl};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
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 description
#[serde(skip_serializing_if = "Option::is_none")]
pub desc: Option<String>,
/// profile file
pub file: 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 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/v0.1.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,
};
let uid = help::get_uid("r");
let file = format!("{uid}.yaml");
let name = name.unwrap_or(uid.clone());
let data = resp.text_with_charset("utf-8").await?;
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")
}
}

View File

@ -1,307 +1,11 @@
use crate::utils::{config, dirs, help, tmpl};
use super::enhance::{PrfData, PrfEnhanced};
use super::prfitem::PrfItem;
use crate::utils::{config, dirs, help};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::{fs, io::Write};
#[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 description
#[serde(skip_serializing_if = "Option::is_none")]
pub desc: Option<String>,
/// profile file
pub file: 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)]
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>,
}
impl PrfOption {
pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
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);
}
return Some(one);
}
if one.is_none() {
return other;
}
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 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/v0.1.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,
};
let uid = help::get_uid("r");
let file = format!("{uid}.yaml");
let name = name.unwrap_or(uid.clone());
let data = resp.text_with_charset("utf-8").await?;
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")
}
}
///
/// ## Profiles Config
///
@ -331,6 +35,10 @@ macro_rules! patch {
}
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());
@ -339,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() {
@ -359,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()
@ -386,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}\"");
@ -406,6 +100,11 @@ impl Profiles {
self.valid = valid;
}
/// 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() {
@ -454,7 +153,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![]);
@ -489,7 +188,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() {
@ -509,15 +208,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()
}
@ -616,64 +315,3 @@ impl Profiles {
})
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PrfEnhanced {
pub current: Mapping,
pub chain: Vec<PrfData>,
pub valid: Vec<String>,
pub callback: String,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PrfEnhancedResult {
pub data: Option<Mapping>,
pub status: String,
pub error: Option<String>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct PrfData {
item: PrfItem,
#[serde(skip_serializing_if = "Option::is_none")]
merge: Option<Mapping>,
#[serde(skip_serializing_if = "Option::is_none")]
script: Option<String>,
}
impl PrfData {
pub fn from_item(item: &PrfItem) -> Option<PrfData> {
match item.itype.as_ref() {
Some(itype) => {
let file = item.file.clone()?;
let path = dirs::app_profiles_dir().join(file);
if !path.exists() {
return None;
}
match itype.as_str() {
"script" => Some(PrfData {
item: item.clone(),
script: Some(fs::read_to_string(path).unwrap_or("".into())),
merge: None,
}),
"merge" => Some(PrfData {
item: item.clone(),
merge: Some(config::read_yaml::<Mapping>(path)),
script: None,
}),
_ => None,
}
}
None => None,
}
}
}

View File

@ -0,0 +1,123 @@
use super::{notice::Notice, ClashInfo};
use crate::log_if_err;
use crate::utils::{config, dirs};
use anyhow::{bail, Result};
use reqwest::header::HeaderMap;
use serde_yaml::Mapping;
use std::{collections::HashMap, time::Duration};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep;
#[derive(Debug)]
pub struct Service {
sidecar: Option<CommandChild>,
}
impl Service {
pub fn new() -> Service {
Service { sidecar: None }
}
pub fn start(&mut self) -> Result<()> {
if self.sidecar.is_some() {
bail!("could not run clash sidecar twice");
}
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),
_ => {}
}
}
});
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
if let Some(sidecar) = self.sidecar.take() {
sidecar.kill()?;
}
Ok(())
}
pub fn restart(&mut self) -> Result<()> {
self.stop()?;
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");
}
let temp_path = dirs::profiles_temp_path();
config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
if info.server.is_none() {
bail!("failed to parse the server");
}
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);
}
tauri::async_runtime::spawn(async move {
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 with status \"{}\"", resp.status());
}
notice.refresh_clash();
// 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(())
}
}
impl Drop for Service {
fn drop(&mut self) {
log_if_err!(self.stop());
}
}

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.enable_system_proxy.clone().unwrap_or(false);
self.old_sysproxy = match SysProxyConfig::get_sys() {
Ok(proxy) => Some(proxy),
Err(_) => None,
};
let bypass = verge.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.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);
// 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.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;
});
}
}

143
src-tauri/src/core/timer.rs Normal file
View File

@ -0,0 +1,143 @@
use super::Core;
use crate::log_if_err;
use anyhow::{bail, Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use std::collections::HashMap;
type TaskID = u64;
pub struct Timer {
delay_timer: DelayTimer,
timer_map: HashMap<String, (TaskID, u64)>,
timer_count: TaskID,
core: Option<Core>,
}
impl Timer {
pub fn new() -> Self {
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() {
match diff {
DiffFlag::Del(tid) => {
log_if_err!(self.delay_timer.remove_task(tid));
}
DiffFlag::Add(tid, val) => {
log_if_err!(self.add_task(uid, tid, val));
}
DiffFlag::Mod(tid, val) => {
log_if_err!(self.delay_timer.remove_task(tid));
log_if_err!(self.add_task(uid, tid, val));
}
}
}
Ok(())
}
/// generate a uid -> update_interval map
fn gen_map(&self) -> HashMap<String, u64> {
let profiles = self.core.as_ref().unwrap().profiles.lock();
let mut new_map = HashMap::new();
if let Some(items) = profiles.get_items() {
for item in items.iter() {
if item.option.is_some() {
let option = item.option.as_ref().unwrap();
let interval = option.update_interval.unwrap_or(0);
if interval > 0 {
new_map.insert(item.uid.clone().unwrap(), interval);
}
}
}
}
new_map
}
/// generate the diff map for refresh
fn gen_diff(&mut self) -> HashMap<String, DiffFlag> {
let mut diff_map = HashMap::new();
let new_map = self.gen_map();
let cur_map = &self.timer_map;
cur_map.iter().for_each(|(uid, (tid, val))| {
let new_val = new_map.get(uid).unwrap_or(&0);
if *new_val == 0 {
diff_map.insert(uid.clone(), DiffFlag::Del(*tid));
} else if new_val != val {
diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val));
}
});
let mut count = self.timer_count;
new_map.iter().for_each(|(uid, val)| {
if cur_map.get(uid).is_none() {
diff_map.insert(uid.clone(), DiffFlag::Add(count, *val));
count += 1;
}
});
self.timer_count = count;
diff_map
}
/// add a cron task
fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> {
let core = self.core.clone().unwrap();
let task = TaskBuilder::default()
.set_task_id(tid)
.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()))
.context("failed to create timer task")?;
self
.delay_timer
.add_task(task)
.context("failed to add timer task")?;
Ok(())
}
/// the task runner
async fn async_task(core: Core, uid: String) {
log::info!("running timer task `{uid}`");
log_if_err!(Core::update_profile_item(core, uid, None).await);
}
}
enum DiffFlag {
Del(TaskID),
Add(TaskID, u64),
Mod(TaskID, u64),
}

View File

@ -1,18 +1,10 @@
use crate::log_if_err;
use crate::{
core::Clash,
utils::{config, dirs, sysopt::SysProxyConfig},
};
use anyhow::{bail, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use crate::utils::{config, dirs};
use anyhow::Result;
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)]
pub struct VergeConfig {
pub struct Verge {
// i18n
pub language: Option<String>,
@ -67,9 +59,9 @@ pub struct VergeTheme {
pub css_injection: Option<String>,
}
impl VergeConfig {
impl Verge {
pub fn new() -> Self {
config::read_yaml::<VergeConfig>(dirs::verge_path())
config::read_yaml::<Verge>(dirs::verge_path())
}
/// Save Verge App Config
@ -80,303 +72,54 @@ impl VergeConfig {
Some("# The Config for Clash Verge App\n\n"),
)
}
}
/// Verge App abilities
#[derive(Debug)]
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 {
fn default() -> Self {
Verge::new()
}
}
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
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
/// only save to file
pub fn patch_config(&mut self, patch: Verge) -> Result<()> {
// only change it
if patch.language.is_some() {
self.config.language = patch.language;
self.language = patch.language;
}
if patch.theme_mode.is_some() {
self.config.theme_mode = patch.theme_mode;
self.theme_mode = patch.theme_mode;
}
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;
self.theme_blur = patch.theme_blur;
}
if patch.theme_setting.is_some() {
self.config.theme_setting = patch.theme_setting;
self.theme_setting = patch.theme_setting;
}
if patch.traffic_graph.is_some() {
self.traffic_graph = patch.traffic_graph;
}
// should update system startup
// system setting
if patch.enable_silent_start.is_some() {
self.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.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.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.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;
self.enable_proxy_guard = patch.enable_proxy_guard;
}
if patch.proxy_guard_duration.is_some() {
self.config.proxy_guard_duration = patch.proxy_guard_duration;
self.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
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();
});
if patch.enable_tun_mode.is_some() {
self.enable_tun_mode = patch.enable_tun_mode;
}
// 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;
});
self.save_file()
}
}

View File

@ -5,11 +5,10 @@
mod cmds;
mod core;
mod states;
mod utils;
use crate::{
core::VergeConfig,
core::Verge,
utils::{resolve, server},
};
use tauri::{
@ -39,9 +38,7 @@ fn main() -> std::io::Result<()> {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.manage(states::VergeState::default())
.manage(states::ClashState::default())
.manage(states::ProfilesState::default())
.manage(core::Core::new())
.setup(|app| Ok(resolve::resolve_setup(app)))
.system_tray(SystemTray::new().with_menu(tray_menu))
.on_system_tray_event(move |app_handle, event| match event {
@ -53,42 +50,38 @@ fn main() -> std::io::Result<()> {
window.set_focus().unwrap();
}
"system_proxy" => {
let verge_state = app_handle.state::<states::VergeState>();
let mut verge = verge_state.0.lock().unwrap();
let core = app_handle.state::<core::Core>();
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.enable_system_proxy.clone().unwrap_or(false)
};
match verge.patch_config(VergeConfig {
let patch = Verge {
enable_system_proxy: Some(new_value),
..VergeConfig::default()
}) {
Ok(_) => verge.update_systray(app_handle).unwrap(),
Err(err) => log::error!("{err}"),
}
..Verge::default()
};
crate::log_if_err!(core.patch_verge(patch, app_handle));
}
"tun_mode" => {
let verge_state = app_handle.state::<states::VergeState>();
let mut verge = verge_state.0.lock().unwrap();
let core = app_handle.state::<core::Core>();
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.enable_tun_mode.clone().unwrap_or(false)
};
match verge.patch_config(VergeConfig {
let patch = Verge {
enable_tun_mode: Some(new_value),
..VergeConfig::default()
}) {
Ok(_) => verge.update_systray(app_handle).unwrap(),
Err(err) => log::error!("{err}"),
}
..Verge::default()
};
crate::log_if_err!(core.patch_verge(patch, app_handle));
}
"restart_clash" => {
let clash_state = app_handle.state::<states::ClashState>();
let profiles_state = app_handle.state::<states::ProfilesState>();
let mut clash = clash_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
crate::log_if_err!(clash.restart_sidecar(&mut profiles));
let core = app_handle.state::<core::Core>();
crate::log_if_err!(core.restart_clash());
}
"quit" => {
resolve::resolve_reset(app_handle);
@ -111,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
@ -129,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,11 +1,11 @@
use crate::core::{Clash, Profiles, Verge};
use std::sync::{Arc, Mutex};
// use crate::core::{Clash, Profiles, Verge};
// use std::sync::{Arc, Mutex};
#[derive(Default)]
pub struct ProfilesState(pub Arc<Mutex<Profiles>>);
// #[derive(Default)]
// pub struct ProfilesState(pub Arc<Mutex<Profiles>>);
#[derive(Default)]
pub struct ClashState(pub Arc<Mutex<Clash>>);
// #[derive(Default)]
// pub struct ClashState(pub Arc<Mutex<Clash>>);
#[derive(Default)]
pub struct VergeState(pub Arc<Mutex<Verge>>);
// #[derive(Default)]
// pub struct VergeState(pub Arc<Mutex<Verge>>);

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

@ -1,5 +1,4 @@
use crate::log_if_err;
use crate::{core::Profiles, states, utils::init, utils::server};
use crate::{core::Core, utils::init, utils::server};
use tauri::{App, AppHandle, Manager};
/// handle something when start app
@ -11,51 +10,26 @@ pub fn resolve_setup(app: &App) {
init::init_app(app.package_info());
// init states
let clash_state = app.state::<states::ClashState>();
let verge_state = app.state::<states::VergeState>();
let profiles_state = app.state::<states::ProfilesState>();
let core = app.state::<Core>();
let mut clash = clash_state.0.lock().unwrap();
let mut verge = verge_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap();
core.set_win(app.get_window("main"));
core.init(app.app_handle());
*profiles = Profiles::read_file();
clash.set_window(app.get_window("main"));
log_if_err!(clash.run_sidecar(&profiles, true));
verge.init_sysproxy(clash.info.port.clone());
log_if_err!(verge.init_launch());
verge.config.enable_system_proxy.map(|enable| {
log_if_err!(app
.tray_handle()
.get_item("system_proxy")
.set_selected(enable));
});
resolve_window(app, verge.config.enable_silent_start.clone());
resolve_window(app);
}
/// reset system proxy
pub fn resolve_reset(app_handle: &AppHandle) {
let verge_state = app_handle.state::<states::VergeState>();
let mut verge = verge_state.0.lock().unwrap();
let core = app_handle.state::<Core>();
let mut sysopt = core.sysopt.lock();
verge.reset_sysproxy();
sysopt.reset_sysproxy();
}
/// customize the window theme
fn resolve_window(app: &App, hide: Option<bool>) {
fn resolve_window(app: &App) {
let window = app.get_window("main").unwrap();
// silent start
hide.map(|hide| {
if hide {
window.hide().unwrap();
}
});
#[cfg(target_os = "windows")]
{
use window_shadows::set_shadow;

View File

@ -12,7 +12,6 @@ const upLineWidth = 4;
const downLineAlpha = 1;
const downLineWidth = 4;
const duration = 16 / 1000;
const defaultList = Array(maxPoint + 2).fill({ up: 0, down: 0 });
type TrafficData = { up: number; down: number };
@ -107,10 +106,9 @@ const TrafficGraph = (props: Props) => {
return 1;
};
const drawBezier = (list: number[]) => {
const o = dx * Math.min(1, countRef.current * duration);
const drawBezier = (list: number[], offset: number) => {
const points = list.map((y, i) => [
(dx * (i - 1) - o + 3) | 0,
(dx * (i - 1) - offset + 3) | 0,
countY(y),
]);
@ -132,9 +130,11 @@ const TrafficGraph = (props: Props) => {
}
};
const drawLine = (list: number[]) => {
const o = dx * Math.min(1, countRef.current * duration);
const points = list.map((y, i) => [(dx * (i - 1) - o) | 0, countY(y)]);
const drawLine = (list: number[], offset: number) => {
const points = list.map((y, i) => [
(dx * (i - 1) - offset) | 0,
countY(y),
]);
context.moveTo(points[0][0], points[0][1]);
@ -144,7 +144,17 @@ const TrafficGraph = (props: Props) => {
}
};
const drawGraph = () => {
const drawGraph = (lastTime: number) => {
const listUp = listRef.current.map((v) => v.up);
const listDown = listRef.current.map((v) => v.down);
const lineStyle = styleRef.current;
const now = Date.now();
const diff = now - lastTime;
const temp = Math.min((diff / 1000) * dx + countRef.current, dx);
const offset = countRef.current === 0 ? 0 : temp;
countRef.current = temp;
context.clearRect(0, 0, width, height);
// Reference lines
@ -159,15 +169,11 @@ const TrafficGraph = (props: Props) => {
context.stroke();
context.closePath();
const listUp = listRef.current.map((v) => v.up);
const listDown = listRef.current.map((v) => v.down);
const lineStyle = styleRef.current;
context.beginPath();
context.globalAlpha = upLineAlpha;
context.lineWidth = upLineWidth;
context.strokeStyle = upLineColor;
lineStyle ? drawBezier(listUp) : drawLine(listUp);
lineStyle ? drawBezier(listUp, offset) : drawLine(listUp, offset);
context.stroke();
context.closePath();
@ -175,16 +181,14 @@ const TrafficGraph = (props: Props) => {
context.globalAlpha = downLineAlpha;
context.lineWidth = downLineWidth;
context.strokeStyle = downLineColor;
lineStyle ? drawBezier(listDown) : drawLine(listDown);
lineStyle ? drawBezier(listDown, offset) : drawLine(listDown, offset);
context.stroke();
context.closePath();
countRef.current += 1;
raf = requestAnimationFrame(drawGraph);
raf = requestAnimationFrame(() => drawGraph(now));
};
drawGraph();
drawGraph(Date.now());
return () => {
cancelAnimationFrame(raf);

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() {