mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
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:
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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内核验证
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user