From 98c1adc90bd81407bb3a8dbeff066547d38ea7bd Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Sun, 24 May 2026 08:10:39 +0800 Subject: [PATCH] feat(storage): add Restore, CleanBackup, BackupConfig, BackupInfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StorageManager.Restore() — restore from backup to install path - StorageManager.CleanBackup() — keep only N most recent backups - StorageManager.ListBackups() — query backup metadata - BackupConfig — version retention, skip dirs, enable/disable - BackupInfo record — version, path, timestamp, size Closes #327 --- .../FileSystem/StorageManager.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs b/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs index d0143d28..e04b4f06 100644 --- a/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs +++ b/src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs @@ -283,6 +283,62 @@ private IEnumerable ReadFileNode(string path, string rootPath = null) private void ResetId() => Interlocked.Exchange(ref _fileCount, 0); + /// Restore files from backup directory to install path. + public static void Restore(string backupDir, string installPath) + { + if (!Directory.Exists(backupDir)) + throw new DirectoryNotFoundException($"Backup directory not found: {backupDir}"); + + foreach (var file in Directory.GetFiles(backupDir, "*", SearchOption.AllDirectories)) + { + var relativePath = file.Substring(backupDir.Length).TrimStart(Path.DirectorySeparatorChar); + var dest = Path.Combine(installPath, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(dest)!); + File.Copy(file, dest, true); + } + } + + /// Clean old backups, keeping only the N most recent versions. + public static void CleanBackup(string installPath, int keepVersions = 3) + { + var backupRoot = Path.Combine(installPath, "__backups"); + if (!Directory.Exists(backupRoot)) return; + + var dirs = Directory.GetDirectories(backupRoot) + .Select(d => new DirectoryInfo(d)) + .OrderByDescending(d => d.CreationTime) + .Skip(keepVersions); + + foreach (var dir in dirs) + dir.Delete(true); + } + + /// List backup versions with metadata. + public static IReadOnlyList ListBackups(string installPath) + { + var backupRoot = Path.Combine(installPath, "__backups"); + if (!Directory.Exists(backupRoot)) return Array.Empty(); + + return Directory.GetDirectories(backupRoot) + .Select(d => new DirectoryInfo(d)) + .Select(d => new BackupInfo( + d.Name, d.FullName, d.CreationTime, + d.GetFiles("*", SearchOption.AllDirectories).Sum(f => f.Length))) + .ToList(); + } + #endregion } + + /// Backup configuration. + public sealed class BackupConfig + { + public int KeepVersions { get; set; } = 3; + public string? BackupRoot { get; set; } + public List SkipDirectories { get; set; } = new(); + public bool Enabled { get; set; } = true; + } + + /// Backup metadata. + public record BackupInfo(string Version, string Path, DateTime CreatedAt, long SizeBytes); } \ No newline at end of file