From 697c25015e08bb6e207e6733622fe018f923c558 Mon Sep 17 00:00:00 2001 From: GyDi Date: Tue, 19 Apr 2022 01:41:20 +0800 Subject: [PATCH 1/6] refactor: wip --- src-tauri/Cargo.lock | 328 ++++++++++++++++++- src-tauri/Cargo.toml | 5 +- src-tauri/src/cmds.rs | 193 +++++------- src-tauri/src/core/clash copy.rs | 520 +++++++++++++++++++++++++++++++ src-tauri/src/core/clash.rs | 338 ++++---------------- src-tauri/src/core/enhance.rs | 66 ++++ src-tauri/src/core/mod.rs | 220 +++++++++++++ src-tauri/src/core/notice.rs | 35 +++ src-tauri/src/core/prfitem.rs | 320 +++++++++++++++++++ src-tauri/src/core/profiles.rs | 367 +--------------------- src-tauri/src/core/service.rs | 121 +++++++ src-tauri/src/core/timer.rs | 20 ++ src-tauri/src/main.rs | 21 +- src-tauri/src/states.rs | 16 +- src-tauri/src/utils/resolve.rs | 33 +- 15 files changed, 1799 insertions(+), 804 deletions(-) create mode 100644 src-tauri/src/core/clash copy.rs create mode 100644 src-tauri/src/core/enhance.rs create mode 100644 src-tauri/src/core/notice.rs create mode 100644 src-tauri/src/core/prfitem.rs create mode 100644 src-tauri/src/core/service.rs create mode 100644 src-tauri/src/core/timer.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 812bccf..7bed2ff 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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,6 +482,7 @@ dependencies = [ "anyhow", "auto-launch", "chrono", + "delay_timer", "dirs", "dunce", "log", @@ -398,6 +545,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 +629,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 +783,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 +812,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 +969,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 +1507,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 +1949,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 +2033,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 +2197,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 +2650,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 +2965,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 +3090,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 +3114,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 +3314,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 +3351,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 +4407,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" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6467b00..0afe654 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ nanoid = "0.4.0" chrono = "0.4.19" serde_json = "1.0" serde_yaml = "0.8" +delay_timer = "0.11.1" 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 +40,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 = [] diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index bafc118..d2d146f 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,25 +1,26 @@ use crate::{ - core::{ClashInfo, PrfItem, PrfOption, Profiles, VergeConfig}, - states::{ClashState, ProfilesState, VergeState}, + core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, VergeConfig}, utils::{dirs, 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}; +type CmdResult = Result; + /// get all profiles from `profiles.yaml` #[tauri::command] -pub fn get_profiles<'a>(profiles_state: State<'_, ProfilesState>) -> Result { - let profiles = profiles_state.0.lock().unwrap(); +pub fn get_profiles(core: State<'_, Core>) -> CmdResult { + let profiles = core.profiles.lock().unwrap(); Ok(profiles.clone()) } /// synchronize data irregularly #[tauri::command] -pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> { - let mut profiles = profiles_state.0.lock().unwrap(); +pub fn sync_profiles(core: State<'_, Core>) -> CmdResult { + let mut profiles = core.profiles.lock().unwrap(); wrap_err!(profiles.sync_file()) } @@ -29,11 +30,11 @@ pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), Str pub async fn import_profile( url: String, option: Option, - 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().unwrap(); wrap_err!(profiles.append_item(item)) } @@ -44,11 +45,11 @@ pub async fn import_profile( pub async fn create_profile( item: PrfItem, // partial file_data: Option, - 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().unwrap(); wrap_err!(profiles.append_item(item)) } @@ -57,12 +58,11 @@ pub async fn create_profile( pub async fn update_profile( index: String, option: Option, - clash_state: State<'_, ClashState>, - profiles_state: State<'_, ProfilesState>, -) -> Result<(), String> { + core: State<'_, Core>, +) -> CmdResult { let (url, opt) = { // must release the lock here - let profiles = profiles_state.0.lock().unwrap(); + let profiles = core.profiles.lock().unwrap(); let item = wrap_err!(profiles.get_item(&index))?; // check the profile type @@ -82,13 +82,12 @@ pub async fn update_profile( 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(); + let mut profiles = core.profiles.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))?; + log_if_err!(core.activate_enhanced(false, false)); } Ok(()) @@ -96,79 +95,55 @@ pub async fn update_profile( /// 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(); - wrap_err!(profiles.put_current(index))?; +pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult { + { + let mut profiles = core.profiles.lock().unwrap(); + wrap_err!(profiles.put_current(index))?; + } - let clash = clash_state.0.lock().unwrap(); - wrap_err!(clash.activate_enhanced(&profiles, false, false)) + log_if_err!(core.activate_enhanced(false, false)); + + Ok(()) } /// change the profile chain #[tauri::command] -pub fn change_profile_chain( - chain: Option>, - 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>, core: State<'_, Core>) -> CmdResult { + { + let mut profiles = core.profiles.lock().unwrap(); + profiles.put_chain(chain); + } - profiles.put_chain(chain); - clash.set_window(app_handle.get_window("main")); + log_if_err!(core.activate_enhanced(false, false)); - wrap_err!(clash.activate_enhanced(&profiles, false, false)) + Ok(()) } /// change the profile valid fields #[tauri::command] -pub fn change_profile_valid( - valid: Option>, - 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>, core: State) -> CmdResult { + let mut profiles = core.profiles.lock().unwrap(); profiles.put_valid(valid); - clash.set_window(app_handle.get_window("main")); - wrap_err!(clash.activate_enhanced(&profiles, false, false)) + log_if_err!(core.activate_enhanced(false, false)); + + Ok(()) } /// 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)) +pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult { + log_if_err!(core.activate_enhanced(false, false)); + Ok(()) } /// 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().unwrap(); if wrap_err!(profiles.delete_item(index))? { - let clash = clash_state.0.lock().unwrap(); - wrap_err!(clash.activate_enhanced(&profiles, false, false))?; + log_if_err!(core.activate_enhanced(false, false)); } Ok(()) @@ -176,19 +151,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().unwrap(); + 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 mut profiles = core.profiles.lock().unwrap(); let item = wrap_err!(profiles.get_item(&index))?; let file = item.file.clone(); @@ -231,11 +203,9 @@ pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) -> /// read the profile item file data #[tauri::command] -pub fn read_profile_file( - index: String, - profiles_state: State<'_, ProfilesState>, -) -> Result { - let profiles = profiles_state.0.lock().unwrap(); +pub fn read_profile_file(index: String, core: State<'_, Core>) -> CmdResult { + let mut profiles = core.profiles.lock().unwrap(); + let item = wrap_err!(profiles.get_item(&index))?; let data = wrap_err!(item.read_file())?; @@ -247,34 +217,36 @@ pub fn read_profile_file( pub fn save_profile_file( index: String, file_data: Option, - 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 mut profiles = core.profiles.lock().unwrap(); 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(); +pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult { + let mut service = core.service.lock().unwrap(); - wrap_err!(clash.restart_sidecar(&mut profiles)) + wrap_err!(service.restart())?; + + // 更新配置 + + log_if_err!(core.activate_enhanced(false, false)); + + Ok(()) } /// 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 { - let clash = clash_state.0.lock().unwrap(); +pub fn get_clash_info(core: State<'_, Core>) -> CmdResult { + let clash = core.clash.lock().unwrap(); Ok(clash.info.clone()) } @@ -282,16 +254,8 @@ pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result, - verge_state: State<'_, VergeState>, - profiles_state: State<'_, ProfilesState>, -) -> 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)) +pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult { + wrap_err!(core.patch_clash(payload)) } /// get the system proxy @@ -303,15 +267,15 @@ pub fn get_sys_proxy() -> Result { /// 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, String> { - let verge = verge_state.0.lock().unwrap(); +pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult> { + let verge = core.verge.lock().unwrap(); Ok(verge.cur_sysproxy.clone()) } /// get the verge config #[tauri::command] -pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result { - let verge = verge_state.0.lock().unwrap(); +pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { + let verge = core.verge.lock().unwrap(); let mut config = verge.config.clone(); if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() { @@ -327,14 +291,12 @@ pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result, - verge_state: State<'_, VergeState>, - profiles_state: State<'_, ProfilesState>, + core: State<'_, Core>, ) -> 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(); + let mut verge = core.verge.lock().unwrap(); wrap_err!(verge.patch_config(payload))?; // change tun mode @@ -348,10 +310,9 @@ pub fn patch_verge_config( } } - let clash = clash_state.0.lock().unwrap(); - let profiles = profiles_state.0.lock().unwrap(); + let profiles = core.profiles.lock().unwrap(); - wrap_err!(clash.activate_enhanced(&profiles, false, false))?; + log_if_err!(core.activate_enhanced(false, false)); } // change system tray diff --git a/src-tauri/src/core/clash copy.rs b/src-tauri/src/core/clash copy.rs new file mode 100644 index 0000000..463ab14 --- /dev/null +++ b/src-tauri/src/core/clash copy.rs @@ -0,0 +1,520 @@ +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, + + /// same as `external-controller` + pub server: Option, + + /// clash secret + pub secret: Option, +} + +pub struct Clash { + /// maintain the clash config + pub config: Mapping, + + /// some info + pub info: ClashInfo, + + /// clash sidecar + pub sidecar: Option, + + /// save the main window + pub window: Option, +} + +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::(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) { + 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) -> 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}"); + } + } +} diff --git a/src-tauri/src/core/clash.rs b/src-tauri/src/core/clash.rs index 463ab14..2e5d402 100644 --- a/src-tauri/src/core/clash.rs +++ b/src-tauri/src/core/clash.rs @@ -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, } -pub struct Clash { - /// maintain the clash config - pub config: Mapping, - - /// some info - pub info: ClashInfo, - - /// clash sidecar - pub sidecar: Option, - - /// save the main window - pub window: Option, -} - -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::(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) { - 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::(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 { + 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) -> 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}"); - } - } -} diff --git a/src-tauri/src/core/enhance.rs b/src-tauri/src/core/enhance.rs new file mode 100644 index 0000000..a1c79e8 --- /dev/null +++ b/src-tauri/src/core/enhance.rs @@ -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, + + pub valid: Vec, + + pub callback: String, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct PrfEnhancedResult { + pub data: Option, + + pub status: String, + + pub error: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct PrfData { + item: PrfItem, + + #[serde(skip_serializing_if = "Option::is_none")] + merge: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + script: Option, +} + +impl PrfData { + pub fn from_item(item: &PrfItem) -> Option { + 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::(path)), + script: None, + }), + _ => None, + } + } + None => None, + } + } +} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 1cf8f1e..caed860 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -1,7 +1,227 @@ +use self::notice::Notice; +use self::service::Service; +use crate::core::enhance::PrfEnhancedResult; +use crate::log_if_err; +use crate::utils::{config, dirs, help}; +use anyhow::{bail, Result}; +use serde_yaml::Mapping; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tauri::Window; +use tokio::time::sleep; + mod clash; +mod enhance; +mod notice; +mod prfitem; mod profiles; +mod service; +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>, + + pub verge: Arc>, + + pub profiles: Arc>, + + pub service: Arc>, + + pub window: Arc>>, +} + +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)), + window: Arc::new(Mutex::new(None)), + } + } + + pub fn init(&self) { + log_if_err!(self.restart_clash()); + + let clash = self.clash.lock().unwrap(); + let mut verge = self.verge.lock().unwrap(); + verge.init_sysproxy(clash.info.port.clone()); + + log_if_err!(verge.init_launch()); + + // system tray + // verge.config.enable_system_proxy.map(|enable| { + // log_if_err!(app + // .tray_handle() + // .get_item("system_proxy") + // .set_selected(enable)); + // }); + } + + /// save the window instance + pub fn set_win(&self, win: Option) { + let mut window = self.window.lock().unwrap(); + *window = win; + } + + /// restart the clash sidecar + pub fn restart_clash(&self) -> Result<()> { + { + let mut service = self.service.lock().unwrap(); + service.restart()?; + } + + self.activate()?; + self.activate_enhanced(false, true) + } + + /// handle the clash config changed + pub fn patch_clash(&self, patch: Mapping) -> Result<()> { + let (changed, port) = { + let mut clash = self.clash.lock().unwrap(); + (clash.patch_config(patch)?, clash.info.port.clone()) + }; + + // todo: port check + + if changed { + let mut service = self.service.lock().unwrap(); + service.restart()?; + + self.activate()?; + self.activate_enhanced(false, true)?; + + let mut verge = self.verge.lock().unwrap(); + verge.init_sysproxy(port); + } + + Ok(()) + } + + /// activate the profile + /// auto activate enhanced profile + pub fn activate(&self) -> Result<()> { + let data = { + let profiles = self.profiles.lock().unwrap(); + let data = profiles.gen_activate()?; + Clash::strict_filter(data) + }; + + let (mut config, info) = { + let clash = self.clash.lock().unwrap(); + 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().unwrap(); + let tun_mode = verge.config.enable_tun_mode.unwrap_or(false); + Clash::_tun_mode(config, tun_mode) + }; + + let notice = { + let window = self.window.lock().unwrap(); + Notice::from(window.clone()) + }; + + let service = self.service.lock().unwrap(); + service.set_config(info, config, notice) + } + + /// enhanced profiles mode + pub fn activate_enhanced(&self, delay: bool, skip: bool) -> Result<()> { + let window = self.window.lock().unwrap(); + 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().unwrap(); + profiles.gen_enhanced(event_name.clone())? + }; + + // do not run enhanced + if payload.chain.len() == 0 { + if skip { + return Ok(()); + } + + return self.activate(); + } + + let tun_mode = { + let verge = self.verge.lock().unwrap(); + verge.config.enable_tun_mode.unwrap_or(false) + }; + + let info = { + let clash = self.clash.lock().unwrap(); + 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().unwrap(); + log_if_err!(service.set_config(info, config, notice)); + + log::info!("profile enhanced status {}", result.status); + } + + result.error.map(|err| log::error!("{err}")); + }); + + // wait the window setup during resolve app + // if delay { + // sleep(Duration::from_secs(2)).await; + // } + + window.emit("script-handler", payload).unwrap(); + + Ok(()) + } +} diff --git a/src-tauri/src/core/notice.rs b/src-tauri/src/core/notice.rs new file mode 100644 index 0000000..8e942da --- /dev/null +++ b/src-tauri/src/core/notice.rs @@ -0,0 +1,35 @@ +use crate::log_if_err; +use tauri::Window; + +#[derive(Debug, Default, Clone)] +pub struct Notice { + win: Option, +} + +impl Notice { + pub fn from(win: Option) -> Notice { + Notice { win } + } + + pub fn set_win(&mut self, win: Option) { + 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")); + } + } +} diff --git a/src-tauri/src/core/prfitem.rs b/src-tauri/src/core/prfitem.rs new file mode 100644 index 0000000..3d99906 --- /dev/null +++ b/src-tauri/src/core/prfitem.rs @@ -0,0 +1,320 @@ +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, + + /// profile item type + /// enum value: remote | local | script | merge + #[serde(rename = "type")] + pub itype: Option, + + /// profile name + pub name: Option, + + /// profile description + #[serde(skip_serializing_if = "Option::is_none")] + pub desc: Option, + + /// profile file + pub file: Option, + + /// source url + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, + + /// selected infomation + #[serde(skip_serializing_if = "Option::is_none")] + pub selected: Option>, + + /// subscription user info + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option, + + /// updated time + pub updated: Option, + + /// some options of the item + #[serde(skip_serializing_if = "Option::is_none")] + pub option: Option, + + /// the file data + #[serde(skip)] + pub file_data: Option, +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct PrfSelected { + pub name: Option, + pub now: Option, +} + +#[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, + + /// for `remote` profile + #[serde(skip_serializing_if = "Option::is_none")] + pub with_proxy: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub update_interval: Option, +} + +impl PrfOption { + pub fn merge(one: Option, other: Option) -> Option { + 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; + } + + pub fn diff_update_interval(one: Option<&Self>, other: Option<&Self>) -> bool { + if one.is_some() && other.is_some() { + let one = one.unwrap(); + let other = other.unwrap(); + + return one.update_interval == other.update_interval; + } + + return false; + } +} + +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) -> Result { + 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) -> Result { + 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, + desc: Option, + option: Option, + ) -> Result { + 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 { + 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 { + 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 { + 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") + } +} diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs index 1955526..af37f9c 100644 --- a/src-tauri/src/core/profiles.rs +++ b/src-tauri/src/core/profiles.rs @@ -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, - - /// profile item type - /// enum value: remote | local | script | merge - #[serde(rename = "type")] - pub itype: Option, - - /// profile name - pub name: Option, - - /// profile description - #[serde(skip_serializing_if = "Option::is_none")] - pub desc: Option, - - /// profile file - pub file: Option, - - /// source url - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, - - /// selected infomation - #[serde(skip_serializing_if = "Option::is_none")] - pub selected: Option>, - - /// subscription user info - #[serde(skip_serializing_if = "Option::is_none")] - pub extra: Option, - - /// updated time - pub updated: Option, - - /// some options of the item - #[serde(skip_serializing_if = "Option::is_none")] - pub option: Option, - - /// the file data - #[serde(skip)] - pub file_data: Option, -} - -#[derive(Default, Debug, Clone, Deserialize, Serialize)] -pub struct PrfSelected { - pub name: Option, - pub now: Option, -} - -#[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, - - /// for `remote` profile - #[serde(skip_serializing_if = "Option::is_none")] - pub with_proxy: Option, -} - -impl PrfOption { - pub fn merge(one: Option, other: Option) -> Option { - 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) -> Result { - 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) -> Result { - 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, - desc: Option, - option: Option, - ) -> Result { - 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 { - 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 { - 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 { - 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::(dirs::profiles_path()); @@ -616,64 +324,3 @@ impl Profiles { }) } } - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct PrfEnhanced { - pub current: Mapping, - - pub chain: Vec, - - pub valid: Vec, - - pub callback: String, -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct PrfEnhancedResult { - pub data: Option, - - pub status: String, - - pub error: Option, -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct PrfData { - item: PrfItem, - - #[serde(skip_serializing_if = "Option::is_none")] - merge: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - script: Option, -} - -impl PrfData { - pub fn from_item(item: &PrfItem) -> Option { - 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::(path)), - script: None, - }), - _ => None, - } - } - None => None, - } - } -} diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs new file mode 100644 index 0000000..b1eb42e --- /dev/null +++ b/src-tauri/src/core/service.rs @@ -0,0 +1,121 @@ +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, +} + +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() + } + + 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()); + } +} diff --git a/src-tauri/src/core/timer.rs b/src-tauri/src/core/timer.rs new file mode 100644 index 0000000..2e94a6f --- /dev/null +++ b/src-tauri/src/core/timer.rs @@ -0,0 +1,20 @@ +use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, Task, TaskBuilder}; +use std::collections::HashMap; + +pub struct Timer { + delay_timer: DelayTimer, + + timer_map: HashMap, + + timer_count: u64, +} + +impl Timer { + pub fn new() -> Self { + Timer { + delay_timer: DelayTimerBuilder::default().build(), + timer_map: HashMap::new(), + timer_count: 1, + } + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 77e99fa..52790e7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,7 +5,6 @@ mod cmds; mod core; -mod states; mod utils; use crate::{ @@ -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,8 +50,8 @@ fn main() -> std::io::Result<()> { window.set_focus().unwrap(); } "system_proxy" => { - let verge_state = app_handle.state::(); - let mut verge = verge_state.0.lock().unwrap(); + let core = app_handle.state::(); + let mut verge = core.verge.lock().unwrap(); let old_value = verge.config.enable_system_proxy.clone().unwrap_or(false); let new_value = !old_value; @@ -68,8 +65,8 @@ fn main() -> std::io::Result<()> { } } "tun_mode" => { - let verge_state = app_handle.state::(); - let mut verge = verge_state.0.lock().unwrap(); + let core = app_handle.state::(); + let mut verge = core.verge.lock().unwrap(); let old_value = verge.config.enable_tun_mode.clone().unwrap_or(false); let new_value = !old_value; @@ -83,12 +80,8 @@ fn main() -> std::io::Result<()> { } } "restart_clash" => { - let clash_state = app_handle.state::(); - let profiles_state = app_handle.state::(); - 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::(); + crate::log_if_err!(core.restart_clash()); } "quit" => { resolve::resolve_reset(app_handle); diff --git a/src-tauri/src/states.rs b/src-tauri/src/states.rs index 1e10192..c22364f 100644 --- a/src-tauri/src/states.rs +++ b/src-tauri/src/states.rs @@ -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>); +// #[derive(Default)] +// pub struct ProfilesState(pub Arc>); -#[derive(Default)] -pub struct ClashState(pub Arc>); +// #[derive(Default)] +// pub struct ClashState(pub Arc>); -#[derive(Default)] -pub struct VergeState(pub Arc>); +// #[derive(Default)] +// pub struct VergeState(pub Arc>); diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 8d69342..5678357 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,5 +1,5 @@ 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,36 +11,21 @@ pub fn resolve_setup(app: &App) { init::init_app(app.package_info()); // init states - let clash_state = app.state::(); - let verge_state = app.state::(); - let profiles_state = app.state::(); + let core = app.state::(); - 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(); - *profiles = Profiles::read_file(); + // clash.set_window(app.get_window("main")); + // log_if_err!(clash.run_sidecar(&profiles, true)); - 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, None); } /// reset system proxy pub fn resolve_reset(app_handle: &AppHandle) { - let verge_state = app_handle.state::(); - let mut verge = verge_state.0.lock().unwrap(); + let core = app_handle.state::(); + let mut verge = core.verge.lock().unwrap(); verge.reset_sysproxy(); } From fac437b8c1eb4dcd38ae795a159d42c7d308f241 Mon Sep 17 00:00:00 2001 From: GyDi Date: Tue, 19 Apr 2022 13:55:26 +0800 Subject: [PATCH 2/6] fix: traffic graph adapt to different fps --- src/components/layout/traffic-graph.tsx | 40 ++++++++++++++----------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/components/layout/traffic-graph.tsx b/src/components/layout/traffic-graph.tsx index a6a1a0e..0a9d376 100644 --- a/src/components/layout/traffic-graph.tsx +++ b/src/components/layout/traffic-graph.tsx @@ -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); From 3076fd19c175a2f07635f4ed70adb4ac7454acf0 Mon Sep 17 00:00:00 2001 From: GyDi Date: Tue, 19 Apr 2022 14:38:59 +0800 Subject: [PATCH 3/6] refactor: mutex --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/cmds.rs | 90 ++++++++++++++++------------------ src-tauri/src/core/mod.rs | 89 ++++++++++++++++++++------------- src-tauri/src/main.rs | 4 +- src-tauri/src/utils/resolve.rs | 19 ++----- 6 files changed, 105 insertions(+), 99 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7bed2ff..d123d20 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -489,6 +489,7 @@ dependencies = [ "log4rs", "nanoid", "open", + "parking_lot 0.12.0", "port_scanner", "reqwest", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0afe654..b1b03ff 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,6 +22,7 @@ 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" } diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index d2d146f..4d1e7a8 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -13,14 +13,14 @@ type CmdResult = Result; /// get all profiles from `profiles.yaml` #[tauri::command] pub fn get_profiles(core: State<'_, Core>) -> CmdResult { - let profiles = core.profiles.lock().unwrap(); + let profiles = core.profiles.lock(); Ok(profiles.clone()) } /// synchronize data irregularly #[tauri::command] pub fn sync_profiles(core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); wrap_err!(profiles.sync_file()) } @@ -34,7 +34,7 @@ pub async fn import_profile( ) -> CmdResult { let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); wrap_err!(profiles.append_item(item)) } @@ -49,7 +49,7 @@ pub async fn create_profile( ) -> CmdResult { let item = wrap_err!(PrfItem::from(item, file_data).await)?; - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); wrap_err!(profiles.append_item(item)) } @@ -62,7 +62,7 @@ pub async fn update_profile( ) -> CmdResult { let (url, opt) = { // must release the lock here - let profiles = core.profiles.lock().unwrap(); + let profiles = core.profiles.lock(); let item = wrap_err!(profiles.get_item(&index))?; // check the profile type @@ -82,12 +82,13 @@ pub async fn update_profile( let fetch_opt = PrfOption::merge(opt, option); let item = wrap_err!(PrfItem::from_url(&url, None, None, fetch_opt).await)?; - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); wrap_err!(profiles.update_item(index.clone(), item))?; // reactivate the profile if Some(index) == profiles.get_current() { - log_if_err!(core.activate_enhanced(false, false)); + drop(profiles); + log_if_err!(core.activate_enhanced(false)); } Ok(()) @@ -96,12 +97,12 @@ pub async fn update_profile( /// change the current profile #[tauri::command] pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult { - { - let mut profiles = core.profiles.lock().unwrap(); - wrap_err!(profiles.put_current(index))?; - } + let mut profiles = core.profiles.lock(); + wrap_err!(profiles.put_current(index))?; - log_if_err!(core.activate_enhanced(false, false)); + drop(profiles); + + log_if_err!(core.activate_enhanced(false)); Ok(()) } @@ -109,12 +110,13 @@ pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult { /// change the profile chain #[tauri::command] pub fn change_profile_chain(chain: Option>, core: State<'_, Core>) -> CmdResult { - { - let mut profiles = core.profiles.lock().unwrap(); - profiles.put_chain(chain); - } + 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, false)); + log_if_err!(core.activate_enhanced(false)); Ok(()) } @@ -122,10 +124,11 @@ pub fn change_profile_chain(chain: Option>, core: State<'_, Core>) - /// change the profile valid fields #[tauri::command] pub fn change_profile_valid(valid: Option>, core: State) -> CmdResult { - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); profiles.put_valid(valid); + drop(profiles); - log_if_err!(core.activate_enhanced(false, false)); + log_if_err!(core.activate_enhanced(false)); Ok(()) } @@ -133,17 +136,19 @@ pub fn change_profile_valid(valid: Option>, core: State) -> Cm /// manually exec enhanced profile #[tauri::command] pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult { - log_if_err!(core.activate_enhanced(false, false)); + log_if_err!(core.activate_enhanced(false)); Ok(()) } /// delete profile item #[tauri::command] pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); if wrap_err!(profiles.delete_item(index))? { - log_if_err!(core.activate_enhanced(false, false)); + drop(profiles); + // std::mem::drop(profiles); + log_if_err!(core.activate_enhanced(false)); } Ok(()) @@ -152,7 +157,7 @@ pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult { /// patch the profile config #[tauri::command] pub fn patch_profile(index: String, profile: PrfItem, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock().unwrap(); + let mut profiles = core.profiles.lock(); wrap_err!(profiles.patch_item(index, profile)) } @@ -160,7 +165,7 @@ pub fn patch_profile(index: String, profile: PrfItem, core: State<'_, Core>) -> /// run vscode command to edit the profile #[tauri::command] pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock().unwrap(); + let profiles = core.profiles.lock(); let item = wrap_err!(profiles.get_item(&index))?; let file = item.file.clone(); @@ -204,7 +209,7 @@ pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult { /// read the profile item file data #[tauri::command] pub fn read_profile_file(index: String, core: State<'_, Core>) -> CmdResult { - let mut profiles = core.profiles.lock().unwrap(); + let profiles = core.profiles.lock(); let item = wrap_err!(profiles.get_item(&index))?; let data = wrap_err!(item.read_file())?; @@ -223,7 +228,7 @@ pub fn save_profile_file( return Ok(()); } - let mut profiles = core.profiles.lock().unwrap(); + let profiles = core.profiles.lock(); let item = wrap_err!(profiles.get_item(&index))?; wrap_err!(item.save_file(file_data.unwrap())) } @@ -231,22 +236,14 @@ pub fn save_profile_file( /// restart the sidecar #[tauri::command] pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult { - let mut service = core.service.lock().unwrap(); - - wrap_err!(service.restart())?; - - // 更新配置 - - log_if_err!(core.activate_enhanced(false, false)); - - Ok(()) + 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] pub fn get_clash_info(core: State<'_, Core>) -> CmdResult { - let clash = core.clash.lock().unwrap(); + let clash = core.clash.lock(); Ok(clash.info.clone()) } @@ -268,14 +265,14 @@ pub fn get_sys_proxy() -> Result { /// which may not the same as system proxy #[tauri::command] pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult> { - let verge = core.verge.lock().unwrap(); + 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 { - let verge = core.verge.lock().unwrap(); + let verge = core.verge.lock(); let mut config = verge.config.clone(); if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() { @@ -296,9 +293,14 @@ pub fn patch_verge_config( let tun_mode = payload.enable_tun_mode.clone(); let system_proxy = payload.enable_system_proxy.clone(); - let mut verge = core.verge.lock().unwrap(); + 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")] @@ -310,14 +312,8 @@ pub fn patch_verge_config( } } - let profiles = core.profiles.lock().unwrap(); - - log_if_err!(core.activate_enhanced(false, false)); - } - - // change system tray - if system_proxy.is_some() || tun_mode.is_some() { - verge.update_systray(&app_handle).unwrap(); + std::mem::drop(verge); + log_if_err!(core.activate_enhanced(false)); } Ok(()) diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index caed860..fb50efe 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -2,10 +2,11 @@ use self::notice::Notice; use self::service::Service; use crate::core::enhance::PrfEnhancedResult; use crate::log_if_err; -use crate::utils::{config, dirs, help}; +use crate::utils::help; use anyhow::{bail, Result}; +use parking_lot::Mutex; use serde_yaml::Mapping; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::time::Duration; use tauri::Window; use tokio::time::sleep; @@ -53,58 +54,76 @@ impl Core { } } - pub fn init(&self) { - log_if_err!(self.restart_clash()); + 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 mut verge = self.verge.lock(); + + let hide = verge.config.enable_silent_start.clone().unwrap_or(false); + + // silent start + if hide { + let window = self.window.lock(); + window.as_ref().map(|win| { + win.hide().unwrap(); + }); + } - let clash = self.clash.lock().unwrap(); - let mut verge = self.verge.lock().unwrap(); verge.init_sysproxy(clash.info.port.clone()); log_if_err!(verge.init_launch()); + log_if_err!(verge.update_systray(&app_handle)); - // system tray - // verge.config.enable_system_proxy.map(|enable| { - // log_if_err!(app - // .tray_handle() - // .get_item("system_proxy") - // .set_selected(enable)); - // }); + drop(clash); + drop(verge); + + // 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)); + }); } /// save the window instance pub fn set_win(&self, win: Option) { - let mut window = self.window.lock().unwrap(); + let mut window = self.window.lock(); *window = win; } /// restart the clash sidecar pub fn restart_clash(&self) -> Result<()> { - { - let mut service = self.service.lock().unwrap(); - service.restart()?; - } + let mut service = self.service.lock(); + service.restart()?; + drop(service); self.activate()?; - self.activate_enhanced(false, true) + self.activate_enhanced(true) } /// handle the clash config changed pub fn patch_clash(&self, patch: Mapping) -> Result<()> { let (changed, port) = { - let mut clash = self.clash.lock().unwrap(); + 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().unwrap(); + let mut service = self.service.lock(); service.restart()?; + drop(service); self.activate()?; - self.activate_enhanced(false, true)?; + self.activate_enhanced(true)?; - let mut verge = self.verge.lock().unwrap(); + let mut verge = self.verge.lock(); verge.init_sysproxy(port); } @@ -115,13 +134,13 @@ impl Core { /// auto activate enhanced profile pub fn activate(&self) -> Result<()> { let data = { - let profiles = self.profiles.lock().unwrap(); + let profiles = self.profiles.lock(); let data = profiles.gen_activate()?; Clash::strict_filter(data) }; let (mut config, info) = { - let clash = self.clash.lock().unwrap(); + let clash = self.clash.lock(); let config = clash.config.clone(); let info = clash.info.clone(); (config, info) @@ -132,23 +151,23 @@ impl Core { } let config = { - let verge = self.verge.lock().unwrap(); + let verge = self.verge.lock(); let tun_mode = verge.config.enable_tun_mode.unwrap_or(false); Clash::_tun_mode(config, tun_mode) }; let notice = { - let window = self.window.lock().unwrap(); + let window = self.window.lock(); Notice::from(window.clone()) }; - let service = self.service.lock().unwrap(); + let service = self.service.lock(); service.set_config(info, config, notice) } /// enhanced profiles mode - pub fn activate_enhanced(&self, delay: bool, skip: bool) -> Result<()> { - let window = self.window.lock().unwrap(); + pub fn activate_enhanced(&self, skip: bool) -> Result<()> { + let window = self.window.lock(); if window.is_none() { bail!("failed to get the main window"); } @@ -158,7 +177,7 @@ impl Core { // generate the payload let payload = { - let profiles = self.profiles.lock().unwrap(); + let profiles = self.profiles.lock(); profiles.gen_enhanced(event_name.clone())? }; @@ -168,16 +187,17 @@ impl Core { return Ok(()); } + drop(window); return self.activate(); } let tun_mode = { - let verge = self.verge.lock().unwrap(); + let verge = self.verge.lock(); verge.config.enable_tun_mode.unwrap_or(false) }; let info = { - let clash = self.clash.lock().unwrap(); + let clash = self.clash.lock(); clash.info.clone() }; @@ -206,7 +226,7 @@ impl Core { let config = Clash::_tun_mode(config, tun_mode); - let service = service.lock().unwrap(); + let service = service.lock(); log_if_err!(service.set_config(info, config, notice)); log::info!("profile enhanced status {}", result.status); @@ -215,7 +235,6 @@ impl Core { result.error.map(|err| log::error!("{err}")); }); - // wait the window setup during resolve app // if delay { // sleep(Duration::from_secs(2)).await; // } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 52790e7..97b777e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -51,7 +51,7 @@ fn main() -> std::io::Result<()> { } "system_proxy" => { let core = app_handle.state::(); - let mut verge = core.verge.lock().unwrap(); + let mut verge = core.verge.lock(); let old_value = verge.config.enable_system_proxy.clone().unwrap_or(false); let new_value = !old_value; @@ -66,7 +66,7 @@ fn main() -> std::io::Result<()> { } "tun_mode" => { let core = app_handle.state::(); - let mut verge = core.verge.lock().unwrap(); + let mut verge = core.verge.lock(); let old_value = verge.config.enable_tun_mode.clone().unwrap_or(false); let new_value = !old_value; diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 5678357..0eb69c4 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,4 +1,3 @@ -use crate::log_if_err; use crate::{core::Core, utils::init, utils::server}; use tauri::{App, AppHandle, Manager}; @@ -14,33 +13,23 @@ pub fn resolve_setup(app: &App) { let core = app.state::(); core.set_win(app.get_window("main")); - core.init(); + core.init(app.app_handle()); - // clash.set_window(app.get_window("main")); - // log_if_err!(clash.run_sidecar(&profiles, true)); - - resolve_window(app, None); + resolve_window(app); } /// reset system proxy pub fn resolve_reset(app_handle: &AppHandle) { let core = app_handle.state::(); - let mut verge = core.verge.lock().unwrap(); + let mut verge = core.verge.lock(); verge.reset_sysproxy(); } /// customize the window theme -fn resolve_window(app: &App, hide: Option) { +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; From b8ad328cdea286013df532ac358a4034ea03f5aa Mon Sep 17 00:00:00 2001 From: GyDi Date: Wed, 20 Apr 2022 01:44:47 +0800 Subject: [PATCH 4/6] refactor: wip --- src-tauri/src/cmds.rs | 138 ++----- src-tauri/src/core/clash copy.rs | 520 ------------------------ src-tauri/src/core/mod.rs | 108 ++++- src-tauri/src/core/prfitem.rs | 2 +- src-tauri/src/core/profiles.rs | 32 +- src-tauri/src/core/service.rs | 2 + src-tauri/src/core/sysopt.rs | 207 ++++++++++ src-tauri/src/core/verge.rs | 263 +----------- src-tauri/src/main.rs | 35 +- src-tauri/src/utils/help.rs | 35 ++ src-tauri/src/utils/resolve.rs | 4 +- src/components/layout/update-dialog.tsx | 4 +- src/services/cmds.ts | 8 +- 13 files changed, 419 insertions(+), 939 deletions(-) delete mode 100644 src-tauri/src/core/clash copy.rs create mode 100644 src-tauri/src/core/sysopt.rs diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 4d1e7a8..0b6604d 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -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 = Result; @@ -17,11 +16,10 @@ pub fn get_profiles(core: State<'_, Core>) -> CmdResult { 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>, 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>, core: State<'_, Core>) - pub fn change_profile_valid(valid: Option>, core: State) -> 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 { - 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> { - 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 { 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 { + 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> { + let sysopt = core.sysopt.lock(); + wrap_err!(sysopt.get_sysproxy()) +} + /// open app config dir #[tauri::command] pub fn open_app_dir() -> Result<(), String> { diff --git a/src-tauri/src/core/clash copy.rs b/src-tauri/src/core/clash copy.rs deleted file mode 100644 index 463ab14..0000000 --- a/src-tauri/src/core/clash copy.rs +++ /dev/null @@ -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, - - /// same as `external-controller` - pub server: Option, - - /// clash secret - pub secret: Option, -} - -pub struct Clash { - /// maintain the clash config - pub config: Mapping, - - /// some info - pub info: ClashInfo, - - /// clash sidecar - pub sidecar: Option, - - /// save the main window - pub window: Option, -} - -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::(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) { - 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) -> 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}"); - } - } -} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index fb50efe..c15179a 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -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>, + pub sysopt: Arc>, + pub window: Arc>>, } @@ -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(()) diff --git a/src-tauri/src/core/prfitem.rs b/src-tauri/src/core/prfitem.rs index 3d99906..48f5a6c 100644 --- a/src-tauri/src/core/prfitem.rs +++ b/src-tauri/src/core/prfitem.rs @@ -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 diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs index af37f9c..fe00c50 100644 --- a/src-tauri/src/core/profiles.rs +++ b/src-tauri/src/core/profiles.rs @@ -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 { 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() } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index b1eb42e..468037e 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -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"); diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs new file mode 100644 index 0000000..8a6d7a8 --- /dev/null +++ b/src-tauri/src/core/sysopt.rs @@ -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, + + /// record the original system proxy + /// recover it when exit + old_sysproxy: Option, + + /// helps to auto launch the app + auto_launch: Option, + + /// record whether the guard async is running or not + guard_state: Arc>, +} + +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, 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, bypass: Option) -> 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> { + Ok(self.cur_sysproxy.clone()) + } + + /// init the auto launch + pub fn init_launch(&mut self, enable: Option) -> 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) -> 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; + }); + } +} diff --git a/src-tauri/src/core/verge.rs b/src-tauri/src/core/verge.rs index eaa789c..f96b40f 100644 --- a/src-tauri/src/core/verge.rs +++ b/src-tauri/src/core/verge.rs @@ -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, - - /// record the original system proxy - /// recover it when exit - old_sysproxy: Option, - - /// helps to auto launch the app - auto_launch: Option, - - /// record whether the guard async is running or not - guard_state: Arc>, } 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) { - 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>) { - 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; - }); - } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 97b777e..9e0e0a8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -51,33 +51,33 @@ fn main() -> std::io::Result<()> { } "system_proxy" => { let core = app_handle.state::(); - 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::(); - 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::(); @@ -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, diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 0613407..0b7dacb 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -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(target: &str, key: &str) -> Option { } } +/// 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) => { diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 0eb69c4..a7d6906 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -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::(); - let mut verge = core.verge.lock(); + let mut sysopt = core.sysopt.lock(); - verge.reset_sysproxy(); + sysopt.reset_sysproxy(); } /// customize the window theme diff --git a/src/components/layout/update-dialog.tsx b/src/components/layout/update-dialog.tsx index 2d2c3f4..f35de41 100644 --- a/src/components/layout/update-dialog.tsx +++ b/src/components/layout/update-dialog.tsx @@ -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) { diff --git a/src/services/cmds.ts b/src/services/cmds.ts index bc4555e..4cc2559 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -6,10 +6,6 @@ export async function getProfiles() { return invoke("get_profiles"); } -export async function syncProfiles() { - return invoke("sync_profiles"); -} - export async function enhanceProfiles() { return invoke("enhance_profiles"); } @@ -94,8 +90,8 @@ export async function restartSidecar() { return invoke("restart_sidecar"); } -export async function killSidecars() { - return invoke("kill_sidecars"); +export async function killSidecar() { + return invoke("kill_sidecar"); } export async function openAppDir() { From 5f7a1fa5cd22497d5c769c74bc6495f331394e08 Mon Sep 17 00:00:00 2001 From: GyDi Date: Wed, 20 Apr 2022 11:17:54 +0800 Subject: [PATCH 5/6] refactor: verge --- src-tauri/src/cmds.rs | 8 ++--- src-tauri/src/core/mod.rs | 14 ++++----- src-tauri/src/core/sysopt.rs | 12 ++++---- src-tauri/src/core/verge.rs | 58 +++++++++++------------------------- src-tauri/src/main.rs | 14 ++++----- 5 files changed, 42 insertions(+), 64 deletions(-) diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 0b6604d..2bef9e9 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,5 +1,5 @@ use crate::{ - core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, VergeConfig}, + core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, Verge}, utils::{dirs, help, sysopt::SysProxyConfig}, }; use crate::{log_if_err, ret_err, wrap_err}; @@ -211,9 +211,9 @@ pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult /// get the verge config #[tauri::command] -pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { +pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { let verge = core.verge.lock(); - let config = verge.config.clone(); + let config = verge.clone(); Ok(config) } @@ -222,7 +222,7 @@ pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { /// this command only save the config and not responsible for other things #[tauri::command] pub fn patch_verge_config( - payload: VergeConfig, + payload: Verge, app_handle: tauri::AppHandle, core: State<'_, Core>, ) -> Result<(), String> { diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index c15179a..3867b2d 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -70,8 +70,8 @@ impl Core { let clash = self.clash.lock(); let verge = self.verge.lock(); - let silent_start = verge.config.enable_silent_start.clone(); - let auto_launch = verge.config.enable_auto_launch.clone(); + let silent_start = verge.enable_silent_start.clone(); + let auto_launch = verge.enable_auto_launch.clone(); // silent start if silent_start.unwrap_or(false) { @@ -143,7 +143,7 @@ impl Core { } /// Patch Verge - pub fn patch_verge(&self, patch: VergeConfig, app_handle: &AppHandle) -> Result<()> { + 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(); @@ -195,8 +195,8 @@ impl Core { 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(); + let system_proxy = verge.enable_system_proxy.as_ref(); + let tun_mode = verge.enable_tun_mode.as_ref(); tray .get_item("system_proxy") @@ -235,7 +235,7 @@ impl Core { let config = { let verge = self.verge.lock(); - let tun_mode = verge.config.enable_tun_mode.unwrap_or(false); + let tun_mode = verge.enable_tun_mode.unwrap_or(false); Clash::_tun_mode(config, tun_mode) }; @@ -277,7 +277,7 @@ impl Core { let tun_mode = { let verge = self.verge.lock(); - verge.config.enable_tun_mode.unwrap_or(false) + verge.enable_tun_mode.unwrap_or(false) }; let info = { diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 8a6d7a8..83957ba 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -34,14 +34,14 @@ impl Sysopt { /// init the sysproxy pub fn init_sysproxy(&mut self, port: Option, verge: &Verge) { if let Some(port) = port { - let enable = verge.config.enable_system_proxy.clone().unwrap_or(false); + 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.config.system_proxy_bypass.clone(); + let bypass = verge.system_proxy_bypass.clone(); let sysproxy = SysProxyConfig::new(enable, port, bypass); if enable { @@ -173,9 +173,9 @@ impl Sysopt { 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); + 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; @@ -191,7 +191,7 @@ impl Sysopt { match &clash.info.port { Some(port) => { - let bypass = verge.config.system_proxy_bypass.clone(); + let bypass = verge.system_proxy_bypass.clone(); let sysproxy = SysProxyConfig::new(true, port.clone(), bypass); log_if_err!(sysproxy.set_sys()); diff --git a/src-tauri/src/core/verge.rs b/src-tauri/src/core/verge.rs index f96b40f..1826590 100644 --- a/src-tauri/src/core/verge.rs +++ b/src-tauri/src/core/verge.rs @@ -1,11 +1,10 @@ -use crate::log_if_err; use crate::utils::{config, dirs}; -use anyhow::{bail, Result}; +use anyhow::Result; use serde::{Deserialize, Serialize}; /// ### `verge.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] -pub struct VergeConfig { +pub struct Verge { // i18n pub language: Option, @@ -60,9 +59,9 @@ pub struct VergeTheme { pub css_injection: Option, } -impl VergeConfig { +impl Verge { pub fn new() -> Self { - config::read_yaml::(dirs::verge_path()) + config::read_yaml::(dirs::verge_path()) } /// Save Verge App Config @@ -73,75 +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, -} - -impl Default for Verge { - fn default() -> Self { - Verge::new() - } -} - -impl Verge { - pub fn new() -> Self { - Verge { - config: VergeConfig::new(), - } - } /// patch verge config /// only save to file - pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> { + 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; + 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.config.traffic_graph = patch.traffic_graph; + self.traffic_graph = patch.traffic_graph; } // system setting if patch.enable_silent_start.is_some() { - self.config.enable_silent_start = patch.enable_silent_start; + self.enable_silent_start = patch.enable_silent_start; } if patch.enable_auto_launch.is_some() { - self.config.enable_auto_launch = patch.enable_auto_launch; + self.enable_auto_launch = patch.enable_auto_launch; } // proxy if patch.enable_system_proxy.is_some() { - self.config.enable_system_proxy = patch.enable_system_proxy; + self.enable_system_proxy = patch.enable_system_proxy; } if patch.system_proxy_bypass.is_some() { - self.config.system_proxy_bypass = patch.system_proxy_bypass; + self.system_proxy_bypass = patch.system_proxy_bypass; } 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; } // tun mode if patch.enable_tun_mode.is_some() { - self.config.enable_tun_mode = patch.enable_tun_mode; + self.enable_tun_mode = patch.enable_tun_mode; } - self.config.save_file() + self.save_file() } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9e0e0a8..906c5f0 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,7 +8,7 @@ mod core; mod utils; use crate::{ - core::VergeConfig, + core::Verge, utils::{resolve, server}, }; use tauri::{ @@ -54,12 +54,12 @@ fn main() -> std::io::Result<()> { let new_value = { let verge = core.verge.lock(); - !verge.config.enable_system_proxy.clone().unwrap_or(false) + !verge.enable_system_proxy.clone().unwrap_or(false) }; - let patch = VergeConfig { + let patch = Verge { enable_system_proxy: Some(new_value), - ..VergeConfig::default() + ..Verge::default() }; crate::log_if_err!(core.patch_verge(patch, app_handle)); @@ -69,12 +69,12 @@ fn main() -> std::io::Result<()> { let new_value = { let verge = core.verge.lock(); - !verge.config.enable_tun_mode.clone().unwrap_or(false) + !verge.enable_tun_mode.clone().unwrap_or(false) }; - let patch = VergeConfig { + let patch = Verge { enable_tun_mode: Some(new_value), - ..VergeConfig::default() + ..Verge::default() }; crate::log_if_err!(core.patch_verge(patch, app_handle)); From 4de944b41efdc1a2cfcb740b091aac5bb3d0795a Mon Sep 17 00:00:00 2001 From: GyDi Date: Wed, 20 Apr 2022 20:37:16 +0800 Subject: [PATCH 6/6] feat: supports cron update profiles --- src-tauri/src/cmds.rs | 37 +--------- src-tauri/src/core/mod.rs | 57 +++++++++++++++ src-tauri/src/core/prfitem.rs | 11 --- src-tauri/src/core/profiles.rs | 5 ++ src-tauri/src/core/timer.rs | 129 ++++++++++++++++++++++++++++++++- 5 files changed, 190 insertions(+), 49 deletions(-) diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 2bef9e9..9d4b6b3 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -58,38 +58,7 @@ pub async fn update_profile( option: Option, core: State<'_, Core>, ) -> CmdResult { - let (url, opt) = { - // must release the lock here - let profiles = core.profiles.lock(); - 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 = core.profiles.lock(); - wrap_err!(profiles.update_item(index.clone(), item))?; - - // reactivate the profile - if Some(index) == profiles.get_current() { - drop(profiles); - log_if_err!(core.activate_enhanced(false)); - } - - Ok(()) + wrap_err!(Core::update_profile_item(core.inner().clone(), index, option).await) } /// change the current profile @@ -213,9 +182,7 @@ pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult #[tauri::command] pub fn get_verge_config(core: State<'_, Core>) -> CmdResult { let verge = core.verge.lock(); - let config = verge.clone(); - - Ok(config) + Ok(verge.clone()) } /// patch the verge config diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 3867b2d..37daa47 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -1,6 +1,7 @@ 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}; @@ -39,6 +40,8 @@ pub struct Core { pub sysopt: Arc>, + pub timer: Arc>, + pub window: Arc>>, } @@ -55,6 +58,7 @@ impl Core { 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)), } } @@ -98,6 +102,11 @@ impl Core { 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 @@ -324,3 +333,51 @@ impl Core { Ok(()) } } + +impl Core { + /// Static function + /// update profile item + pub async fn update_profile_item( + core: Core, + uid: String, + option: Option, + ) -> 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(()) + } +} diff --git a/src-tauri/src/core/prfitem.rs b/src-tauri/src/core/prfitem.rs index 48f5a6c..333b5bd 100644 --- a/src-tauri/src/core/prfitem.rs +++ b/src-tauri/src/core/prfitem.rs @@ -102,17 +102,6 @@ impl PrfOption { return one; } - - pub fn diff_update_interval(one: Option<&Self>, other: Option<&Self>) -> bool { - if one.is_some() && other.is_some() { - let one = one.unwrap(); - let other = other.unwrap(); - - return one.update_interval == other.update_interval; - } - - return false; - } } impl Default for PrfItem { diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs index fe00c50..60f1a9a 100644 --- a/src-tauri/src/core/profiles.rs +++ b/src-tauri/src/core/profiles.rs @@ -100,6 +100,11 @@ impl Profiles { self.valid = valid; } + /// get items ref + pub fn get_items(&self) -> Option<&Vec> { + self.items.as_ref() + } + /// find the item by the uid pub fn get_item(&self, uid: &String) -> Result<&PrfItem> { if self.items.is_some() { diff --git a/src-tauri/src/core/timer.rs b/src-tauri/src/core/timer.rs index 2e94a6f..e857e9d 100644 --- a/src-tauri/src/core/timer.rs +++ b/src-tauri/src/core/timer.rs @@ -1,12 +1,19 @@ -use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, Task, TaskBuilder}; +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, + timer_map: HashMap, - timer_count: u64, + timer_count: TaskID, + + core: Option, } impl Timer { @@ -15,6 +22,122 @@ impl Timer { delay_timer: DelayTimerBuilder::default().build(), timer_map: HashMap::new(), timer_count: 1, + core: None, } } + + pub fn set_core(&mut self, core: Core) { + self.core = Some(core); + } + + /// Correctly update all cron tasks + pub fn refresh(&mut self) -> Result<()> { + if self.core.is_none() { + bail!("unhandle error for core is none"); + } + + let diff_map = self.gen_diff(); + + for (uid, diff) in diff_map.into_iter() { + 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 { + 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 { + 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), }