diff --git a/URLNotesGrabberCORE/Program.cs b/URLNotesGrabberCORE/Program.cs index 8f85d99..94911cf 100644 --- a/URLNotesGrabberCORE/Program.cs +++ b/URLNotesGrabberCORE/Program.cs @@ -185,6 +185,8 @@ namespace URLNotesGrabberCORE Console.WriteLine("-output\t Export posts from TL.db back to .txt files in each blog's TTFolderPath"); + Console.WriteLine("-revert [blogname]\t Inverse of -output: restore *.bak back to *.txt (current .txt saved as next-free .bkN)"); + Console.WriteLine("-correct [bakPath]\t Dry-run: report multi-line field updates available from a BAK directory"); Console.WriteLine("-correct -apply [bakPath]\t Apply BAK-file corrections to matching posts (prompts yes/no)"); @@ -329,6 +331,10 @@ namespace URLNotesGrabberCORE OutputMode.Run(config); break; + case "-revert": + RevertMode.Run(config, args.Length > 1 ? args[1] : null); + break; + case "-correct": { bool applyChanges = args.Skip(1).Any(a => string.Equals(a, "-apply", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "--apply", StringComparison.OrdinalIgnoreCase)); diff --git a/URLNotesGrabberCORE/RevertMode.cs b/URLNotesGrabberCORE/RevertMode.cs new file mode 100644 index 0000000..7cc3b0b --- /dev/null +++ b/URLNotesGrabberCORE/RevertMode.cs @@ -0,0 +1,114 @@ +using Microsoft.Extensions.Configuration; + +namespace URLNotesGrabberCORE +{ + // Inverse of OutputMode. For each Blog with a TTFolderPath, restores every *.bak back + // to its *.txt, first preserving the current *.txt as the next-free *.bkN. Consumes + // the *.bak (File.Move). Filesystem-only; reads the DB only to enumerate folders. + public static class RevertMode + { + public static int Run(IConfiguration config, string? blogFilter = null) + { + DataAccess.EnsureTTFileHelperColumnsExist(); + + var blogs = DataAccess.GetAllBlogsWithTTFolderPath(); + if (!string.IsNullOrWhiteSpace(blogFilter)) + blogs = blogs.Where(b => string.Equals(b.BlogName, blogFilter, StringComparison.OrdinalIgnoreCase)).ToList(); + + Console.WriteLine($"Found {blogs.Count} blog(s) to process."); + + // Count total *.bak files up front so the confirmation prompt is meaningful. + int totalBakFiles = 0; + foreach (var (_, ttFolderPath) in blogs) + { + if (string.IsNullOrWhiteSpace(ttFolderPath) || !Directory.Exists(ttFolderPath)) + continue; + try { totalBakFiles += Directory.GetFiles(ttFolderPath, "*.bak").Length; } + catch { } + } + + if (totalBakFiles == 0) + { + Console.WriteLine("No .bak files found in any blog folder. Nothing to revert."); + return 0; + } + + Console.Write($"WARNING: This will restore {totalBakFiles} .bak file(s) over their .txt files across {blogs.Count} blog folder(s). " + + $"Current .txt files are preserved as the next-free .bkN. Continue? (yes/no): "); + string? response = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(response) || !response.Equals("yes", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine("Operation cancelled."); + return 0; + } + + foreach (var (blogName, ttFolderPath) in blogs) + { + Console.WriteLine($"\nProcessing blog: {blogName}"); + + if (string.IsNullOrWhiteSpace(ttFolderPath) || !Directory.Exists(ttFolderPath)) + { + Console.WriteLine($" TTFolderPath does not exist or is not set. Skipping."); + continue; + } + + Console.WriteLine($" TTFolderPath: {ttFolderPath}"); + + var (restored, backedUp) = RevertFolder(ttFolderPath); + Console.WriteLine($" Restored {restored} file(s); backed up {backedUp} current .txt file(s)."); + } + + Console.WriteLine("\nRevert mode complete."); + return 0; + } + + // For each *.bak: back up the current *.txt to the next-free *.bkN, then File.Move(bak -> txt). + private static (int restored, int backedUp) RevertFolder(string folderPath) + { + int restored = 0, backedUp = 0; + + var bakFiles = Directory.GetFiles(folderPath, "*.bak"); + if (bakFiles.Length == 0) + { + Console.WriteLine(" Nothing to revert (no .bak files)."); + return (restored, backedUp); + } + + foreach (var bakFile in bakFiles) + { + try + { + string txtPath = Path.ChangeExtension(bakFile, ".txt"); + + if (File.Exists(txtPath)) + { + string bkPath = NextFreeBkPath(txtPath); + File.Move(txtPath, bkPath); + backedUp++; + Console.WriteLine($" Backed up {Path.GetFileName(txtPath)} -> {Path.GetFileName(bkPath)}"); + } + + File.Move(bakFile, txtPath); + restored++; + Console.WriteLine($" Restored {Path.GetFileName(bakFile)} -> {Path.GetFileName(txtPath)}"); + } + catch (Exception ex) + { + Console.WriteLine($" Error reverting {Path.GetFileName(bakFile)}: {ex.Message}"); + } + } + + return (restored, backedUp); + } + + // Returns the lowest unused .bkN path for a given .txt file (.bk1, .bk2, ...). + private static string NextFreeBkPath(string txtFile) + { + for (int n = 1; ; n++) + { + string candidate = Path.ChangeExtension(txtFile, $".bk{n}"); + if (!File.Exists(candidate)) return candidate; + } + } + } +}