diff --git a/src-tauri/src/cmd/app.rs b/src-tauri/src/cmd/app.rs index 11a9207f..b0b9d480 100644 --- a/src-tauri/src/cmd/app.rs +++ b/src-tauri/src/cmd/app.rs @@ -12,6 +12,7 @@ use smartstring::alias::String; use std::path::Path; use tauri::{AppHandle, Manager}; use tokio::fs; +use tokio::io::AsyncWriteExt; /// 打开应用程序所在目录 #[tauri::command] @@ -116,7 +117,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult } if !icon_cache_dir.exists() { - let _ = std::fs::create_dir_all(&icon_cache_dir); + let _ = fs::create_dir_all(&icon_cache_dir).await; } let temp_path = icon_cache_dir.join(format!("{}.downloading", name.as_str())); @@ -140,7 +141,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult if is_image && !is_html { { - let mut file = match std::fs::File::create(&temp_path) { + let mut file = match fs::File::create(&temp_path).await { Ok(file) => file, Err(_) => { if icon_path.exists() { @@ -149,12 +150,12 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult return Err("Failed to create temporary file".into()); } }; - - std::io::copy(&mut content.as_ref(), &mut file).stringify_err()?; + file.write_all(content.as_ref()).await.stringify_err()?; + file.flush().await.stringify_err()?; } if !icon_path.exists() { - match std::fs::rename(&temp_path, &icon_path) { + match fs::rename(&temp_path, &icon_path).await { Ok(_) => {} Err(_) => { let _ = temp_path.remove_if_exists().await; diff --git a/src-tauri/src/cmd/backup.rs b/src-tauri/src/cmd/backup.rs index 8610b682..a105f4d8 100644 --- a/src-tauri/src/cmd/backup.rs +++ b/src-tauri/src/cmd/backup.rs @@ -11,8 +11,8 @@ pub async fn create_local_backup() -> CmdResult<()> { /// List local backups #[tauri::command] -pub fn list_local_backup() -> CmdResult> { - feat::list_local_backup().stringify_err() +pub async fn list_local_backup() -> CmdResult> { + feat::list_local_backup().await.stringify_err() } /// Delete local backup @@ -29,6 +29,8 @@ pub async fn restore_local_backup(filename: String) -> CmdResult<()> { /// Export local backup to a user selected destination #[tauri::command] -pub fn export_local_backup(filename: String, destination: String) -> CmdResult<()> { - feat::export_local_backup(filename, destination).stringify_err() +pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> { + feat::export_local_backup(filename, destination) + .await + .stringify_err() } diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index cf3675f7..f429e4fd 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -8,6 +8,7 @@ use crate::{config::*, feat, logging, utils::logging::Type}; use compact_str::CompactString; use serde_yaml_ng::Mapping; use smartstring::alias::String; +use tokio::fs; /// 复制Clash环境变量 #[tauri::command] @@ -158,11 +159,9 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult { return Err("DNS config file not found".into()); } - let dns_yaml = tokio::fs::read_to_string(&dns_path) - .await - .stringify_err_log(|e| { - logging!(error, Type::Config, "Failed to read DNS config: {e}"); - })?; + let dns_yaml = fs::read_to_string(&dns_path).await.stringify_err_log(|e| { + logging!(error, Type::Config, "Failed to read DNS config: {e}"); + })?; // 解析DNS配置 let patch_config = serde_yaml_ng::from_str::(&dns_yaml) diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 15177936..fc36cf56 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -631,10 +631,15 @@ pub async fn view_profile(index: String) -> CmdResult { /// 读取配置文件内容 #[tauri::command] pub async fn read_profile_file(index: String) -> CmdResult { - let profiles = Config::profiles().await; - let profiles_ref = profiles.latest_ref(); - let item = profiles_ref.get_item(&index).stringify_err()?; - let data = item.read_file().stringify_err()?; + let item = { + let profiles = Config::profiles().await; + let profiles_ref = profiles.latest_ref(); + PrfItem { + file: profiles_ref.get_item(&index).stringify_err()?.file.clone(), + ..Default::default() + } + }; + let data = item.read_file().await.stringify_err()?; Ok(data) } diff --git a/src-tauri/src/cmd/save_profile.rs b/src-tauri/src/cmd/save_profile.rs index a1cd3474..cb216e8b 100644 --- a/src-tauri/src/cmd/save_profile.rs +++ b/src-tauri/src/cmd/save_profile.rs @@ -12,28 +12,37 @@ use tokio::fs; /// 保存profiles的配置 #[tauri::command] pub async fn save_profile_file(index: String, file_data: Option) -> CmdResult { - if file_data.is_none() { - return Ok(()); - } + let file_data = match file_data { + Some(d) => d, + None => return Ok(()), + }; - // 在异步操作前完成所有文件操作 - let (file_path, original_content, is_merge_file) = { + // 在异步操作前获取必要元数据并释放锁 + let (rel_path, is_merge_file) = { let profiles = Config::profiles().await; let profiles_guard = profiles.latest_ref(); let item = profiles_guard.get_item(&index).stringify_err()?; - // 确定是否为merge类型文件 let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge"); - let content = item.read_file().stringify_err()?; let path = item.file.clone().ok_or("file field is null")?; - let profiles_dir = dirs::app_profiles_dir().stringify_err()?; - (profiles_dir.join(path.as_str()), content, is_merge) + (path, is_merge) }; + // 读取原始内容(在释放profiles_guard后进行) + let original_content = PrfItem { + file: Some(rel_path.clone()), + ..Default::default() + } + .read_file() + .await + .stringify_err()?; + + let profiles_dir = dirs::app_profiles_dir().stringify_err()?; + let file_path = profiles_dir.join(rel_path.as_str()); + let file_path_str = file_path.to_string_lossy().to_string(); + // 保存新的配置文件 - let file_data = file_data.ok_or("file_data is None")?; fs::write(&file_path, &file_data).await.stringify_err()?; - let file_path_str = file_path.to_string_lossy().to_string(); logging!( info, Type::Config, @@ -42,84 +51,91 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR is_merge_file ); - // 对于 merge 文件,只进行语法验证,不进行后续内核验证 if is_merge_file { - logging!( - info, - Type::Config, - "[cmd配置save] 检测到merge文件,只进行语法验证" - ); - match CoreConfigValidator::validate_config_file(&file_path_str, Some(true)).await { - Ok((true, _)) => { - logging!(info, Type::Config, "[cmd配置save] merge文件语法验证通过"); - // 成功后尝试更新整体配置 - match CoreManager::global().update_config().await { - Ok(_) => { - // 配置更新成功,刷新前端 - handle::Handle::refresh_clash(); - } - Err(e) => { - logging!( - warn, - Type::Config, - "[cmd配置save] 更新整体配置时发生错误: {}", - e - ); - } - } - return Ok(()); - } - Ok((false, error_msg)) => { + return handle_merge_file(&file_path_str, &file_path, &original_content).await; + } + + handle_full_validation(&file_path_str, &file_path, &original_content).await +} + +async fn restore_original( + file_path: &std::path::Path, + original_content: &str, +) -> Result<(), String> { + fs::write(file_path, original_content).await.stringify_err() +} + +fn is_script_error(err: &str, file_path_str: &str) -> bool { + file_path_str.ends_with(".js") + || err.contains("Script syntax error") + || err.contains("Script must contain a main function") + || err.contains("Failed to read script file") +} + +async fn handle_merge_file( + file_path_str: &str, + file_path: &std::path::Path, + original_content: &str, +) -> CmdResult { + logging!( + info, + Type::Config, + "[cmd配置save] 检测到merge文件,只进行语法验证" + ); + + match CoreConfigValidator::validate_config_file(file_path_str, Some(true)).await { + Ok((true, _)) => { + logging!(info, Type::Config, "[cmd配置save] merge文件语法验证通过"); + if let Err(e) = CoreManager::global().update_config().await { logging!( warn, Type::Config, - "[cmd配置save] merge文件语法验证失败: {}", - error_msg + "[cmd配置save] 更新整体配置时发生错误: {}", + e ); - // 恢复原始配置文件 - fs::write(&file_path, original_content) - .await - .stringify_err()?; - // 发送合并文件专用错误通知 - let result = (false, error_msg.clone()); - crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件"); - return Ok(()); - } - Err(e) => { - logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e); - // 恢复原始配置文件 - fs::write(&file_path, original_content) - .await - .stringify_err()?; - return Err(e.to_string().into()); + } else { + handle::Handle::refresh_clash(); } + Ok(()) + } + Ok((false, error_msg)) => { + logging!( + warn, + Type::Config, + "[cmd配置save] merge文件语法验证失败: {}", + error_msg + ); + restore_original(file_path, original_content).await?; + let result = (false, error_msg.clone()); + crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件"); + Ok(()) + } + Err(e) => { + logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e); + restore_original(file_path, original_content).await?; + Err(e.to_string().into()) } } +} - // 非merge文件使用完整验证流程 - match CoreConfigValidator::validate_config_file(&file_path_str, None).await { +async fn handle_full_validation( + file_path_str: &str, + file_path: &std::path::Path, + original_content: &str, +) -> CmdResult { + match CoreConfigValidator::validate_config_file(file_path_str, None).await { Ok((true, _)) => { logging!(info, Type::Config, "[cmd配置save] 验证成功"); Ok(()) } Ok((false, error_msg)) => { logging!(warn, Type::Config, "[cmd配置save] 验证失败: {}", error_msg); - // 恢复原始配置文件 - fs::write(&file_path, original_content) - .await - .stringify_err()?; - - // 智能判断错误类型 - let is_script_error = file_path_str.ends_with(".js") - || error_msg.contains("Script syntax error") - || error_msg.contains("Script must contain a main function") - || error_msg.contains("Failed to read script file"); + restore_original(file_path, original_content).await?; if error_msg.contains("YAML syntax error") || error_msg.contains("Failed to read file:") - || (!file_path_str.ends_with(".js") && !is_script_error) + || (!file_path_str.ends_with(".js") && !is_script_error(&error_msg, file_path_str)) { - // 普通YAML错误使用YAML通知处理 logging!( info, Type::Config, @@ -127,8 +143,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR ); let result = (false, error_msg.clone()); crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件"); - } else if is_script_error { - // 脚本错误使用专门的通知处理 + } else if is_script_error(&error_msg, file_path_str) { logging!( info, Type::Config, @@ -137,7 +152,6 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR let result = (false, error_msg.clone()); crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件"); } else { - // 普通配置错误使用一般通知 logging!( info, Type::Config, @@ -150,10 +164,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR } Err(e) => { logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e); - // 恢复原始配置文件 - fs::write(&file_path, original_content) - .await - .stringify_err()?; + restore_original(file_path, original_content).await?; Err(e.to_string().into()) } } diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 46c86478..d26eac70 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -7,7 +7,8 @@ use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use serde_yaml_ng::Mapping; use smartstring::alias::String; -use std::{fs, time::Duration}; +use std::time::Duration; +use tokio::fs; #[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct PrfItem { @@ -546,24 +547,28 @@ impl PrfItem { } /// get the file data - pub fn read_file(&self) -> Result { + pub async fn read_file(&self) -> Result { let file = self .file - .clone() + .as_ref() .ok_or_else(|| anyhow::anyhow!("could not find the file"))?; let path = dirs::app_profiles_dir()?.join(file.as_str()); - let content = fs::read_to_string(path).context("failed to read the file")?; + let content = fs::read_to_string(path) + .await + .context("failed to read the file")?; Ok(content.into()) } /// save the file data - pub fn save_file(&self, data: String) -> Result<()> { + pub async fn save_file(&self, data: String) -> Result<()> { let file = self .file - .clone() + .as_ref() .ok_or_else(|| anyhow::anyhow!("could not find the file"))?; let path = dirs::app_profiles_dir()?.join(file.as_str()); - fs::write(path, data.as_bytes()).context("failed to save the file") + fs::write(path, data.as_bytes()) + .await + .context("failed to save the file") } } diff --git a/src-tauri/src/core/backup.rs b/src-tauri/src/core/backup.rs index d9e46c68..345d1f05 100644 --- a/src-tauri/src/core/backup.rs +++ b/src-tauri/src/core/backup.rs @@ -1,4 +1,4 @@ -use crate::{config::Config, utils::dirs}; +use crate::{config::Config, process::AsyncHandler, utils::dirs}; use anyhow::Error; use once_cell::sync::OnceCell; use parking_lot::Mutex; @@ -7,13 +7,12 @@ use smartstring::alias::String; use std::{ collections::HashMap, env::{consts::OS, temp_dir}, - fs, io::Write, path::PathBuf, sync::Arc, time::Duration, }; -use tokio::time::timeout; +use tokio::{fs, time::timeout}; use zip::write::SimpleFileOptions; // 应用版本常量,来自 tauri.conf.json @@ -170,7 +169,7 @@ impl WebDavClient { let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name).into(); // 读取文件并上传,如果失败尝试一次重试 - let file_content = fs::read(&file_path)?; + let file_content = fs::read(&file_path).await?; // 添加超时保护 let upload_result = timeout( @@ -212,7 +211,7 @@ impl WebDavClient { let fut = async { let response = client.get(path.as_str()).await?; let content = response.bytes().await?; - fs::write(&storage_path, &content)?; + fs::write(&storage_path, &content).await?; Ok::<(), Error>(()) }; @@ -250,18 +249,19 @@ impl WebDavClient { } } -pub fn create_backup() -> Result<(String, PathBuf), Error> { +pub async fn create_backup() -> Result<(String, PathBuf), Error> { let now = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string(); let zip_file_name: String = format!("{OS}-backup-{now}.zip").into(); let zip_path = temp_dir().join(zip_file_name.as_str()); - let file = fs::File::create(&zip_path)?; + let value = zip_path.clone(); + let file = AsyncHandler::spawn_blocking(move || std::fs::File::create(&value)).await??; let mut zip = zip::ZipWriter::new(file); zip.add_directory("profiles/", SimpleFileOptions::default())?; 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?; + + if let Ok(mut entries) = fs::read_dir(dirs::app_profiles_dir()?).await { + while let Some(entry) = entries.next_entry().await? { let path = entry.path(); if path.is_file() { let file_name_os = entry.file_name(); @@ -270,16 +270,16 @@ pub fn create_backup() -> Result<(String, PathBuf), Error> { .ok_or_else(|| anyhow::Error::msg("Invalid file name encoding"))?; let backup_path = format!("profiles/{}", file_name); zip.start_file(backup_path, options)?; - let file_content = fs::read(&path)?; + let file_content = fs::read(&path).await?; zip.write_all(&file_content)?; } } } zip.start_file(dirs::CLASH_CONFIG, options)?; - zip.write_all(fs::read(dirs::clash_path()?)?.as_slice())?; + zip.write_all(fs::read(dirs::clash_path()?).await?.as_slice())?; - let mut verge_config: serde_json::Value = - serde_yaml_ng::from_str(&fs::read_to_string(dirs::verge_path()?)?)?; + let verge_text = fs::read_to_string(dirs::verge_path()?).await?; + let mut verge_config: serde_json::Value = serde_yaml_ng::from_str(&verge_text)?; if let Some(obj) = verge_config.as_object_mut() { obj.remove("webdav_username"); obj.remove("webdav_password"); @@ -291,11 +291,11 @@ pub fn create_backup() -> Result<(String, PathBuf), Error> { let dns_config_path = dirs::app_home_dir()?.join(dirs::DNS_CONFIG); if dns_config_path.exists() { zip.start_file(dirs::DNS_CONFIG, options)?; - zip.write_all(fs::read(&dns_config_path)?.as_slice())?; + zip.write_all(fs::read(&dns_config_path).await?.as_slice())?; } zip.start_file(dirs::PROFILE_YAML, options)?; - zip.write_all(fs::read(dirs::profiles_path()?)?.as_slice())?; + zip.write_all(fs::read(dirs::profiles_path()?).await?.as_slice())?; zip.finish()?; Ok((zip_file_name, zip_path)) } diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index ed9f21e8..fe657d11 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -2,6 +2,7 @@ use once_cell::sync::OnceCell; use tauri::Emitter; use tauri::tray::TrayIconBuilder; use tauri_plugin_mihomo::models::Proxies; +use tokio::fs; #[cfg(target_os = "macos")] pub mod speed_rate; use crate::config::PrfSelected; @@ -26,7 +27,6 @@ use smartstring::alias::String; use std::collections::HashMap; use std::sync::Arc; use std::{ - fs, sync::atomic::{AtomicBool, Ordering}, time::{Duration, Instant}, }; @@ -86,7 +86,7 @@ impl TrayState { let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false); if is_common_tray_icon && let Ok(Some(common_icon_path)) = find_target_icons("common") - && let Ok(icon_data) = fs::read(common_icon_path) + && let Ok(icon_data) = fs::read(common_icon_path).await { return (true, icon_data); } @@ -123,7 +123,7 @@ impl TrayState { let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false); if is_sysproxy_tray_icon && let Ok(Some(sysproxy_icon_path)) = find_target_icons("sysproxy") - && let Ok(icon_data) = fs::read(sysproxy_icon_path) + && let Ok(icon_data) = fs::read(sysproxy_icon_path).await { return (true, icon_data); } @@ -160,7 +160,7 @@ impl TrayState { let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false); if is_tun_tray_icon && let Ok(Some(tun_icon_path)) = find_target_icons("tun") - && let Ok(icon_data) = fs::read(tun_icon_path) + && let Ok(icon_data) = fs::read(tun_icon_path).await { return (true, icon_data); } diff --git a/src-tauri/src/core/validate.rs b/src-tauri/src/core/validate.rs index 44673cc0..204497a0 100644 --- a/src-tauri/src/core/validate.rs +++ b/src-tauri/src/core/validate.rs @@ -1,9 +1,9 @@ use anyhow::Result; use scopeguard::defer; use smartstring::alias::String; -use std::path::Path; use std::sync::atomic::{AtomicBool, Ordering}; use tauri_plugin_shell::ShellExt; +use tokio::fs; use crate::config::{Config, ConfigType}; use crate::core::handle; @@ -33,19 +33,16 @@ impl CoreConfigValidator { impl CoreConfigValidator { /// 检查文件是否为脚本文件 - fn is_script_file

