use crate::{ config::{Config, IVerge}, core::backup, logging_error, utils::{ dirs::{app_home_dir, local_backup_dir}, logging::Type, }, }; use anyhow::{Result, anyhow}; use chrono::Utc; use reqwest_dav::list_cmd::ListFile; 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<()> { let (file_name, temp_file_path) = backup::create_backup().map_err(|err| { log::error!(target: "app", "Failed to create backup: {err:#?}"); err })?; if let Err(err) = backup::WebDavClient::global() .upload(temp_file_path.clone(), file_name) .await { log::error!(target: "app", "Failed to upload to WebDAV: {err:#?}"); // 上传失败时重置客户端缓存 backup::WebDavClient::global().reset(); return Err(err); } if let Err(err) = std::fs::remove_file(&temp_file_path) { log::warn!(target: "app", "Failed to remove temp file: {err:#?}"); } Ok(()) } /// List WebDAV backups pub async fn list_wevdav_backup() -> Result> { backup::WebDavClient::global().list().await.map_err(|err| { log::error!(target: "app", "Failed to list WebDAV backup files: {err:#?}"); err }) } /// Delete WebDAV backup pub async fn delete_webdav_backup(filename: String) -> Result<()> { backup::WebDavClient::global() .delete(filename) .await .map_err(|err| { log::error!(target: "app", "Failed to delete WebDAV backup file: {err:#?}"); err }) } /// Restore WebDAV backup pub async fn restore_webdav_backup(filename: String) -> Result<()> { 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 backup_storage_path = app_home_dir() .map_err(|e| anyhow::anyhow!("Failed to get app home dir: {e}"))? .join(&filename); backup::WebDavClient::global() .download(filename, backup_storage_path.clone()) .await .map_err(|err| { log::error!(target: "app", "Failed to download WebDAV backup file: {err:#?}"); err })?; // extract zip file let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?; zip.extract(app_home_dir()?)?; logging_error!( Type::Backup, super::patch_verge( IVerge { webdav_url, webdav_username, webdav_password, ..IVerge::default() }, false ) .await ); // 最后删除临时文件 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> { 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::::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(()) }