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

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

View File

@@ -11,8 +11,8 @@ pub async fn create_local_backup() -> CmdResult<()> {
/// List local backups
#[tauri::command]
pub fn list_local_backup() -> CmdResult<Vec<LocalBackupFile>> {
feat::list_local_backup().stringify_err()
pub async fn list_local_backup() -> CmdResult<Vec<LocalBackupFile>> {
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()
}

View File

@@ -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::<serde_yaml_ng::Mapping>(&dns_yaml)

View File

@@ -631,10 +631,15 @@ pub async fn view_profile(index: String) -> CmdResult {
/// 读取配置文件内容
#[tauri::command]
pub async fn read_profile_file(index: String) -> CmdResult<String> {
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)
}

View File

@@ -12,28 +12,37 @@ use tokio::fs;
/// 保存profiles的配置
#[tauri::command]
pub async fn save_profile_file(index: String, file_data: Option<String>) -> 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<String>) -> 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<String>) -> 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<String>) -> 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<String>) -> 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())
}
}