diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c3e605b8..5830c57d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1078,6 +1078,7 @@ dependencies = [ "imageproc", "log", "log4rs", + "mihomo_api", "mockito", "nanoid", "network-interface", @@ -3723,9 +3724,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" dependencies = [ "serde", ] @@ -3896,6 +3897,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mihomo_api" +version = "0.0.0" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "mime" version = "0.3.17" @@ -5996,9 +6007,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] @@ -6038,9 +6049,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -6060,9 +6071,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa 1.0.14", "memchr", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 24808a49..cb71a46f 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -68,6 +68,7 @@ tokio-tungstenite = "0.26.1" futures = "0.3" sys-locale = "0.3.1" async-trait = "0.1.86" +mihomo_api = { path = "./src/crate_mihomo_api" } [target.'cfg(windows)'.dependencies] runas = "=1.2.0" @@ -126,3 +127,8 @@ crate-type = ["staticlib", "cdylib", "rlib"] env_logger = "0.11.0" mockito = "1.2.0" tempfile = "3.17.1" + +[workspace] +members = [ + "src/crate_mihomo_api" +] diff --git a/src-tauri/src/cmd/proxy.rs b/src-tauri/src/cmd/proxy.rs index e2ddf771..3e0f0823 100644 --- a/src-tauri/src/cmd/proxy.rs +++ b/src-tauri/src/cmd/proxy.rs @@ -1,35 +1,26 @@ use super::CmdResult; -use crate::module::mihomo::MihomoManager; -use tauri::async_runtime; +use crate::core; +use mihomo_api; + #[tauri::command] pub async fn get_proxies() -> CmdResult { - let proxies = async_runtime::spawn_blocking(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - let manager = MihomoManager::new(); - { - let mut write_guard = manager.write(); - rt.block_on(write_guard.refresh_proxies()); - } - let read_guard = manager.read(); - read_guard.fetch_proxies().clone() - }) - .await.map_err(|e| e.to_string())?; - Ok(proxies) + let (mihomo_server, _) = core::clash_api::clash_client_info().unwrap(); + let mihomo = mihomo_api::MihomoManager::new(mihomo_server); + Ok(mihomo + .refresh_proxies() + .await + .unwrap() + .get_proxies()) } #[tauri::command] pub async fn get_providers_proxies() -> CmdResult { - let providers_proxies = async_runtime::spawn_blocking(|| { - let rt = tokio::runtime::Runtime::new().unwrap(); - let manager = MihomoManager::new(); - { - let mut write_guard = manager.write(); - rt.block_on(write_guard.refresh_providers_proxies()); - } - let read_guard = manager.read(); - read_guard.fetch_providers_proxies().clone() - }) - .await.map_err(|e| e.to_string())?; - Ok(providers_proxies) -} \ No newline at end of file + let (mihomo_server, _) = core::clash_api::clash_client_info().unwrap(); + let mihomo = mihomo_api::MihomoManager::new(mihomo_server); + Ok(mihomo + .refresh_providers_proxies() + .await + .unwrap() + .get_providers_proxies()) +} diff --git a/src-tauri/src/config/api/mihomo.rs b/src-tauri/src/config/api/mihomo.rs deleted file mode 100644 index 46873b96..00000000 --- a/src-tauri/src/config/api/mihomo.rs +++ /dev/null @@ -1 +0,0 @@ -pub const MIHOMO_URL: &str = concat!("http://", "127.0.0.1", ":", "9097"); diff --git a/src-tauri/src/config/api/mod.rs b/src-tauri/src/config/api/mod.rs deleted file mode 100644 index 0f3c8057..00000000 --- a/src-tauri/src/config/api/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod mihomo; diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index a3ddace0..69b1b123 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -21,6 +21,3 @@ pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) { return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;"; } "#; - - -pub mod api; \ No newline at end of file diff --git a/src-tauri/src/core/clash_api.rs b/src-tauri/src/core/clash_api.rs index 60af1204..d6115b4e 100644 --- a/src-tauri/src/core/clash_api.rs +++ b/src-tauri/src/core/clash_api.rs @@ -76,7 +76,7 @@ pub async fn get_proxy_delay( } /// 根据clash info获取clash服务地址和请求头 -fn clash_client_info() -> Result<(String, HeaderMap)> { +pub fn clash_client_info() -> Result<(String, HeaderMap)> { let client = { Config::clash().data().get_client_info() }; let server = format!("http://{}", client.server); diff --git a/src-tauri/src/crate_mihomo_api/Cargo.toml b/src-tauri/src/crate_mihomo_api/Cargo.toml new file mode 100644 index 00000000..1a3ac3bf --- /dev/null +++ b/src-tauri/src/crate_mihomo_api/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mihomo_api" +edition = "2024" + +[features] +debug = [] + +[dependencies] +reqwest = { version = "0.12.12", features = ["json"] } +serde = { version = "1.0.218", features = ["derive"] } +serde_json = "1.0.140" + +[dev-dependencies] +tokio = { version = "1.43.0", features = ["rt", "macros"] } \ No newline at end of file diff --git a/src-tauri/src/crate_mihomo_api/src/lib.rs b/src-tauri/src/crate_mihomo_api/src/lib.rs new file mode 100644 index 00000000..0b503650 --- /dev/null +++ b/src-tauri/src/crate_mihomo_api/src/lib.rs @@ -0,0 +1,76 @@ +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; +pub mod model; +pub use model::{MihomoData, MihomoManager}; + +impl MihomoManager { + pub fn new(mihomo_server: String) -> Self { + Self { + mihomo_server, + data: Arc::new(Mutex::new(MihomoData { + proxies: serde_json::Value::Null, + providers_proxies: serde_json::Value::Null, + })), + } + } + + fn update_proxies(&self, proxies: serde_json::Value) { + let mut data = self.data.lock().unwrap(); + data.proxies = proxies; + } + + fn update_providers_proxies(&self, providers_proxies: serde_json::Value) { + let mut data = self.data.lock().unwrap(); + data.providers_proxies = providers_proxies; + } + + pub fn get_proxies(&self) -> serde_json::Value { + let data = self.data.lock().unwrap(); + data.proxies.clone() + } + + pub fn get_providers_proxies(&self) -> serde_json::Value { + let data = self.data.lock().unwrap(); + data.providers_proxies.clone() + } + + pub async fn refresh_proxies(&self) -> Result<&Self, String> { + let url = format!("{}/proxies", self.mihomo_server); + let response = reqwest::ClientBuilder::new() + .no_proxy() + .timeout(Duration::from_secs(3)) + .build() + .map_err(|e| e.to_string())? + .get(url) + .send() + .await + .map_err(|e| e.to_string())? + .json::() + .await + .map_err(|e| e.to_string())?; + let proxies = response; + self.update_proxies(proxies); + Ok(self) + } + + pub async fn refresh_providers_proxies(&self) -> Result<&Self, String> { + let url = format!("{}/providers/proxies", self.mihomo_server); + let response = reqwest::ClientBuilder::new() + .no_proxy() + .timeout(Duration::from_secs(3)) + .build() + .map_err(|e| e.to_string())? + .get(url) + .send() + .await + .map_err(|e| e.to_string())? + .json::() + .await + .map_err(|e| e.to_string())?; + let proxies = response; + self.update_providers_proxies(proxies); + Ok(self) + } +} diff --git a/src-tauri/src/crate_mihomo_api/src/model.rs b/src-tauri/src/crate_mihomo_api/src/model.rs new file mode 100644 index 00000000..4af4b1be --- /dev/null +++ b/src-tauri/src/crate_mihomo_api/src/model.rs @@ -0,0 +1,27 @@ +use std::sync::{Arc, Mutex}; + +pub struct MihomoData { + pub(crate) proxies: serde_json::Value, + pub(crate) providers_proxies: serde_json::Value, +} + +#[derive(Clone)] +pub struct MihomoManager { + pub(crate) mihomo_server: String, + pub(crate) data: Arc>, +} + +#[cfg(feature = "debug")] +impl Drop for MihomoData { + fn drop(&mut self) { + println!("Dropping MihomoData"); + } +} + +#[cfg(feature = "debug")] +impl Drop for MihomoManager { + fn drop(&mut self) { + println!("Dropping MihomoManager"); + } + +} \ No newline at end of file diff --git a/src-tauri/src/crate_mihomo_api/tests/test_mihomo_api.rs b/src-tauri/src/crate_mihomo_api/tests/test_mihomo_api.rs new file mode 100644 index 00000000..681c6b10 --- /dev/null +++ b/src-tauri/src/crate_mihomo_api/tests/test_mihomo_api.rs @@ -0,0 +1,28 @@ +use mihomo_api; + +#[test] +fn test_mihomo_manager_init() { + let manager = mihomo_api::MihomoManager::new("url".into()); + assert_eq!(manager.get_proxies(), serde_json::Value::Null); + assert_eq!(manager.get_providers_proxies(), serde_json::Value::Null); +} + +#[tokio::test] +async fn test_refresh_proxies() { + let manager = mihomo_api::MihomoManager::new("http://127.0.0.1:9097".into()); + let manager = manager.refresh_proxies().await.unwrap(); + let proxies = manager.get_proxies(); + let providers = manager.get_providers_proxies(); + assert_ne!(proxies, serde_json::Value::Null); + assert_eq!(providers, serde_json::Value::Null); +} + +#[tokio::test] +async fn test_refresh_providers_proxies() { + let manager = mihomo_api::MihomoManager::new("http://127.0.0.1:9097".into()); + let manager = manager.refresh_providers_proxies().await.unwrap(); + let proxies = manager.get_proxies(); + let providers = manager.get_providers_proxies(); + assert_eq!(proxies, serde_json::Value::Null); + assert_ne!(providers, serde_json::Value::Null); +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ccfddebe..b8a9ee33 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,7 +4,6 @@ mod core; mod enhance; mod feat; mod utils; -mod model; mod module; use crate::core::hotkey; use crate::utils::{resolve, resolve::resolve_scheme, server}; diff --git a/src-tauri/src/model/api/common.rs b/src-tauri/src/model/api/common.rs deleted file mode 100644 index cd83945c..00000000 --- a/src-tauri/src/model/api/common.rs +++ /dev/null @@ -1,20 +0,0 @@ -use reqwest::Client; - -#[allow(unused)] -pub(crate) struct ApiCaller<'a> { - pub(crate) url: &'a str, - pub(crate) client: Client, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_api_caller() { - let _api_caller = ApiCaller { - url: "https://example.com", - client: Client::new(), - }; - } -} diff --git a/src-tauri/src/model/api/mihomo.rs b/src-tauri/src/model/api/mihomo.rs deleted file mode 100644 index ad14be62..00000000 --- a/src-tauri/src/model/api/mihomo.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::common::ApiCaller; - -pub struct MihomoAPICaller { - pub(crate) caller: ApiCaller<'static>, -} diff --git a/src-tauri/src/model/api/mod.rs b/src-tauri/src/model/api/mod.rs deleted file mode 100644 index bee8fa98..00000000 --- a/src-tauri/src/model/api/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod common; -pub mod mihomo; diff --git a/src-tauri/src/model/mod.rs b/src-tauri/src/model/mod.rs deleted file mode 100644 index c78317c1..00000000 --- a/src-tauri/src/model/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod api; \ No newline at end of file diff --git a/src-tauri/src/module/api/common.rs b/src-tauri/src/module/api/common.rs deleted file mode 100644 index dab4cc69..00000000 --- a/src-tauri/src/module/api/common.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::model::api::common::ApiCaller; -use async_trait::async_trait; -use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue}, - RequestBuilder, -}; -use serde::de::DeserializeOwned; - -impl<'a> ApiCaller<'a> { - pub async fn send_request( - &self, - method: &str, - path: &str, - body: Option<&str>, - headers: Option>, - ) -> Result { - let full_url = format!("{}{}", self.url, path); // 拼接完整 URL - let mut request: RequestBuilder = match method { - "GET" => self.client.get(&full_url), - "POST" => self - .client - .post(&full_url) - .body(body.unwrap_or("").to_string()), - "PUT" => self - .client - .put(&full_url) - .body(body.unwrap_or("").to_string()), - "DELETE" => self.client.delete(&full_url), - _ => return Err("Unsupported HTTP method".to_string()), - }; - - // 处理 headers - if let Some(hdrs) = headers { - let mut header_map = HeaderMap::new(); - for (key, value) in hdrs { - if let (Ok(header_name), Ok(header_value)) = ( - HeaderName::from_bytes(key.as_bytes()), - HeaderValue::from_str(value), - ) { - header_map.insert(header_name, header_value); - } - } - request = request.headers(header_map); - } - - let response = request.send().await.map_err(|e| e.to_string())?; - response.text().await.map_err(|e| e.to_string()) - } -} - -#[allow(unused)] -#[async_trait] -pub trait ApiCallerTrait: Send + Sync { - async fn call_api( - &self, - method: &str, - path: &str, - body: Option<&str>, - headers: Option> - ) -> Result - where - T: DeserializeOwned + Send + Sync; - - fn parse_json_response(json_str: &str) -> Result - where - T: DeserializeOwned, - { - serde_json::from_str(json_str).map_err(|e| e.to_string()) - } -} diff --git a/src-tauri/src/module/api/mihomo.rs b/src-tauri/src/module/api/mihomo.rs deleted file mode 100644 index 09b6d0fa..00000000 --- a/src-tauri/src/module/api/mihomo.rs +++ /dev/null @@ -1,108 +0,0 @@ -use super::common::ApiCallerTrait; -use crate::config::api::mihomo::MIHOMO_URL; -use crate::model::api::common::ApiCaller; -use crate::model::api::mihomo::MihomoAPICaller; - -use async_trait::async_trait; -use once_cell::sync::OnceCell; -use parking_lot::RwLock; -use reqwest::Client; -use serde::de::DeserializeOwned; -use std::sync::Arc; - -impl MihomoAPICaller { - #[allow(dead_code)] - pub fn new() -> Arc> { - static INSTANCE: OnceCell>> = OnceCell::new(); - INSTANCE - .get_or_init(|| { - let client = Client::new(); - Arc::new(RwLock::new(MihomoAPICaller { - caller: ApiCaller { - url: MIHOMO_URL, - client, - }, - })) - }) - .clone() - } -} - -#[async_trait] -impl ApiCallerTrait for MihomoAPICaller { - async fn call_api( - &self, - method: &str, - path: &str, - body: Option<&str>, - headers: Option>, - ) -> Result - where - T: DeserializeOwned + Send + Sync, - { - let response = self - .caller - .send_request(method, path, body, headers) - .await - .map_err(|e| e.to_string())?; - Self::parse_json_response::(&response) - } -} - -#[allow(unused)] -impl MihomoAPICaller { - pub async fn get_proxies() -> Result { - Self::new() - .read() - .call_api("GET", "/proxies", None, None) - .await - } - - pub async fn get_providers_proxies() -> Result { - Self::new() - .read() - .call_api("GET", "/providers/proxies", None, None) - .await - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_mihomo_api_singleton() { - let mihomo_api_caller1 = MihomoAPICaller::new(); - let mihomo_api_caller2 = MihomoAPICaller::new(); - assert!(Arc::ptr_eq(&mihomo_api_caller1, &mihomo_api_caller2)); - } - - #[tokio::test] - async fn test_mihomo_api_version() { - let mihomo_caller = MihomoAPICaller::new(); - let response: Result = mihomo_caller - .read() - .call_api("GET", "/version", None, None) - .await; - assert!(response.is_ok()); - } - - #[tokio::test] - async fn test_mihomo_get_proxies() { - let response = MihomoAPICaller::get_proxies().await; - assert!(response.is_ok()); - if let Ok(proxies) = &response { - assert!(!proxies.get("proxies").is_none()); - } - } - - #[tokio::test] - async fn test_mihomo_get_providers_proxies() { - let response = MihomoAPICaller::get_providers_proxies().await; - println!("{:?}", response); - assert!(response.is_ok()); - if let Ok(providers_proxies) = &response { - assert!(!providers_proxies.get("providers").is_none()); - } - } -} diff --git a/src-tauri/src/module/api/mod.rs b/src-tauri/src/module/api/mod.rs deleted file mode 100644 index bee8fa98..00000000 --- a/src-tauri/src/module/api/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod common; -pub mod mihomo; diff --git a/src-tauri/src/module/mihomo.rs b/src-tauri/src/module/mihomo.rs deleted file mode 100644 index c1201d91..00000000 --- a/src-tauri/src/module/mihomo.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::model::api::mihomo::MihomoAPICaller; -use once_cell::sync::OnceCell; -use parking_lot::RwLock; -use std::sync::Arc; - -#[allow(unused)] -pub struct MihomoManager { - proxies: serde_json::Value, - providers_proxies: serde_json::Value, -} - -#[allow(unused)] -impl MihomoManager { - pub fn new() -> Arc> { - static INSTANCE: OnceCell>> = OnceCell::new(); - INSTANCE - .get_or_init(|| { - Arc::new(RwLock::new(MihomoManager { - proxies: serde_json::Value::Null, - providers_proxies: serde_json::Value::Null, - })) - }) - .clone() - } - - pub fn fetch_proxies(&self) -> &serde_json::Value { - &self.proxies - } - - pub fn fetch_providers_proxies(&self) -> &serde_json::Value { - &self.providers_proxies - } - - pub async fn refresh_proxies(&mut self) { - match MihomoAPICaller::get_proxies().await { - Ok(proxies) => { - self.proxies = proxies; - } - Err(e) => { - log::error!("Failed to get proxies: {}", e); - } - } - } - - pub async fn refresh_providers_proxies(&mut self) { - match MihomoAPICaller::get_providers_proxies().await { - Ok(providers_proxies) => { - self.providers_proxies = providers_proxies; - }, - Err(e) => { - log::error!("Failed to get providers proxies: {}", e); - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - #[tokio::test] - async fn test_mihomo_manager_singleton() { - let manager1 = MihomoManager::new(); - let manager2 = MihomoManager::new(); - - assert!( - Arc::ptr_eq(&manager1, &manager2), - "Should return same instance" - ); - - let manager = manager1.read(); - assert!(manager.proxies.is_null()); - assert!(manager.providers_proxies.is_null()); - } - - #[tokio::test] - async fn test_refresh_proxies() { - let manager = MihomoManager::new(); - - // Test initial state - { - let data = manager.read(); - assert!(data.proxies.is_null()); - } - - // Test refresh - { - let mut data = manager.write(); - data.refresh_proxies().await; - // Note: Since this depends on external API call, - // we can only verify that the refresh call completes - // without panicking. For more thorough testing, - // we would need to mock the API caller. - } - } - - #[tokio::test] - async fn test_refresh_providers_proxies() { - let manager = MihomoManager::new(); - - // Test initial state - { - let data = manager.read(); - assert!(data.providers_proxies.is_null()); - } - - // Test refresh - { - let mut data = manager.write(); - data.refresh_providers_proxies().await; - // Note: Since this depends on external API call, - // we can only verify that the refresh call completes - // without panicking. For more thorough testing, - // we would need to mock the API caller. - } - } - - #[tokio::test] - async fn test_fetch_proxies() { - let manager = MihomoManager::new(); - - // Test initial state - { - let data = manager.read(); - let proxies = data.fetch_proxies(); - assert!(proxies.is_null()); - } - - // Test after refresh - { - let mut data = manager.write(); - data.refresh_proxies().await; - let _proxies = data.fetch_proxies(); - // Can only verify the method returns without panicking - // Would need API mocking for more thorough testing - } - } - - #[tokio::test] - async fn test_fetch_providers_proxies() { - let manager = MihomoManager::new(); - - // Test initial state - { - let data = manager.read(); - let providers_proxies = data.fetch_providers_proxies(); - assert!(providers_proxies.is_null()); - } - - // Test after refresh - { - let mut data = manager.write(); - data.refresh_providers_proxies().await; - let _providers_proxies = data.fetch_providers_proxies(); - // Can only verify the method returns without panicking - // Would need API mocking for more thorough testing - } - } -} diff --git a/src-tauri/src/module/mod.rs b/src-tauri/src/module/mod.rs index 0692611c..f146b310 100644 --- a/src-tauri/src/module/mod.rs +++ b/src-tauri/src/module/mod.rs @@ -1,3 +1 @@ -pub mod api; -pub mod sysinfo; -pub mod mihomo; \ No newline at end of file +pub mod sysinfo; \ No newline at end of file