using System.Reflection; namespace dy.net.service { public sealed class DatabaseUpgradeBackup { private readonly string _databaseRoot; private readonly string _version; private readonly string _markerPath; private DatabaseUpgradeBackup(string databaseRoot, string version) { _databaseRoot = databaseRoot; _version = version; _markerPath = Path.Combine(databaseRoot, ".schema-version"); } public static DatabaseUpgradeBackup Prepare(string dbPath) { var root = string.IsNullOrWhiteSpace(dbPath) ? Path.Combine(Environment.CurrentDirectory, "db") : Path.Combine(dbPath, "db"); Directory.CreateDirectory(root); var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "0.2.24"; var backup = new DatabaseUpgradeBackup(root, version); backup.CreateIfNeeded(); return backup; } public void MarkSchemaReady() => File.WriteAllText(_markerPath, _version); private void CreateIfNeeded() { var database = Path.Combine(_databaseRoot, "dy.sqlite"); if (!File.Exists(database)) return; if (File.Exists(_markerPath) && string.Equals(File.ReadAllText(_markerPath).Trim(), _version, StringComparison.Ordinal)) return; var backupRoot = Path.Combine(_databaseRoot, "upgrade-backups"); Directory.CreateDirectory(backupRoot); var versionPrefix = $"pre-{_version}-"; if (!Directory.EnumerateDirectories(backupRoot, versionPrefix + "*").Any()) { var destination = Path.Combine(backupRoot, versionPrefix + DateTime.Now.ToString("yyyyMMdd-HHmmss")); Directory.CreateDirectory(destination); CopyIfPresent(database, Path.Combine(destination, "dy.sqlite")); CopyIfPresent(database + "-wal", Path.Combine(destination, "dy.sqlite-wal")); CopyIfPresent(database + "-shm", Path.Combine(destination, "dy.sqlite-shm")); CopyDirectory(Path.Combine(_databaseRoot, "keys"), Path.Combine(destination, "keys")); } foreach (var old in new DirectoryInfo(backupRoot).GetDirectories() .OrderByDescending(x => x.CreationTimeUtc).Skip(3)) { try { old.Delete(true); } catch (Exception ex) { Serilog.Log.Warning(ex, "清理旧升级备份失败:{Path}", old.FullName); } } } private static void CopyIfPresent(string source, string destination) { if (File.Exists(source)) File.Copy(source, destination, false); } private static void CopyDirectory(string source, string destination) { if (!Directory.Exists(source)) return; Directory.CreateDirectory(destination); foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)) { var relative = Path.GetRelativePath(source, file); var target = Path.Combine(destination, relative); Directory.CreateDirectory(Path.GetDirectoryName(target)!); File.Copy(file, target, false); } } } }