(path: P) -> Result - where - P: AsRef + std::fmt::Display, - { + async fn is_script_file(path: &str) -> Result { // 1. 先通过扩展名快速判断 - if has_ext(&path, "yaml") || has_ext(&path, "yml") { + if has_ext(path, "yaml") || has_ext(path, "yml") { return Ok(false); // YAML文件不是脚本文件 - } else if has_ext(&path, "js") { + } else if has_ext(path, "js") { return Ok(true); // JS文件是脚本文件 } // 2. 读取文件内容 - let content = match std::fs::read_to_string(&path) { + let content = match fs::read_to_string(path).await { Ok(content) => content, Err(err) => { logging!( @@ -115,11 +112,11 @@ impl CoreConfigValidator { } /// 只进行文件语法检查,不进行完整验证 - fn validate_file_syntax(config_path: &str) -> Result<(bool, String)> { + async fn validate_file_syntax(config_path: &str) -> Result<(bool, String)> { logging!(info, Type::Validate, "开始检查文件: {}", config_path); // 读取文件内容 - let content = match std::fs::read_to_string(config_path) { + let content = match fs::read_to_string(config_path).await { Ok(content) => content, Err(err) => { let error_msg = format!("Failed to read file: {err}").into(); @@ -144,9 +141,9 @@ impl CoreConfigValidator { } /// 验证脚本文件语法 - fn validate_script_file(path: &str) -> Result<(bool, String)> { + async fn validate_script_file(path: &str) -> Result<(bool, String)> { // 读取脚本内容 - let content = match std::fs::read_to_string(path) { + let content = match fs::read_to_string(path).await { Ok(content) => content, Err(err) => { let error_msg = format!("Failed to read script file: {err}").into(); @@ -216,14 +213,14 @@ impl CoreConfigValidator { "检测到Merge文件,仅进行语法检查: {}", config_path ); - return Self::validate_file_syntax(config_path); + return Self::validate_file_syntax(config_path).await; } // 检查是否为脚本文件 let is_script = if config_path.ends_with(".js") { true } else { - match Self::is_script_file(config_path) { + match Self::is_script_file(config_path).await { Ok(result) => result, Err(err) => { // 如果无法确定文件类型,尝试使用Clash内核验证 @@ -246,7 +243,7 @@ impl CoreConfigValidator { "检测到脚本文件,使用JavaScript验证: {}", config_path ); - return Self::validate_script_file(config_path); + return Self::validate_script_file(config_path).await; } // 对YAML配置文件使用Clash内核验证 diff --git a/src-tauri/src/enhance/chain.rs b/src-tauri/src/enhance/chain.rs index 8238061c..a9a81ac8 100644 --- a/src-tauri/src/enhance/chain.rs +++ b/src-tauri/src/enhance/chain.rs @@ -5,7 +5,7 @@ use crate::{ }; use serde_yaml_ng::Mapping; use smartstring::alias::String; -use std::fs; +use tokio::fs; #[derive(Debug, Clone)] pub struct ChainItem { @@ -83,7 +83,7 @@ impl AsyncChainItemFrom for Option { match itype { "script" => Some(ChainItem { uid, - data: ChainType::Script(fs::read_to_string(path).ok()?.into()), + data: ChainType::Script(fs::read_to_string(path).await.ok()?.into()), }), "merge" => Some(ChainItem { uid, diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index fc8089c0..7f7d71c8 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -6,10 +6,12 @@ pub mod seq; mod tun; use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*}; +use crate::utils::dirs; use crate::{config::Config, utils::tmpl}; use serde_yaml_ng::Mapping; use smartstring::alias::String; use std::collections::{HashMap, HashSet}; +use tokio::fs; type ResultLog = Vec<(String, String)>; #[derive(Debug)] @@ -437,34 +439,29 @@ fn apply_builtin_scripts( config } -fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping { - if enable_dns_settings { - use crate::utils::dirs; - use std::fs; +async fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping { + if enable_dns_settings && let Ok(app_dir) = dirs::app_home_dir() { + let dns_path = app_dir.join("dns_config.yaml"); - if let Ok(app_dir) = dirs::app_home_dir() { - let dns_path = app_dir.join("dns_config.yaml"); - - if dns_path.exists() - && let Ok(dns_yaml) = fs::read_to_string(&dns_path) - && let Ok(dns_config) = serde_yaml_ng::from_str::(&dns_yaml) + if dns_path.exists() + && let Ok(dns_yaml) = fs::read_to_string(&dns_path).await + && let Ok(dns_config) = serde_yaml_ng::from_str::(&dns_yaml) + { + if let Some(hosts_value) = dns_config.get("hosts") + && hosts_value.is_mapping() { - if let Some(hosts_value) = dns_config.get("hosts") - && hosts_value.is_mapping() - { - config.insert("hosts".into(), hosts_value.clone()); - log::info!(target: "app", "apply hosts configuration"); - } + config.insert("hosts".into(), hosts_value.clone()); + log::info!(target: "app", "apply hosts configuration"); + } - if let Some(dns_value) = dns_config.get("dns") { - if let Some(dns_mapping) = dns_value.as_mapping() { - config.insert("dns".into(), dns_mapping.clone().into()); - log::info!(target: "app", "apply dns_config.yaml (dns section)"); - } - } else { - config.insert("dns".into(), dns_config.into()); - log::info!(target: "app", "apply dns_config.yaml"); + if let Some(dns_value) = dns_config.get("dns") { + if let Some(dns_mapping) = dns_value.as_mapping() { + config.insert("dns".into(), dns_mapping.clone().into()); + log::info!(target: "app", "apply dns_config.yaml (dns section)"); } + } else { + config.insert("dns".into(), dns_config.into()); + log::info!(target: "app", "apply dns_config.yaml"); } } } @@ -540,7 +537,7 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { config = use_sort(config); // dns settings - config = apply_dns_settings(config, enable_dns_settings); + config = apply_dns_settings(config, enable_dns_settings).await; let mut exists_set = HashSet::new(); exists_set.extend(exists_keys); diff --git a/src-tauri/src/feat/backup.rs b/src-tauri/src/feat/backup.rs index e2f0957a..7d0bf9a8 100644 --- a/src-tauri/src/feat/backup.rs +++ b/src-tauri/src/feat/backup.rs @@ -2,6 +2,7 @@ use crate::{ config::{Config, IVerge}, core::backup, logging, logging_error, + process::AsyncHandler, utils::{ dirs::{PathBufExec, app_home_dir, local_backup_dir}, logging::Type, @@ -12,7 +13,8 @@ use chrono::Utc; use reqwest_dav::list_cmd::ListFile; use serde::Serialize; use smartstring::alias::String; -use std::{fs, path::PathBuf}; +use std::path::PathBuf; +use tokio::fs; #[derive(Debug, Serialize)] pub struct LocalBackupFile { @@ -24,7 +26,7 @@ pub struct LocalBackupFile { /// Create a backup and upload to WebDAV pub async fn create_backup_and_upload_webdav() -> Result<()> { - let (file_name, temp_file_path) = backup::create_backup().map_err(|err| { + let (file_name, temp_file_path) = backup::create_backup().await.map_err(|err| { logging!(error, Type::Backup, "Failed to create backup: {err:#?}"); err })?; @@ -97,7 +99,9 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> { })?; // extract zip file - let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?; + let value = backup_storage_path.clone(); + let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&value)).await??; + let mut zip = zip::ZipArchive::new(file)?; zip.extract(app_home_dir()?)?; logging_error!( Type::Backup, @@ -119,7 +123,7 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> { /// Create a backup and save to local storage pub async fn create_local_backup() -> Result<()> { - let (file_name, temp_file_path) = backup::create_backup().map_err(|err| { + let (file_name, temp_file_path) = backup::create_backup().await.map_err(|err| { logging!( error, Type::Backup, @@ -131,7 +135,7 @@ pub async fn create_local_backup() -> Result<()> { let backup_dir = local_backup_dir()?; let target_path = backup_dir.join(file_name.as_str()); - if let Err(err) = move_file(temp_file_path.clone(), target_path.clone()) { + if let Err(err) = move_file(temp_file_path.clone(), target_path.clone()).await { logging!( error, Type::Backup, @@ -151,12 +155,12 @@ pub async fn create_local_backup() -> Result<()> { Ok(()) } -fn move_file(from: PathBuf, to: PathBuf) -> Result<()> { +async fn move_file(from: PathBuf, to: PathBuf) -> Result<()> { if let Some(parent) = to.parent() { - fs::create_dir_all(parent)?; + fs::create_dir_all(parent).await?; } - match fs::rename(&from, &to) { + match fs::rename(&from, &to).await { Ok(_) => Ok(()), Err(rename_err) => { // Attempt copy + remove as fallback, covering cross-device moves @@ -165,8 +169,11 @@ fn move_file(from: PathBuf, to: PathBuf) -> Result<()> { Type::Backup, "Failed to rename backup file directly, fallback to copy/remove: {rename_err:#?}" ); - fs::copy(&from, &to).map_err(|err| anyhow!("Failed to copy backup file: {err:#?}"))?; + fs::copy(&from, &to) + .await + .map_err(|err| anyhow!("Failed to copy backup file: {err:#?}"))?; fs::remove_file(&from) + .await .map_err(|err| anyhow!("Failed to remove temp backup file: {err:#?}"))?; Ok(()) } @@ -174,24 +181,25 @@ fn move_file(from: PathBuf, to: PathBuf) -> Result<()> { } /// List local backups -pub fn list_local_backup() -> Result> { +pub async fn list_local_backup() -> Result> { let backup_dir = local_backup_dir()?; if !backup_dir.exists() { return Ok(vec![]); } let mut backups = Vec::new(); - for entry in fs::read_dir(&backup_dir)? { - let entry = entry?; + let mut dir = fs::read_dir(&backup_dir).await?; + while let Some(entry) = dir.next_entry().await? { let path = entry.path(); - if !path.is_file() { + let metadata = entry.metadata().await?; + if !metadata.is_file() { continue; } - let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else { - continue; + let file_name = match path.file_name().and_then(|name| name.to_str()) { + Some(name) => name, + None => continue, }; - let metadata = entry.metadata()?; let last_modified = metadata .modified() .map(|time| chrono::DateTime::::from(time).to_rfc3339()) @@ -239,7 +247,8 @@ pub async fn restore_local_backup(filename: String) -> Result<()> { let webdav_username = verge_data.webdav_username.clone(); let webdav_password = verge_data.webdav_password.clone(); - let mut zip = zip::ZipArchive::new(fs::File::open(&target_path)?)?; + let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&target_path)).await??; + let mut zip = zip::ZipArchive::new(file)?; zip.extract(app_home_dir()?)?; logging_error!( Type::Backup, @@ -258,7 +267,7 @@ pub async fn restore_local_backup(filename: String) -> Result<()> { } /// Export local backup file to user selected destination -pub fn export_local_backup(filename: String, destination: String) -> Result<()> { +pub async fn export_local_backup(filename: String, destination: String) -> Result<()> { let backup_dir = local_backup_dir()?; let source_path = backup_dir.join(filename.as_str()); if !source_path.exists() { @@ -267,10 +276,11 @@ pub fn export_local_backup(filename: String, destination: String) -> Result<()> let dest_path = PathBuf::from(destination.as_str()); if let Some(parent) = dest_path.parent() { - fs::create_dir_all(parent)?; + fs::create_dir_all(parent).await?; } fs::copy(&source_path, &dest_path) + .await .map(|_| ()) .map_err(|err| anyhow!("Failed to export backup file: {err:#?}"))?; Ok(())