mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
feat: local backup (#5054)
* feat: local backup * refactor(backup): make local backup helpers synchronous and clean up redundant checks - Converted local backup helpers to synchronous functions to remove unused async warnings and align command signatures. - Updated list/delete/export commands to call the sync feature functions directly without awaits while preserving behavior. - Simplified destination directory creation to always ensure parent folders exist without redundant checks, satisfying Clippy.
This commit is contained in:
33
src-tauri/src/cmd/backup.rs
Normal file
33
src-tauri/src/cmd/backup.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use super::CmdResult;
|
||||
use crate::{feat, wrap_err};
|
||||
use feat::LocalBackupFile;
|
||||
|
||||
/// Create a local backup
|
||||
#[tauri::command]
|
||||
pub fn create_local_backup() -> CmdResult<()> {
|
||||
wrap_err!(feat::create_local_backup())
|
||||
}
|
||||
|
||||
/// List local backups
|
||||
#[tauri::command]
|
||||
pub fn list_local_backup() -> CmdResult<Vec<LocalBackupFile>> {
|
||||
wrap_err!(feat::list_local_backup())
|
||||
}
|
||||
|
||||
/// Delete local backup
|
||||
#[tauri::command]
|
||||
pub fn delete_local_backup(filename: String) -> CmdResult<()> {
|
||||
wrap_err!(feat::delete_local_backup(filename))
|
||||
}
|
||||
|
||||
/// Restore local backup
|
||||
#[tauri::command]
|
||||
pub async fn restore_local_backup(filename: String) -> CmdResult<()> {
|
||||
wrap_err!(feat::restore_local_backup(filename).await)
|
||||
}
|
||||
|
||||
/// Export local backup to a user selected destination
|
||||
#[tauri::command]
|
||||
pub fn export_local_backup(filename: String, destination: String) -> CmdResult<()> {
|
||||
wrap_err!(feat::export_local_backup(filename, destination))
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
// Command modules
|
||||
pub mod app;
|
||||
pub mod backup;
|
||||
pub mod clash;
|
||||
pub mod lightweight;
|
||||
pub mod media_unlock_checker;
|
||||
@@ -21,6 +22,7 @@ pub mod webdav;
|
||||
|
||||
// Re-export all command functions for backwards compatibility
|
||||
pub use app::*;
|
||||
pub use backup::*;
|
||||
pub use clash::*;
|
||||
pub use lightweight::*;
|
||||
pub use media_unlock_checker::*;
|
||||
|
||||
@@ -2,11 +2,24 @@ use crate::{
|
||||
config::{Config, IVerge},
|
||||
core::backup,
|
||||
logging_error,
|
||||
utils::{dirs::app_home_dir, logging::Type},
|
||||
utils::{
|
||||
dirs::{app_home_dir, local_backup_dir},
|
||||
logging::Type,
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, anyhow};
|
||||
use chrono::Utc;
|
||||
use reqwest_dav::list_cmd::ListFile;
|
||||
use std::fs;
|
||||
use serde::Serialize;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct LocalBackupFile {
|
||||
pub filename: String,
|
||||
pub path: String,
|
||||
pub last_modified: String,
|
||||
pub content_length: u64,
|
||||
}
|
||||
|
||||
/// Create a backup and upload to WebDAV
|
||||
pub async fn create_backup_and_upload_webdav() -> Result<()> {
|
||||
@@ -90,3 +103,147 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
fs::remove_file(backup_storage_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a backup and save to local storage
|
||||
pub fn create_local_backup() -> Result<()> {
|
||||
let (file_name, temp_file_path) = backup::create_backup().map_err(|err| {
|
||||
log::error!(target: "app", "Failed to create local backup: {err:#?}");
|
||||
err
|
||||
})?;
|
||||
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let target_path = backup_dir.join(&file_name);
|
||||
|
||||
if let Err(err) = move_file(temp_file_path.clone(), target_path.clone()) {
|
||||
log::error!(target: "app", "Failed to move local backup file: {err:#?}");
|
||||
// 清理临时文件
|
||||
if let Err(clean_err) = std::fs::remove_file(&temp_file_path) {
|
||||
log::warn!(
|
||||
target: "app",
|
||||
"Failed to remove temp backup file after move error: {clean_err:#?}"
|
||||
);
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_file(from: PathBuf, to: PathBuf) -> Result<()> {
|
||||
if let Some(parent) = to.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
match fs::rename(&from, &to) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(rename_err) => {
|
||||
// Attempt copy + remove as fallback, covering cross-device moves
|
||||
log::warn!(
|
||||
target: "app",
|
||||
"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::remove_file(&from)
|
||||
.map_err(|err| anyhow!("Failed to remove temp backup file: {err:#?}"))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List local backups
|
||||
pub 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 path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
|
||||
continue;
|
||||
};
|
||||
let metadata = entry.metadata()?;
|
||||
let last_modified = metadata
|
||||
.modified()
|
||||
.map(|time| chrono::DateTime::<Utc>::from(time).to_rfc3339())
|
||||
.unwrap_or_default();
|
||||
backups.push(LocalBackupFile {
|
||||
filename: file_name.to_string(),
|
||||
path: path.to_string_lossy().to_string(),
|
||||
last_modified,
|
||||
content_length: metadata.len(),
|
||||
});
|
||||
}
|
||||
|
||||
backups.sort_by(|a, b| b.filename.cmp(&a.filename));
|
||||
Ok(backups)
|
||||
}
|
||||
|
||||
/// Delete local backup
|
||||
pub fn delete_local_backup(filename: String) -> Result<()> {
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let target_path = backup_dir.join(&filename);
|
||||
if !target_path.exists() {
|
||||
log::warn!(target: "app", "Local backup file not found: {}", filename);
|
||||
return Ok(());
|
||||
}
|
||||
fs::remove_file(target_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restore local backup
|
||||
pub async fn restore_local_backup(filename: String) -> Result<()> {
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let target_path = backup_dir.join(&filename);
|
||||
if !target_path.exists() {
|
||||
return Err(anyhow!("Backup file not found: {}", filename));
|
||||
}
|
||||
|
||||
let verge = Config::verge().await;
|
||||
let verge_data = verge.latest_ref().clone();
|
||||
let webdav_url = verge_data.webdav_url.clone();
|
||||
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)?)?;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
logging_error!(
|
||||
Type::Backup,
|
||||
super::patch_verge(
|
||||
IVerge {
|
||||
webdav_url,
|
||||
webdav_username,
|
||||
webdav_password,
|
||||
..IVerge::default()
|
||||
},
|
||||
false
|
||||
)
|
||||
.await
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export local backup file to user selected destination
|
||||
pub fn export_local_backup(filename: String, destination: String) -> Result<()> {
|
||||
let backup_dir = local_backup_dir()?;
|
||||
let source_path = backup_dir.join(&filename);
|
||||
if !source_path.exists() {
|
||||
return Err(anyhow!("Backup file not found: {}", filename));
|
||||
}
|
||||
|
||||
let dest_path = PathBuf::from(destination);
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
fs::copy(&source_path, &dest_path)
|
||||
.map(|_| ())
|
||||
.map_err(|err| anyhow!("Failed to export backup file: {err:#?}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -204,6 +204,11 @@ mod app_init {
|
||||
cmd::script_validate_notice,
|
||||
cmd::validate_script_file,
|
||||
// Backup and WebDAV
|
||||
cmd::create_local_backup,
|
||||
cmd::list_local_backup,
|
||||
cmd::delete_local_backup,
|
||||
cmd::restore_local_backup,
|
||||
cmd::export_local_backup,
|
||||
cmd::create_webdav_backup,
|
||||
cmd::save_webdav_config,
|
||||
cmd::list_webdav_backup,
|
||||
|
||||
@@ -120,6 +120,13 @@ pub fn app_logs_dir() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("logs"))
|
||||
}
|
||||
|
||||
/// local backups dir
|
||||
pub fn local_backup_dir() -> Result<PathBuf> {
|
||||
let dir = app_home_dir()?.join(BACKUP_DIR);
|
||||
fs::create_dir_all(&dir)?;
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
pub fn clash_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join(CLASH_CONFIG))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user