添加win 系统托盘显示代理节点
且gui和托盘刷新机制
This commit is contained in:
204
src-tauri/Cargo.lock
generated
204
src-tauri/Cargo.lock
generated
@@ -151,7 +151,7 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation 0.3.1",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.59.0",
|
||||
"wl-clipboard-rs",
|
||||
@@ -964,6 +964,12 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.30"
|
||||
@@ -1112,6 +1118,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"hex",
|
||||
"hmac",
|
||||
"isahc",
|
||||
"kode-bridge",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -1120,7 +1127,7 @@ dependencies = [
|
||||
"network-interface",
|
||||
"once_cell",
|
||||
"open",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"percent-encoding",
|
||||
"port_scanner",
|
||||
"regex",
|
||||
@@ -1544,6 +1551,37 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl"
|
||||
version = "0.4.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc"
|
||||
dependencies = [
|
||||
"curl-sys",
|
||||
"libc",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"socket2 0.6.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl-sys"
|
||||
version = "0.4.83+curl-8.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libnghttp2-sys",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
@@ -1589,7 +1627,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
"parking_lot_core 0.9.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1603,7 +1641,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
"parking_lot_core 0.9.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3537,6 +3575,34 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isahc"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
|
||||
dependencies = [
|
||||
"async-channel 1.9.0",
|
||||
"castaway",
|
||||
"crossbeam-utils",
|
||||
"curl",
|
||||
"curl-sys",
|
||||
"encoding_rs",
|
||||
"event-listener 2.5.3",
|
||||
"futures-lite 1.13.0",
|
||||
"http 0.2.12",
|
||||
"log",
|
||||
"mime",
|
||||
"once_cell",
|
||||
"parking_lot 0.11.2",
|
||||
"polling 2.8.0",
|
||||
"slab",
|
||||
"sluice",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"url",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
@@ -3676,7 +3742,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"interprocess",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
@@ -3777,6 +3843,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libnghttp2-sys"
|
||||
version = "0.1.11+1.64.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b6c24e48a7167cffa7119da39d577fa482e66c688a4aac016bee862e1a713c4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.6"
|
||||
@@ -3785,7 +3861,7 @@ checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.5.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3797,6 +3873,18 @@ dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
@@ -3886,7 +3974,7 @@ dependencies = [
|
||||
"log",
|
||||
"log-mdc",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde-value",
|
||||
@@ -4805,6 +4893,17 @@ version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
@@ -4812,7 +4911,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
"parking_lot_core 0.9.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4823,7 +4936,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.5.16",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
@@ -4862,9 +4975,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
@@ -5639,6 +5752,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.16"
|
||||
@@ -5692,9 +5814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -6507,6 +6629,17 @@ version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
|
||||
[[package]]
|
||||
name = "sluice"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
|
||||
dependencies = [
|
||||
"async-channel 1.9.0",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -6576,7 +6709,7 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core 0.2.2",
|
||||
"raw-window-handle",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.5.16",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -6633,7 +6766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"phf_shared 0.11.3",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
@@ -6822,7 +6955,7 @@ dependencies = [
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"raw-window-handle",
|
||||
"scopeguard",
|
||||
"tao-macros",
|
||||
@@ -6870,9 +7003,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.8.2"
|
||||
version = "2.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a54629607ea3084a8b455c1ebe888cbdfc4de02fa5edb2e40db0dc97091007e3"
|
||||
checksum = "5d545ccf7b60dcd44e07c6fb5aeb09140966f0aabd5d2aa14a6821df7bc99348"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7034,11 +7167,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.1"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fec67f32d7a06d80bd3dc009fdb678c35a66116d9cb8cd2bb32e406c2b5bbd2"
|
||||
checksum = "6d430110d4ee102a9b673d3c03ff48098c80fe8ca71ba1ff52d8a5919538a1a6"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -7136,13 +7270,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-notification"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe06ed89cff6d0ec06ff4f544fb961e4718348a33309f56ccb2086e77bc9116"
|
||||
checksum = "d2fbc86b929b5376ab84b25c060f966d146b2fbd59b6af8264027b343c82c219"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify-rust",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -7165,9 +7299,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25"
|
||||
checksum = "54777d0c0d8add34eea3ced84378619ef5b97996bd967d3038c668feefd21071"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log",
|
||||
@@ -7258,9 +7392,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb0f10f831f75832ac74d14d98f701868f9a8adccef2c249b466cf70b607db9"
|
||||
checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.3.1",
|
||||
@@ -7598,7 +7732,7 @@ dependencies = [
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"parking_lot 0.12.4",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
@@ -7981,6 +8115,16 @@ dependencies = [
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-futures"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
|
||||
dependencies = [
|
||||
"pin-project",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
@@ -9290,9 +9434,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.53.1"
|
||||
version = "0.53.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5698e50a589268aec06d2219f48b143222f7b5ad9aa690118b8dce0a8dcac574"
|
||||
checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.1",
|
||||
|
||||
@@ -33,7 +33,7 @@ once_cell = "1.21.3"
|
||||
port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.6"
|
||||
parking_lot = "0.12.4"
|
||||
percent-encoding = "2.3.1"
|
||||
percent-encoding = "2.3.2"
|
||||
tokio = { version = "1.47.1", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
@@ -42,9 +42,9 @@ tokio = { version = "1.47.1", features = [
|
||||
] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
reqwest = { version = "0.12.23", features = ["json", "rustls-tls", "cookies"] }
|
||||
regex = "1.11.1"
|
||||
regex = "1.11.2"
|
||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs" }
|
||||
tauri = { version = "2.8.2", features = [
|
||||
tauri = { version = "2.8.4", features = [
|
||||
"protocol-asset",
|
||||
"devtools",
|
||||
"tray-icon",
|
||||
@@ -52,12 +52,12 @@ tauri = { version = "2.8.2", features = [
|
||||
"image-png",
|
||||
] }
|
||||
network-interface = { version = "2.0.3", features = ["serde"] }
|
||||
tauri-plugin-shell = "2.3.0"
|
||||
tauri-plugin-shell = "2.3.1"
|
||||
tauri-plugin-dialog = "2.3.3"
|
||||
tauri-plugin-fs = "2.4.2"
|
||||
tauri-plugin-process = "2.3.0"
|
||||
tauri-plugin-clipboard-manager = "2.3.0"
|
||||
tauri-plugin-deep-link = "2.4.1"
|
||||
tauri-plugin-deep-link = "2.4.2"
|
||||
tauri-plugin-devtools = "2.0.1"
|
||||
tauri-plugin-window-state = "2.4.0"
|
||||
zip = "=4.2.0"
|
||||
@@ -75,9 +75,10 @@ hex = "0.4.3"
|
||||
scopeguard = "1.2.0"
|
||||
kode-bridge = "0.2.1-rc1"
|
||||
dashmap = "6.1.0"
|
||||
tauri-plugin-notification = "2.3.0"
|
||||
tauri-plugin-notification = "2.3.1"
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
tokio-stream = "0.1.17"
|
||||
isahc = { version = "1.7.2", features = ["parking_lot"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.2.0"
|
||||
|
||||
@@ -2,10 +2,10 @@ use super::CmdResult;
|
||||
use crate::{
|
||||
config::{
|
||||
profiles::{
|
||||
profiles_append_item_safe, profiles_delete_item_safe, profiles_patch_item_safe,
|
||||
profiles_reorder_safe, profiles_save_file_safe,
|
||||
profiles_append_item_with_filedata_safe, profiles_delete_item_safe,
|
||||
profiles_patch_item_safe, profiles_reorder_safe, profiles_save_file_safe,
|
||||
},
|
||||
Config, IProfiles, PrfItem, PrfOption,
|
||||
profiles_append_item_safe, Config, IProfiles, PrfItem, PrfOption,
|
||||
},
|
||||
core::{handle, timer::Timer, tray::Tray, CoreManager},
|
||||
feat, logging,
|
||||
@@ -225,8 +225,8 @@ pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||
/// 创建新的profile
|
||||
/// 创建一个新的配置文件
|
||||
#[tauri::command]
|
||||
pub async fn create_profile(item: PrfItem, _file_data: Option<String>) -> CmdResult {
|
||||
match profiles_append_item_safe(item).await {
|
||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||
match profiles_append_item_with_filedata_safe(item, file_data).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => match err.to_string().as_str() {
|
||||
"the file already exists" => Err("the file already exists".into()),
|
||||
@@ -253,10 +253,6 @@ pub async fn delete_profile(index: String) -> CmdResult {
|
||||
// 使用Send-safe helper函数
|
||||
let should_update = wrap_err!(profiles_delete_item_safe(index).await)?;
|
||||
|
||||
// 删除后自动清理冗余文件
|
||||
let profiles = Config::profiles().await;
|
||||
let _ = profiles.latest_ref().auto_cleanup();
|
||||
|
||||
if should_update {
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
|
||||
use crate::{
|
||||
config::{profiles::profiles_append_item_safe, PrfItem},
|
||||
config::{profiles_append_item_safe, PrfItem},
|
||||
core::{handle, CoreManager},
|
||||
enhance, logging,
|
||||
process::AsyncHandler,
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::utils::{
|
||||
tmpl,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Mapping;
|
||||
use std::{fs, time::Duration};
|
||||
@@ -266,7 +265,7 @@ impl PrfItem {
|
||||
};
|
||||
|
||||
// 使用网络管理器发送请求
|
||||
let resp = match NetworkManager::global()
|
||||
let resp = match NetworkManager::new()
|
||||
.get_with_interrupt(
|
||||
url,
|
||||
proxy_type,
|
||||
@@ -284,7 +283,7 @@ impl PrfItem {
|
||||
};
|
||||
|
||||
let status_code = resp.status();
|
||||
if !StatusCode::is_success(&status_code) {
|
||||
if !status_code.is_success() {
|
||||
bail!("failed to fetch remote profile with status {status_code}")
|
||||
}
|
||||
|
||||
@@ -350,7 +349,7 @@ impl PrfItem {
|
||||
let uid = help::get_uid("R");
|
||||
let file = format!("{uid}.yaml");
|
||||
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||
let data = resp.text_with_charset("utf-8").await?;
|
||||
let data = resp.text_with_charset()?;
|
||||
|
||||
// process the charset "UTF-8 with BOM"
|
||||
let data = data.trim_start_matches('\u{feff}');
|
||||
|
||||
@@ -735,33 +735,30 @@ impl IProfiles {
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn auto_cleanup(&self) -> Result<()> {
|
||||
match self.cleanup_orphaned_files() {
|
||||
Ok(result) => {
|
||||
if !result.deleted_files.is_empty() {
|
||||
log::info!(
|
||||
target: "app",
|
||||
"自动清理完成,删除了 {} 个冗余文件",
|
||||
result.deleted_files.len()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(target: "app", "自动清理失败: {e}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊的Send-safe helper函数,完全避免跨await持有guard
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn profiles_append_item_with_filedata_safe(
|
||||
item: PrfItem,
|
||||
file_data: Option<String>,
|
||||
) -> Result<()> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let item = PrfItem::from(item, file_data).await?;
|
||||
let profiles = Config::profiles().await;
|
||||
let mut profiles_guard = profiles.data_mut();
|
||||
profiles_guard.append_item(item).await
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Task join error: {}", e))?
|
||||
}
|
||||
|
||||
pub async fn profiles_append_item_safe(item: PrfItem) -> Result<()> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let profiles = Config::profiles().await;
|
||||
let mut profiles_guard = profiles.data_mut();
|
||||
profiles_guard.append_item(item).await
|
||||
@@ -773,7 +770,7 @@ pub async fn profiles_append_item_safe(item: PrfItem) -> Result<()> {
|
||||
|
||||
pub async fn profiles_patch_item_safe(index: String, item: PrfItem) -> Result<()> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let profiles = Config::profiles().await;
|
||||
let mut profiles_guard = profiles.data_mut();
|
||||
profiles_guard.patch_item(index, item).await
|
||||
@@ -785,7 +782,7 @@ pub async fn profiles_patch_item_safe(index: String, item: PrfItem) -> Result<()
|
||||
|
||||
pub async fn profiles_delete_item_safe(index: String) -> Result<bool> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let profiles = Config::profiles().await;
|
||||
let mut profiles_guard = profiles.data_mut();
|
||||
profiles_guard.delete_item(index).await
|
||||
@@ -797,7 +794,7 @@ pub async fn profiles_delete_item_safe(index: String) -> Result<bool> {
|
||||
|
||||
pub async fn profiles_reorder_safe(active_id: String, over_id: String) -> Result<()> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let profiles = Config::profiles().await;
|
||||
let mut profiles_guard = profiles.data_mut();
|
||||
profiles_guard.reorder(active_id, over_id).await
|
||||
@@ -809,7 +806,7 @@ pub async fn profiles_reorder_safe(active_id: String, over_id: String) -> Result
|
||||
|
||||
pub async fn profiles_save_file_safe() -> Result<()> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_guard = profiles.data_mut();
|
||||
profiles_guard.save_file().await
|
||||
@@ -821,7 +818,7 @@ pub async fn profiles_save_file_safe() -> Result<()> {
|
||||
|
||||
pub async fn profiles_draft_update_item_safe(index: String, item: PrfItem) -> Result<()> {
|
||||
AsyncHandler::spawn_blocking(move || {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
AsyncHandler::handle().block_on(async {
|
||||
let profiles = Config::profiles().await;
|
||||
let mut profiles_guard = profiles.draft_mut();
|
||||
profiles_guard.update_item(index, item).await
|
||||
|
||||
@@ -522,3 +522,54 @@ impl Handle {
|
||||
*self.is_exiting.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl Handle {
|
||||
pub fn set_activation_policy(&self, policy: tauri::ActivationPolicy) -> Result<(), String> {
|
||||
let app_handle = self.app_handle();
|
||||
if let Some(app_handle) = app_handle.as_ref() {
|
||||
app_handle
|
||||
.set_activation_policy(policy)
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("AppHandle not initialized".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_activation_policy_regular(&self) {
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Regular) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set regular activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_activation_policy_accessory(&self) {
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Accessory) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set accessory activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_activation_policy_prohibited(&self) {
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Prohibited) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set prohibited activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::{
|
||||
utils::{logging::Type, resolve},
|
||||
};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use tauri::Manager;
|
||||
|
||||
/// Restart the Clash core
|
||||
pub async fn restart_clash_core() {
|
||||
@@ -31,7 +30,7 @@ pub async fn restart_app() {
|
||||
handle::Handle::global()
|
||||
.app_handle()
|
||||
.map(|app_handle| {
|
||||
tauri::process::restart(&app_handle.env());
|
||||
app_handle.restart();
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
logging_error!(
|
||||
@@ -123,7 +122,7 @@ pub async fn test_delay(url: String) -> anyhow::Result<u32> {
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let response = NetworkManager::global()
|
||||
let response = NetworkManager::new()
|
||||
.get_with_interrupt(&url, proxy_type, Some(10), user_agent, false)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::AppHandleManager;
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{handle, sysopt, CoreManager},
|
||||
@@ -259,5 +257,5 @@ pub async fn hide() {
|
||||
let _ = window.hide();
|
||||
}
|
||||
}
|
||||
AppHandleManager::global().set_activation_policy_accessory();
|
||||
handle::Handle::global().set_activation_policy_accessory();
|
||||
}
|
||||
|
||||
@@ -26,120 +26,10 @@ use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tokio::time::{timeout, Duration};
|
||||
use utils::logging::Type;
|
||||
|
||||
/// A global singleton handle to the application.
|
||||
pub struct AppHandleManager {
|
||||
handle: Mutex<Option<AppHandle>>,
|
||||
}
|
||||
|
||||
impl AppHandleManager {
|
||||
/// Create a new AppHandleManager instance
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
handle: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the app handle manager with an app handle.
|
||||
pub fn init(&self, handle: AppHandle) {
|
||||
let mut app_handle = self.handle.lock();
|
||||
if app_handle.is_none() {
|
||||
*app_handle = Some(handle);
|
||||
logging!(
|
||||
info,
|
||||
Type::Setup,
|
||||
true,
|
||||
"AppHandleManager initialized with handle"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the app handle if it has been initialized.
|
||||
fn get(&self) -> Option<AppHandle> {
|
||||
self.handle.lock().clone()
|
||||
}
|
||||
|
||||
/// Get the app handle, panics if it hasn't been initialized.
|
||||
pub fn get_handle(&self) -> AppHandle {
|
||||
if let Some(handle) = self.get() {
|
||||
handle
|
||||
} else {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
"AppHandle not initialized - ensure init() was called first"
|
||||
);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the app handle has been initialized.
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.handle.lock().is_some()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_activation_policy(&self, policy: tauri::ActivationPolicy) -> Result<(), String> {
|
||||
let app_handle = self.handle.lock();
|
||||
if let Some(app_handle) = app_handle.as_ref() {
|
||||
app_handle
|
||||
.set_activation_policy(policy)
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("AppHandle not initialized".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_activation_policy_regular(&self) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Regular) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set regular activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_activation_policy_accessory(&self) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Accessory) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set accessory activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_activation_policy_prohibited(&self) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Err(e) = self.set_activation_policy(tauri::ActivationPolicy::Prohibited) {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Setup,
|
||||
true,
|
||||
"Failed to set prohibited activation policy: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use unified singleton macro
|
||||
singleton_with_logging!(AppHandleManager, INSTANCE, "AppHandleManager");
|
||||
|
||||
/// Application initialization helper functions
|
||||
mod app_init {
|
||||
use crate::core::handle;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Initialize singleton monitoring for other instances
|
||||
@@ -150,7 +40,7 @@ mod app_init {
|
||||
Ok(result) => {
|
||||
if result.is_err() {
|
||||
logging!(info, Type::Setup, true, "检测到已有应用实例运行");
|
||||
if let Some(app_handle) = AppHandleManager::global().get() {
|
||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||
app_handle.exit(0);
|
||||
} else {
|
||||
std::process::exit(0);
|
||||
@@ -271,9 +161,6 @@ mod app_init {
|
||||
|
||||
/// Initialize core components synchronously
|
||||
pub async fn init_core_sync(app_handle: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化核心句柄...");
|
||||
core::handle::Handle::global().init(app_handle.clone());
|
||||
|
||||
@@ -424,7 +311,7 @@ pub fn run() {
|
||||
app_init::init_singleton_check();
|
||||
|
||||
// Initialize network manager
|
||||
utils::network::NetworkManager::global().init();
|
||||
utils::network::NetworkManager::new().init();
|
||||
|
||||
// Initialize portable flag
|
||||
let _ = utils::dirs::init_portable_flag();
|
||||
@@ -511,12 +398,14 @@ pub fn run() {
|
||||
|
||||
/// Event handling helper functions
|
||||
mod event_handlers {
|
||||
use crate::core::handle;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Handle application ready/resumed events
|
||||
pub fn handle_ready_resumed(app_handle: &AppHandle) {
|
||||
logging!(info, Type::System, true, "应用就绪或恢复");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
handle::Handle::global().init(app_handle.clone());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
@@ -538,11 +427,11 @@ pub fn run() {
|
||||
has_visible_windows
|
||||
);
|
||||
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
handle::Handle::global().init(app_handle.clone());
|
||||
|
||||
if !has_visible_windows {
|
||||
// 当没有可见窗口时,设置为 regular 模式并显示主窗口
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
|
||||
logging!(info, Type::System, true, "没有可见窗口,尝试显示主窗口");
|
||||
|
||||
@@ -562,7 +451,7 @@ pub fn run() {
|
||||
/// Handle window close requests
|
||||
pub fn handle_window_close(api: &tauri::WindowEvent) {
|
||||
#[cfg(target_os = "macos")]
|
||||
AppHandleManager::global().set_activation_policy_accessory();
|
||||
handle::Handle::global().set_activation_policy_accessory();
|
||||
|
||||
if core::handle::Handle::global().is_exiting() {
|
||||
return;
|
||||
|
||||
@@ -9,8 +9,6 @@ use crate::{
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::logging_error;
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::AppHandleManager;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use delay_timer::prelude::TaskBuilder;
|
||||
@@ -173,7 +171,7 @@ pub async fn entry_lightweight_mode() {
|
||||
let _ = webview.destroy();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
AppHandleManager::global().set_activation_policy_accessory();
|
||||
handle::Handle::global().set_activation_policy_accessory();
|
||||
}
|
||||
set_lightweight_mode(true).await;
|
||||
let _ = cancel_light_weight_timer();
|
||||
@@ -213,7 +211,7 @@ pub async fn exit_lightweight_mode() {
|
||||
|
||||
// macOS激活策略
|
||||
#[cfg(target_os = "macos")]
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
|
||||
// 重置UI就绪状态
|
||||
crate::utils::resolve::reset_ui_ready();
|
||||
|
||||
@@ -8,7 +8,6 @@ use tauri::{async_runtime, async_runtime::JoinHandle};
|
||||
pub struct AsyncHandler;
|
||||
|
||||
impl AsyncHandler {
|
||||
#[allow(dead_code)]
|
||||
pub fn handle() -> async_runtime::RuntimeHandle {
|
||||
async_runtime::handle()
|
||||
}
|
||||
|
||||
@@ -1,143 +1,97 @@
|
||||
use anyhow::Result;
|
||||
use parking_lot::Mutex;
|
||||
use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder, Response};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Once,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
use isahc::http::{
|
||||
header::{HeaderMap, HeaderValue, USER_AGENT},
|
||||
StatusCode, Uri,
|
||||
};
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
use isahc::prelude::*;
|
||||
use isahc::{config::SslOption, HttpClient};
|
||||
use std::sync::Once;
|
||||
use std::time::{Duration, Instant};
|
||||
use sysproxy::Sysproxy;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::timeout;
|
||||
|
||||
use crate::utils::logging::Type;
|
||||
use crate::{config::Config, logging, process::AsyncHandler, singleton_lazy};
|
||||
use crate::config::Config;
|
||||
|
||||
// HTTP2 相关
|
||||
const H2_CONNECTION_WINDOW_SIZE: u32 = 1024 * 1024;
|
||||
const H2_STREAM_WINDOW_SIZE: u32 = 1024 * 1024;
|
||||
const H2_MAX_FRAME_SIZE: u32 = 16 * 1024;
|
||||
const H2_KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(5);
|
||||
const H2_KEEP_ALIVE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const POOL_MAX_IDLE_PER_HOST: usize = 5;
|
||||
const POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
|
||||
/// 网络管理器
|
||||
pub struct NetworkManager {
|
||||
runtime: Arc<Runtime>,
|
||||
self_proxy_client: Mutex<Option<Client>>,
|
||||
system_proxy_client: Mutex<Option<Client>>,
|
||||
no_proxy_client: Mutex<Option<Client>>,
|
||||
init: Once,
|
||||
last_connection_error: Mutex<Option<(Instant, String)>>,
|
||||
connection_error_count: AtomicUsize,
|
||||
#[derive(Debug)]
|
||||
pub struct HttpResponse {
|
||||
status: StatusCode,
|
||||
headers: HeaderMap,
|
||||
body: String,
|
||||
}
|
||||
|
||||
// Use singleton_lazy macro to replace lazy_static!
|
||||
singleton_lazy!(NetworkManager, NETWORK_MANAGER, NetworkManager::new);
|
||||
impl HttpResponse {
|
||||
pub fn new(status: StatusCode, headers: HeaderMap, body: String) -> Self {
|
||||
Self {
|
||||
status,
|
||||
headers,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
pub fn text_with_charset(&self) -> Result<&str> {
|
||||
Ok(&self.body)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ProxyType {
|
||||
None,
|
||||
Localhost,
|
||||
System,
|
||||
}
|
||||
|
||||
pub struct NetworkManager {
|
||||
self_proxy_client: Mutex<Option<HttpClient>>,
|
||||
system_proxy_client: Mutex<Option<HttpClient>>,
|
||||
no_proxy_client: Mutex<Option<HttpClient>>,
|
||||
init: Once,
|
||||
last_connection_error: Mutex<Option<(Instant, String)>>,
|
||||
connection_error_count: Mutex<usize>,
|
||||
}
|
||||
|
||||
impl NetworkManager {
|
||||
fn new() -> Self {
|
||||
// 创建专用的异步运行时,线程数限制为4个
|
||||
let runtime = match Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.thread_name("clash-verge-network")
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
{
|
||||
Ok(runtime) => runtime,
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to create network runtime: {}. Using fallback single-threaded runtime.",
|
||||
e
|
||||
);
|
||||
// Fallback to current thread runtime
|
||||
match Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.thread_name("clash-verge-network-fallback")
|
||||
.build()
|
||||
{
|
||||
Ok(fallback_runtime) => fallback_runtime,
|
||||
Err(fallback_err) => {
|
||||
log::error!(
|
||||
"Failed to create fallback runtime: {}. This is critical.",
|
||||
fallback_err
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NetworkManager {
|
||||
runtime: Arc::new(runtime),
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
self_proxy_client: Mutex::new(None),
|
||||
system_proxy_client: Mutex::new(None),
|
||||
no_proxy_client: Mutex::new(None),
|
||||
init: Once::new(),
|
||||
last_connection_error: Mutex::new(None),
|
||||
connection_error_count: AtomicUsize::new(0),
|
||||
connection_error_count: Mutex::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// 初始化网络客户端
|
||||
pub fn init(&self) {
|
||||
self.init.call_once(|| {
|
||||
self.runtime.spawn(async {
|
||||
logging!(info, Type::Network, true, "初始化网络管理器");
|
||||
|
||||
// 创建无代理客户端
|
||||
let no_proxy_client = match ClientBuilder::new()
|
||||
.use_rustls_tls()
|
||||
.no_proxy()
|
||||
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
|
||||
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(30))
|
||||
.build()
|
||||
{
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
true,
|
||||
"Failed to build no_proxy client: {}",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut no_proxy_guard = NetworkManager::global().no_proxy_client.lock();
|
||||
*no_proxy_guard = Some(no_proxy_client);
|
||||
|
||||
logging!(info, Type::Network, true, "网络管理器初始化完成");
|
||||
});
|
||||
});
|
||||
self.init.call_once(|| {});
|
||||
}
|
||||
|
||||
fn record_connection_error(&self, error: &str) {
|
||||
let mut last_error = self.last_connection_error.lock();
|
||||
async fn record_connection_error(&self, error: &str) {
|
||||
let mut last_error = self.last_connection_error.lock().await;
|
||||
*last_error = Some((Instant::now(), error.to_string()));
|
||||
|
||||
self.connection_error_count.fetch_add(1, Ordering::Relaxed);
|
||||
let mut count = self.connection_error_count.lock().await;
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
fn should_reset_clients(&self) -> bool {
|
||||
let error_count = self.connection_error_count.load(Ordering::Relaxed);
|
||||
let last_error = self.last_connection_error.lock();
|
||||
async fn should_reset_clients(&self) -> bool {
|
||||
let count = *self.connection_error_count.lock().await;
|
||||
let last_error_guard = self.last_connection_error.lock().await;
|
||||
|
||||
if error_count > 5 {
|
||||
if count > 5 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some((time, _)) = *last_error {
|
||||
if time.elapsed() < Duration::from_secs(30) && error_count > 2 {
|
||||
if let Some((time, _)) = &*last_error_guard {
|
||||
if time.elapsed() < Duration::from_secs(30) && count > 2 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -145,60 +99,57 @@ impl NetworkManager {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn reset_clients(&self) {
|
||||
logging!(info, Type::Network, true, "正在重置所有HTTP客户端");
|
||||
{
|
||||
let mut client = self.self_proxy_client.lock();
|
||||
*client = None;
|
||||
}
|
||||
{
|
||||
let mut client = self.system_proxy_client.lock();
|
||||
*client = None;
|
||||
}
|
||||
{
|
||||
let mut client = self.no_proxy_client.lock();
|
||||
*client = None;
|
||||
}
|
||||
self.connection_error_count.store(0, Ordering::Relaxed);
|
||||
pub async fn reset_clients(&self) {
|
||||
*self.self_proxy_client.lock().await = None;
|
||||
*self.system_proxy_client.lock().await = None;
|
||||
*self.no_proxy_client.lock().await = None;
|
||||
*self.connection_error_count.lock().await = 0;
|
||||
}
|
||||
|
||||
fn build_client(
|
||||
&self,
|
||||
proxy_uri: Option<Uri>,
|
||||
default_headers: HeaderMap,
|
||||
accept_invalid_certs: bool,
|
||||
timeout_secs: Option<u64>,
|
||||
) -> Result<HttpClient> {
|
||||
let proxy_uri_clone = proxy_uri.clone();
|
||||
let headers_clone = default_headers.clone();
|
||||
let client = {
|
||||
let mut builder = HttpClient::builder();
|
||||
|
||||
builder = match proxy_uri_clone {
|
||||
Some(uri) => builder.proxy(Some(uri)),
|
||||
None => builder.proxy(None),
|
||||
};
|
||||
|
||||
for (name, value) in headers_clone.iter() {
|
||||
builder = builder.default_header(name, value);
|
||||
}
|
||||
|
||||
if accept_invalid_certs {
|
||||
builder = builder.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS);
|
||||
}
|
||||
|
||||
if let Some(secs) = timeout_secs {
|
||||
builder = builder.timeout(Duration::from_secs(secs));
|
||||
}
|
||||
|
||||
Ok(builder.build()?)
|
||||
};
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
/// 创建带有自定义选项的HTTP请求
|
||||
pub async fn create_request(
|
||||
&self,
|
||||
url: &str,
|
||||
proxy_type: ProxyType,
|
||||
timeout_secs: Option<u64>,
|
||||
user_agent: Option<String>,
|
||||
accept_invalid_certs: bool,
|
||||
) -> RequestBuilder {
|
||||
if self.should_reset_clients() {
|
||||
self.reset_clients();
|
||||
}
|
||||
|
||||
let mut builder = ClientBuilder::new()
|
||||
.use_rustls_tls()
|
||||
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
|
||||
.pool_idle_timeout(POOL_IDLE_TIMEOUT)
|
||||
.connect_timeout(DEFAULT_CONNECT_TIMEOUT)
|
||||
.http2_initial_stream_window_size(H2_STREAM_WINDOW_SIZE)
|
||||
.http2_initial_connection_window_size(H2_CONNECTION_WINDOW_SIZE)
|
||||
.http2_adaptive_window(true)
|
||||
.http2_keep_alive_interval(Some(H2_KEEP_ALIVE_INTERVAL))
|
||||
.http2_keep_alive_timeout(H2_KEEP_ALIVE_TIMEOUT)
|
||||
.http2_max_frame_size(H2_MAX_FRAME_SIZE)
|
||||
.tcp_keepalive(Some(Duration::from_secs(10)))
|
||||
.http2_max_header_list_size(16 * 1024);
|
||||
|
||||
if let Some(timeout) = timeout_secs {
|
||||
builder = builder.timeout(Duration::from_secs(timeout));
|
||||
} else {
|
||||
builder = builder.timeout(DEFAULT_REQUEST_TIMEOUT);
|
||||
}
|
||||
|
||||
match proxy_type {
|
||||
ProxyType::None => {
|
||||
builder = builder.no_proxy();
|
||||
}
|
||||
) -> Result<HttpClient> {
|
||||
let proxy_uri = match proxy_type {
|
||||
ProxyType::None => None,
|
||||
ProxyType::Localhost => {
|
||||
let port = {
|
||||
let verge_port = Config::verge().await.latest_ref().verge_mixed_port;
|
||||
@@ -207,94 +158,31 @@ impl NetworkManager {
|
||||
None => Config::clash().await.latest_ref().get_mixed_port(),
|
||||
}
|
||||
};
|
||||
|
||||
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
||||
|
||||
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
proxy_scheme.parse::<Uri>().ok()
|
||||
}
|
||||
ProxyType::System => {
|
||||
use sysproxy::Sysproxy;
|
||||
|
||||
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
|
||||
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
|
||||
|
||||
if let Ok(proxy) = Proxy::http(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = Proxy::https(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
if let Ok(proxy) = Proxy::all(&proxy_scheme) {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder = builder.danger_accept_invalid_certs(accept_invalid_certs);
|
||||
|
||||
if let Some(ua) = user_agent {
|
||||
builder = builder.user_agent(ua);
|
||||
} else {
|
||||
use crate::utils::resolve::VERSION;
|
||||
|
||||
let version = match VERSION.get() {
|
||||
Some(v) => format!("clash-verge/v{v}"),
|
||||
None => "clash-verge/unknown".to_string(),
|
||||
};
|
||||
|
||||
builder = builder.user_agent(version);
|
||||
}
|
||||
|
||||
let client = match builder.build() {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
true,
|
||||
"Failed to build custom HTTP client: {}",
|
||||
e
|
||||
);
|
||||
// Return a simple no-proxy client as fallback
|
||||
match ClientBuilder::new()
|
||||
.use_rustls_tls()
|
||||
.no_proxy()
|
||||
.timeout(DEFAULT_REQUEST_TIMEOUT)
|
||||
.build()
|
||||
{
|
||||
Ok(fallback_client) => fallback_client,
|
||||
Err(fallback_err) => {
|
||||
logging!(
|
||||
error,
|
||||
Type::Network,
|
||||
true,
|
||||
"Failed to create fallback client: {}",
|
||||
fallback_err
|
||||
);
|
||||
self.record_connection_error(&format!(
|
||||
"Critical client build failure: {}",
|
||||
fallback_err
|
||||
));
|
||||
// Return a minimal client that will likely fail but won't panic
|
||||
ClientBuilder::new().build().unwrap_or_else(|_| {
|
||||
// If even the most basic client fails, this is truly critical
|
||||
std::process::exit(1);
|
||||
})
|
||||
}
|
||||
proxy_scheme.parse::<Uri>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.get(url)
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
USER_AGENT,
|
||||
HeaderValue::from_str(
|
||||
&user_agent
|
||||
.unwrap_or_else(|| format!("clash-verge/v{}", env!("CARGO_PKG_VERSION"))),
|
||||
)?,
|
||||
);
|
||||
|
||||
let client = self.build_client(proxy_uri, headers, accept_invalid_certs, timeout_secs)?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub async fn get_with_interrupt(
|
||||
@@ -304,51 +192,38 @@ impl NetworkManager {
|
||||
timeout_secs: Option<u64>,
|
||||
user_agent: Option<String>,
|
||||
accept_invalid_certs: bool,
|
||||
) -> Result<Response> {
|
||||
let request = self
|
||||
.create_request(
|
||||
url,
|
||||
proxy_type,
|
||||
timeout_secs,
|
||||
user_agent,
|
||||
accept_invalid_certs,
|
||||
)
|
||||
.await;
|
||||
) -> Result<HttpResponse> {
|
||||
if self.should_reset_clients().await {
|
||||
self.reset_clients().await;
|
||||
}
|
||||
|
||||
let timeout_duration = timeout_secs.unwrap_or(20);
|
||||
let client = self
|
||||
.create_request(proxy_type, timeout_secs, user_agent, accept_invalid_certs)
|
||||
.await?;
|
||||
|
||||
let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
let timeout_duration = Duration::from_secs(timeout_secs.unwrap_or(20));
|
||||
let url_owned = url.to_string();
|
||||
|
||||
let url_clone = url.to_string();
|
||||
let watchdog = AsyncHandler::spawn(move || async move {
|
||||
tokio::time::sleep(Duration::from_secs(timeout_duration)).await;
|
||||
let _ = cancel_tx.send(());
|
||||
logging!(warn, Type::Network, true, "请求超时取消: {}", url_clone);
|
||||
});
|
||||
|
||||
let result = tokio::select! {
|
||||
result = request.send() => result,
|
||||
_ = cancel_rx => {
|
||||
self.record_connection_error(&format!("Request interrupted for: {url}"));
|
||||
return Err(anyhow::anyhow!("Request interrupted after {} seconds", timeout_duration));
|
||||
let response = match timeout(timeout_duration, async {
|
||||
let mut response = client.get_async(&url_owned).await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
let body = response.text().await?;
|
||||
Ok::<_, anyhow::Error>(HttpResponse::new(status, headers, body))
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(res) => res?,
|
||||
Err(_) => {
|
||||
self.record_connection_error(&format!("Request interrupted: {}", url))
|
||||
.await;
|
||||
return Err(anyhow::anyhow!(
|
||||
"Request interrupted after {}s",
|
||||
timeout_duration.as_secs()
|
||||
));
|
||||
}
|
||||
};
|
||||
watchdog.abort();
|
||||
|
||||
match result {
|
||||
Ok(response) => Ok(response),
|
||||
Err(e) => {
|
||||
self.record_connection_error(&e.to_string());
|
||||
Err(anyhow::anyhow!("Failed to send HTTP request: {}", e))
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
/// 代理类型
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ProxyType {
|
||||
None,
|
||||
Localhost,
|
||||
System,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::AppHandleManager;
|
||||
use crate::{
|
||||
config::{Config, PrfItem},
|
||||
core::*,
|
||||
@@ -138,18 +136,6 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
logging_error!(Type::Config, true, Config::init_config().await);
|
||||
logging!(info, Type::Config, true, "配置初始化完成");
|
||||
|
||||
// 启动时清理冗余的 Profile 文件
|
||||
logging!(info, Type::Setup, true, "开始清理冗余的Profile文件...");
|
||||
|
||||
match Config::profiles().await.latest_ref().auto_cleanup() {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Setup, true, "启动时Profile文件清理完成");
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
logging!(trace, Type::Core, true, "启动核心管理器...");
|
||||
logging_error!(Type::Core, true, CoreManager::global().init().await);
|
||||
|
||||
@@ -197,9 +183,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if is_silent_start {
|
||||
use crate::AppHandleManager;
|
||||
|
||||
AppHandleManager::global().set_activation_policy_accessory();
|
||||
handle::Handle::global().set_activation_policy_accessory();
|
||||
}
|
||||
}
|
||||
create_window(!is_silent_start).await;
|
||||
@@ -285,9 +269,7 @@ pub async fn create_window(is_show: bool) -> bool {
|
||||
let _ = window.set_focus();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
}
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -444,9 +426,7 @@ pub async fn create_window(is_show: bool) -> bool {
|
||||
let _ = window_clone.set_focus();
|
||||
logging!(info, Type::Window, true, "窗口已立即显示");
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
}
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
|
||||
let timeout_seconds = if crate::module::lightweight::is_in_lightweight_mode() {
|
||||
3
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::{core::handle, logging, utils::logging::Type};
|
||||
use tauri::{Manager, WebviewWindow, Wry};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::AppHandleManager;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use scopeguard;
|
||||
@@ -283,7 +280,7 @@ impl WindowManager {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
logging!(info, Type::Window, true, "应用 macOS 特定的激活策略");
|
||||
AppHandleManager::global().set_activation_policy_regular();
|
||||
handle::Handle::global().set_activation_policy_regular();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
Reference in New Issue
Block a user