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 std::path::Path;
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
/// 打开应用程序所在目录
|
/// 打开应用程序所在目录
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -116,7 +117,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !icon_cache_dir.exists() {
|
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()));
|
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 {
|
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,
|
Ok(file) => file,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
if icon_path.exists() {
|
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());
|
return Err("Failed to create temporary file".into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
file.write_all(content.as_ref()).await.stringify_err()?;
|
||||||
std::io::copy(&mut content.as_ref(), &mut file).stringify_err()?;
|
file.flush().await.stringify_err()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !icon_path.exists() {
|
if !icon_path.exists() {
|
||||||
match std::fs::rename(&temp_path, &icon_path) {
|
match fs::rename(&temp_path, &icon_path).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let _ = temp_path.remove_if_exists().await;
|
let _ = temp_path.remove_if_exists().await;
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ pub async fn create_local_backup() -> CmdResult<()> {
|
|||||||
|
|
||||||
/// List local backups
|
/// List local backups
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn list_local_backup() -> CmdResult<Vec<LocalBackupFile>> {
|
pub async fn list_local_backup() -> CmdResult<Vec<LocalBackupFile>> {
|
||||||
feat::list_local_backup().stringify_err()
|
feat::list_local_backup().await.stringify_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete local backup
|
/// Delete local backup
|
||||||
@@ -29,6 +29,8 @@ pub async fn restore_local_backup(filename: String) -> CmdResult<()> {
|
|||||||
|
|
||||||
/// Export local backup to a user selected destination
|
/// Export local backup to a user selected destination
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {
|
pub async fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {
|
||||||
feat::export_local_backup(filename, destination).stringify_err()
|
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 compact_str::CompactString;
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
/// 复制Clash环境变量
|
/// 复制Clash环境变量
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -158,11 +159,9 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult {
|
|||||||
return Err("DNS config file not found".into());
|
return Err("DNS config file not found".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let dns_yaml = tokio::fs::read_to_string(&dns_path)
|
let dns_yaml = fs::read_to_string(&dns_path).await.stringify_err_log(|e| {
|
||||||
.await
|
logging!(error, Type::Config, "Failed to read DNS config: {e}");
|
||||||
.stringify_err_log(|e| {
|
})?;
|
||||||
logging!(error, Type::Config, "Failed to read DNS config: {e}");
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// 解析DNS配置
|
// 解析DNS配置
|
||||||
let patch_config = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml)
|
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]
|
#[tauri::command]
|
||||||
pub async fn read_profile_file(index: String) -> CmdResult<String> {
|
pub async fn read_profile_file(index: String) -> CmdResult<String> {
|
||||||
let profiles = Config::profiles().await;
|
let item = {
|
||||||
let profiles_ref = profiles.latest_ref();
|
let profiles = Config::profiles().await;
|
||||||
let item = profiles_ref.get_item(&index).stringify_err()?;
|
let profiles_ref = profiles.latest_ref();
|
||||||
let data = item.read_file().stringify_err()?;
|
PrfItem {
|
||||||
|
file: profiles_ref.get_item(&index).stringify_err()?.file.clone(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let data = item.read_file().await.stringify_err()?;
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,28 +12,37 @@ use tokio::fs;
|
|||||||
/// 保存profiles的配置
|
/// 保存profiles的配置
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdResult {
|
||||||
if file_data.is_none() {
|
let file_data = match file_data {
|
||||||
return Ok(());
|
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 = Config::profiles().await;
|
||||||
let profiles_guard = profiles.latest_ref();
|
let profiles_guard = profiles.latest_ref();
|
||||||
let item = profiles_guard.get_item(&index).stringify_err()?;
|
let item = profiles_guard.get_item(&index).stringify_err()?;
|
||||||
// 确定是否为merge类型文件
|
|
||||||
let is_merge = item.itype.as_ref().is_some_and(|t| t == "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 path = item.file.clone().ok_or("file field is null")?;
|
||||||
let profiles_dir = dirs::app_profiles_dir().stringify_err()?;
|
(path, is_merge)
|
||||||
(profiles_dir.join(path.as_str()), content, 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()?;
|
fs::write(&file_path, &file_data).await.stringify_err()?;
|
||||||
|
|
||||||
let file_path_str = file_path.to_string_lossy().to_string();
|
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
@@ -42,84 +51,91 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
|||||||
is_merge_file
|
is_merge_file
|
||||||
);
|
);
|
||||||
|
|
||||||
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
|
|
||||||
if is_merge_file {
|
if is_merge_file {
|
||||||
logging!(
|
return handle_merge_file(&file_path_str, &file_path, &original_content).await;
|
||||||
info,
|
}
|
||||||
Type::Config,
|
|
||||||
"[cmd配置save] 检测到merge文件,只进行语法验证"
|
handle_full_validation(&file_path_str, &file_path, &original_content).await
|
||||||
);
|
}
|
||||||
match CoreConfigValidator::validate_config_file(&file_path_str, Some(true)).await {
|
|
||||||
Ok((true, _)) => {
|
async fn restore_original(
|
||||||
logging!(info, Type::Config, "[cmd配置save] merge文件语法验证通过");
|
file_path: &std::path::Path,
|
||||||
// 成功后尝试更新整体配置
|
original_content: &str,
|
||||||
match CoreManager::global().update_config().await {
|
) -> Result<(), String> {
|
||||||
Ok(_) => {
|
fs::write(file_path, original_content).await.stringify_err()
|
||||||
// 配置更新成功,刷新前端
|
}
|
||||||
handle::Handle::refresh_clash();
|
|
||||||
}
|
fn is_script_error(err: &str, file_path_str: &str) -> bool {
|
||||||
Err(e) => {
|
file_path_str.ends_with(".js")
|
||||||
logging!(
|
|| err.contains("Script syntax error")
|
||||||
warn,
|
|| err.contains("Script must contain a main function")
|
||||||
Type::Config,
|
|| err.contains("Failed to read script file")
|
||||||
"[cmd配置save] 更新整体配置时发生错误: {}",
|
}
|
||||||
e
|
|
||||||
);
|
async fn handle_merge_file(
|
||||||
}
|
file_path_str: &str,
|
||||||
}
|
file_path: &std::path::Path,
|
||||||
return Ok(());
|
original_content: &str,
|
||||||
}
|
) -> CmdResult {
|
||||||
Ok((false, error_msg)) => {
|
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!(
|
logging!(
|
||||||
warn,
|
warn,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
"[cmd配置save] merge文件语法验证失败: {}",
|
"[cmd配置save] 更新整体配置时发生错误: {}",
|
||||||
error_msg
|
e
|
||||||
);
|
);
|
||||||
// 恢复原始配置文件
|
} else {
|
||||||
fs::write(&file_path, original_content)
|
handle::Handle::refresh_clash();
|
||||||
.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());
|
|
||||||
}
|
}
|
||||||
|
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文件使用完整验证流程
|
async fn handle_full_validation(
|
||||||
match CoreConfigValidator::validate_config_file(&file_path_str, None).await {
|
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, _)) => {
|
Ok((true, _)) => {
|
||||||
logging!(info, Type::Config, "[cmd配置save] 验证成功");
|
logging!(info, Type::Config, "[cmd配置save] 验证成功");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok((false, error_msg)) => {
|
Ok((false, error_msg)) => {
|
||||||
logging!(warn, Type::Config, "[cmd配置save] 验证失败: {}", error_msg);
|
logging!(warn, Type::Config, "[cmd配置save] 验证失败: {}", error_msg);
|
||||||
// 恢复原始配置文件
|
restore_original(file_path, original_content).await?;
|
||||||
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");
|
|
||||||
|
|
||||||
if error_msg.contains("YAML syntax error")
|
if error_msg.contains("YAML syntax error")
|
||||||
|| error_msg.contains("Failed to read file:")
|
|| 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!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Config,
|
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());
|
let result = (false, error_msg.clone());
|
||||||
crate::cmd::validate::handle_yaml_validation_notice(&result, "YAML配置文件");
|
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!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Config,
|
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());
|
let result = (false, error_msg.clone());
|
||||||
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
|
crate::cmd::validate::handle_script_validation_notice(&result, "脚本文件");
|
||||||
} else {
|
} else {
|
||||||
// 普通配置错误使用一般通知
|
|
||||||
logging!(
|
logging!(
|
||||||
info,
|
info,
|
||||||
Type::Config,
|
Type::Config,
|
||||||
@@ -150,10 +164,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e);
|
logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e);
|
||||||
// 恢复原始配置文件
|
restore_original(file_path, original_content).await?;
|
||||||
fs::write(&file_path, original_content)
|
|
||||||
.await
|
|
||||||
.stringify_err()?;
|
|
||||||
Err(e.to_string().into())
|
Err(e.to_string().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ use anyhow::{Context, Result, bail};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::{fs, time::Duration};
|
use std::time::Duration;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
pub struct PrfItem {
|
pub struct PrfItem {
|
||||||
@@ -546,24 +547,28 @@ impl PrfItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// get the file data
|
/// get the file data
|
||||||
pub fn read_file(&self) -> Result<String> {
|
pub async fn read_file(&self) -> Result<String> {
|
||||||
let file = self
|
let file = self
|
||||||
.file
|
.file
|
||||||
.clone()
|
.as_ref()
|
||||||
.ok_or_else(|| anyhow::anyhow!("could not find the file"))?;
|
.ok_or_else(|| anyhow::anyhow!("could not find the file"))?;
|
||||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
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())
|
Ok(content.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// save the file data
|
/// save the file data
|
||||||
pub fn save_file(&self, data: String) -> Result<()> {
|
pub async fn save_file(&self, data: String) -> Result<()> {
|
||||||
let file = self
|
let file = self
|
||||||
.file
|
.file
|
||||||
.clone()
|
.as_ref()
|
||||||
.ok_or_else(|| anyhow::anyhow!("could not find the file"))?;
|
.ok_or_else(|| anyhow::anyhow!("could not find the file"))?;
|
||||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
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 anyhow::Error;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -7,13 +7,12 @@ use smartstring::alias::String;
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env::{consts::OS, temp_dir},
|
env::{consts::OS, temp_dir},
|
||||||
fs,
|
|
||||||
io::Write,
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::time::timeout;
|
use tokio::{fs, time::timeout};
|
||||||
use zip::write::SimpleFileOptions;
|
use zip::write::SimpleFileOptions;
|
||||||
|
|
||||||
// 应用版本常量,来自 tauri.conf.json
|
// 应用版本常量,来自 tauri.conf.json
|
||||||
@@ -170,7 +169,7 @@ impl WebDavClient {
|
|||||||
let webdav_path: String = format!("{}/{}", dirs::BACKUP_DIR, file_name).into();
|
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(
|
let upload_result = timeout(
|
||||||
@@ -212,7 +211,7 @@ impl WebDavClient {
|
|||||||
let fut = async {
|
let fut = async {
|
||||||
let response = client.get(path.as_str()).await?;
|
let response = client.get(path.as_str()).await?;
|
||||||
let content = response.bytes().await?;
|
let content = response.bytes().await?;
|
||||||
fs::write(&storage_path, &content)?;
|
fs::write(&storage_path, &content).await?;
|
||||||
Ok::<(), Error>(())
|
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 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_file_name: String = format!("{OS}-backup-{now}.zip").into();
|
||||||
let zip_path = temp_dir().join(zip_file_name.as_str());
|
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);
|
let mut zip = zip::ZipWriter::new(file);
|
||||||
zip.add_directory("profiles/", SimpleFileOptions::default())?;
|
zip.add_directory("profiles/", SimpleFileOptions::default())?;
|
||||||
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||||
if let Ok(entries) = fs::read_dir(dirs::app_profiles_dir()?) {
|
|
||||||
for entry in entries {
|
if let Ok(mut entries) = fs::read_dir(dirs::app_profiles_dir()?).await {
|
||||||
let entry = entry?;
|
while let Some(entry) = entries.next_entry().await? {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let file_name_os = entry.file_name();
|
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"))?;
|
.ok_or_else(|| anyhow::Error::msg("Invalid file name encoding"))?;
|
||||||
let backup_path = format!("profiles/{}", file_name);
|
let backup_path = format!("profiles/{}", file_name);
|
||||||
zip.start_file(backup_path, options)?;
|
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.write_all(&file_content)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zip.start_file(dirs::CLASH_CONFIG, options)?;
|
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 =
|
let verge_text = fs::read_to_string(dirs::verge_path()?).await?;
|
||||||
serde_yaml_ng::from_str(&fs::read_to_string(dirs::verge_path()?)?)?;
|
let mut verge_config: serde_json::Value = serde_yaml_ng::from_str(&verge_text)?;
|
||||||
if let Some(obj) = verge_config.as_object_mut() {
|
if let Some(obj) = verge_config.as_object_mut() {
|
||||||
obj.remove("webdav_username");
|
obj.remove("webdav_username");
|
||||||
obj.remove("webdav_password");
|
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);
|
let dns_config_path = dirs::app_home_dir()?.join(dirs::DNS_CONFIG);
|
||||||
if dns_config_path.exists() {
|
if dns_config_path.exists() {
|
||||||
zip.start_file(dirs::DNS_CONFIG, options)?;
|
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.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()?;
|
zip.finish()?;
|
||||||
Ok((zip_file_name, zip_path))
|
Ok((zip_file_name, zip_path))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use once_cell::sync::OnceCell;
|
|||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
use tauri::tray::TrayIconBuilder;
|
use tauri::tray::TrayIconBuilder;
|
||||||
use tauri_plugin_mihomo::models::Proxies;
|
use tauri_plugin_mihomo::models::Proxies;
|
||||||
|
use tokio::fs;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod speed_rate;
|
pub mod speed_rate;
|
||||||
use crate::config::PrfSelected;
|
use crate::config::PrfSelected;
|
||||||
@@ -26,7 +27,6 @@ use smartstring::alias::String;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@@ -86,7 +86,7 @@ impl TrayState {
|
|||||||
let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false);
|
let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false);
|
||||||
if is_common_tray_icon
|
if is_common_tray_icon
|
||||||
&& let Ok(Some(common_icon_path)) = find_target_icons("common")
|
&& 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);
|
return (true, icon_data);
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ impl TrayState {
|
|||||||
let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false);
|
let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false);
|
||||||
if is_sysproxy_tray_icon
|
if is_sysproxy_tray_icon
|
||||||
&& let Ok(Some(sysproxy_icon_path)) = find_target_icons("sysproxy")
|
&& 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);
|
return (true, icon_data);
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ impl TrayState {
|
|||||||
let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false);
|
let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false);
|
||||||
if is_tun_tray_icon
|
if is_tun_tray_icon
|
||||||
&& let Ok(Some(tun_icon_path)) = find_target_icons("tun")
|
&& 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);
|
return (true, icon_data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::config::{Config, ConfigType};
|
use crate::config::{Config, ConfigType};
|
||||||
use crate::core::handle;
|
use crate::core::handle;
|
||||||
@@ -33,19 +33,16 @@ impl CoreConfigValidator {
|
|||||||
|
|
||||||
impl CoreConfigValidator {
|
impl CoreConfigValidator {
|
||||||
/// 检查文件是否为脚本文件
|
/// 检查文件是否为脚本文件
|
||||||
fn is_script_file<P>(path: P) -> Result<bool>
|
async fn is_script_file(path: &str) -> Result<bool> {
|
||||||
where
|
|
||||||
P: AsRef<Path> + std::fmt::Display,
|
|
||||||
{
|
|
||||||
// 1. 先通过扩展名快速判断
|
// 1. 先通过扩展名快速判断
|
||||||
if has_ext(&path, "yaml") || has_ext(&path, "yml") {
|
if has_ext(path, "yaml") || has_ext(path, "yml") {
|
||||||
return Ok(false); // YAML文件不是脚本文件
|
return Ok(false); // YAML文件不是脚本文件
|
||||||
} else if has_ext(&path, "js") {
|
} else if has_ext(path, "js") {
|
||||||
return Ok(true); // JS文件是脚本文件
|
return Ok(true); // JS文件是脚本文件
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 读取文件内容
|
// 2. 读取文件内容
|
||||||
let content = match std::fs::read_to_string(&path) {
|
let content = match fs::read_to_string(path).await {
|
||||||
Ok(content) => content,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(
|
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);
|
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,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Failed to read file: {err}").into();
|
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,
|
Ok(content) => content,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error_msg = format!("Failed to read script file: {err}").into();
|
let error_msg = format!("Failed to read script file: {err}").into();
|
||||||
@@ -216,14 +213,14 @@ impl CoreConfigValidator {
|
|||||||
"检测到Merge文件,仅进行语法检查: {}",
|
"检测到Merge文件,仅进行语法检查: {}",
|
||||||
config_path
|
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") {
|
let is_script = if config_path.ends_with(".js") {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
match Self::is_script_file(config_path) {
|
match Self::is_script_file(config_path).await {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 如果无法确定文件类型,尝试使用Clash内核验证
|
// 如果无法确定文件类型,尝试使用Clash内核验证
|
||||||
@@ -246,7 +243,7 @@ impl CoreConfigValidator {
|
|||||||
"检测到脚本文件,使用JavaScript验证: {}",
|
"检测到脚本文件,使用JavaScript验证: {}",
|
||||||
config_path
|
config_path
|
||||||
);
|
);
|
||||||
return Self::validate_script_file(config_path);
|
return Self::validate_script_file(config_path).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对YAML配置文件使用Clash内核验证
|
// 对YAML配置文件使用Clash内核验证
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ChainItem {
|
pub struct ChainItem {
|
||||||
@@ -83,7 +83,7 @@ impl AsyncChainItemFrom for Option<ChainItem> {
|
|||||||
match itype {
|
match itype {
|
||||||
"script" => Some(ChainItem {
|
"script" => Some(ChainItem {
|
||||||
uid,
|
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 {
|
"merge" => Some(ChainItem {
|
||||||
uid,
|
uid,
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ pub mod seq;
|
|||||||
mod tun;
|
mod tun;
|
||||||
|
|
||||||
use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
|
use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
|
||||||
|
use crate::utils::dirs;
|
||||||
use crate::{config::Config, utils::tmpl};
|
use crate::{config::Config, utils::tmpl};
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
type ResultLog = Vec<(String, String)>;
|
type ResultLog = Vec<(String, String)>;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -437,34 +439,29 @@ fn apply_builtin_scripts(
|
|||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping {
|
async fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping {
|
||||||
if enable_dns_settings {
|
if enable_dns_settings && let Ok(app_dir) = dirs::app_home_dir() {
|
||||||
use crate::utils::dirs;
|
let dns_path = app_dir.join("dns_config.yaml");
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
if let Ok(app_dir) = dirs::app_home_dir() {
|
if dns_path.exists()
|
||||||
let dns_path = app_dir.join("dns_config.yaml");
|
&& 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 dns_path.exists()
|
{
|
||||||
&& let Ok(dns_yaml) = fs::read_to_string(&dns_path)
|
if let Some(hosts_value) = dns_config.get("hosts")
|
||||||
&& let Ok(dns_config) = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&dns_yaml)
|
&& hosts_value.is_mapping()
|
||||||
{
|
{
|
||||||
if let Some(hosts_value) = dns_config.get("hosts")
|
config.insert("hosts".into(), hosts_value.clone());
|
||||||
&& hosts_value.is_mapping()
|
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_value) = dns_config.get("dns") {
|
||||||
if let Some(dns_mapping) = dns_value.as_mapping() {
|
if let Some(dns_mapping) = dns_value.as_mapping() {
|
||||||
config.insert("dns".into(), dns_mapping.clone().into());
|
config.insert("dns".into(), dns_mapping.clone().into());
|
||||||
log::info!(target: "app", "apply dns_config.yaml (dns section)");
|
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");
|
|
||||||
}
|
}
|
||||||
|
} 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);
|
config = use_sort(config);
|
||||||
|
|
||||||
// dns settings
|
// 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();
|
let mut exists_set = HashSet::new();
|
||||||
exists_set.extend(exists_keys);
|
exists_set.extend(exists_keys);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::{
|
|||||||
config::{Config, IVerge},
|
config::{Config, IVerge},
|
||||||
core::backup,
|
core::backup,
|
||||||
logging, logging_error,
|
logging, logging_error,
|
||||||
|
process::AsyncHandler,
|
||||||
utils::{
|
utils::{
|
||||||
dirs::{PathBufExec, app_home_dir, local_backup_dir},
|
dirs::{PathBufExec, app_home_dir, local_backup_dir},
|
||||||
logging::Type,
|
logging::Type,
|
||||||
@@ -12,7 +13,8 @@ use chrono::Utc;
|
|||||||
use reqwest_dav::list_cmd::ListFile;
|
use reqwest_dav::list_cmd::ListFile;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::{fs, path::PathBuf};
|
use std::path::PathBuf;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct LocalBackupFile {
|
pub struct LocalBackupFile {
|
||||||
@@ -24,7 +26,7 @@ pub struct LocalBackupFile {
|
|||||||
|
|
||||||
/// Create a backup and upload to WebDAV
|
/// Create a backup and upload to WebDAV
|
||||||
pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
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:#?}");
|
logging!(error, Type::Backup, "Failed to create backup: {err:#?}");
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
@@ -97,7 +99,9 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// extract zip file
|
// 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()?)?;
|
zip.extract(app_home_dir()?)?;
|
||||||
logging_error!(
|
logging_error!(
|
||||||
Type::Backup,
|
Type::Backup,
|
||||||
@@ -119,7 +123,7 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
|||||||
|
|
||||||
/// Create a backup and save to local storage
|
/// Create a backup and save to local storage
|
||||||
pub async fn create_local_backup() -> Result<()> {
|
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!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Backup,
|
Type::Backup,
|
||||||
@@ -131,7 +135,7 @@ pub async fn create_local_backup() -> Result<()> {
|
|||||||
let backup_dir = local_backup_dir()?;
|
let backup_dir = local_backup_dir()?;
|
||||||
let target_path = backup_dir.join(file_name.as_str());
|
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!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Backup,
|
Type::Backup,
|
||||||
@@ -151,12 +155,12 @@ pub async fn create_local_backup() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
|
async fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
|
||||||
if let Some(parent) = to.parent() {
|
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(()),
|
Ok(_) => Ok(()),
|
||||||
Err(rename_err) => {
|
Err(rename_err) => {
|
||||||
// Attempt copy + remove as fallback, covering cross-device moves
|
// Attempt copy + remove as fallback, covering cross-device moves
|
||||||
@@ -165,8 +169,11 @@ fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
|
|||||||
Type::Backup,
|
Type::Backup,
|
||||||
"Failed to rename backup file directly, fallback to copy/remove: {rename_err:#?}"
|
"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)
|
fs::remove_file(&from)
|
||||||
|
.await
|
||||||
.map_err(|err| anyhow!("Failed to remove temp backup file: {err:#?}"))?;
|
.map_err(|err| anyhow!("Failed to remove temp backup file: {err:#?}"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -174,24 +181,25 @@ fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// List local backups
|
/// 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()?;
|
let backup_dir = local_backup_dir()?;
|
||||||
if !backup_dir.exists() {
|
if !backup_dir.exists() {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut backups = Vec::new();
|
let mut backups = Vec::new();
|
||||||
for entry in fs::read_dir(&backup_dir)? {
|
let mut dir = fs::read_dir(&backup_dir).await?;
|
||||||
let entry = entry?;
|
while let Some(entry) = dir.next_entry().await? {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if !path.is_file() {
|
let metadata = entry.metadata().await?;
|
||||||
|
if !metadata.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
|
let file_name = match path.file_name().and_then(|name| name.to_str()) {
|
||||||
continue;
|
Some(name) => name,
|
||||||
|
None => continue,
|
||||||
};
|
};
|
||||||
let metadata = entry.metadata()?;
|
|
||||||
let last_modified = metadata
|
let last_modified = metadata
|
||||||
.modified()
|
.modified()
|
||||||
.map(|time| chrono::DateTime::<Utc>::from(time).to_rfc3339())
|
.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_username = verge_data.webdav_username.clone();
|
||||||
let webdav_password = verge_data.webdav_password.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()?)?;
|
zip.extract(app_home_dir()?)?;
|
||||||
logging_error!(
|
logging_error!(
|
||||||
Type::Backup,
|
Type::Backup,
|
||||||
@@ -258,7 +267,7 @@ pub async fn restore_local_backup(filename: String) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Export local backup file to user selected destination
|
/// 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 backup_dir = local_backup_dir()?;
|
||||||
let source_path = backup_dir.join(filename.as_str());
|
let source_path = backup_dir.join(filename.as_str());
|
||||||
if !source_path.exists() {
|
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());
|
let dest_path = PathBuf::from(destination.as_str());
|
||||||
if let Some(parent) = dest_path.parent() {
|
if let Some(parent) = dest_path.parent() {
|
||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::copy(&source_path, &dest_path)
|
fs::copy(&source_path, &dest_path)
|
||||||
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|err| anyhow!("Failed to export backup file: {err:#?}"))?;
|
.map_err(|err| anyhow!("Failed to export backup file: {err:#?}"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user