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

View File

@@ -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<String> {
pub async fn read_file(&self) -> Result<String> {
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")
}
}

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内核验证

View File

@@ -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<ChainItem> {
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,

View File

@@ -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::<serde_yaml_ng::Mapping>(&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::<serde_yaml_ng::Mapping>(&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<String>, HashMap<String, ResultLog>) {
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);

View File

@@ -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<Vec<LocalBackupFile>> {
pub async fn list_local_backup() -> Result<Vec<LocalBackupFile>> {
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::<Utc>::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(())