refactor: convert file operations to async using tokio fs (#5267)

* refactor: convert file operations to async using tokio fs

* refactor: integrate AsyncHandler for file operations in backup processes
This commit is contained in:
Tunglies
2025-11-01 16:46:03 +08:00
committed by Tunglies
parent 413f29e22a
commit b3b8eeb577
12 changed files with 210 additions and 183 deletions

View File

@@ -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))
}

View File

@@ -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);
}

View File

@@ -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<P>(path: P) -> Result<bool>
where
P: AsRef<Path> + std::fmt::Display,
{
async fn is_script_file(path: &str) -> Result<bool> {
// 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内核验证