feat(auto-backup): implement centralized auto-backup manager and UI (#5374)

* feat(auto-backup): implement centralized auto-backup manager and UI

- Introduced AutoBackupManager to handle verge settings, run a background scheduler, debounce change-driven backups, and trim auto-labeled archives (keeps 20); wired into startup and config refresh hooks
  (src-tauri/src/module/auto_backup.rs:28-209, src-tauri/src/utils/resolve/mod.rs:64-136, src-tauri/src/feat/config.rs:102-238)

- Extended verge schema and backup helpers so scheduled/change-based settings persist, create_local_backup can rename archives, and profile/global-extend mutations now trigger backups
  (src-tauri/src/config/verge.rs:162-536, src/types/types.d.ts:857-859, src-tauri/src/feat/backup.rs:125-189, src-tauri/src/cmd/profile.rs:66-476, src-tauri/src/cmd/save_profile.rs:21-82)

- Added Auto Backup settings panel in backup dialog with dual toggles + interval selector; localized new strings across all locales
  (src/components/setting/mods/auto-backup-settings.tsx:1-138, src/components/setting/mods/backup-viewer.tsx:28-309, src/locales/en/settings.json:312-326 and mirrored entries)

- Regenerated typed i18n resources for strong typing in React
  (src/types/generated/i18n-keys.ts, src/types/generated/i18n-resources.ts)

* refactor(setting/backup): restructure backup dialog for consistent layout

* refactor(ui): unify settings dialog style

* fix(backup): only trigger auto-backup on valid saves & restore restarts app safely

* fix(backup): scrub console.log leak and rewire WebDAV dialog to actually probe server

* refactor: rename SubscriptionChange to ProfileChange

* chore: update i18n

* chore: WebDAV i18n improvements

* refactor(backup): error handling

* refactor(auto-backup): wrap scheduler startup with maybe_start_runner

* refactor: remove the redundant throw in handleExport

* feat(backup-history-viewer): improve WebDAV handling and UI fallback

* feat(auto-backup): trigger backups on all profile edits & improve interval input UX

* refactor: use InputAdornment

* docs: Changelog.md
This commit is contained in:
Sline
2025-11-10 13:49:14 +08:00
committed by GitHub
parent 78d5cb5eca
commit 838e401796
45 changed files with 1714 additions and 794 deletions

View File

@@ -11,6 +11,7 @@ use crate::{
},
core::{CoreManager, handle, timer::Timer, tray::Tray},
feat, logging,
module::auto_backup::{AutoBackupManager, AutoBackupTrigger},
process::AsyncHandler,
ret_err,
utils::{dirs, help, logging::Type},
@@ -90,6 +91,7 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
}
logging!(info, Type::Cmd, "[导入订阅] 导入完成: {}", url);
AutoBackupManager::trigger_backup(AutoBackupTrigger::ProfileChange);
Ok(())
}
@@ -122,6 +124,7 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
handle::Handle::notify_profile_changed(uid.clone());
}
Config::profiles().await.apply();
AutoBackupManager::trigger_backup(AutoBackupTrigger::ProfileChange);
Ok(())
}
Err(err) => {
@@ -164,6 +167,7 @@ pub async fn delete_profile(index: String) -> CmdResult {
// 发送配置变更通知
logging!(info, Type::Cmd, "[删除订阅] 发送配置变更通知: {}", index);
handle::Handle::notify_profile_changed(index);
AutoBackupManager::trigger_backup(AutoBackupTrigger::ProfileChange);
}
Err(e) => {
logging!(error, Type::Cmd, "{}", e);
@@ -460,6 +464,7 @@ pub async fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
});
}
AutoBackupManager::trigger_backup(AutoBackupTrigger::ProfileChange);
Ok(())
}

View File

@@ -4,6 +4,7 @@ use crate::{
config::{Config, PrfItem},
core::{CoreManager, handle, validate::CoreConfigValidator},
logging,
module::auto_backup::{AutoBackupManager, AutoBackupTrigger},
utils::{dirs, logging::Type},
};
use smartstring::alias::String;
@@ -17,6 +18,12 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
None => return Ok(()),
};
let backup_trigger = match index.as_str() {
"Merge" => Some(AutoBackupTrigger::GlobalMerge),
"Script" => Some(AutoBackupTrigger::GlobalScript),
_ => Some(AutoBackupTrigger::ProfileChange),
};
// 在异步操作前获取必要元数据并释放锁
let (rel_path, is_merge_file) = {
let profiles = Config::profiles().await;
@@ -51,11 +58,17 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
is_merge_file
);
if is_merge_file {
return handle_merge_file(&file_path_str, &file_path, &original_content).await;
let changes_applied = if is_merge_file {
handle_merge_file(&file_path_str, &file_path, &original_content).await?
} else {
handle_full_validation(&file_path_str, &file_path, &original_content).await?
};
if changes_applied && let Some(trigger) = backup_trigger {
AutoBackupManager::trigger_backup(trigger);
}
handle_full_validation(&file_path_str, &file_path, &original_content).await
Ok(())
}
async fn restore_original(
@@ -76,7 +89,7 @@ async fn handle_merge_file(
file_path_str: &str,
file_path: &std::path::Path,
original_content: &str,
) -> CmdResult {
) -> CmdResult<bool> {
logging!(
info,
Type::Config,
@@ -96,7 +109,7 @@ async fn handle_merge_file(
} else {
handle::Handle::refresh_clash();
}
Ok(())
Ok(true)
}
Ok((false, error_msg)) => {
logging!(
@@ -108,7 +121,7 @@ async fn handle_merge_file(
restore_original(file_path, original_content).await?;
let result = (false, error_msg.clone());
crate::cmd::validate::handle_yaml_validation_notice(&result, "合并配置文件");
Ok(())
Ok(false)
}
Err(e) => {
logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e);
@@ -122,11 +135,11 @@ async fn handle_full_validation(
file_path_str: &str,
file_path: &std::path::Path,
original_content: &str,
) -> CmdResult {
) -> CmdResult<bool> {
match CoreConfigValidator::validate_config_file(file_path_str, None).await {
Ok((true, _)) => {
logging!(info, Type::Config, "[cmd配置save] 验证成功");
Ok(())
Ok(true)
}
Ok((false, error_msg)) => {
logging!(warn, Type::Config, "[cmd配置save] 验证失败: {}", error_msg);
@@ -160,7 +173,7 @@ async fn handle_full_validation(
handle::Handle::notice_message("config_validate::error", error_msg.to_owned());
}
Ok(())
Ok(false)
}
Err(e) => {
logging!(error, Type::Config, "[cmd配置save] 验证过程发生错误: {}", e);