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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user