feat: fold the TTFolderPath refresh into --output

TL.db syncs between machines whose absolute paths differ, so a single
TTFolderPath column cannot be correct on both at once -- the stored paths are
only trustworthy on the machine that wrote them. That made --updatepaths a
mandatory prelude to every --output rather than the one-time setup step it
looks like.

--output now refreshes the column from the TumblThree Index metadata before
exporting. The root comes from the first non-flag argument, else
appSettings:PathTTRoot. With no root available it says so and exports whatever
TL.db already holds; a root whose Index folder is missing is a hard stop, since
silently exporting stale paths is the failure this change exists to prevent.
--norefresh skips the refresh for a pure export.

The scan is extracted from UpdateBlogPathsRunner.Run into a reusable Scan() that
returns counts instead of only printing them, so --updatepaths keeps its
per-file detail while --output prints a single summary line rather than a few
hundred lines ahead of the export.

Verified against a throwaway database: a stale cross-machine path is repaired
and the export lands in the correct local folder; no configured root warns and
continues (exit 0); a missing Index folder stops (exit 1); --norefresh skips the
refresh and exports (exit 0).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
jim
2026-08-05 12:09:55 -05:00
co-authored by Claude Opus 5
parent 721224bc13
commit 8f4177a0c9
3 changed files with 136 additions and 27 deletions
+83 -22
View File
@@ -4,32 +4,58 @@ namespace URLNotesGrabberCORE
{
// Port of ThreeTxtFileHelper/UpdateBlogPaths.cs. Reads .tumblr / .tmblrpriv metadata
// files from a root\Index folder and populates Blogs.TTFolderPath in TL.db.
//
// Scan() is the reusable engine: --updatepaths wraps it as a standalone command and
// --output calls it as a refresh step, because a TL.db synced between machines cannot
// hold one absolute path that is correct on both.
public static class UpdateBlogPathsRunner
{
public static int Run(string rootPath)
public enum ScanOutcome
{
Completed,
NoRootConfigured,
IndexFolderMissing
}
public sealed class ScanResult
{
public ScanOutcome Outcome { get; init; }
public string RootPath { get; init; } = string.Empty;
public string IndexPath { get; init; } = string.Empty;
public int MetadataFiles { get; init; }
public int Written { get; init; }
public int Unchanged { get; init; }
public int NoLocation { get; init; }
public int NoMatchingRow { get; init; }
public int Errors { get; init; }
}
// verbose: log a line per metadata file. --updatepaths wants that detail; --output
// only wants the counts, since a few hundred lines before the export would bury it.
public static ScanResult Scan(string? rootPath, bool verbose)
{
if (string.IsNullOrWhiteSpace(rootPath))
{
Console.WriteLine("UpdateBlogPaths: rootPath is required.");
return 1;
}
return new ScanResult { Outcome = ScanOutcome.NoRootConfigured };
DataAccess.EnsureTTFileHelperColumnsExist();
string indexPath = Path.Combine(rootPath, "Index");
if (!Directory.Exists(indexPath))
{
Console.WriteLine($"Index folder not found at: {indexPath}");
return 1;
return new ScanResult
{
Outcome = ScanOutcome.IndexFolderMissing,
RootPath = rootPath,
IndexPath = indexPath
};
}
Console.WriteLine($"Scanning Index folder: {indexPath}");
var blogFiles = Directory.GetFiles(indexPath, "*.tumblr")
.Concat(Directory.GetFiles(indexPath, "*.tmblrpriv"))
.ToList();
Console.WriteLine($"Found {blogFiles.Count} blog metadata files");
if (verbose)
Console.WriteLine($"Found {blogFiles.Count} blog metadata files");
int updatedCount = 0;
int unchangedCount = 0;
@@ -55,7 +81,8 @@ namespace URLNotesGrabberCORE
if (DataAccess.SetBlogTTFolderPath(blogName, fileDownloadLocation))
{
updatedCount++;
Console.WriteLine($"Updated {blogName}: {fileDownloadLocation}");
if (verbose)
Console.WriteLine($"Updated {blogName}: {fileDownloadLocation}");
}
else if (DataAccess.BlogExists(blogName))
{
@@ -70,13 +97,15 @@ namespace URLNotesGrabberCORE
else
{
noLocationCount++;
Console.WriteLine($"Empty FileDownloadLocation in {blogFile}");
if (verbose)
Console.WriteLine($"Empty FileDownloadLocation in {blogFile}");
}
}
else
{
noLocationCount++;
Console.WriteLine($"No FileDownloadLocation found in {blogFile}");
if (verbose)
Console.WriteLine($"No FileDownloadLocation found in {blogFile}");
}
}
catch (Exception ex)
@@ -86,18 +115,50 @@ namespace URLNotesGrabberCORE
}
}
return new ScanResult
{
Outcome = ScanOutcome.Completed,
RootPath = rootPath,
IndexPath = indexPath,
MetadataFiles = blogFiles.Count,
Written = updatedCount,
Unchanged = unchangedCount,
NoLocation = noLocationCount,
NoMatchingRow = noRowCount,
Errors = errorCount
};
}
public static int Run(string rootPath)
{
if (string.IsNullOrWhiteSpace(rootPath))
{
Console.WriteLine("UpdateBlogPaths: rootPath is required.");
return 1;
}
string indexPath = Path.Combine(rootPath, "Index");
Console.WriteLine($"Scanning Index folder: {indexPath}");
var result = Scan(rootPath, verbose: true);
if (result.Outcome == ScanOutcome.IndexFolderMissing)
{
Console.WriteLine($"Index folder not found at: {result.IndexPath}");
return 1;
}
Console.WriteLine($"\n========== UpdateBlogPaths summary ==========");
Console.WriteLine($"Metadata files: {blogFiles.Count}");
Console.WriteLine($"TTFolderPath written: {updatedCount}");
Console.WriteLine($"Already correct: {unchangedCount}");
Console.WriteLine($"No FileDownloadLocation: {noLocationCount}");
Console.WriteLine($"No matching blog row: {noRowCount}");
Console.WriteLine($"Errors: {errorCount}");
Console.WriteLine($"Metadata files: {result.MetadataFiles}");
Console.WriteLine($"TTFolderPath written: {result.Written}");
Console.WriteLine($"Already correct: {result.Unchanged}");
Console.WriteLine($"No FileDownloadLocation: {result.NoLocation}");
Console.WriteLine($"No matching blog row: {result.NoMatchingRow}");
Console.WriteLine($"Errors: {result.Errors}");
int stored = DataAccess.CountBlogsWithTTFolderPath();
Console.WriteLine($"\nBlogs now holding a TTFolderPath: {stored}");
Console.WriteLine($"\nBlogs now holding a TTFolderPath: {DataAccess.CountBlogsWithTTFolderPath()}");
return errorCount == 0 ? 0 : 2;
return result.Errors == 0 ? 0 : 2;
}
}
}