mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
feat: migrate mihomo to use kode-bridge IPC on Windows and Unix (#4051)
* Refactor Mihomo API integration and remove crate_mihomo_api
- Removed the `mihomo_api` crate and its dependencies from the project.
- Introduced `IpcManager` for handling IPC communication with Mihomo.
- Implemented IPC methods for managing proxies, connections, and configurations.
- Updated `MihomoManager` to utilize `IpcManager` instead of the removed crate.
- Added platform-specific IPC socket path handling for macOS, Linux, and Windows.
- Cleaned up related tests and configuration files.
* fix: remove duplicate permission entry in desktop capabilities
* refactor: replace MihomoManager with IpcManager and remove Mihomo module
* fix: restore tempfile dependency in dev-dependencies
* fix: update kode-bridge dependency to use git source from the dev branch
* feat: migrate mihomo to use kode-bridge IPC on Windows
This commit implements a comprehensive migration from legacy service IPC to the kode-bridge library for Windows IPC communication. Key changes include:
Replace service_ipc with kode-bridge IpcManager for all mihomo communications
Simplify proxy commands using new caching mechanism with ProxyRequestCache
Add Windows named pipe (\.\pipe\mihomo) and Unix socket IPC endpoint configuration
Update Tauri permissions and dependencies (dashmap, tauri-plugin-notification)
Add IPC logging support and improve error handling
Fix Windows IPC path handling in directory utilities
This migration enables better cross-platform IPC support and improved performance for mihomo proxy core communication.
* doc: add IPC communication with Mihomo kernel, removing Restful API dependency
* fix: standardize logging type naming from IPC to Ipc for consistency
* refactor: clean up and optimize code structure across multiple components and services
- Removed unnecessary comments and whitespace in various files.
- Improved code readability and maintainability by restructuring functions and components.
- Updated localization files for consistency and accuracy.
- Enhanced performance by optimizing hooks and utility functions.
- General code cleanup in settings, pages, and services to adhere to best practices.
* fix: simplify URL formatting in test_proxy_delay method
* fix: update kode-bridge dependency to version 0.1.3 and change source to crates.io
* fix: update macOS target versions in development workflow
* Revert "fix: update macOS target versions in development workflow"
This reverts commit b9831357e4.
* feat: enhance IPC path handling for Unix systems and improve directory safety checks
* feat: add conditional compilation for Unix-specific IPC path handling
* chore: update cagro.lock
* feat: add external controller configuration and UI support
* Refactor proxy and connection management to use IPC-based commands
- Updated `get_proxies` function in `proxy.rs` to call the new IPC command.
- Renamed `get_refresh_proxies` to `get_proxies` in `ipc/general.rs` for consistency.
- Added new IPC commands for managing proxies, connections, and configurations in `cmds.ts`.
- Refactored API calls in various components to use the new IPC commands instead of HTTP requests.
- Improved error handling and response management in the new IPC functions.
- Cleaned up unused API functions in `api.ts` and redirected relevant calls to `cmds.ts`.
- Enhanced connection management features including health checks and updates for proxy providers.
* chore: update dependencies and improve error handling in IPC manager
* fix: downgrade zip dependency from 4.3.0 to 4.2.0
* feat: Implement traffic and memory data monitoring service
- Added `TrafficService` and `TrafficManager` to manage traffic and memory data collection.
- Introduced commands to get traffic and memory data, start and stop the traffic service.
- Integrated IPC calls for traffic and memory data retrieval in the frontend.
- Updated `AppDataProvider` and `EnhancedTrafficStats` components to utilize new data fetching methods.
- Removed WebSocket connections for traffic and memory data, replaced with IPC polling.
- Added logging for better traceability of data fetching and service status.
* refactor: unify external controller handling and improve IPC path resolution
* fix: replace direct IPC path retrieval with guard function for external controller
* fix: convert external controller IPC path to string for proper insertion in config map
* fix: update dependencies and improve IPC response handling
* fix: remove unnecessary unix conditional for ipc path import
* Refactor traffic and memory monitoring to use IPC stream; remove TrafficService and TrafficManager. Introduce new IPC-based data retrieval methods for traffic and memory, including formatted data and system overview. Update frontend components to utilize new APIs for enhanced data display and management.
* chore: bump crate rand version to 0.9.2
* feat: Implement enhanced traffic monitoring system with data compression and sampling
- Introduced `useTrafficMonitorEnhanced` hook for advanced traffic data management.
- Added `TrafficDataSampler` class for handling raw and compressed traffic data.
- Implemented reference counting to manage data collection based on component usage.
- Enhanced data validation with `SystemMonitorValidator` for API responses.
- Created diagnostic tools for monitoring performance and error tracking.
- Updated existing hooks to utilize the new enhanced monitoring features.
- Added utility functions for generating and formatting diagnostic reports.
* feat(ipc): improve URL encoding and error handling for IPC requests
- Add percent-encoding for URL paths to handle special characters properly
- Enhance error handling in update_proxy with proper logging
- Remove excessive debug logging to reduce noise
- Update kode-bridge dependency to v0.1.5
- Fix JSON parsing error handling in PUT requests
Changes include:
- Proper URL encoding for connection IDs, proxy names, and test URLs
- Enhanced error handling with fallback responses in updateProxy
- Comment out verbose debug logs in traffic monitoring and data validation
- Update dependency version for improved IPC functionality
* feat: major improvements in architecture, traffic monitoring, and data validation
* Refactor traffic graph components: Replace EnhancedTrafficGraph with EnhancedCanvasTrafficGraph, improve rendering performance, and enhance visual elements. Remove deprecated code and ensure compatibility with global data management.
* chore: update UPDATELOG.md for v2.4.0 release, refine traffic monitoring system details, and enhance IPC functionality
* chore: update UPDATELOG.md to reflect removal of deprecated MihomoManager and unify IPC control
* refactor: remove global traffic service testing method from cmds.ts
* Update src/components/home/enhanced-canvas-traffic-graph.tsx
* Update src/hooks/use-traffic-monitor-enhanced.ts
* Update src/components/layout/layout-traffic.tsx
* refactor: remove debug state management from LayoutTraffic component
---------
This commit is contained in:
203
src-tauri/Cargo.lock
generated
203
src-tauri/Cargo.lock
generated
@@ -2,6 +2,22 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d"
|
||||
dependencies = [
|
||||
"ab_glyph_rasterizer",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph_rasterizer"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
@@ -1109,6 +1125,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
name = "clash-verge"
|
||||
version = "2.4.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1116,22 +1133,23 @@ dependencies = [
|
||||
"boa_engine",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"dashmap 7.0.0-rc2",
|
||||
"dashmap 6.1.0",
|
||||
"deelevate",
|
||||
"delay_timer",
|
||||
"dirs 6.0.0",
|
||||
"dunce",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"gethostname 1.0.2",
|
||||
"getrandom 0.3.3",
|
||||
"hex",
|
||||
"hmac",
|
||||
"image",
|
||||
"kode-bridge",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"log4rs",
|
||||
"mihomo_api",
|
||||
"nanoid",
|
||||
"network-interface",
|
||||
"once_cell",
|
||||
@@ -1167,6 +1185,8 @@ dependencies = [
|
||||
"tauri-plugin-window-state",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.24.0",
|
||||
"tungstenite 0.27.0",
|
||||
"users",
|
||||
"warp",
|
||||
"winapi",
|
||||
@@ -1589,20 +1609,6 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "7.0.0-rc2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a1e35a65fe0538a60167f0ada6e195ad5d477f6ddae273943596d4a1a5730b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"equivalent",
|
||||
"hashbrown 0.15.4",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
@@ -1891,6 +1897,12 @@ dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doctest-file"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.11"
|
||||
@@ -2840,13 +2852,28 @@ checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"headers-core 0.2.0",
|
||||
"http 0.2.12",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"headers-core 0.3.0",
|
||||
"http 1.3.1",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers-core"
|
||||
version = "0.2.0"
|
||||
@@ -2856,6 +2883,15 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers-core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
||||
dependencies = [
|
||||
"http 1.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -3497,6 +3533,21 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d"
|
||||
dependencies = [
|
||||
"doctest-file",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"recvmsg",
|
||||
"tokio",
|
||||
"widestring",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intrusive-collections"
|
||||
version = "0.9.7"
|
||||
@@ -3711,6 +3762,34 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kode-bridge"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "971cfb2bdf5db3721fc822240b4e6e05b5d3aa8c85eb5f7ad4dc25ed0a3ad7e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"headers 0.4.1",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"httparse",
|
||||
"hyper 1.6.0",
|
||||
"interprocess",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"toml 0.8.23",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.8-speedreader"
|
||||
@@ -4083,16 +4162,6 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mihomo_api"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -4864,6 +4933,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
|
||||
dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.3"
|
||||
@@ -5769,6 +5847,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "recvmsg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.15"
|
||||
@@ -7804,7 +7888,19 @@ dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
"tungstenite 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite 0.24.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8168,6 +8264,12 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.21.0"
|
||||
@@ -8187,6 +8289,41 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror 1.0.69",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
@@ -8483,7 +8620,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"headers 0.3.9",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.32",
|
||||
"log",
|
||||
@@ -8497,7 +8634,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-tungstenite 0.21.0",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -8808,6 +8945,12 @@ dependencies = [
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
||||
@@ -41,6 +41,8 @@ tokio = { version = "1.46.1", features = [
|
||||
"time",
|
||||
"sync",
|
||||
] }
|
||||
tokio-tungstenite = "0.24.0"
|
||||
futures-util = "0.3.31"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
reqwest = { version = "0.12.22", features = ["json", "rustls-tls", "cookies"] }
|
||||
regex = "1.11.1"
|
||||
@@ -70,15 +72,17 @@ getrandom = "0.3.3"
|
||||
futures = "0.3.31"
|
||||
sys-locale = "0.3.2"
|
||||
async-trait = "0.1.88"
|
||||
mihomo_api = { path = "src_crates/crate_mihomo_api" }
|
||||
ab_glyph = "0.2.29"
|
||||
tungstenite = "0.27.0"
|
||||
libc = "0.2.174"
|
||||
gethostname = "1.0.2"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.9"
|
||||
hex = "0.4.3"
|
||||
scopeguard = "1.2.0"
|
||||
kode-bridge = "0.1.5"
|
||||
dashmap = "6.1.0"
|
||||
tauri-plugin-notification = "2.3.0"
|
||||
dashmap = "7.0.0-rc2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.2.0"
|
||||
@@ -141,10 +145,3 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
[dev-dependencies]
|
||||
criterion = "0.6.0"
|
||||
tempfile = "3.20.0"
|
||||
|
||||
[workspace]
|
||||
members = ["src_crates/crate_mihomo_api"]
|
||||
|
||||
[[bench]]
|
||||
name = "draft_benchmark"
|
||||
harness = false
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use super::CmdResult;
|
||||
use crate::{
|
||||
config::*, core::*, feat, module::mihomo::MihomoManager, process::AsyncHandler, wrap_err,
|
||||
};
|
||||
use crate::{config::*, core::*, feat, ipc::IpcManager, process::AsyncHandler, wrap_err};
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
/// 复制Clash环境变量
|
||||
@@ -90,9 +88,11 @@ pub async fn clash_api_get_proxy_delay(
|
||||
url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> CmdResult<serde_json::Value> {
|
||||
MihomoManager::global()
|
||||
.test_proxy_delay(&name, url, timeout)
|
||||
.await
|
||||
wrap_err!(
|
||||
IpcManager::global()
|
||||
.test_proxy_delay(&name, url, timeout)
|
||||
.await
|
||||
)
|
||||
}
|
||||
|
||||
/// 测试URL延迟
|
||||
@@ -267,3 +267,273 @@ pub async fn validate_dns_config() -> CmdResult<(bool, String)> {
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取Clash版本信息
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_version() -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(IpcManager::global().get_version().await)
|
||||
}
|
||||
|
||||
/// 获取Clash配置
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_config() -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(IpcManager::global().get_config().await)
|
||||
}
|
||||
|
||||
/// 更新地理数据
|
||||
#[tauri::command]
|
||||
pub async fn update_geo_data() -> CmdResult {
|
||||
wrap_err!(IpcManager::global().update_geo_data().await)
|
||||
}
|
||||
|
||||
/// 升级Clash核心
|
||||
#[tauri::command]
|
||||
pub async fn upgrade_clash_core() -> CmdResult {
|
||||
wrap_err!(IpcManager::global().upgrade_core().await)
|
||||
}
|
||||
|
||||
/// 获取规则
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_rules() -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(IpcManager::global().get_rules().await)
|
||||
}
|
||||
|
||||
/// 更新代理选择
|
||||
#[tauri::command]
|
||||
pub async fn update_proxy_choice(group: String, proxy: String) -> CmdResult {
|
||||
wrap_err!(IpcManager::global().update_proxy(&group, &proxy).await)
|
||||
}
|
||||
|
||||
/// 获取代理提供者
|
||||
#[tauri::command]
|
||||
pub async fn get_proxy_providers() -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(IpcManager::global().get_providers_proxies().await)
|
||||
}
|
||||
|
||||
/// 获取规则提供者
|
||||
#[tauri::command]
|
||||
pub async fn get_rule_providers() -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(IpcManager::global().get_rule_providers().await)
|
||||
}
|
||||
|
||||
/// 代理提供者健康检查
|
||||
#[tauri::command]
|
||||
pub async fn proxy_provider_health_check(name: String) -> CmdResult {
|
||||
wrap_err!(
|
||||
IpcManager::global()
|
||||
.proxy_provider_health_check(&name)
|
||||
.await
|
||||
)
|
||||
}
|
||||
|
||||
/// 更新代理提供者
|
||||
#[tauri::command]
|
||||
pub async fn update_proxy_provider(name: String) -> CmdResult {
|
||||
wrap_err!(IpcManager::global().update_proxy_provider(&name).await)
|
||||
}
|
||||
|
||||
/// 更新规则提供者
|
||||
#[tauri::command]
|
||||
pub async fn update_rule_provider(name: String) -> CmdResult {
|
||||
wrap_err!(IpcManager::global().update_rule_provider(&name).await)
|
||||
}
|
||||
|
||||
/// 获取连接
|
||||
#[tauri::command]
|
||||
pub async fn get_clash_connections() -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(IpcManager::global().get_connections().await)
|
||||
}
|
||||
|
||||
/// 删除连接
|
||||
#[tauri::command]
|
||||
pub async fn delete_clash_connection(id: String) -> CmdResult {
|
||||
wrap_err!(IpcManager::global().delete_connection(&id).await)
|
||||
}
|
||||
|
||||
/// 关闭所有连接
|
||||
#[tauri::command]
|
||||
pub async fn close_all_clash_connections() -> CmdResult {
|
||||
wrap_err!(IpcManager::global().close_all_connections().await)
|
||||
}
|
||||
|
||||
/// 获取流量数据 (使用新的IPC流式监控)
|
||||
#[tauri::command]
|
||||
pub async fn get_traffic_data() -> CmdResult<serde_json::Value> {
|
||||
log::info!(target: "app", "开始获取流量数据 (IPC流式)");
|
||||
let traffic = crate::ipc::get_current_traffic().await;
|
||||
let result = serde_json::json!({
|
||||
"up": traffic.total_up,
|
||||
"down": traffic.total_down,
|
||||
"up_rate": traffic.up_rate,
|
||||
"down_rate": traffic.down_rate,
|
||||
"last_updated": traffic.last_updated.elapsed().as_secs()
|
||||
});
|
||||
log::info!(target: "app", "获取流量数据结果: up={}, down={}, up_rate={}, down_rate={}",
|
||||
traffic.total_up, traffic.total_down, traffic.up_rate, traffic.down_rate);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 获取内存数据 (使用新的IPC流式监控)
|
||||
#[tauri::command]
|
||||
pub async fn get_memory_data() -> CmdResult<serde_json::Value> {
|
||||
log::info!(target: "app", "开始获取内存数据 (IPC流式)");
|
||||
let memory = crate::ipc::get_current_memory().await;
|
||||
let usage_percent = if memory.oslimit > 0 {
|
||||
(memory.inuse as f64 / memory.oslimit as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let result = serde_json::json!({
|
||||
"inuse": memory.inuse,
|
||||
"oslimit": memory.oslimit,
|
||||
"usage_percent": usage_percent,
|
||||
"last_updated": memory.last_updated.elapsed().as_secs()
|
||||
});
|
||||
log::info!(target: "app", "获取内存数据结果: inuse={}, oslimit={}, usage={}%",
|
||||
memory.inuse, memory.oslimit, usage_percent);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 启动流量监控服务 (IPC流式监控自动启动,此函数为兼容性保留)
|
||||
#[tauri::command]
|
||||
pub async fn start_traffic_service() -> CmdResult {
|
||||
log::info!(target: "app", "启动流量监控服务 (IPC流式监控)");
|
||||
// 新的IPC监控在首次访问时自动启动
|
||||
// 触发一次访问以确保监控器已初始化
|
||||
let _ = crate::ipc::get_current_traffic().await;
|
||||
let _ = crate::ipc::get_current_memory().await;
|
||||
log::info!(target: "app", "IPC流式监控已激活");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 停止流量监控服务 (IPC流式监控无需显式停止,此函数为兼容性保留)
|
||||
#[tauri::command]
|
||||
pub async fn stop_traffic_service() -> CmdResult {
|
||||
log::info!(target: "app", "停止流量监控服务请求 (IPC流式监控)");
|
||||
// 新的IPC监控是持久的,无需显式停止
|
||||
log::info!(target: "app", "IPC流式监控继续运行");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取格式化的流量数据 (包含单位,便于前端显示)
|
||||
#[tauri::command]
|
||||
pub async fn get_formatted_traffic_data() -> CmdResult<serde_json::Value> {
|
||||
log::info!(target: "app", "获取格式化流量数据");
|
||||
let (up_rate, down_rate, total_up, total_down, is_fresh) =
|
||||
crate::ipc::get_formatted_traffic().await;
|
||||
let result = serde_json::json!({
|
||||
"up_rate_formatted": up_rate,
|
||||
"down_rate_formatted": down_rate,
|
||||
"total_up_formatted": total_up,
|
||||
"total_down_formatted": total_down,
|
||||
"is_fresh": is_fresh
|
||||
});
|
||||
log::debug!(target: "app", "格式化流量数据: ↑{up_rate}/s ↓{down_rate}/s (总计: ↑{total_up} ↓{total_down})");
|
||||
// Clippy: variables can be used directly in the format string
|
||||
// log::debug!(target: "app", "格式化流量数据: ↑{up_rate}/s ↓{down_rate}/s (总计: ↑{total_up} ↓{total_down})");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 获取格式化的内存数据 (包含单位,便于前端显示)
|
||||
#[tauri::command]
|
||||
pub async fn get_formatted_memory_data() -> CmdResult<serde_json::Value> {
|
||||
log::info!(target: "app", "获取格式化内存数据");
|
||||
let (inuse, oslimit, usage_percent, is_fresh) = crate::ipc::get_formatted_memory().await;
|
||||
let result = serde_json::json!({
|
||||
"inuse_formatted": inuse,
|
||||
"oslimit_formatted": oslimit,
|
||||
"usage_percent": usage_percent,
|
||||
"is_fresh": is_fresh
|
||||
});
|
||||
log::debug!(target: "app", "格式化内存数据: {inuse} / {oslimit} ({usage_percent:.1}%)");
|
||||
// Clippy: variables can be used directly in the format string
|
||||
// log::debug!(target: "app", "格式化内存数据: {inuse} / {oslimit} ({usage_percent:.1}%)");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 获取系统监控概览 (流量+内存,便于前端一次性获取所有状态)
|
||||
#[tauri::command]
|
||||
pub async fn get_system_monitor_overview() -> CmdResult<serde_json::Value> {
|
||||
log::debug!(target: "app", "获取系统监控概览");
|
||||
|
||||
// 并发获取流量和内存数据
|
||||
let (traffic, memory) = tokio::join!(
|
||||
crate::ipc::get_current_traffic(),
|
||||
crate::ipc::get_current_memory()
|
||||
);
|
||||
|
||||
let (traffic_formatted, memory_formatted) = tokio::join!(
|
||||
crate::ipc::get_formatted_traffic(),
|
||||
crate::ipc::get_formatted_memory()
|
||||
);
|
||||
|
||||
let traffic_is_fresh = traffic.last_updated.elapsed().as_secs() < 5;
|
||||
let memory_is_fresh = memory.last_updated.elapsed().as_secs() < 10;
|
||||
|
||||
let result = serde_json::json!({
|
||||
"traffic": {
|
||||
"raw": {
|
||||
"up": traffic.total_up,
|
||||
"down": traffic.total_down,
|
||||
"up_rate": traffic.up_rate,
|
||||
"down_rate": traffic.down_rate
|
||||
},
|
||||
"formatted": {
|
||||
"up_rate": traffic_formatted.0,
|
||||
"down_rate": traffic_formatted.1,
|
||||
"total_up": traffic_formatted.2,
|
||||
"total_down": traffic_formatted.3
|
||||
},
|
||||
"is_fresh": traffic_is_fresh
|
||||
},
|
||||
"memory": {
|
||||
"raw": {
|
||||
"inuse": memory.inuse,
|
||||
"oslimit": memory.oslimit,
|
||||
"usage_percent": if memory.oslimit > 0 {
|
||||
(memory.inuse as f64 / memory.oslimit as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
},
|
||||
"formatted": {
|
||||
"inuse": memory_formatted.0,
|
||||
"oslimit": memory_formatted.1,
|
||||
"usage_percent": memory_formatted.2
|
||||
},
|
||||
"is_fresh": memory_is_fresh
|
||||
},
|
||||
"overall_status": if traffic_is_fresh && memory_is_fresh { "healthy" } else { "stale" }
|
||||
});
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 获取代理组延迟
|
||||
#[tauri::command]
|
||||
pub async fn get_group_proxy_delays(
|
||||
group_name: String,
|
||||
url: Option<String>,
|
||||
timeout: Option<i32>,
|
||||
) -> CmdResult<serde_json::Value> {
|
||||
wrap_err!(
|
||||
IpcManager::global()
|
||||
.get_group_proxy_delays(&group_name, url, timeout.unwrap_or(10000))
|
||||
.await
|
||||
)
|
||||
}
|
||||
|
||||
/// 检查调试是否启用
|
||||
#[tauri::command]
|
||||
pub async fn is_clash_debug_enabled() -> CmdResult<bool> {
|
||||
match IpcManager::global().is_debug_enabled().await {
|
||||
Ok(enabled) => Ok(enabled),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// 垃圾回收
|
||||
#[tauri::command]
|
||||
pub async fn clash_gc() -> CmdResult {
|
||||
wrap_err!(IpcManager::global().gc().await)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use anyhow::Result;
|
||||
|
||||
// Common result type used by command functions
|
||||
pub type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
// Command modules
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use super::CmdResult;
|
||||
use crate::module::mihomo::MihomoManager;
|
||||
use crate::{ipc::IpcManager, state::proxy::ProxyRequestCache};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::state::proxy::ProxyRequestCache;
|
||||
|
||||
const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(60);
|
||||
const PROVIDERS_REFRESH_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
||||
let manager = MihomoManager::global();
|
||||
let manager = IpcManager::global();
|
||||
let cache = ProxyRequestCache::global();
|
||||
let key = ProxyRequestCache::make_key("proxies", "default");
|
||||
let value = cache
|
||||
.get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async {
|
||||
manager.get_refresh_proxies().await.expect("fetch failed")
|
||||
manager.get_proxies().await.expect("fetch failed")
|
||||
})
|
||||
.await;
|
||||
Ok((*value).clone())
|
||||
@@ -31,7 +29,7 @@ pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
||||
let manager = MihomoManager::global();
|
||||
let manager = IpcManager::global();
|
||||
let cache = ProxyRequestCache::global();
|
||||
let key = ProxyRequestCache::make_key("providers", "default");
|
||||
let value = cache
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::config::Config;
|
||||
use crate::utils::dirs::{ipc_path, path_to_str};
|
||||
use crate::utils::{dirs, help};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -57,6 +59,16 @@ impl IClashTemp {
|
||||
map.insert("ipv6".into(), true.into());
|
||||
map.insert("mode".into(), "rule".into());
|
||||
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||
#[cfg(unix)]
|
||||
map.insert(
|
||||
"external-controller-unix".into(),
|
||||
Self::guard_external_controller_ipc().into(),
|
||||
);
|
||||
#[cfg(windows)]
|
||||
map.insert(
|
||||
"external-controller-pipe".into(),
|
||||
Self::guard_external_controller_ipc().into(),
|
||||
);
|
||||
cors_map.insert("allow-private-network".into(), true.into());
|
||||
cors_map.insert(
|
||||
"allow-origins".into(),
|
||||
@@ -87,7 +99,12 @@ impl IClashTemp {
|
||||
let mixed_port = Self::guard_mixed_port(&config);
|
||||
let socks_port = Self::guard_socks_port(&config);
|
||||
let port = Self::guard_port(&config);
|
||||
let ctrl = Self::guard_server_ctrl(&config);
|
||||
let ctrl = Self::guard_external_controller(&config);
|
||||
#[cfg(unix)]
|
||||
let external_controller_unix = Self::guard_external_controller_ipc();
|
||||
#[cfg(windows)]
|
||||
let external_controller_pipe = Self::guard_external_controller_ipc();
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
config.insert("redir-port".into(), redir_port.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -97,6 +114,16 @@ impl IClashTemp {
|
||||
config.insert("port".into(), port.into());
|
||||
config.insert("external-controller".into(), ctrl.into());
|
||||
|
||||
#[cfg(unix)]
|
||||
config.insert(
|
||||
"external-controller-unix".into(),
|
||||
external_controller_unix.into(),
|
||||
);
|
||||
#[cfg(windows)]
|
||||
config.insert(
|
||||
"external-controller-pipe".into(),
|
||||
external_controller_pipe.into(),
|
||||
);
|
||||
config
|
||||
}
|
||||
|
||||
@@ -245,6 +272,26 @@ impl IClashTemp {
|
||||
.unwrap_or("127.0.0.1:9097".into())
|
||||
}
|
||||
|
||||
pub fn guard_external_controller(config: &Mapping) -> String {
|
||||
// 在初始化阶段,直接返回配置中的值,不进行额外检查
|
||||
// 这样可以避免在配置加载期间的循环依赖
|
||||
Self::guard_server_ctrl(config)
|
||||
}
|
||||
|
||||
pub fn guard_external_controller_with_setting(config: &Mapping) -> String {
|
||||
// 检查 enable_external_controller 设置,用于运行时配置生成
|
||||
let enable_external_controller = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_external_controller
|
||||
.unwrap_or(false);
|
||||
|
||||
if enable_external_controller {
|
||||
Self::guard_server_ctrl(config)
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn guard_client_ctrl(config: &Mapping) -> String {
|
||||
let value = Self::guard_server_ctrl(config);
|
||||
match SocketAddr::from_str(value.as_str()) {
|
||||
@@ -257,6 +304,11 @@ impl IClashTemp {
|
||||
Err(_) => "127.0.0.1:9097".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn guard_external_controller_ipc() -> String {
|
||||
// 总是使用当前的 IPC 路径,确保配置文件与运行时路径一致
|
||||
path_to_str(&ipc_path().unwrap()).unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
|
||||
@@ -204,6 +204,9 @@ pub struct IVerge {
|
||||
/// 启用代理页面自动滚动
|
||||
pub enable_hover_jump_navigator: Option<bool>,
|
||||
|
||||
/// 启用外部控制器
|
||||
pub enable_external_controller: Option<bool>,
|
||||
|
||||
/// 服务状态跟踪
|
||||
pub service_state: Option<crate::core::service::ServiceState>,
|
||||
}
|
||||
@@ -403,6 +406,7 @@ impl IVerge {
|
||||
enable_dns_settings: Some(false),
|
||||
home_cards: None,
|
||||
service_state: None,
|
||||
enable_external_controller: Some(false),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
@@ -491,6 +495,7 @@ impl IVerge {
|
||||
patch!(enable_dns_settings);
|
||||
patch!(home_cards);
|
||||
patch!(service_state);
|
||||
patch!(enable_external_controller);
|
||||
}
|
||||
|
||||
/// 在初始化前尝试拿到单例端口的值
|
||||
@@ -586,6 +591,7 @@ pub struct IVergeResponse {
|
||||
pub enable_dns_settings: Option<bool>,
|
||||
pub home_cards: Option<serde_json::Value>,
|
||||
pub enable_hover_jump_navigator: Option<bool>,
|
||||
pub enable_external_controller: Option<bool>,
|
||||
pub service_state: Option<crate::core::service::ServiceState>,
|
||||
}
|
||||
|
||||
@@ -658,6 +664,7 @@ impl From<IVerge> for IVergeResponse {
|
||||
enable_dns_settings: verge.enable_dns_settings,
|
||||
home_cards: verge.home_cards,
|
||||
enable_hover_jump_navigator: verge.enable_hover_jump_navigator,
|
||||
enable_external_controller: verge.enable_external_controller,
|
||||
service_state: verge.service_state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::{
|
||||
handle,
|
||||
service::{self},
|
||||
},
|
||||
ipc::IpcManager,
|
||||
logging, logging_error,
|
||||
module::mihomo::MihomoManager,
|
||||
utils::{
|
||||
dirs,
|
||||
help::{self},
|
||||
@@ -413,10 +413,7 @@ impl CoreManager {
|
||||
logging_error!(Type::Core, true, "{}", msg);
|
||||
msg
|
||||
});
|
||||
match MihomoManager::global()
|
||||
.put_configs_force(run_path_str?)
|
||||
.await
|
||||
{
|
||||
match IpcManager::global().put_configs_force(run_path_str?).await {
|
||||
Ok(_) => {
|
||||
Config::runtime().apply();
|
||||
logging!(info, Type::Core, true, "Configuration updated successfully");
|
||||
|
||||
@@ -2,11 +2,12 @@ use once_cell::sync::OnceCell;
|
||||
use tauri::tray::TrayIconBuilder;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod speed_rate;
|
||||
use crate::ipc::Rate;
|
||||
use crate::{
|
||||
cmd,
|
||||
config::Config,
|
||||
feat, logging,
|
||||
module::{lightweight::is_in_lightweight_mode, mihomo::Rate},
|
||||
module::lightweight::is_in_lightweight_mode,
|
||||
utils::{dirs::find_target_icons, i18n::t, resolve::VERSION},
|
||||
Type,
|
||||
};
|
||||
|
||||
@@ -234,7 +234,22 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
config.insert(key, value);
|
||||
// 处理 external-controller 键的开关逻辑
|
||||
if key.as_str() == Some("external-controller") {
|
||||
let enable_external_controller = Config::verge()
|
||||
.latest_ref()
|
||||
.enable_external_controller
|
||||
.unwrap_or(false);
|
||||
|
||||
if enable_external_controller {
|
||||
config.insert(key, value);
|
||||
} else {
|
||||
// 如果禁用了外部控制器,设置为空字符串
|
||||
config.insert(key, "".into());
|
||||
}
|
||||
} else {
|
||||
config.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{handle, tray, CoreManager},
|
||||
ipc::IpcManager,
|
||||
logging_error,
|
||||
module::mihomo::MihomoManager,
|
||||
process::AsyncHandler,
|
||||
utils::{logging::Type, resolve},
|
||||
};
|
||||
@@ -38,12 +38,12 @@ pub fn restart_app() {
|
||||
|
||||
fn after_change_clash_mode() {
|
||||
AsyncHandler::spawn(move || async {
|
||||
match MihomoManager::global().get_connections().await {
|
||||
match IpcManager::global().get_connections().await {
|
||||
Ok(connections) => {
|
||||
if let Some(connections_array) = connections["connections"].as_array() {
|
||||
for connection in connections_array {
|
||||
if let Some(id) = connection["id"].as_str() {
|
||||
let _ = MihomoManager::global().delete_connection(id).await;
|
||||
let _ = IpcManager::global().delete_connection(id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ pub fn change_clash_mode(mode: String) {
|
||||
});
|
||||
AsyncHandler::spawn(move || async move {
|
||||
log::debug!(target: "app", "change clash mode to {mode}");
|
||||
match MihomoManager::global().patch_configs(json_value).await {
|
||||
match IpcManager::global().patch_configs(json_value).await {
|
||||
Ok(_) => {
|
||||
// 更新订阅
|
||||
Config::clash().data_mut().patch_config(mapping);
|
||||
|
||||
@@ -95,6 +95,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
||||
let tray_event = patch.tray_event;
|
||||
let home_cards = patch.home_cards.clone();
|
||||
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
|
||||
let enable_external_controller = patch.enable_external_controller;
|
||||
let res: std::result::Result<(), anyhow::Error> = {
|
||||
// Initialize with no flags set
|
||||
let mut update_flags: i32 = UpdateFlags::None as i32;
|
||||
@@ -165,6 +166,11 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|
||||
update_flags |= UpdateFlags::LighteWeight as i32;
|
||||
}
|
||||
|
||||
// 处理 external-controller 的开关
|
||||
if enable_external_controller.is_some() {
|
||||
update_flags |= UpdateFlags::RestartCore as i32;
|
||||
}
|
||||
|
||||
// Process updates based on flags
|
||||
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
|
||||
Config::generate().await?;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
config::{Config, IVerge},
|
||||
core::handle,
|
||||
ipc::IpcManager,
|
||||
process::AsyncHandler,
|
||||
};
|
||||
use std::env;
|
||||
@@ -18,10 +19,7 @@ pub fn toggle_system_proxy() {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
// 如果当前系统代理即将关闭,且自动关闭连接设置为true,则关闭所有连接
|
||||
if enable && auto_close_connection {
|
||||
if let Err(err) = crate::module::mihomo::MihomoManager::global()
|
||||
.close_all_connections()
|
||||
.await
|
||||
{
|
||||
if let Err(err) = IpcManager::global().close_all_connections().await {
|
||||
log::error!(target: "app", "Failed to close all connections: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::AppHandleManager;
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{handle, sysopt, CoreManager},
|
||||
ipc::IpcManager,
|
||||
logging,
|
||||
module::mihomo::MihomoManager,
|
||||
utils::logging::Type,
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ async fn clean_async() -> bool {
|
||||
});
|
||||
match timeout(
|
||||
Duration::from_secs(2),
|
||||
MihomoManager::global().patch_configs(disable_tun),
|
||||
IpcManager::global().patch_configs(disable_tun),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
397
src-tauri/src/ipc/general.rs
Normal file
397
src-tauri/src/ipc/general.rs
Normal file
@@ -0,0 +1,397 @@
|
||||
use kode_bridge::{
|
||||
errors::{AnyError, AnyResult},
|
||||
IpcHttpClient, LegacyResponse,
|
||||
};
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, logging::Type},
|
||||
};
|
||||
|
||||
// Helper function to create AnyError from string
|
||||
fn create_error(msg: impl Into<String>) -> AnyError {
|
||||
Box::new(std::io::Error::other(msg.into()))
|
||||
}
|
||||
|
||||
pub struct IpcManager {
|
||||
ipc_path: String,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<IpcManager> = OnceLock::new();
|
||||
|
||||
impl IpcManager {
|
||||
pub fn global() -> &'static IpcManager {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let instance = IpcManager {
|
||||
ipc_path: ipc_path.to_string(),
|
||||
};
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"IpcManager initialized with IPC path: {}",
|
||||
instance.ipc_path
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IpcManager {
|
||||
pub async fn request(
|
||||
&self,
|
||||
method: &str,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
) -> AnyResult<LegacyResponse> {
|
||||
let client = IpcHttpClient::new(&self.ipc_path)?;
|
||||
client.request(method, path, body).await
|
||||
}
|
||||
}
|
||||
|
||||
impl IpcManager {
|
||||
pub async fn send_request(
|
||||
&self,
|
||||
method: &str,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
) -> AnyResult<serde_json::Value> {
|
||||
let response = IpcManager::global().request(method, path, body).await?;
|
||||
match method {
|
||||
"GET" => Ok(response.json()?),
|
||||
"PATCH" => {
|
||||
if response.status == 204 {
|
||||
Ok(serde_json::json!({"code": 204}))
|
||||
} else {
|
||||
Ok(response.json()?)
|
||||
}
|
||||
}
|
||||
"PUT" => {
|
||||
if response.status == 204 {
|
||||
Ok(serde_json::json!({"code": 204}))
|
||||
} else {
|
||||
// 尝试解析JSON,如果失败则返回错误信息
|
||||
match response.json() {
|
||||
Ok(json) => Ok(json),
|
||||
Err(_) => Ok(serde_json::json!({
|
||||
"code": response.status,
|
||||
"message": response.body,
|
||||
"error": "failed to parse response as JSON"
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Ok(response.json()?),
|
||||
}
|
||||
}
|
||||
|
||||
// 基础代理信息获取
|
||||
pub async fn get_proxies(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/proxies";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
// 代理提供者信息获取
|
||||
pub async fn get_providers_proxies(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/providers/proxies";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
// 连接管理
|
||||
pub async fn get_connections(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/connections";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
pub async fn delete_connection(&self, id: &str) -> AnyResult<()> {
|
||||
let encoded_id = utf8_percent_encode(id, NON_ALPHANUMERIC).to_string();
|
||||
let url = format!("/connections/{encoded_id}");
|
||||
let response = self.send_request("DELETE", &url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"].as_str().unwrap_or("unknown error"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn close_all_connections(&self) -> AnyResult<()> {
|
||||
let url = "/connections";
|
||||
let response = self.send_request("DELETE", url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IpcManager {
|
||||
#[allow(dead_code)]
|
||||
pub async fn is_mihomo_running(&self) -> AnyResult<()> {
|
||||
let url = "/version";
|
||||
let _response = self.send_request("GET", url, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn put_configs_force(&self, clash_config_path: &str) -> AnyResult<()> {
|
||||
let url = "/configs?force=true";
|
||||
let payload = serde_json::json!({
|
||||
"path": clash_config_path,
|
||||
});
|
||||
let _response = self.send_request("PUT", url, Some(&payload)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn patch_configs(&self, config: serde_json::Value) -> AnyResult<()> {
|
||||
let url = "/configs";
|
||||
let response = self.send_request("PATCH", url, Some(&config)).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_proxy_delay(
|
||||
&self,
|
||||
name: &str,
|
||||
test_url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> AnyResult<serde_json::Value> {
|
||||
let test_url =
|
||||
test_url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string());
|
||||
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string();
|
||||
let encoded_test_url = utf8_percent_encode(&test_url, NON_ALPHANUMERIC).to_string();
|
||||
let url = format!("/proxies/{encoded_name}/delay?url={encoded_test_url}&timeout={timeout}");
|
||||
let response = self.send_request("GET", &url, None).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// 版本和配置相关
|
||||
pub async fn get_version(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/version";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
pub async fn get_config(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/configs";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
pub async fn update_geo_data(&self) -> AnyResult<()> {
|
||||
let url = "/configs/geo";
|
||||
let response = self.send_request("POST", url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upgrade_core(&self) -> AnyResult<()> {
|
||||
let url = "/upgrade";
|
||||
let response = self.send_request("POST", url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// 规则相关
|
||||
pub async fn get_rules(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/rules";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
pub async fn get_rule_providers(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/providers/rules";
|
||||
self.send_request("GET", url, None).await
|
||||
}
|
||||
|
||||
pub async fn update_rule_provider(&self, name: &str) -> AnyResult<()> {
|
||||
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string();
|
||||
let url = format!("/providers/rules/{encoded_name}");
|
||||
let response = self.send_request("PUT", &url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// 代理相关
|
||||
pub async fn update_proxy(&self, group: &str, proxy: &str) -> AnyResult<()> {
|
||||
// 使用 percent-encoding 进行正确的 URL 编码
|
||||
let encoded_group = utf8_percent_encode(group, NON_ALPHANUMERIC).to_string();
|
||||
let url = format!("/proxies/{encoded_group}");
|
||||
let payload = serde_json::json!({
|
||||
"name": proxy
|
||||
});
|
||||
|
||||
let response = match self.send_request("PUT", &url, Some(&payload)).await {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
error,
|
||||
crate::utils::logging::Type::Ipc,
|
||||
true,
|
||||
"IPC: updateProxy encountered error: {} (ignored, always returning true)",
|
||||
e
|
||||
);
|
||||
// Always return a successful response as serde_json::Value
|
||||
serde_json::json!({"code": 204})
|
||||
}
|
||||
};
|
||||
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
let error_msg = response["message"].as_str().unwrap_or_else(|| {
|
||||
if let Some(error) = response.get("error") {
|
||||
error.as_str().unwrap_or("unknown error")
|
||||
} else {
|
||||
"failed to update proxy"
|
||||
}
|
||||
});
|
||||
|
||||
logging!(
|
||||
error,
|
||||
crate::utils::logging::Type::Ipc,
|
||||
true,
|
||||
"IPC: updateProxy failed: {}",
|
||||
error_msg
|
||||
);
|
||||
|
||||
Err(create_error(error_msg.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn proxy_provider_health_check(&self, name: &str) -> AnyResult<()> {
|
||||
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string();
|
||||
let url = format!("/providers/proxies/{encoded_name}/healthcheck");
|
||||
let response = self.send_request("GET", &url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_proxy_provider(&self, name: &str) -> AnyResult<()> {
|
||||
let encoded_name = utf8_percent_encode(name, NON_ALPHANUMERIC).to_string();
|
||||
let url = format!("/providers/proxies/{encoded_name}");
|
||||
let response = self.send_request("PUT", &url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟测试相关
|
||||
pub async fn get_group_proxy_delays(
|
||||
&self,
|
||||
group_name: &str,
|
||||
url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> AnyResult<serde_json::Value> {
|
||||
let test_url = url.unwrap_or_else(|| "https://cp.cloudflare.com/generate_204".to_string());
|
||||
let encoded_group_name = utf8_percent_encode(group_name, NON_ALPHANUMERIC).to_string();
|
||||
let encoded_test_url = utf8_percent_encode(&test_url, NON_ALPHANUMERIC).to_string();
|
||||
let url =
|
||||
format!("/group/{encoded_group_name}/delay?url={encoded_test_url}&timeout={timeout}");
|
||||
self.send_request("GET", &url, None).await
|
||||
}
|
||||
|
||||
// 调试相关
|
||||
pub async fn is_debug_enabled(&self) -> AnyResult<bool> {
|
||||
let url = "/debug/pprof";
|
||||
match self.send_request("GET", url, None).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gc(&self) -> AnyResult<()> {
|
||||
let url = "/debug/gc";
|
||||
let response = self.send_request("PUT", url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(create_error(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// 流量数据相关
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_traffic(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/traffic";
|
||||
logging!(info, Type::Ipc, true, "IPC: 发送 GET 请求到 {}", url);
|
||||
let result = self.send_request("GET", url, None).await;
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"IPC: /traffic 请求结果: {:?}",
|
||||
result
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
// 内存相关
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_memory(&self) -> AnyResult<serde_json::Value> {
|
||||
let url = "/memory";
|
||||
logging!(info, Type::Ipc, true, "IPC: 发送 GET 请求到 {}", url);
|
||||
let result = self.send_request("GET", url, None).await;
|
||||
logging!(info, Type::Ipc, true, "IPC: /memory 请求结果: {:?}", result);
|
||||
result
|
||||
}
|
||||
}
|
||||
135
src-tauri/src/ipc/memory.rs
Normal file
135
src-tauri/src/ipc/memory.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use kode_bridge::IpcStreamClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, logging::Type},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct MemoryData {
|
||||
pub inuse: u64,
|
||||
pub oslimit: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CurrentMemory {
|
||||
pub inuse: u64,
|
||||
pub oslimit: u64,
|
||||
pub last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Default for CurrentMemory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inuse: 0,
|
||||
oslimit: 0,
|
||||
last_updated: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal memory monitor
|
||||
pub struct MemoryMonitor {
|
||||
current: Arc<RwLock<CurrentMemory>>,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<MemoryMonitor> = OnceLock::new();
|
||||
|
||||
impl MemoryMonitor {
|
||||
pub fn global() -> &'static MemoryMonitor {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let client = IpcStreamClient::new(ipc_path).unwrap();
|
||||
|
||||
let instance = MemoryMonitor::new(client);
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"MemoryMonitor initialized with IPC path: {}",
|
||||
ipc_path
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
|
||||
fn new(client: IpcStreamClient) -> Self {
|
||||
let current = Arc::new(RwLock::new(CurrentMemory::default()));
|
||||
let monitor_current = current.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let _ = client
|
||||
.get("/memory")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.process_lines(|line| {
|
||||
if let Ok(memory) = serde_json::from_str::<MemoryData>(line.trim()) {
|
||||
tokio::spawn({
|
||||
let current = monitor_current.clone();
|
||||
async move {
|
||||
*current.write().await = CurrentMemory {
|
||||
inuse: memory.inuse,
|
||||
oslimit: memory.oslimit,
|
||||
last_updated: Instant::now(),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_secs(2)).await; // Memory updates less frequently
|
||||
}
|
||||
});
|
||||
|
||||
Self { current }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> CurrentMemory {
|
||||
self.current.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn is_fresh(&self) -> bool {
|
||||
self.current.read().await.last_updated.elapsed() < Duration::from_secs(10)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_bytes(bytes: u64) -> String {
|
||||
const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
|
||||
let (mut val, mut unit) = (bytes as f64, 0);
|
||||
while val >= 1024.0 && unit < 3 {
|
||||
val /= 1024.0;
|
||||
unit += 1;
|
||||
}
|
||||
format!("{:.1}{}", val, UNITS[unit])
|
||||
}
|
||||
|
||||
pub async fn get_current_memory() -> CurrentMemory {
|
||||
MemoryMonitor::global().current().await
|
||||
}
|
||||
|
||||
pub async fn get_formatted_memory() -> (String, String, f64, bool) {
|
||||
let monitor = MemoryMonitor::global();
|
||||
let memory = monitor.current().await;
|
||||
let is_fresh = monitor.is_fresh().await;
|
||||
|
||||
let usage_percent = if memory.oslimit > 0 {
|
||||
(memory.inuse as f64 / memory.oslimit as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
(
|
||||
fmt_bytes(memory.inuse),
|
||||
fmt_bytes(memory.oslimit),
|
||||
usage_percent,
|
||||
is_fresh,
|
||||
)
|
||||
}
|
||||
12
src-tauri/src/ipc/mod.rs
Normal file
12
src-tauri/src/ipc/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod general;
|
||||
pub mod memory;
|
||||
pub mod traffic;
|
||||
|
||||
pub use general::IpcManager;
|
||||
pub use memory::{get_current_memory, get_formatted_memory};
|
||||
pub use traffic::{get_current_traffic, get_formatted_traffic};
|
||||
|
||||
pub struct Rate {
|
||||
// pub up: usize,
|
||||
// pub down: usize,
|
||||
}
|
||||
148
src-tauri/src/ipc/traffic.rs
Normal file
148
src-tauri/src/ipc/traffic.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use kode_bridge::IpcStreamClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::{sync::RwLock, time::Duration};
|
||||
|
||||
use crate::{
|
||||
logging,
|
||||
utils::{dirs::ipc_path, logging::Type},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TrafficData {
|
||||
pub up: u64,
|
||||
pub down: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CurrentTraffic {
|
||||
pub up_rate: u64,
|
||||
pub down_rate: u64,
|
||||
pub total_up: u64,
|
||||
pub total_down: u64,
|
||||
pub last_updated: Instant,
|
||||
}
|
||||
|
||||
impl Default for CurrentTraffic {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
up_rate: 0,
|
||||
down_rate: 0,
|
||||
total_up: 0,
|
||||
total_down: 0,
|
||||
last_updated: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal traffic monitor
|
||||
pub struct TrafficMonitor {
|
||||
current: Arc<RwLock<CurrentTraffic>>,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<TrafficMonitor> = OnceLock::new();
|
||||
|
||||
impl TrafficMonitor {
|
||||
pub fn global() -> &'static TrafficMonitor {
|
||||
INSTANCE.get_or_init(|| {
|
||||
let ipc_path_buf = ipc_path().unwrap();
|
||||
let ipc_path = ipc_path_buf.to_str().unwrap_or_default();
|
||||
let client = IpcStreamClient::new(ipc_path).unwrap();
|
||||
|
||||
let instance = TrafficMonitor::new(client);
|
||||
logging!(
|
||||
info,
|
||||
Type::Ipc,
|
||||
true,
|
||||
"TrafficMonitor initialized with IPC path: {}",
|
||||
ipc_path
|
||||
);
|
||||
instance
|
||||
})
|
||||
}
|
||||
|
||||
fn new(client: IpcStreamClient) -> Self {
|
||||
let current = Arc::new(RwLock::new(CurrentTraffic::default()));
|
||||
let monitor_current = current.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut last: Option<TrafficData> = None;
|
||||
loop {
|
||||
let _ = client
|
||||
.get("/traffic")
|
||||
.timeout(Duration::from_secs(10))
|
||||
.process_lines(|line| {
|
||||
if let Ok(traffic) = serde_json::from_str::<TrafficData>(line.trim()) {
|
||||
let (up_rate, down_rate) = last
|
||||
.as_ref()
|
||||
.map(|l| {
|
||||
(
|
||||
traffic.up.saturating_sub(l.up),
|
||||
traffic.down.saturating_sub(l.down),
|
||||
)
|
||||
})
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
tokio::spawn({
|
||||
let current = monitor_current.clone();
|
||||
async move {
|
||||
*current.write().await = CurrentTraffic {
|
||||
up_rate,
|
||||
down_rate,
|
||||
total_up: traffic.up,
|
||||
total_down: traffic.down,
|
||||
last_updated: Instant::now(),
|
||||
};
|
||||
}
|
||||
});
|
||||
last = Some(traffic);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
});
|
||||
|
||||
Self { current }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> CurrentTraffic {
|
||||
self.current.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn is_fresh(&self) -> bool {
|
||||
self.current.read().await.last_updated.elapsed() < Duration::from_secs(5)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_bytes(bytes: u64) -> String {
|
||||
const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
|
||||
let (mut val, mut unit) = (bytes as f64, 0);
|
||||
while val >= 1024.0 && unit < 3 {
|
||||
val /= 1024.0;
|
||||
unit += 1;
|
||||
}
|
||||
format!("{:.1}{}", val, UNITS[unit])
|
||||
}
|
||||
|
||||
pub async fn get_current_traffic() -> CurrentTraffic {
|
||||
TrafficMonitor::global().current().await
|
||||
}
|
||||
|
||||
pub async fn get_formatted_traffic() -> (String, String, String, String, bool) {
|
||||
let monitor = TrafficMonitor::global();
|
||||
let traffic = monitor.current().await;
|
||||
let is_fresh = monitor.is_fresh().await;
|
||||
|
||||
(
|
||||
fmt_bytes(traffic.up_rate),
|
||||
fmt_bytes(traffic.down_rate),
|
||||
fmt_bytes(traffic.total_up),
|
||||
fmt_bytes(traffic.total_down),
|
||||
is_fresh,
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub mod config;
|
||||
mod core;
|
||||
mod enhance;
|
||||
mod feat;
|
||||
mod ipc;
|
||||
mod module;
|
||||
mod process;
|
||||
mod state;
|
||||
@@ -271,6 +272,30 @@ pub fn run() {
|
||||
cmd::check_dns_config_exists,
|
||||
cmd::get_dns_config_content,
|
||||
cmd::validate_dns_config,
|
||||
cmd::get_clash_version,
|
||||
cmd::get_clash_config,
|
||||
cmd::update_geo_data,
|
||||
cmd::upgrade_clash_core,
|
||||
cmd::get_clash_rules,
|
||||
cmd::update_proxy_choice,
|
||||
cmd::get_proxy_providers,
|
||||
cmd::get_rule_providers,
|
||||
cmd::proxy_provider_health_check,
|
||||
cmd::update_proxy_provider,
|
||||
cmd::update_rule_provider,
|
||||
cmd::get_clash_connections,
|
||||
cmd::delete_clash_connection,
|
||||
cmd::close_all_clash_connections,
|
||||
cmd::get_group_proxy_delays,
|
||||
cmd::is_clash_debug_enabled,
|
||||
cmd::clash_gc,
|
||||
cmd::get_traffic_data,
|
||||
cmd::get_memory_data,
|
||||
cmd::get_formatted_traffic_data,
|
||||
cmd::get_formatted_memory_data,
|
||||
cmd::get_system_monitor_overview,
|
||||
cmd::start_traffic_service,
|
||||
cmd::stop_traffic_service,
|
||||
// verge
|
||||
cmd::get_verge_config,
|
||||
cmd::patch_verge_config,
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
use crate::config::Config;
|
||||
use mihomo_api;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::http::HeaderMap;
|
||||
|
||||
// 缓存的最大有效期(5秒)
|
||||
const CACHE_TTL: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct Rate {
|
||||
pub up: u64,
|
||||
pub down: u64,
|
||||
}
|
||||
// 缓存MihomoManager实例
|
||||
struct MihomoCache {
|
||||
manager: mihomo_api::MihomoManager,
|
||||
created_at: Instant,
|
||||
server: String,
|
||||
}
|
||||
// 使用RwLock替代Mutex,允许多个读取操作并发进行
|
||||
pub struct MihomoManager {
|
||||
mihomo_cache: RwLock<Option<MihomoCache>>,
|
||||
create_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl MihomoManager {
|
||||
fn __global() -> &'static MihomoManager {
|
||||
static INSTANCE: Lazy<MihomoManager> = Lazy::new(|| MihomoManager {
|
||||
mihomo_cache: RwLock::new(None),
|
||||
create_lock: Mutex::new(()),
|
||||
});
|
||||
&INSTANCE
|
||||
}
|
||||
|
||||
pub fn global() -> mihomo_api::MihomoManager {
|
||||
let instance = MihomoManager::__global();
|
||||
|
||||
// 尝试从缓存读取(只需读锁)
|
||||
{
|
||||
let cache = instance.mihomo_cache.read();
|
||||
if let Some(cache_entry) = &*cache {
|
||||
let (current_server, _) = MihomoManager::get_clash_client_info()
|
||||
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
|
||||
|
||||
// 检查缓存是否有效
|
||||
if cache_entry.server == current_server
|
||||
&& cache_entry.created_at.elapsed() < CACHE_TTL
|
||||
{
|
||||
return cache_entry.manager.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存无效,获取创建锁
|
||||
let _create_guard = instance.create_lock.lock();
|
||||
|
||||
// 再次检查缓存(双重检查锁定模式)
|
||||
{
|
||||
let cache = instance.mihomo_cache.read();
|
||||
if let Some(cache_entry) = &*cache {
|
||||
let (current_server, _) = MihomoManager::get_clash_client_info()
|
||||
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
|
||||
|
||||
if cache_entry.server == current_server
|
||||
&& cache_entry.created_at.elapsed() < CACHE_TTL
|
||||
{
|
||||
return cache_entry.manager.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新实例
|
||||
let (current_server, headers) = MihomoManager::get_clash_client_info()
|
||||
.unwrap_or_else(|| (String::new(), HeaderMap::new()));
|
||||
let manager = mihomo_api::MihomoManager::new(current_server.clone(), headers);
|
||||
|
||||
// 更新缓存
|
||||
{
|
||||
let mut cache = instance.mihomo_cache.write();
|
||||
*cache = Some(MihomoCache {
|
||||
manager: manager.clone(),
|
||||
created_at: Instant::now(),
|
||||
server: current_server,
|
||||
});
|
||||
}
|
||||
|
||||
manager
|
||||
}
|
||||
}
|
||||
|
||||
impl MihomoManager {
|
||||
pub fn get_clash_client_info() -> Option<(String, HeaderMap)> {
|
||||
let client = { Config::clash().latest_ref().get_client_info() };
|
||||
let server = format!("http://{}", client.server);
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("Content-Type", "application/json".parse().unwrap());
|
||||
if let Some(secret) = client.secret {
|
||||
let secret = format!("Bearer {secret}").parse().unwrap();
|
||||
headers.insert("Authorization", secret);
|
||||
}
|
||||
|
||||
Some((server, headers))
|
||||
}
|
||||
|
||||
// 已移除未使用的 get_clash_client_info_or_default 和 get_traffic_ws_url 方法
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod lightweight;
|
||||
pub mod mihomo;
|
||||
pub mod sysinfo;
|
||||
|
||||
@@ -242,3 +242,39 @@ pub fn get_encryption_key() -> Result<Vec<u8>> {
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn ensure_mihomo_safe_dir() -> Option<PathBuf> {
|
||||
["/var/tmp", "/tmp"]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.find(|path| path.exists())
|
||||
.or_else(|| {
|
||||
std::env::var_os("HOME").and_then(|home| {
|
||||
let home_config = PathBuf::from(home).join(".config");
|
||||
if home_config.exists() || fs::create_dir_all(&home_config).is_ok() {
|
||||
Some(home_config)
|
||||
} else {
|
||||
log::error!(target: "app", "Failed to create safe directory: {home_config:?}");
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn ipc_path() -> Result<PathBuf> {
|
||||
ensure_mihomo_safe_dir()
|
||||
.map(|base_dir| base_dir.join("verge").join("verge-mihomo.sock"))
|
||||
.or_else(|| {
|
||||
app_home_dir()
|
||||
.ok()
|
||||
.map(|dir| dir.join("verge").join("verge-mihomo.sock"))
|
||||
})
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to determine ipc path"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn ipc_path() -> Result<PathBuf> {
|
||||
Ok(PathBuf::from(r"\\.\pipe\verge-mihomo"))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ pub enum Type {
|
||||
Lightweight,
|
||||
Network,
|
||||
ProxyMode,
|
||||
Ipc,
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
@@ -37,6 +38,7 @@ impl fmt::Display for Type {
|
||||
Type::Lightweight => write!(f, "[Lightweight]"),
|
||||
Type::Network => write!(f, "[Network]"),
|
||||
Type::ProxyMode => write!(f, "[ProxMode]"),
|
||||
Type::Ipc => write!(f, "[IPC]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,32 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
log::trace!(target: "app", "启动内嵌服务器...");
|
||||
server::embed_server();
|
||||
|
||||
logging!(trace, Type::Core, true, "启动 IPC 监控服务...");
|
||||
// IPC 监控器将在首次调用时自动初始化
|
||||
|
||||
// // 启动测试线程,持续打印流量数据
|
||||
// logging!(info, Type::Core, true, "启动流量数据测试线程...");
|
||||
// AsyncHandler::spawn(|| async {
|
||||
// let mut interval = tokio::time::interval(std::time::Duration::from_secs(2));
|
||||
// loop {
|
||||
// interval.tick().await;
|
||||
|
||||
// let traffic_data = get_current_traffic().await;
|
||||
// let memory_data = get_current_memory().await;
|
||||
|
||||
// println!("=== Traffic Data Test (IPC) ===");
|
||||
// println!(
|
||||
// "Traffic - Up: {} bytes/s, Down: {} bytes/s, Last Updated: {:?}",
|
||||
// traffic_data.up_rate, traffic_data.down_rate, traffic_data.last_updated
|
||||
// );
|
||||
// println!(
|
||||
// "Memory - InUse: {} bytes, OSLimit: {:?}, Last Updated: {:?}",
|
||||
// memory_data.inuse, memory_data.oslimit, memory_data.last_updated
|
||||
// );
|
||||
// println!("==============================");
|
||||
// }
|
||||
// });
|
||||
|
||||
logging_error!(Type::Tray, true, tray::Tray::global().init());
|
||||
|
||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# LOCAL_SOCK="/Users/tunglies/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev.dev/mihomo.sock"
|
||||
LOCAL_SOCK="/Users/tunglies/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/mihomo.sock"
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "mihomo_api"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.12.22", features = ["json"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = { version = "1.46.1", features = ["rt", "macros", "time"] }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -1,147 +0,0 @@
|
||||
use reqwest::{Method, header::HeaderMap};
|
||||
use serde_json::{Value, json};
|
||||
use std::time::Duration;
|
||||
pub mod model;
|
||||
pub use model::MihomoManager;
|
||||
|
||||
impl MihomoManager {
|
||||
pub fn new(mihomo_server: String, headers: HeaderMap) -> Self {
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.default_headers(headers)
|
||||
.no_proxy()
|
||||
.timeout(Duration::from_secs(15))
|
||||
.pool_max_idle_per_host(5)
|
||||
.pool_idle_timeout(Duration::from_secs(15))
|
||||
.build()
|
||||
.expect("Failed to build reqwest client");
|
||||
|
||||
Self {
|
||||
mihomo_server,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_request(
|
||||
&self,
|
||||
method: Method,
|
||||
url: String,
|
||||
data: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let client_response = self
|
||||
.client
|
||||
.request(method.clone(), &url)
|
||||
.json(&data.unwrap_or(json!({})))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let response = match method {
|
||||
Method::PATCH => {
|
||||
let status = client_response.status();
|
||||
if status.as_u16() == 204 {
|
||||
json!({"code": 204})
|
||||
} else {
|
||||
client_response
|
||||
.json::<serde_json::Value>()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
}
|
||||
Method::PUT => json!(client_response.text().await.map_err(|e| e.to_string())?),
|
||||
_ => client_response
|
||||
.json::<serde_json::Value>()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?,
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn get_refresh_proxies(&self) -> Result<Value, String> {
|
||||
let url = format!("{}/proxies", self.mihomo_server);
|
||||
let proxies = self.send_request(Method::GET, url, None).await?;
|
||||
Ok(proxies)
|
||||
}
|
||||
|
||||
pub async fn get_providers_proxies(&self) -> Result<Value, String> {
|
||||
let url = format!("{}/providers/proxies", self.mihomo_server);
|
||||
let providers_proxies = self.send_request(Method::GET, url, None).await?;
|
||||
Ok(providers_proxies)
|
||||
}
|
||||
|
||||
pub async fn close_all_connections(&self) -> Result<(), String> {
|
||||
let url = format!("{}/connections", self.mihomo_server);
|
||||
let response = self.send_request(Method::DELETE, url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MihomoManager {
|
||||
pub async fn is_mihomo_running(&self) -> Result<(), String> {
|
||||
let url = format!("{}/version", self.mihomo_server);
|
||||
let _response = self.send_request(Method::GET, url, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), String> {
|
||||
let url = format!("{}/configs?force=true", self.mihomo_server);
|
||||
let payload = serde_json::json!({
|
||||
"path": clash_config_path,
|
||||
});
|
||||
let _response = self.send_request(Method::PUT, url, Some(payload)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn patch_configs(&self, config: serde_json::Value) -> Result<(), String> {
|
||||
let url = format!("{}/configs", self.mihomo_server);
|
||||
let response = self.send_request(Method::PATCH, url, Some(config)).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_proxy_delay(
|
||||
&self,
|
||||
name: &str,
|
||||
test_url: Option<String>,
|
||||
timeout: i32,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let test_url = test_url.unwrap_or("https://cp.cloudflare.com/generate_204".to_string());
|
||||
let url = format!(
|
||||
"{}/proxies/{}/delay?url={}&timeout={}",
|
||||
self.mihomo_server, name, test_url, timeout
|
||||
);
|
||||
let response = self.send_request(Method::GET, url, None).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn get_connections(&self) -> Result<serde_json::Value, String> {
|
||||
let url = format!("{}/connections", self.mihomo_server);
|
||||
let response = self.send_request(Method::GET, url, None).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn delete_connection(&self, id: &str) -> Result<(), String> {
|
||||
let url = format!("{}/connections/{}", self.mihomo_server, id);
|
||||
let response = self.send_request(Method::DELETE, url, None).await?;
|
||||
if response["code"] == 204 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(response["message"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown error")
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#[derive(Clone)]
|
||||
pub struct MihomoManager {
|
||||
pub(crate) mihomo_server: String,
|
||||
pub(crate) client: reqwest::Client,
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use reqwest::header::HeaderMap;
|
||||
|
||||
#[test]
|
||||
fn test_mihomo_manager_init() {
|
||||
let _ = mihomo_api::MihomoManager::new("url".into(), HeaderMap::new());
|
||||
assert_eq!(true, true);
|
||||
}
|
||||
Reference in New Issue
Block a user