Files
URLNotesGrabberCore/URLNotesGrabberCORE/RevertMode.cs
T
jimandClaude Opus 4.8 b576a9cdf3 fix: make -revert scan the PathInput tree like the no-parameter run
-revert was DB-driven, searching each blog's Blogs.TTFolderPath
non-recursively for *.bak. That tree differs from the no-parameter run,
which recursively walks PathInput. Rewrite RevertMode to recursively walk
PathInput (filesystem-only, no DB), with the optional [blogname] argument
now filtering by path substring. Restore mechanics unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-05-28 16:30:26 -05:00

111 lines
4.4 KiB
C#

using Microsoft.Extensions.Configuration;
namespace URLNotesGrabberCORE
{
// 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)
{
string? root = config["appSettings:PathInput"];
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root))
{
Console.WriteLine($"PathInput is not set or does not exist: '{root}'. Nothing to revert.");
return 0;
}
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))
{
Console.WriteLine("Operation cancelled.");
return 0;
}
int restored = 0, backedUp = 0;
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 {bakFile} -> {Path.GetFileName(txtPath)}");
}
catch (Exception ex)
{
Console.WriteLine($" Error reverting {bakFile}: {ex.Message}");
}
}
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<string> 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, ...).
private static string NextFreeBkPath(string txtFile)
{
for (int n = 1; ; n++)
{
string candidate = Path.ChangeExtension(txtFile, $".bk{n}");
if (!File.Exists(candidate)) return candidate;
}
}
}
}