diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c59baf01..148a7f38 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -142,3 +142,52 @@ crate-type = ["staticlib", "cdylib", "rlib"] [dev-dependencies] criterion = "0.7.0" tempfile = "3.20.0" + +[lints.clippy] +# Core categories - most important for code safety and correctness +correctness = { level = "deny", priority = -1 } +suspicious = { level = "deny", priority = -1 } + +# Critical safety lints - warn for now due to extensive existing usage +unwrap_used = "warn" +expect_used = "warn" +panic = "deny" +unimplemented = "deny" + +# Development quality lints +todo = "warn" +dbg_macro = "warn" +#print_stdout = "warn" +#print_stderr = "warn" + +# Performance lints for proxy application +#clone_on_ref_ptr = "warn" +#rc_clone_in_vec_init = "warn" +#large_stack_arrays = "warn" +#large_const_arrays = "warn" + +# Security lints +#integer_division = "warn" +#lossy_float_literal = "warn" +#default_numeric_fallback = "warn" + +# Mutex and async lints - strict control +#async_yields_async = "deny" # Prevents missing await in async blocks +#mutex_atomic = "deny" # Use atomics instead of Mutex +#mutex_integer = "deny" # Use AtomicInt instead of Mutex +#rc_mutex = "deny" # Single-threaded Rc with Mutex is wrong +#unused_async = "allow" # Too many false positives in Tauri/framework code + +# Common style improvements +#redundant_else = "allow" # Too many in existing code +#needless_continue = "allow" # Too many in existing code +#needless_raw_string_hashes = "allow" # Too many in existing code + +# Disable noisy categories for existing codebase but keep them available +#style = { level = "allow", priority = -1 } +#complexity = { level = "allow", priority = -1 } +#perf = { level = "allow", priority = -1 } +#pedantic = { level = "allow", priority = -1 } +#nursery = { level = "allow", priority = -1 } +#restriction = { level = "allow", priority = -1 } + diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 35cd29db..05e771af 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -44,7 +44,7 @@ pub async fn patch_clash_mode(payload: String) -> CmdResult { /// 切换Clash核心 #[tauri::command] pub async fn change_clash_core(clash_core: String) -> CmdResult> { - log::info!(target: "app", "changing core to {clash_core}"); + logging!(info, Type::Config, "changing core to {clash_core}"); match CoreManager::global() .change_core(Some(clash_core.clone())) @@ -54,14 +54,18 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult> // 切换内核后重启内核 match CoreManager::global().restart_core().await { Ok(_) => { - log::info!(target: "app", "core changed and restarted to {clash_core}"); + logging!( + info, + Type::Core, + "core changed and restarted to {clash_core}" + ); handle::Handle::notice_message("config_core::change_success", &clash_core); handle::Handle::refresh_clash(); Ok(None) } Err(err) => { let error_msg = format!("Core changed but failed to restart: {err}"); - log::error!(target: "app", "{error_msg}"); + logging!(error, Type::Core, "{error_msg}"); handle::Handle::notice_message("config_core::change_error", &error_msg); Ok(Some(error_msg)) } @@ -69,7 +73,7 @@ pub async fn change_clash_core(clash_core: String) -> CmdResult> } Err(err) => { let error_msg = err.to_string(); - log::error!(target: "app", "failed to change core: {error_msg}"); + logging!(error, Type::Core, "failed to change core: {error_msg}"); handle::Handle::notice_message("config_core::change_error", &error_msg); Ok(Some(error_msg)) } @@ -141,7 +145,7 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult { // 保存DNS配置到文件 let yaml_str = serde_yaml::to_string(&dns_config).map_err(|e| e.to_string())?; fs::write(&dns_path, yaml_str).map_err(|e| e.to_string())?; - log::info!(target: "app", "DNS config saved to {dns_path:?}"); + logging!(info, Type::Config, "DNS config saved to {dns_path:?}"); Ok(()) } @@ -162,20 +166,20 @@ pub fn apply_dns_config(apply: bool) -> CmdResult { let dns_path = match dirs::app_home_dir() { Ok(path) => path.join("dns_config.yaml"), Err(e) => { - log::error!(target: "app", "Failed to get home dir: {e}"); + logging!(error, Type::Config, "Failed to get home dir: {e}"); return; } }; if !dns_path.exists() { - log::warn!(target: "app", "DNS config file not found"); + logging!(warn, Type::Config, "DNS config file not found"); return; } let dns_yaml = match std::fs::read_to_string(&dns_path) { Ok(content) => content, Err(e) => { - log::error!(target: "app", "Failed to read DNS config: {e}"); + logging!(error, Type::Config, "Failed to read DNS config: {e}"); return; } }; @@ -188,12 +192,12 @@ pub fn apply_dns_config(apply: bool) -> CmdResult { patch } Err(e) => { - log::error!(target: "app", "Failed to parse DNS config: {e}"); + logging!(error, Type::Config, "Failed to parse DNS config: {e}"); return; } }; - log::info!(target: "app", "Applying DNS config from file"); + logging!(info, Type::Config, "Applying DNS config from file"); // 重新生成配置,确保DNS配置被正确应用 // 这里不调用patch_clash以避免将DNS配置写入config.yaml @@ -203,36 +207,52 @@ pub fn apply_dns_config(apply: bool) -> CmdResult { // 首先重新生成配置 if let Err(err) = Config::generate().await { - log::error!(target: "app", "Failed to regenerate config with DNS: {err}"); + logging!( + error, + Type::Config, + "Failed to regenerate config with DNS: {err}" + ); return; } // 然后应用新配置 if let Err(err) = CoreManager::global().update_config().await { - log::error!(target: "app", "Failed to apply config with DNS: {err}"); + logging!( + error, + Type::Config, + "Failed to apply config with DNS: {err}" + ); } else { - log::info!(target: "app", "DNS config successfully applied"); + logging!(info, Type::Config, "DNS config successfully applied"); handle::Handle::refresh_clash(); } } else { // 当关闭DNS设置时,不需要对配置进行任何修改 // 直接重新生成配置,让enhance函数自动跳过DNS配置的加载 - log::info!(target: "app", "DNS settings disabled, regenerating config"); + logging!( + info, + Type::Config, + "DNS settings disabled, regenerating config" + ); // 重新生成配置 if let Err(err) = Config::generate().await { - log::error!(target: "app", "Failed to regenerate config: {err}"); + logging!(error, Type::Config, "Failed to regenerate config: {err}"); return; } // 应用新配置 match CoreManager::global().update_config().await { Ok(_) => { - log::info!(target: "app", "Config regenerated successfully"); + logging!(info, Type::Config, "Config regenerated successfully"); handle::Handle::refresh_clash(); } Err(err) => { - log::error!(target: "app", "Failed to apply regenerated config: {err}"); + logging!( + error, + Type::Config, + "Failed to apply regenerated config: {err}" + ); } } } @@ -307,7 +327,10 @@ pub async fn get_clash_config() -> CmdResult { let key = ProxyRequestCache::make_key("clash_config", "default"); let value = cache .get_or_fetch(key, CONFIG_REFRESH_INTERVAL, || async { - manager.get_config().await.expect("fetch failed") + manager.get_config().await.unwrap_or_else(|e| { + logging!(error, Type::Cmd, "Failed to fetch clash config: {e}"); + serde_json::Value::Object(serde_json::Map::new()) + }) }) .await; Ok((*value).clone()) diff --git a/src-tauri/src/cmd/media_unlock_checker.rs b/src-tauri/src/cmd/media_unlock_checker.rs index 1b4c056b..11bb9b0e 100644 --- a/src-tauri/src/cmd/media_unlock_checker.rs +++ b/src-tauri/src/cmd/media_unlock_checker.rs @@ -1,3 +1,4 @@ +use crate::{logging, utils::logging::Type}; use chrono::Local; use regex::Regex; use reqwest::Client; @@ -250,7 +251,23 @@ async fn check_gemini(client: &Client) -> UnlockItem { let status = if is_ok { "Yes" } else { "No" }; // 尝试提取国家代码 - let re = Regex::new(r#",2,1,200,"([A-Z]{3})""#).unwrap(); + let re = match Regex::new(r#",2,1,200,"([A-Z]{3})""#) { + Ok(re) => re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Gemini regex: {}", + e + ); + return UnlockItem { + name: "Gemini".to_string(), + status: "Failed".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let region = re.captures(&body).and_then(|caps| { caps.get(1).map(|m| { let country_code = m.as_str(); @@ -303,7 +320,23 @@ async fn check_youtube_premium(client: &Client) -> UnlockItem { } } else if body_lower.contains("ad-free") { // 尝试解析国家代码 - let re = Regex::new(r#"id="country-code"[^>]*>([^<]+)<"#).unwrap(); + let re = match Regex::new(r#"id="country-code"[^>]*>([^<]+)<"#) { + Ok(re) => re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile YouTube Premium regex: {}", + e + ); + return UnlockItem { + name: "Youtube Premium".to_string(), + status: "Failed".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let region = re.captures(&body).and_then(|caps| { caps.get(1).map(|m| { let country_code = m.as_str().trim(); @@ -350,11 +383,16 @@ async fn check_bahamut_anime(client: &Client) -> UnlockItem { let cookie_store = Arc::new(reqwest::cookie::Jar::default()); // 使用带Cookie的客户端 - let client_with_cookies = reqwest::Client::builder() + let client_with_cookies = match reqwest::Client::builder() .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36") .cookie_provider(Arc::clone(&cookie_store)) - .build() - .unwrap_or_else(|_| client.clone()); + .build() { + Ok(client) => client, + Err(e) => { + logging!(error, Type::Network, "Failed to create client with cookies for Bahamut Anime: {}", e); + client.clone() + } + }; // 第一步:获取设备ID (会自动保存Cookie) let device_url = "https://ani.gamer.com.tw/ajax/getdeviceid.php"; @@ -363,10 +401,21 @@ async fn check_bahamut_anime(client: &Client) -> UnlockItem { match response.text().await { Ok(text) => { // 使用正则提取deviceid - let re = Regex::new(r#""deviceid"\s*:\s*"([^"]+)"#).unwrap(); - re.captures(&text) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) - .unwrap_or_default() + match Regex::new(r#""deviceid"\s*:\s*"([^"]+)"#) { + Ok(re) => re + .captures(&text) + .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) + .unwrap_or_default(), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile deviceid regex for Bahamut Anime: {}", + e + ); + String::new() + } + } } Err(_) => String::new(), } @@ -421,17 +470,25 @@ async fn check_bahamut_anime(client: &Client) -> UnlockItem { .await { Ok(response) => match response.text().await { - Ok(body) => { - let region_re = Regex::new(r#"data-geo="([^"]+)"#).unwrap(); - region_re + Ok(body) => match Regex::new(r#"data-geo="([^"]+)"#) { + Ok(region_re) => region_re .captures(&body) .and_then(|caps| caps.get(1)) .map(|m| { let country_code = m.as_str(); let emoji = country_code_to_emoji(country_code); format!("{emoji}{country_code}") - }) - } + }), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile region regex for Bahamut Anime: {}", + e + ); + None + } + }, Err(_) => None, }, Err(_) => None, @@ -495,8 +552,40 @@ async fn check_netflix(client: &Client) -> UnlockItem { } // 获取状态码 - let status1 = result1.unwrap().status().as_u16(); - let status2 = result2.unwrap().status().as_u16(); + let status1 = match result1 { + Ok(response) => response.status().as_u16(), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to get Netflix response 1: {}", + e + ); + return UnlockItem { + name: "Netflix".to_string(), + status: "Failed".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; + let status2 = match result2 { + Ok(response) => response.status().as_u16(), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to get Netflix response 2: {}", + e + ); + return UnlockItem { + name: "Netflix".to_string(), + status: "Failed".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; // 根据状态码判断解锁状况 if status1 == 404 && status2 == 404 { @@ -685,7 +774,23 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { }; } - let device_response = device_result.unwrap(); + let device_response = match device_result { + Ok(response) => response, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to get Disney+ device response: {}", + e + ); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (Network Connection)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; // 检查是否 403 错误 if device_response.status().as_u16() == 403 { @@ -710,7 +815,23 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { }; // 提取 assertion - let re = Regex::new(r#""assertion"\s*:\s*"([^"]+)"#).unwrap(); + let re = match Regex::new(r#""assertion"\s*:\s*"([^"]+)"#) { + Ok(re) => re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile assertion regex for Disney+: {}", + e + ); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (Regex Error)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let assertion = match re.captures(&device_body) { Some(caps) => caps.get(1).map(|m| m.as_str().to_string()), None => None, @@ -729,7 +850,18 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { let token_url = "https://disney.api.edge.bamgrid.com/token"; // 构建请求体 - 使用表单数据格式而非 JSON - let assertion_str = assertion.unwrap(); + let assertion_str = match assertion { + Some(assertion) => assertion, + None => { + logging!(error, Type::Network, "No assertion found for Disney+"); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (No Assertion)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let token_body = [ ( "grant_type", @@ -762,7 +894,23 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { }; } - let token_response = token_result.unwrap(); + let token_response = match token_result { + Ok(response) => response, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to get Disney+ token response: {}", + e + ); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (Network Connection)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let token_status = token_response.status(); // 保存原始响应用于调试 @@ -798,10 +946,20 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { .map(|s| s.to_string()), Err(_) => { // 如果 JSON 解析失败,尝试使用正则表达式 - let refresh_token_re = Regex::new(r#""refresh_token"\s*:\s*"([^"]+)"#).unwrap(); - refresh_token_re - .captures(&token_body_text) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) + match Regex::new(r#""refresh_token"\s*:\s*"([^"]+)"#) { + Ok(refresh_token_re) => refresh_token_re + .captures(&token_body_text) + .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile refresh_token regex for Disney+: {}", + e + ); + None + } + } } }; @@ -825,7 +983,7 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { // GraphQL API 通常接受 JSON 格式 let graphql_payload = format!( r#"{{"query":"mutation refreshToken($input: RefreshTokenInput!) {{ refreshToken(refreshToken: $input) {{ activeSession {{ sessionId }} }} }}","variables":{{"input":{{"refreshToken":"{}"}}}}}}"#, - refresh_token.unwrap() + refresh_token.unwrap_or_default() ); let graphql_result = client @@ -857,21 +1015,56 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { }; // 解析 GraphQL 响应获取区域信息 - let graphql_response = graphql_result.unwrap(); + let graphql_response = match graphql_result { + Ok(response) => response, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to get Disney+ GraphQL response: {}", + e + ); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (Network Connection)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let graphql_status = graphql_response.status(); - let graphql_body_text = (graphql_response.text().await).unwrap_or_default(); + let graphql_body_text = match graphql_response.text().await { + Ok(text) => text, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to read Disney+ GraphQL response text: {}", + e + ); + String::new() + } + }; // 如果 GraphQL 响应为空或明显错误,尝试直接获取区域信息 if graphql_body_text.is_empty() || graphql_status.as_u16() >= 400 { // 尝试直接从主页获取区域信息 let region_from_main = match client.get("https://www.disneyplus.com/").send().await { Ok(response) => match response.text().await { - Ok(body) => { - let region_re = Regex::new(r#"region"\s*:\s*"([^"]+)"#).unwrap(); - region_re + Ok(body) => match Regex::new(r#"region"\s*:\s*"([^"]+)"#) { + Ok(region_re) => region_re .captures(&body) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) - } + .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Disney+ main page region regex: {}", + e + ); + None + } + }, Err(_) => None, }, Err(_) => None, @@ -913,13 +1106,45 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { } // 提取国家代码 - let region_re = Regex::new(r#""countryCode"\s*:\s*"([^"]+)"#).unwrap(); + let region_re = match Regex::new(r#""countryCode"\s*:\s*"([^"]+)"#) { + Ok(re) => re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Disney+ countryCode regex: {}", + e + ); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (Regex Error)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let region_code = region_re .captures(&graphql_body_text) .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())); // 提取支持状态 - let supported_re = Regex::new(r#""inSupportedLocation"\s*:\s*(false|true)"#).unwrap(); + let supported_re = match Regex::new(r#""inSupportedLocation"\s*:\s*(false|true)"#) { + Ok(re) => re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Disney+ supported location regex: {}", + e + ); + return UnlockItem { + name: "Disney+".to_string(), + status: "Failed (Regex Error)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let in_supported_location = supported_re .captures(&graphql_body_text) .and_then(|caps| caps.get(1).map(|m| m.as_str() == "true")); @@ -929,12 +1154,20 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { // 尝试直接从主页获取区域信息 let region_from_main = match client.get("https://www.disneyplus.com/").send().await { Ok(response) => match response.text().await { - Ok(body) => { - let region_re = Regex::new(r#"region"\s*:\s*"([^"]+)"#).unwrap(); - region_re + Ok(body) => match Regex::new(r#"region"\s*:\s*"([^"]+)"#) { + Ok(region_re) => region_re .captures(&body) - .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())) - } + .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())), + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Disney+ main page region regex: {}", + e + ); + None + } + }, Err(_) => None, }, Err(_) => None, @@ -958,7 +1191,18 @@ async fn check_disney_plus(client: &Client) -> UnlockItem { }; } - let region = region_code.unwrap(); + let region = match region_code { + Some(code) => code, + None => { + logging!(error, Type::Network, "No region code found for Disney+"); + return UnlockItem { + name: "Disney+".to_string(), + status: "No".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; // 判断日本地区 if region == "JP" { @@ -1028,13 +1272,47 @@ async fn check_prime_video(client: &Client) -> UnlockItem { } // 解析响应内容 - match result.unwrap().text().await { + let response = match result { + Ok(response) => response, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to get Prime Video response: {}", + e + ); + return UnlockItem { + name: "Prime Video".to_string(), + status: "Failed (Network Connection)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; + + match response.text().await { Ok(body) => { // 检查是否被地区限制 let is_blocked = body.contains("isServiceRestricted"); // 提取地区信息 - let region_re = Regex::new(r#""currentTerritory":"([^"]+)"#).unwrap(); + let region_re = match Regex::new(r#""currentTerritory":"([^"]+)"#) { + Ok(re) => re, + Err(e) => { + logging!( + error, + Type::Network, + "Failed to compile Prime Video region regex: {}", + e + ); + return UnlockItem { + name: "Prime Video".to_string(), + status: "Failed (Regex Error)".to_string(), + region: None, + check_time: Some(get_local_date_string()), + }; + } + }; let region_code = region_re .captures(&body) .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string())); @@ -1287,9 +1565,17 @@ pub async fn check_media_unlock() -> Result, String> { } // 获取所有结果 - let results = Arc::try_unwrap(results) - .expect("无法获取结果,可能仍有引用存在") - .into_inner(); + let results = match Arc::try_unwrap(results) { + Ok(mutex) => mutex.into_inner(), + Err(_) => { + logging!( + error, + Type::Network, + "Failed to unwrap results Arc, references still exist" + ); + return Err("Failed to collect results".to_string()); + } + }; Ok(results) } diff --git a/src-tauri/src/cmd/proxy.rs b/src-tauri/src/cmd/proxy.rs index 007118a9..1b72627f 100644 --- a/src-tauri/src/cmd/proxy.rs +++ b/src-tauri/src/cmd/proxy.rs @@ -1,5 +1,5 @@ use super::CmdResult; -use crate::{ipc::IpcManager, state::proxy::ProxyRequestCache}; +use crate::{ipc::IpcManager, logging, state::proxy::ProxyRequestCache, utils::logging::Type}; use std::time::Duration; const PROXIES_REFRESH_INTERVAL: Duration = Duration::from_secs(60); @@ -12,7 +12,10 @@ pub async fn get_proxies() -> CmdResult { let key = ProxyRequestCache::make_key("proxies", "default"); let value = cache .get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async { - manager.get_proxies().await.expect("fetch failed") + manager.get_proxies().await.unwrap_or_else(|e| { + logging!(error, Type::Cmd, "Failed to fetch proxies: {e}"); + serde_json::Value::Object(serde_json::Map::new()) + }) }) .await; Ok((*value).clone()) @@ -34,7 +37,10 @@ pub async fn get_providers_proxies() -> CmdResult { let key = ProxyRequestCache::make_key("providers", "default"); let value = cache .get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async { - manager.get_providers_proxies().await.expect("fetch failed") + manager.get_providers_proxies().await.unwrap_or_else(|e| { + logging!(error, Type::Cmd, "Failed to fetch provider proxies: {e}"); + serde_json::Value::Object(serde_json::Map::new()) + }) }) .await; Ok((*value).clone()) diff --git a/src-tauri/src/cmd/save_profile.rs b/src-tauri/src/cmd/save_profile.rs index 9d9bdfa0..4b264e17 100644 --- a/src-tauri/src/cmd/save_profile.rs +++ b/src-tauri/src/cmd/save_profile.rs @@ -29,7 +29,8 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR }; // 保存新的配置文件 - wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?; + let file_data = file_data.ok_or("file_data is None")?; + wrap_err!(fs::write(&file_path, &file_data))?; let file_path_str = file_path.to_string_lossy().to_string(); logging!( @@ -139,17 +140,29 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR || (!file_path_str.ends_with(".js") && !is_script_error) { // 普通YAML错误使用YAML通知处理 - log::info!(target: "app", "[cmd配置save] YAML配置文件验证失败,发送通知"); + logging!( + info, + Type::Config, + "[cmd配置save] YAML配置文件验证失败,发送通知" + ); let result = (false, error_msg.clone()); crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件"); } else if is_script_error { // 脚本错误使用专门的通知处理 - log::info!(target: "app", "[cmd配置save] 脚本文件验证失败,发送通知"); + logging!( + info, + Type::Config, + "[cmd配置save] 脚本文件验证失败,发送通知" + ); let result = (false, error_msg.clone()); crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件"); } else { // 普通配置错误使用一般通知 - log::info!(target: "app", "[cmd配置save] 其他类型验证失败,发送一般通知"); + logging!( + info, + Type::Config, + "[cmd配置save] 其他类型验证失败,发送一般通知" + ); handle::Handle::notice_message("config_validate::error", &error_msg); } diff --git a/src-tauri/src/cmd/system.rs b/src-tauri/src/cmd/system.rs index 6dcc312a..c7cc0478 100644 --- a/src-tauri/src/cmd/system.rs +++ b/src-tauri/src/cmd/system.rs @@ -1,7 +1,9 @@ use super::CmdResult; use crate::{ core::{handle, CoreManager}, + logging, module::sysinfo::PlatformSpecification, + utils::logging::Type, }; use once_cell::sync::Lazy; use std::{ @@ -26,10 +28,12 @@ pub async fn export_diagnostic_info() -> CmdResult<()> { let sysinfo = PlatformSpecification::new_async().await; let info = format!("{sysinfo:?}"); - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = handle::Handle::global() + .app_handle() + .ok_or("Failed to get app handle")?; let cliboard = app_handle.clipboard(); if cliboard.write_text(info).is_err() { - log::error!(target: "app", "Failed to write to clipboard"); + logging!(error, Type::System, "Failed to write to clipboard"); } Ok(()) } diff --git a/src-tauri/src/config/clash.rs b/src-tauri/src/config/clash.rs index 81a91d34..dc8e92a2 100644 --- a/src-tauri/src/config/clash.rs +++ b/src-tauri/src/config/clash.rs @@ -19,7 +19,9 @@ impl IClashTemp { Ok(mut map) => { template.0.keys().for_each(|key| { if !map.contains_key(key) { - map.insert(key.clone(), template.0.get(key).unwrap().clone()); + if let Some(value) = template.0.get(key) { + map.insert(key.clone(), value.clone()); + } } }); // 确保 secret 字段存在且不为空 @@ -307,7 +309,13 @@ impl IClashTemp { pub fn guard_external_controller_ipc() -> String { // 总是使用当前的 IPC 路径,确保配置文件与运行时路径一致 - path_to_str(&ipc_path().unwrap()).unwrap().to_string() + ipc_path() + .ok() + .and_then(|path| path_to_str(&path).ok().map(|s| s.to_string())) + .unwrap_or_else(|| { + log::error!(target: "app", "Failed to get IPC path, using default"); + "127.0.0.1:9090".to_string() + }) } } diff --git a/src-tauri/src/config/draft.rs b/src-tauri/src/config/draft.rs index 041c79e6..ac82d685 100644 --- a/src-tauri/src/config/draft.rs +++ b/src-tauri/src/config/draft.rs @@ -46,11 +46,18 @@ impl Draft> { if guard.1.is_none() { let mut guard = RwLockUpgradableReadGuard::upgrade(guard); guard.1 = Some(guard.0.clone()); - return RwLockWriteGuard::map(guard, |inner| inner.1.as_mut().unwrap()); + return RwLockWriteGuard::map(guard, |inner| { + inner.1.as_mut().unwrap_or_else(|| { + unreachable!("Draft was just created above, this should never fail") + }) + }); } // 已存在草稿,升级为写锁映射 RwLockWriteGuard::map(RwLockUpgradableReadGuard::upgrade(guard), |inner| { - inner.1.as_mut().unwrap() + inner + .1 + .as_mut() + .unwrap_or_else(|| unreachable!("Draft should exist when guard.1.is_some()")) }) } diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index a526cc56..88053dc2 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -147,12 +147,15 @@ impl PrfItem { bail!("type should not be null"); } - match item.itype.unwrap().as_str() { + let itype = item + .itype + .ok_or_else(|| anyhow::anyhow!("type should not be null"))?; + match itype.as_str() { "remote" => { - if item.url.is_none() { - bail!("url should not be null"); - } - let url = item.url.as_ref().unwrap().as_str(); + let url = item + .url + .as_ref() + .ok_or_else(|| anyhow::anyhow!("url should not be null"))?; let name = item.name; let desc = item.desc; PrfItem::from_url(url, name, desc, item.option).await @@ -549,22 +552,20 @@ impl PrfItem { /// get the file data pub fn read_file(&self) -> Result { - if self.file.is_none() { - bail!("could not find the file"); - } - - let file = self.file.clone().unwrap(); + let file = self + .file + .clone() + .ok_or_else(|| anyhow::anyhow!("could not find the file"))?; let path = dirs::app_profiles_dir()?.join(file); fs::read_to_string(path).context("failed to read the file") } /// save the file data pub fn save_file(&self, data: String) -> Result<()> { - if self.file.is_none() { - bail!("could not find the file"); - } - - let file = self.file.clone().unwrap(); + let file = self + .file + .clone() + .ok_or_else(|| anyhow::anyhow!("could not find the file"))?; let path = dirs::app_profiles_dir()?.join(file); fs::write(path, data.as_bytes()).context("failed to save the file") } diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index c717065b..ce4df733 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -77,10 +77,11 @@ impl IProfiles { } if let Some(current) = patch.current { - let items = self.items.as_ref().unwrap(); - let some_uid = Some(current); - if items.iter().any(|e| e.uid == some_uid) { - self.current = some_uid; + if let Some(items) = self.items.as_ref() { + let some_uid = Some(current); + if items.iter().any(|e| e.uid == some_uid) { + self.current = some_uid; + } } } @@ -127,7 +128,9 @@ impl IProfiles { bail!("the file should not be null"); } - let file = item.file.clone().unwrap(); + let file = item.file.clone().ok_or_else(|| { + anyhow::anyhow!("file field is required when file_data is provided") + })?; let path = dirs::app_profiles_dir()?.join(&file); fs::File::create(path) @@ -168,11 +171,12 @@ impl IProfiles { } } - if old_index.is_none() || new_index.is_none() { - return Ok(()); - } - let item = items.remove(old_index.unwrap()); - items.insert(new_index.unwrap(), item); + let (old_idx, new_idx) = match (old_index, new_index) { + (Some(old), Some(new)) => (old, new), + _ => return Ok(()), + }; + let item = items.remove(old_idx); + items.insert(new_idx, item); self.items = Some(items); self.save_file() } diff --git a/src-tauri/src/core/backup.rs b/src-tauri/src/core/backup.rs index 47052eef..0f618957 100644 --- a/src-tauri/src/core/backup.rs +++ b/src-tauri/src/core/backup.rs @@ -117,8 +117,7 @@ impl WebDavClient { attempt.follow() } })) - .build() - .unwrap(), + .build()?, ) .set_host(config.url) .set_auth(reqwest_dav::Auth::Basic(config.username, config.password)) @@ -243,12 +242,17 @@ pub fn create_backup() -> Result<(String, PathBuf), Error> { let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); if let Ok(entries) = fs::read_dir(dirs::app_profiles_dir()?) { for entry in entries { - let entry = entry.unwrap(); + let entry = entry?; let path = entry.path(); if path.is_file() { - let backup_path = format!("profiles/{}", entry.file_name().to_str().unwrap()); + let file_name_os = entry.file_name(); + let file_name = file_name_os + .to_str() + .ok_or_else(|| anyhow::Error::msg("Invalid file name encoding"))?; + let backup_path = format!("profiles/{}", file_name); zip.start_file(backup_path, options)?; - zip.write_all(fs::read(path).unwrap().as_slice())?; + let file_content = fs::read(&path)?; + zip.write_all(&file_content)?; } } } diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 02f4ed26..50717a68 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -249,7 +249,11 @@ impl CoreManager { let clash_core = Config::verge().latest_ref().get_valid_clash_core(); logging!(info, Type::Config, true, "使用内核: {}", clash_core); - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = handle::Handle::global().app_handle().ok_or_else(|| { + let msg = "Failed to get app handle"; + logging!(error, Type::Core, true, "{}", msg); + anyhow::anyhow!(msg) + })?; let app_dir = dirs::app_home_dir()?; let app_dir_str = dirs::path_to_str(&app_dir)?; logging!(info, Type::Config, true, "验证目录: {}", app_dir_str); @@ -1108,8 +1112,12 @@ impl CoreManager { logging!(error, Type::Core, true, "{}", error_message); return Err(error_message.to_string()); } - let core: &str = &clash_core.clone().unwrap(); - if !IVerge::VALID_CLASH_CORES.contains(&core) { + let core = clash_core.as_ref().ok_or_else(|| { + let msg = "Clash core should not be None"; + logging!(error, Type::Core, true, "{}", msg); + msg.to_string() + })?; + if !IVerge::VALID_CLASH_CORES.contains(&core.as_str()) { let error_message = format!("Clash core invalid name: {core}"); logging!(error, Type::Core, true, "{}", error_message); return Err(error_message); diff --git a/src-tauri/src/core/handle.rs b/src-tauri/src/core/handle.rs index 5ded1885..68845586 100644 --- a/src-tauri/src/core/handle.rs +++ b/src-tauri/src/core/handle.rs @@ -82,121 +82,124 @@ impl NotificationSystem { *self.last_emit_time.write() = Instant::now(); - self.worker_handle = Some( - thread::Builder::new() - .name("frontend-notifier".into()) - .spawn(move || { - let handle = Handle::global(); + match thread::Builder::new() + .name("frontend-notifier".into()) + .spawn(move || { + let handle = Handle::global(); - while !handle.is_exiting() { - match rx.recv_timeout(Duration::from_millis(100)) { - Ok(event) => { - let system_guard = handle.notification_system.read(); - if system_guard.as_ref().is_none() { - log::warn!("NotificationSystem not found in handle while processing event."); - continue; - } - let system = system_guard.as_ref().unwrap(); + while !handle.is_exiting() { + match rx.recv_timeout(Duration::from_millis(100)) { + Ok(event) => { + let system_guard = handle.notification_system.read(); + let Some(system) = system_guard.as_ref() else { + log::warn!("NotificationSystem not found in handle while processing event."); + continue; + }; - let is_emergency = *system.emergency_mode.read(); + let is_emergency = *system.emergency_mode.read(); - if is_emergency { - if let FrontendEvent::NoticeMessage { ref status, .. } = event { - if status == "info" { - log::warn!( - "Emergency mode active, skipping info message" - ); - continue; - } + if is_emergency { + if let FrontendEvent::NoticeMessage { ref status, .. } = event { + if status == "info" { + log::warn!( + "Emergency mode active, skipping info message" + ); + continue; } } + } - if let Some(window) = handle.get_window() { - *system.last_emit_time.write() = Instant::now(); + if let Some(window) = handle.get_window() { + *system.last_emit_time.write() = Instant::now(); - let (event_name_str, payload_result) = match event { - FrontendEvent::RefreshClash => { - ("verge://refresh-clash-config", Ok(serde_json::json!("yes"))) - } - FrontendEvent::RefreshVerge => { - ("verge://refresh-verge-config", Ok(serde_json::json!("yes"))) - } - FrontendEvent::NoticeMessage { status, message } => { - match serde_json::to_value((status, message)) { - Ok(p) => ("verge://notice-message", Ok(p)), - Err(e) => { - log::error!("Failed to serialize NoticeMessage payload: {e}"); - ("verge://notice-message", Err(e)) - } - } - } - FrontendEvent::ProfileChanged { current_profile_id } => { - ("profile-changed", Ok(serde_json::json!(current_profile_id))) - } - FrontendEvent::TimerUpdated { profile_index } => { - ("verge://timer-updated", Ok(serde_json::json!(profile_index))) - } - FrontendEvent::StartupCompleted => { - ("verge://startup-completed", Ok(serde_json::json!(null))) - } - FrontendEvent::ProfileUpdateStarted { uid } => { - ("profile-update-started", Ok(serde_json::json!({ "uid": uid }))) - } - FrontendEvent::ProfileUpdateCompleted { uid } => { - ("profile-update-completed", Ok(serde_json::json!({ "uid": uid }))) - } - }; - - if let Ok(payload) = payload_result { - match window.emit(event_name_str, payload) { - Ok(_) => { - system.stats.total_sent.fetch_add(1, Ordering::SeqCst); - // 记录成功发送的事件 - if log::log_enabled!(log::Level::Debug) { - log::debug!("Successfully emitted event: {event_name_str}"); - } - } + let (event_name_str, payload_result) = match event { + FrontendEvent::RefreshClash => { + ("verge://refresh-clash-config", Ok(serde_json::json!("yes"))) + } + FrontendEvent::RefreshVerge => { + ("verge://refresh-verge-config", Ok(serde_json::json!("yes"))) + } + FrontendEvent::NoticeMessage { status, message } => { + match serde_json::to_value((status, message)) { + Ok(p) => ("verge://notice-message", Ok(p)), Err(e) => { - log::warn!("Failed to emit event {event_name_str}: {e}"); - system.stats.total_errors.fetch_add(1, Ordering::SeqCst); - *system.stats.last_error_time.write() = Some(Instant::now()); - - let errors = system.stats.total_errors.load(Ordering::SeqCst); - const EMIT_ERROR_THRESHOLD: u64 = 10; - if errors > EMIT_ERROR_THRESHOLD && !*system.emergency_mode.read() { - log::warn!( - "Reached {EMIT_ERROR_THRESHOLD} emit errors, entering emergency mode" - ); - *system.emergency_mode.write() = true; - } + log::error!("Failed to serialize NoticeMessage payload: {e}"); + ("verge://notice-message", Err(e)) + } + } + } + FrontendEvent::ProfileChanged { current_profile_id } => { + ("profile-changed", Ok(serde_json::json!(current_profile_id))) + } + FrontendEvent::TimerUpdated { profile_index } => { + ("verge://timer-updated", Ok(serde_json::json!(profile_index))) + } + FrontendEvent::StartupCompleted => { + ("verge://startup-completed", Ok(serde_json::json!(null))) + } + FrontendEvent::ProfileUpdateStarted { uid } => { + ("profile-update-started", Ok(serde_json::json!({ "uid": uid }))) + } + FrontendEvent::ProfileUpdateCompleted { uid } => { + ("profile-update-completed", Ok(serde_json::json!({ "uid": uid }))) + } + }; + + if let Ok(payload) = payload_result { + match window.emit(event_name_str, payload) { + Ok(_) => { + system.stats.total_sent.fetch_add(1, Ordering::SeqCst); + // 记录成功发送的事件 + if log::log_enabled!(log::Level::Debug) { + log::debug!("Successfully emitted event: {event_name_str}"); + } + } + Err(e) => { + log::warn!("Failed to emit event {event_name_str}: {e}"); + system.stats.total_errors.fetch_add(1, Ordering::SeqCst); + *system.stats.last_error_time.write() = Some(Instant::now()); + + let errors = system.stats.total_errors.load(Ordering::SeqCst); + const EMIT_ERROR_THRESHOLD: u64 = 10; + if errors > EMIT_ERROR_THRESHOLD && !*system.emergency_mode.read() { + log::warn!( + "Reached {EMIT_ERROR_THRESHOLD} emit errors, entering emergency mode" + ); + *system.emergency_mode.write() = true; } } - } else { - system.stats.total_errors.fetch_add(1, Ordering::SeqCst); - *system.stats.last_error_time.write() = Some(Instant::now()); - log::warn!("Skipped emitting event due to payload serialization error for {event_name_str}"); } } else { - log::warn!("No window found, skipping event emit."); + system.stats.total_errors.fetch_add(1, Ordering::SeqCst); + *system.stats.last_error_time.write() = Some(Instant::now()); + log::warn!("Skipped emitting event due to payload serialization error for {event_name_str}"); } - thread::sleep(Duration::from_millis(20)); - } - Err(mpsc::RecvTimeoutError::Timeout) => { - continue; - } - Err(mpsc::RecvTimeoutError::Disconnected) => { - log::info!( - "Notification channel disconnected, exiting worker thread" - ); - break; + } else { + log::warn!("No window found, skipping event emit."); } + thread::sleep(Duration::from_millis(20)); + } + Err(mpsc::RecvTimeoutError::Timeout) => { + continue; + } + Err(mpsc::RecvTimeoutError::Disconnected) => { + log::info!( + "Notification channel disconnected, exiting worker thread" + ); + break; } } + } - log::info!("Notification worker thread exiting"); - }) - .expect("Failed to start notification worker thread"), - ); + log::info!("Notification worker thread exiting"); + }) { + Ok(handle) => { + self.worker_handle = Some(handle); + } + Err(e) => { + log::error!("Failed to start notification worker thread: {e}"); + } + } } /// 发送事件到队列 diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 0a6ba7a3..89ba08be 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -195,7 +195,9 @@ impl Hotkey { hotkey: &str, function: HotkeyFunction, ) -> Result<()> { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = handle::Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; let manager = app_handle.global_shortcut(); logging!( @@ -351,7 +353,9 @@ impl Hotkey { } pub fn reset(&self) -> Result<()> { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = handle::Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; let manager = app_handle.global_shortcut(); manager.unregister_all()?; Ok(()) @@ -364,7 +368,9 @@ impl Hotkey { } pub fn unregister(&self, hotkey: &str) -> Result<()> { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = handle::Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for hotkey registration"))?; let manager = app_handle.global_shortcut(); manager.unregister(hotkey)?; logging!(debug, Type::Hotkey, "Unregister hotkey {}", hotkey); @@ -438,7 +444,17 @@ impl Hotkey { impl Drop for Hotkey { fn drop(&mut self) { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + logging!( + error, + Type::Hotkey, + "Failed to get app handle during hotkey cleanup" + ); + return; + } + }; if let Err(e) = app_handle.global_shortcut().unregister_all() { logging!( error, diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 3ec24829..9539ed1c 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -138,7 +138,7 @@ pub async fn uninstall_service() -> Result<()> { if !status.success() { bail!( "failed to uninstall service with status {}", - status.code().unwrap() + status.code().unwrap_or(-1) ); } @@ -172,7 +172,7 @@ pub async fn install_service() -> Result<()> { if !status.success() { bail!( "failed to install service with status {}", - status.code().unwrap() + status.code().unwrap_or(-1) ); } @@ -254,13 +254,13 @@ pub async fn uninstall_service() -> Result<()> { Type::Service, true, "uninstall status code:{}", - status.code().unwrap() + status.code().unwrap_or(-1) ); if !status.success() { bail!( "failed to uninstall service with status {}", - status.code().unwrap() + status.code().unwrap_or(-1) ); } @@ -294,13 +294,13 @@ pub async fn install_service() -> Result<()> { Type::Service, true, "install status code:{}", - status.code().unwrap() + status.code().unwrap_or(-1) ); if !status.success() { bail!( "failed to install service with status {}", - status.code().unwrap() + status.code().unwrap_or(-1) ); } @@ -384,7 +384,7 @@ pub async fn uninstall_service() -> Result<()> { if !status.success() { bail!( "failed to uninstall service with status {}", - status.code().unwrap() + status.code().unwrap_or(-1) ); } @@ -420,7 +420,7 @@ pub async fn install_service() -> Result<()> { if !status.success() { bail!( "failed to install service with status {}", - status.code().unwrap() + status.code().unwrap_or(-1) ); } diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 5dec7b6f..688995ee 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -149,7 +149,9 @@ impl Sysopt { use anyhow::bail; use tauri_plugin_shell::ShellExt; - let app_handle = Handle::global().app_handle().unwrap(); + let app_handle = Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; let binary_path = dirs::service_path()?; let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); @@ -160,23 +162,27 @@ impl Sysopt { let shell = app_handle.shell(); let output = if pac_enable { let address = format!("http://{proxy_host}:{pac_port}/commands/pac"); - let output = shell - .command(sysproxy_exe.as_path().to_str().unwrap()) + let sysproxy_str = sysproxy_exe + .as_path() + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid sysproxy.exe path"))?; + shell + .command(sysproxy_str) .args(["pac", address.as_str()]) .output() - .await - .unwrap(); - output + .await? } else { let address = format!("{proxy_host}:{port}"); let bypass = get_bypass(); - let output = shell - .command(sysproxy_exe.as_path().to_str().unwrap()) + let sysproxy_str = sysproxy_exe + .as_path() + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid sysproxy.exe path"))?; + shell + .command(sysproxy_str) .args(["global", address.as_str(), bypass.as_ref()]) .output() - .await - .unwrap(); - output + .await? }; if !output.status.success() { @@ -218,7 +224,9 @@ impl Sysopt { use anyhow::bail; use tauri_plugin_shell::ShellExt; - let app_handle = Handle::global().app_handle().unwrap(); + let app_handle = Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; let binary_path = dirs::service_path()?; let sysproxy_exe = binary_path.with_file_name("sysproxy.exe"); @@ -228,12 +236,15 @@ impl Sysopt { } let shell = app_handle.shell(); + let sysproxy_str = sysproxy_exe + .as_path() + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid sysproxy.exe path"))?; let output = shell - .command(sysproxy_exe.as_path().to_str().unwrap()) + .command(sysproxy_str) .args(["set", "1"]) .output() - .await - .unwrap(); + .await?; if !output.status.success() { bail!("sysproxy exe run failed"); @@ -279,7 +290,10 @@ impl Sysopt { /// 尝试使用原来的自启动方法 fn try_original_autostart_method(&self, is_enable: bool) { - let app_handle = Handle::global().app_handle().unwrap(); + let Some(app_handle) = Handle::global().app_handle() else { + log::error!(target: "app", "App handle not available for autostart"); + return; + }; let autostart_manager = app_handle.autolaunch(); if is_enable { @@ -306,7 +320,9 @@ impl Sysopt { } // 回退到原来的方法 - let app_handle = Handle::global().app_handle().unwrap(); + let app_handle = Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("App handle not available"))?; let autostart_manager = app_handle.autolaunch(); match autostart_manager.is_enabled() { diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 2cddf579..74ebf61b 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -71,9 +71,10 @@ impl TrayState { let verge = Config::verge().latest_ref().clone(); let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false); if is_common_tray_icon { - if let Some(common_icon_path) = find_target_icons("common").unwrap() { - let icon_data = fs::read(common_icon_path).unwrap(); - return (true, icon_data); + if let Ok(Some(common_icon_path)) = find_target_icons("common") { + if let Ok(icon_data) = fs::read(common_icon_path) { + return (true, icon_data); + } } } #[cfg(target_os = "macos")] @@ -105,9 +106,10 @@ impl TrayState { let verge = Config::verge().latest_ref().clone(); let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false); if is_sysproxy_tray_icon { - if let Some(sysproxy_icon_path) = find_target_icons("sysproxy").unwrap() { - let icon_data = fs::read(sysproxy_icon_path).unwrap(); - return (true, icon_data); + if let Ok(Some(sysproxy_icon_path)) = find_target_icons("sysproxy") { + if let Ok(icon_data) = fs::read(sysproxy_icon_path) { + return (true, icon_data); + } } } #[cfg(target_os = "macos")] @@ -139,9 +141,10 @@ impl TrayState { let verge = Config::verge().latest_ref().clone(); let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false); if is_tun_tray_icon { - if let Some(tun_icon_path) = find_target_icons("tun").unwrap() { - let icon_data = fs::read(tun_icon_path).unwrap(); - return (true, icon_data); + if let Ok(Some(tun_icon_path)) = find_target_icons("tun") { + if let Ok(icon_data) = fs::read(tun_icon_path) { + return (true, icon_data); + } } } #[cfg(target_os = "macos")] @@ -188,10 +191,14 @@ impl Tray { /// 更新托盘点击行为 pub fn update_click_behavior(&self) -> Result<()> { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = handle::Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray update"))?; let tray_event = { Config::verge().latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); - let tray = app_handle.tray_by_id("main").unwrap(); + let tray = app_handle + .tray_by_id("main") + .ok_or_else(|| anyhow::anyhow!("Failed to get main tray"))?; match tray_event.as_str() { "tray_menu" => tray.set_show_menu_on_left_click(true)?, _ => tray.set_show_menu_on_left_click(false)?, @@ -360,8 +367,12 @@ impl Tray { /// 更新托盘显示状态的函数 pub fn update_tray_display(&self) -> Result<()> { - let app_handle = handle::Handle::global().app_handle().unwrap(); - let _tray = app_handle.tray_by_id("main").unwrap(); + let app_handle = handle::Handle::global() + .app_handle() + .ok_or_else(|| anyhow::anyhow!("Failed to get app handle for tray update"))?; + let _tray = app_handle + .tray_by_id("main") + .ok_or_else(|| anyhow::anyhow!("Failed to get main tray"))?; // 更新菜单 self.update_menu()?; @@ -562,9 +573,8 @@ fn create_tray_menu( is_current_profile, None::<&str>, ) - .unwrap() }) - .collect(); + .collect::, _>>()?; let profile_menu_items: Vec<&dyn IsMenuItem> = profile_menu_items .iter() .map(|item| item as &dyn IsMenuItem) @@ -576,8 +586,7 @@ fn create_tray_menu( t("Dashboard"), true, hotkeys.get("open_or_close_dashboard").map(|s| s.as_str()), - ) - .unwrap(); + )?; let rule_mode = &CheckMenuItem::with_id( app_handle, @@ -586,8 +595,7 @@ fn create_tray_menu( true, mode == "rule", hotkeys.get("clash_mode_rule").map(|s| s.as_str()), - ) - .unwrap(); + )?; let global_mode = &CheckMenuItem::with_id( app_handle, @@ -596,8 +604,7 @@ fn create_tray_menu( true, mode == "global", hotkeys.get("clash_mode_global").map(|s| s.as_str()), - ) - .unwrap(); + )?; let direct_mode = &CheckMenuItem::with_id( app_handle, @@ -606,8 +613,7 @@ fn create_tray_menu( true, mode == "direct", hotkeys.get("clash_mode_direct").map(|s| s.as_str()), - ) - .unwrap(); + )?; let profiles = &Submenu::with_id_and_items( app_handle, @@ -615,8 +621,7 @@ fn create_tray_menu( t("Profiles"), true, &profile_menu_items, - ) - .unwrap(); + )?; let system_proxy = &CheckMenuItem::with_id( app_handle, @@ -625,8 +630,7 @@ fn create_tray_menu( true, system_proxy_enabled, hotkeys.get("toggle_system_proxy").map(|s| s.as_str()), - ) - .unwrap(); + )?; let tun_mode = &CheckMenuItem::with_id( app_handle, @@ -635,8 +639,7 @@ fn create_tray_menu( true, tun_mode_enabled, hotkeys.get("toggle_tun_mode").map(|s| s.as_str()), - ) - .unwrap(); + )?; let lighteweight_mode = &CheckMenuItem::with_id( app_handle, @@ -645,11 +648,9 @@ fn create_tray_menu( true, is_lightweight_mode, hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()), - ) - .unwrap(); + )?; - let copy_env = - &MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>).unwrap(); + let copy_env = &MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>)?; let open_app_dir = &MenuItem::with_id( app_handle, @@ -657,8 +658,7 @@ fn create_tray_menu( t("Conf Dir"), true, None::<&str>, - ) - .unwrap(); + )?; let open_core_dir = &MenuItem::with_id( app_handle, @@ -666,8 +666,7 @@ fn create_tray_menu( t("Core Dir"), true, None::<&str>, - ) - .unwrap(); + )?; let open_logs_dir = &MenuItem::with_id( app_handle, @@ -675,8 +674,7 @@ fn create_tray_menu( t("Logs Dir"), true, None::<&str>, - ) - .unwrap(); + )?; let open_dir = &Submenu::with_id_and_items( app_handle, @@ -684,8 +682,7 @@ fn create_tray_menu( t("Open Dir"), true, &[open_app_dir, open_core_dir, open_logs_dir], - ) - .unwrap(); + )?; let restart_clash = &MenuItem::with_id( app_handle, @@ -693,8 +690,7 @@ fn create_tray_menu( t("Restart Clash Core"), true, None::<&str>, - ) - .unwrap(); + )?; let restart_app = &MenuItem::with_id( app_handle, @@ -702,8 +698,7 @@ fn create_tray_menu( t("Restart App"), true, None::<&str>, - ) - .unwrap(); + )?; let app_version = &MenuItem::with_id( app_handle, @@ -711,8 +706,7 @@ fn create_tray_menu( format!("{} {version}", t("Verge Version")), true, None::<&str>, - ) - .unwrap(); + )?; let more = &Submenu::with_id_and_items( app_handle, @@ -720,13 +714,11 @@ fn create_tray_menu( t("More"), true, &[restart_clash, restart_app, app_version], - ) - .unwrap(); + )?; - let quit = - &MenuItem::with_id(app_handle, "quit", t("Exit"), true, Some("CmdOrControl+Q")).unwrap(); + let quit = &MenuItem::with_id(app_handle, "quit", t("Exit"), true, Some("CmdOrControl+Q"))?; - let separator = &PredefinedMenuItem::separator(app_handle).unwrap(); + let separator = &PredefinedMenuItem::separator(app_handle)?; let menu = tauri::menu::MenuBuilder::new(app_handle) .items(&[ @@ -748,8 +740,7 @@ fn create_tray_menu( separator, quit, ]) - .build() - .unwrap(); + .build()?; Ok(menu) } diff --git a/src-tauri/src/enhance/merge.rs b/src-tauri/src/enhance/merge.rs index 0aa81e7e..1da09dec 100644 --- a/src-tauri/src/enhance/merge.rs +++ b/src-tauri/src/enhance/merge.rs @@ -18,7 +18,10 @@ pub fn use_merge(merge: Mapping, config: Mapping) -> Mapping { deep_merge(&mut config, &Value::from(merge)); - let config = config.as_mapping().unwrap().clone(); + let config = config.as_mapping().cloned().unwrap_or_else(|| { + log::error!("Failed to convert merged config to mapping, using empty mapping"); + Mapping::new() + }); config } diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs index 11a89728..7db30ea6 100644 --- a/src-tauri/src/enhance/script.rs +++ b/src-tauri/src/enhance/script.rs @@ -7,7 +7,7 @@ pub fn use_script( config: Mapping, name: String, ) -> Result<(Mapping, Vec<(String, String)>)> { - use boa_engine::{native_function::NativeFunction, Context, JsValue, Source}; + use boa_engine::{native_function::NativeFunction, Context, JsString, JsValue, Source}; use std::{cell::RefCell, rc::Rc}; let mut context = Context::default(); @@ -20,10 +20,29 @@ pub fn use_script( 2, NativeFunction::from_closure( move |_: &JsValue, args: &[JsValue], context: &mut Context| { - let level = args.first().unwrap().to_string(context)?; - let level = level.to_std_string().unwrap(); - let data = args.get(1).unwrap().to_string(context)?; - let data = data.to_std_string().unwrap(); + let level = args.first().ok_or_else(|| { + boa_engine::JsError::from_opaque( + JsString::from("Missing level argument").into(), + ) + })?; + let level = level.to_string(context)?; + let level = level.to_std_string().map_err(|_| { + boa_engine::JsError::from_opaque( + JsString::from("Failed to convert level to string").into(), + ) + })?; + + let data = args.get(1).ok_or_else(|| { + boa_engine::JsError::from_opaque( + JsString::from("Missing data argument").into(), + ) + })?; + let data = data.to_string(context)?; + let data = data.to_std_string().map_err(|_| { + boa_engine::JsError::from_opaque( + JsString::from("Failed to convert data to string").into(), + ) + })?; let mut out = copy_outputs.borrow_mut(); out.push((level, data)); Ok(JsValue::undefined()) @@ -61,8 +80,12 @@ pub fn use_script( if !result.is_string() { anyhow::bail!("main function should return object"); } - let result = result.to_string(&mut context).unwrap(); - let result = result.to_std_string().unwrap(); + let result = result + .to_string(&mut context) + .map_err(|e| anyhow::anyhow!("Failed to convert JS result to string: {}", e))?; + let result = result + .to_std_string() + .map_err(|_| anyhow::anyhow!("Failed to convert JS string to std string"))?; // 直接解析JSON结果,不做其他解析 let res: Result = parse_json_safely(&result); @@ -124,10 +147,11 @@ fn test_script() { enable: false "#; - let config = serde_yaml::from_str(config).unwrap(); - let (config, results) = use_script(script.into(), config, "".to_string()).unwrap(); + let config = serde_yaml::from_str(config).expect("Failed to parse test config YAML"); + let (config, results) = use_script(script.into(), config, "".to_string()) + .expect("Script execution should succeed in test"); - let _ = serde_yaml::to_string(&config).unwrap(); + let _ = serde_yaml::to_string(&config).expect("Failed to serialize config to YAML"); let yaml_config_size = std::mem::size_of_val(&config); dbg!(yaml_config_size); let box_yaml_config_size = std::mem::size_of_val(&Box::new(config)); @@ -145,13 +169,14 @@ fn test_escape_unescape() { println!("Escaped: {escaped}"); let json_str = r#"{"key":"value","nested":{"key":"value"}}"#; - let parsed = parse_json_safely(json_str).unwrap(); + let parsed = parse_json_safely(json_str).expect("Failed to parse test JSON safely"); assert!(parsed.contains_key("key")); assert!(parsed.contains_key("nested")); let quoted_json_str = r#""{"key":"value","nested":{"key":"value"}}""#; - let parsed_quoted = parse_json_safely(quoted_json_str).unwrap(); + let parsed_quoted = + parse_json_safely(quoted_json_str).expect("Failed to parse quoted test JSON safely"); assert!(parsed_quoted.contains_key("key")); assert!(parsed_quoted.contains_key("nested")); diff --git a/src-tauri/src/feat/backup.rs b/src-tauri/src/feat/backup.rs index 07adc5fc..c4fd656b 100644 --- a/src-tauri/src/feat/backup.rs +++ b/src-tauri/src/feat/backup.rs @@ -57,7 +57,9 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> { let webdav_username = verge_data.webdav_username.clone(); let webdav_password = verge_data.webdav_password.clone(); - let backup_storage_path = app_home_dir().unwrap().join(&filename); + let backup_storage_path = app_home_dir() + .map_err(|e| anyhow::anyhow!("Failed to get app home dir: {e}"))? + .join(&filename); backup::WebDavClient::global() .download(filename, backup_storage_path.clone()) .await diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index 33c1fb15..97ab0b77 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -2,7 +2,7 @@ use crate::{ config::Config, core::{handle, tray, CoreManager}, ipc::IpcManager, - logging_error, + logging, logging_error, process::AsyncHandler, utils::{logging::Type, resolve}, }; @@ -30,7 +30,10 @@ pub fn restart_app() { AsyncHandler::spawn(move || async move { logging_error!(Type::Core, true, CoreManager::global().stop_core().await); resolve::resolve_reset_async().await; - let app_handle = handle::Handle::global().app_handle().unwrap(); + let Some(app_handle) = handle::Handle::global().app_handle() else { + logging!(error, Type::Core, "Failed to get app handle for restart"); + return; + }; std::thread::sleep(std::time::Duration::from_secs(1)); tauri::process::restart(&app_handle.env()); }); diff --git a/src-tauri/src/feat/config.rs b/src-tauri/src/feat/config.rs index 7a7b7273..c8b50b9b 100644 --- a/src-tauri/src/feat/config.rs +++ b/src-tauri/src/feat/config.rs @@ -191,7 +191,9 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { sysopt::Sysopt::global().update_sysproxy().await?; } if (update_flags & (UpdateFlags::Hotkey as i32)) != 0 { - hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?; + if let Some(hotkeys) = patch.hotkeys { + hotkey::Hotkey::global().update(hotkeys)?; + } } if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 { tray::Tray::global().update_menu()?; @@ -206,7 +208,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> { tray::Tray::global().update_click_behavior()?; } if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 { - if enable_auto_light_weight.unwrap() { + if enable_auto_light_weight.unwrap_or(false) { lightweight::enable_auto_light_weight_mode(); } else { lightweight::disable_auto_light_weight_mode(); diff --git a/src-tauri/src/feat/profile.rs b/src-tauri/src/feat/profile.rs index 281fa7ed..893aa7e8 100644 --- a/src-tauri/src/feat/profile.rs +++ b/src-tauri/src/feat/profile.rs @@ -11,7 +11,14 @@ use anyhow::{bail, Result}; /// Toggle proxy profile pub fn toggle_proxy_profile(profile_index: String) { AsyncHandler::spawn(|| async move { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let Some(app_handle) = handle::Handle::global().app_handle() else { + logging!( + error, + Type::Config, + "Failed to get app handle for profile toggle" + ); + return; + }; match cmd::patch_profiles_config_by_profile_index(app_handle, profile_index).await { Ok(_) => { let _ = tray::Tray::global().update_menu(); @@ -50,9 +57,14 @@ pub async fn update_profile( log::info!(target: "app", "[订阅更新] {} 是远程订阅,URL: {}", uid, - item.url.clone().unwrap() + item.url.clone().ok_or_else(|| anyhow::anyhow!("Profile URL is None"))? ); - Some((item.url.clone().unwrap(), item.option.clone())) + Some(( + item.url + .clone() + .ok_or_else(|| anyhow::anyhow!("Profile URL is None"))?, + item.option.clone(), + )) } }; @@ -137,7 +149,13 @@ pub async fn update_profile( logging!(info, Type::Config, true, "[订阅更新] 更新成功"); handle::Handle::refresh_clash(); if let Err(err) = cmd::proxy::force_refresh_proxies().await { - logging!(error, Type::Config, true, "[订阅更新] 代理组刷新失败: {}", err); + logging!( + error, + Type::Config, + true, + "[订阅更新] 代理组刷新失败: {}", + err + ); } } Err(err) => { diff --git a/src-tauri/src/feat/proxy.rs b/src-tauri/src/feat/proxy.rs index c3564238..bbe31583 100644 --- a/src-tauri/src/feat/proxy.rs +++ b/src-tauri/src/feat/proxy.rs @@ -2,7 +2,9 @@ use crate::{ config::{Config, IVerge}, core::handle, ipc::IpcManager, + logging, process::AsyncHandler, + utils::logging::Type, }; use std::env; use tauri_plugin_clipboard_manager::ClipboardExt; @@ -71,7 +73,14 @@ pub fn copy_clash_env() { .unwrap_or_else(|| "127.0.0.1".to_string()) }); - let app_handle = handle::Handle::global().app_handle().unwrap(); + let Some(app_handle) = handle::Handle::global().app_handle() else { + logging!( + error, + Type::System, + "Failed to get app handle for proxy operation" + ); + return; + }; let port = { Config::verge() .latest_ref() diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index 951c2bf7..29f30713 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -67,7 +67,14 @@ pub fn quit() { logging!(debug, Type::System, true, "启动退出流程"); // 获取应用句柄并设置退出标志 - let app_handle = handle::Handle::global().app_handle().unwrap(); + let Some(app_handle) = handle::Handle::global().app_handle() else { + logging!( + error, + Type::System, + "Failed to get app handle for quit operation" + ); + return; + }; handle::Handle::global().set_is_exiting(); // 优先关闭窗口,提供立即反馈 diff --git a/src-tauri/src/ipc/general.rs b/src-tauri/src/ipc/general.rs index 19ee27ea..5818cb2c 100644 --- a/src-tauri/src/ipc/general.rs +++ b/src-tauri/src/ipc/general.rs @@ -26,7 +26,16 @@ pub struct IpcManager { impl IpcManager { fn new() -> Self { - let ipc_path_buf = ipc_path().unwrap(); + let ipc_path_buf = ipc_path().unwrap_or_else(|e| { + logging!( + error, + crate::utils::logging::Type::Ipc, + true, + "Failed to get IPC path: {}", + e + ); + std::path::PathBuf::from("/tmp/clash-verge-ipc") // fallback path + }); let ipc_path = ipc_path_buf.to_str().unwrap_or_default(); Self { ipc_path: ipc_path.to_string(), diff --git a/src-tauri/src/ipc/logs.rs b/src-tauri/src/ipc/logs.rs index 83248327..7b576158 100644 --- a/src-tauri/src/ipc/logs.rs +++ b/src-tauri/src/ipc/logs.rs @@ -28,7 +28,7 @@ impl LogItem { let now = SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap() + .unwrap_or_else(|_| std::time::Duration::from_secs(0)) .as_secs(); // Simple time formatting (HH:MM:SS) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 37c3628b..83dabe61 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -60,7 +60,16 @@ impl AppHandleManager { /// Get the app handle, panics if it hasn't been initialized. pub fn get_handle(&self) -> AppHandle { - self.get().expect("AppHandle not initialized") + if let Some(handle) = self.get() { + handle + } else { + logging!( + error, + Type::Setup, + "AppHandle not initialized - ensure init() was called first" + ); + std::process::exit(1) + } } /// Check if the app handle has been initialized. @@ -667,7 +676,16 @@ pub fn run() { // Build the application let app = builder .build(tauri::generate_context!()) - .expect("error while running tauri application"); + .unwrap_or_else(|e| { + logging!( + error, + Type::Setup, + true, + "Failed to build Tauri application: {}", + e + ); + std::process::exit(1); + }); app.run(|app_handle, e| match e { tauri::RunEvent::Ready | tauri::RunEvent::Resumed => { diff --git a/src-tauri/src/module/lightweight.rs b/src-tauri/src/module/lightweight.rs index 98d5ba97..72a4a780 100644 --- a/src-tauri/src/module/lightweight.rs +++ b/src-tauri/src/module/lightweight.rs @@ -124,7 +124,10 @@ pub fn set_lightweight_mode(value: bool) { } pub fn enable_auto_light_weight_mode() { - Timer::global().init().unwrap(); + if let Err(e) = Timer::global().init() { + logging!(error, Type::Lightweight, "Failed to initialize timer: {e}"); + return; + } logging!(info, Type::Lightweight, true, "开启自动轻量模式"); setup_window_close_listener(); setup_webview_focus_listener(); diff --git a/src-tauri/src/module/sysinfo.rs b/src-tauri/src/module/sysinfo.rs index 0d594fb8..394b177a 100644 --- a/src-tauri/src/module/sysinfo.rs +++ b/src-tauri/src/module/sysinfo.rs @@ -32,7 +32,17 @@ impl PlatformSpecification { let system_kernel_version = System::kernel_version().unwrap_or("Null".into()); let system_arch = System::cpu_arch(); - let handler = handle::Handle::global().app_handle().unwrap(); + let Some(handler) = handle::Handle::global().app_handle() else { + return Self { + system_name, + system_version, + system_kernel_version, + system_arch, + verge_version: "unknown".into(), + running_mode: "NotRunning".to_string(), + is_admin: false, + }; + }; let verge_version = handler.package_info().version.to_string(); // 使用默认值避免在同步上下文中执行异步操作 diff --git a/src-tauri/src/state/proxy.rs b/src-tauri/src/state/proxy.rs index 777eacda..a536a646 100644 --- a/src-tauri/src/state/proxy.rs +++ b/src-tauri/src/state/proxy.rs @@ -60,7 +60,13 @@ impl ProxyRequestCache { expires_at: Instant::now() + ttl, }; let _ = cell.set(entry); - Arc::clone(&cell.get().unwrap().value) + // Safe to unwrap here as we just set the value + Arc::clone( + &cell + .get() + .unwrap_or_else(|| unreachable!("Cell value should exist after set")) + .value, + ) } } diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index 8f58936a..225de7df 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -163,8 +163,13 @@ pub fn find_target_icons(target: &str) -> Result> { if matching_files.is_empty() { Ok(None) } else { - let first = path_to_str(matching_files.first().unwrap())?; - Ok(Some(first.to_string())) + match matching_files.first() { + Some(first_path) => { + let first = path_to_str(first_path)?; + Ok(Some(first.to_string())) + } + None => Ok(None), + } } } diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index cfa39da0..174c66c8 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -401,7 +401,14 @@ pub fn init_scheme() -> Result<()> { } pub async fn startup_script() -> Result<()> { - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + return Err(anyhow::anyhow!( + "app_handle not available for startup script execution" + )); + } + }; let script_path = { let verge = Config::verge(); diff --git a/src-tauri/src/utils/network.rs b/src-tauri/src/utils/network.rs index 6532a5d0..600ceade 100644 --- a/src-tauri/src/utils/network.rs +++ b/src-tauri/src/utils/network.rs @@ -40,13 +40,37 @@ singleton_lazy!(NetworkManager, NETWORK_MANAGER, NetworkManager::new); impl NetworkManager { fn new() -> Self { // 创建专用的异步运行时,线程数限制为4个 - let runtime = Builder::new_multi_thread() + let runtime = match Builder::new_multi_thread() .worker_threads(4) .thread_name("clash-verge-network") .enable_io() .enable_time() .build() - .expect("Failed to create network runtime"); + { + Ok(runtime) => runtime, + Err(e) => { + log::error!( + "Failed to create network runtime: {}. Using fallback single-threaded runtime.", + e + ); + // Fallback to current thread runtime + match Builder::new_current_thread() + .enable_io() + .enable_time() + .thread_name("clash-verge-network-fallback") + .build() + { + Ok(fallback_runtime) => fallback_runtime, + Err(fallback_err) => { + log::error!( + "Failed to create fallback runtime: {}. This is critical.", + fallback_err + ); + std::process::exit(1); + } + } + } + }; NetworkManager { runtime: Arc::new(runtime), @@ -66,7 +90,7 @@ impl NetworkManager { logging!(info, Type::Network, true, "初始化网络管理器"); // 创建无代理客户端 - let no_proxy_client = ClientBuilder::new() + let no_proxy_client = match ClientBuilder::new() .use_rustls_tls() .no_proxy() .pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST) @@ -74,7 +98,19 @@ impl NetworkManager { .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(30)) .build() - .expect("Failed to build no_proxy client"); + { + Ok(client) => client, + Err(e) => { + logging!( + error, + Type::Network, + true, + "Failed to build no_proxy client: {}", + e + ); + return; + } + }; let mut no_proxy_guard = NetworkManager::global().no_proxy_client.lock(); *no_proxy_guard = Some(no_proxy_client); @@ -214,7 +250,45 @@ impl NetworkManager { builder = builder.user_agent(version); } - let client = builder.build().expect("Failed to build custom HTTP client"); + let client = match builder.build() { + Ok(client) => client, + Err(e) => { + logging!( + error, + Type::Network, + true, + "Failed to build custom HTTP client: {}", + e + ); + // Return a simple no-proxy client as fallback + match ClientBuilder::new() + .use_rustls_tls() + .no_proxy() + .timeout(DEFAULT_REQUEST_TIMEOUT) + .build() + { + Ok(fallback_client) => fallback_client, + Err(fallback_err) => { + logging!( + error, + Type::Network, + true, + "Failed to create fallback client: {}", + fallback_err + ); + self.record_connection_error(&format!( + "Critical client build failure: {}", + fallback_err + )); + // Return a minimal client that will likely fail but won't panic + ClientBuilder::new().build().unwrap_or_else(|_| { + // If even the most basic client fails, this is truly critical + std::process::exit(1); + }) + } + } + } + }; client.get(url) } diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index c05645db..c6569556 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -344,8 +344,21 @@ pub fn create_window(is_show: bool) -> bool { logging!(debug, Type::Window, true, "[ScopeGuard] 窗口创建状态已重置"); }); + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + logging!( + error, + Type::Window, + true, + "无法获取app_handle,窗口创建失败" + ); + return false; + } + }; + match tauri::WebviewWindowBuilder::new( - &handle::Handle::global().app_handle().unwrap(), + &app_handle, "main", /* the unique window label */ tauri::WebviewUrl::App("index.html".into()), ) @@ -647,7 +660,17 @@ pub async fn resolve_scheme(param: String) -> Result<()> { create_window(false); match PrfItem::from_url(url.as_ref(), name, None, None).await { Ok(item) => { - let uid = item.uid.clone().unwrap(); + let uid = match item.uid.clone() { + Some(uid) => uid, + None => { + logging!(error, Type::Config, true, "Profile item missing UID"); + handle::Handle::notice_message( + "import_sub_url::error", + "Profile item missing UID".to_string(), + ); + return Ok(()); + } + }; let _ = wrap_err!(Config::profiles().data_mut().append_item(item)); handle::Handle::notice_message("import_sub_url::ok", uid); } @@ -725,10 +748,22 @@ async fn resolve_random_port_config() -> Result<()> { pub async fn set_public_dns(dns_server: String) { use crate::{core::handle, utils::dirs}; use tauri_plugin_shell::ShellExt; - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + log::error!(target: "app", "app_handle not available for DNS configuration"); + return; + } + }; log::info!(target: "app", "try to set system dns"); - let resource_dir = dirs::app_resources_dir().unwrap(); + let resource_dir = match dirs::app_resources_dir() { + Ok(dir) => dir, + Err(e) => { + log::error!(target: "app", "Failed to get resource directory: {}", e); + return; + } + }; let script = resource_dir.join("set_dns.sh"); if !script.exists() { log::error!(target: "app", "set_dns.sh not found"); @@ -761,9 +796,21 @@ pub async fn set_public_dns(dns_server: String) { pub async fn restore_public_dns() { use crate::{core::handle, utils::dirs}; use tauri_plugin_shell::ShellExt; - let app_handle = handle::Handle::global().app_handle().unwrap(); + let app_handle = match handle::Handle::global().app_handle() { + Some(handle) => handle, + None => { + log::error!(target: "app", "app_handle not available for DNS restoration"); + return; + } + }; log::info!(target: "app", "try to unset system dns"); - let resource_dir = dirs::app_resources_dir().unwrap(); + let resource_dir = match dirs::app_resources_dir() { + Ok(dir) => dir, + Err(e) => { + log::error!(target: "app", "Failed to get resource directory: {}", e); + return; + } + }; let script = resource_dir.join("unset_dns.sh"); if !script.exists() { log::error!(target: "app", "unset_dns.sh not found");