diff --git a/URLNotesGrabberCORE/Program.cs b/URLNotesGrabberCORE/Program.cs index 94911cf..11fabfc 100644 --- a/URLNotesGrabberCORE/Program.cs +++ b/URLNotesGrabberCORE/Program.cs @@ -185,7 +185,7 @@ 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("-revert [blogname]\t Recursively scan the PathInput tree and restore *.bak back to *.txt (current .txt saved as next-free .bkN); optional blogname filters by path substring"); Console.WriteLine("-correct [bakPath]\t Dry-run: report multi-line field updates available from a BAK directory"); diff --git a/URLNotesGrabberCORE/RevertMode.cs b/URLNotesGrabberCORE/RevertMode.cs index 7cc3b0b..4c41dbb 100644 --- a/URLNotesGrabberCORE/RevertMode.cs +++ b/URLNotesGrabberCORE/RevertMode.cs @@ -2,38 +2,36 @@ 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. + // Inverse of OutputMode. Recursively walks the PathInput tree (the same directory tree the + // no-parameter run uses) and restores every *.bak back to its *.txt, first preserving the + // current *.txt as the next-free *.bkN. Consumes the *.bak (File.Move). Filesystem-only; + // does not read the DB. An optional blogname argument filters by path substring. 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) + string? root = config["appSettings:PathInput"]; + if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) { - 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."); + Console.WriteLine($"PathInput is not set or does not exist: '{root}'. 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). " + + Console.WriteLine($"Searching for .bak files under: {root}"); + + // Recursively collect every *.bak, optionally filtered by path substring (blogname). + var bakFiles = EnumerateBakFiles(root) + .Where(f => string.IsNullOrWhiteSpace(blogFilter) + || f.IndexOf(blogFilter, StringComparison.OrdinalIgnoreCase) >= 0) + .ToList(); + + if (bakFiles.Count == 0) + { + Console.WriteLine("No .bak files found. Nothing to revert."); + return 0; + } + + Console.Write($"WARNING: This will restore {bakFiles.Count} .bak file(s) over their .txt files. " + $"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)) @@ -42,38 +40,7 @@ namespace URLNotesGrabberCORE 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 @@ -85,20 +52,49 @@ namespace URLNotesGrabberCORE string bkPath = NextFreeBkPath(txtPath); File.Move(txtPath, bkPath); backedUp++; - Console.WriteLine($" Backed up {Path.GetFileName(txtPath)} -> {Path.GetFileName(bkPath)}"); + Console.WriteLine($" Backed up {Path.GetFileName(txtPath)} -> {Path.GetFileName(bkPath)}"); } File.Move(bakFile, txtPath); restored++; - Console.WriteLine($" Restored {Path.GetFileName(bakFile)} -> {Path.GetFileName(txtPath)}"); + Console.WriteLine($" Restored {bakFile} -> {Path.GetFileName(txtPath)}"); } catch (Exception ex) { - Console.WriteLine($" Error reverting {Path.GetFileName(bakFile)}: {ex.Message}"); + Console.WriteLine($" Error reverting {bakFile}: {ex.Message}"); } } - return (restored, backedUp); + Console.WriteLine($"\nRevert mode complete. Restored {restored} file(s); backed up {backedUp} current .txt file(s)."); + return 0; + } + + // Recursively yields every *.bak path under root. Per-directory try/catch so an + // inaccessible folder doesn't abort the whole walk (mirrors TraverseDirectory). + private static IEnumerable EnumerateBakFiles(string path) + { + string[] subDirs; + try { subDirs = Directory.GetDirectories(path); } + catch (Exception ex) + { + Console.WriteLine($" Skipping '{path}': {ex.Message}"); + yield break; + } + + foreach (var dir in subDirs) + foreach (var bak in EnumerateBakFiles(dir)) + yield return bak; + + string[] bakFiles; + try { bakFiles = Directory.GetFiles(path, "*.bak"); } + catch (Exception ex) + { + Console.WriteLine($" Skipping files in '{path}': {ex.Message}"); + yield break; + } + + foreach (var bak in bakFiles) + yield return bak; } // Returns the lowest unused .bkN path for a given .txt file (.bk1, .bk2, ...).