Merge branch 'claude/ttfolderpath-not-set-8c9b45' into master

This commit is contained in:
jim
2026-08-05 12:09:55 -05:00
3 changed files with 136 additions and 27 deletions
+49 -3
View File
@@ -8,13 +8,16 @@ namespace URLNotesGrabberCORE
// field order). Reads from TL.db via DataAccess.GetAllPostsForBlog. // field order). Reads from TL.db via DataAccess.GetAllPostsForBlog.
public static class OutputMode public static class OutputMode
{ {
public static int Run(IConfiguration config) public static int Run(IConfiguration config, string[]? args = null)
{ {
DataAccess.EnsureTTFileHelperColumnsExist(); DataAccess.EnsureTTFileHelperColumnsExist();
string dbPath = DataAccess.GetActiveDbPath(); string dbPath = DataAccess.GetActiveDbPath();
Console.WriteLine($"Database: {Path.GetFullPath(dbPath)}"); Console.WriteLine($"Database: {Path.GetFullPath(dbPath)}");
if (!RefreshPaths(config, args ?? Array.Empty<string>()))
return 1;
var blogs = DataAccess.GetAllBlogsWithTTFolderPath(); var blogs = DataAccess.GetAllBlogsWithTTFolderPath();
int activeBlogs = DataAccess.CountActiveBlogs(); int activeBlogs = DataAccess.CountActiveBlogs();
Console.WriteLine($"{blogs.Count} of {activeBlogs} active blog(s) have a TTFolderPath."); Console.WriteLine($"{blogs.Count} of {activeBlogs} active blog(s) have a TTFolderPath.");
@@ -22,8 +25,8 @@ namespace URLNotesGrabberCORE
if (blogs.Count == 0) if (blogs.Count == 0)
{ {
Console.WriteLine($"\nNothing to export: no blog in {Path.GetFullPath(dbPath)} has a TTFolderPath."); Console.WriteLine($"\nNothing to export: no blog in {Path.GetFullPath(dbPath)} has a TTFolderPath.");
Console.WriteLine("Run --updatepaths <root> on this machine to populate it from <root>\\Index\\*.tumblr / *.tmblrpriv,"); Console.WriteLine("Point --output at a TumblThree root so it can populate them: --output <root>,");
Console.WriteLine("or set appSettings:PathTTRoot and run --updatepaths with no argument."); Console.WriteLine("or set appSettings:PathTTRoot so the refresh runs automatically.");
return 1; return 1;
} }
@@ -93,6 +96,49 @@ namespace URLNotesGrabberCORE
return 0; return 0;
} }
// Re-reads the TumblThree Index metadata into Blogs.TTFolderPath before exporting.
// A TL.db synced between machines cannot hold one absolute path that is valid on
// both, so the stored paths are only trustworthy on the machine that wrote them --
// which makes this refresh part of a normal export rather than a separate chore.
// Returns false only when the run should stop.
private static bool RefreshPaths(IConfiguration config, string[] args)
{
var settings = config.GetSection("appSettings");
if (args.Any(a => string.Equals(a, "--norefresh", StringComparison.OrdinalIgnoreCase)))
{
Console.WriteLine("Path refresh skipped (--norefresh); exporting to whatever paths TL.db already holds.");
return true;
}
string? root = args.FirstOrDefault(a => !a.StartsWith("--", StringComparison.Ordinal))
?? settings.GetValue<string>("PathTTRoot");
var result = UpdateBlogPathsRunner.Scan(root, verbose: false);
switch (result.Outcome)
{
case UpdateBlogPathsRunner.ScanOutcome.NoRootConfigured:
Console.WriteLine("No TumblThree root configured (appSettings:PathTTRoot is empty and none was passed),");
Console.WriteLine("so TTFolderPath was not refreshed. Pass one as --output <root> to refresh it.");
return true;
case UpdateBlogPathsRunner.ScanOutcome.IndexFolderMissing:
// Silently exporting stale paths here would defeat the point of folding
// the refresh in, so a bad root is a hard stop.
Console.WriteLine($"Index folder not found at: {result.IndexPath}");
Console.WriteLine("Fix the root (or pass --norefresh to export the paths already in TL.db).");
return false;
default:
Console.WriteLine($"Refreshed paths from {result.IndexPath}: " +
$"{result.MetadataFiles} metadata file(s), {result.Written} written, " +
$"{result.Unchanged} already correct, {result.NoLocation} without a location, " +
$"{result.NoMatchingRow} without a blog row, {result.Errors} error(s).");
return true;
}
}
private static void RenameExistingTxtFilesToBak(string folderPath) private static void RenameExistingTxtFilesToBak(string folderPath)
{ {
try try
+4 -2
View File
@@ -343,7 +343,7 @@ namespace URLNotesGrabberCORE
break; break;
case "--output": case "--output":
exitCode = OutputMode.Run(config); exitCode = OutputMode.Run(config, args.Skip(1).ToArray());
break; break;
case "--revert": case "--revert":
@@ -439,7 +439,9 @@ namespace URLNotesGrabberCORE
Console.WriteLine("--ingest [blogname]\t Ingest Tumblr .txt exports from appSettings:PathTTRoot into TL.db (all blogs, or single blog if name given)"); Console.WriteLine("--ingest [blogname]\t Ingest Tumblr .txt exports from appSettings:PathTTRoot into TL.db (all blogs, or single blog if name given)");
Console.WriteLine("--output\t Export posts from TL.db back to .txt files in each blog's TTFolderPath"); Console.WriteLine("--output [rootPath]\t Refresh Blogs.TTFolderPath from <root>\\Index (or appSettings:PathTTRoot), then export posts from TL.db back to .txt files in each blog's folder");
Console.WriteLine("--output --norefresh\t Export without refreshing TTFolderPath first");
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("--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");
+79 -18
View File
@@ -4,31 +4,57 @@ namespace URLNotesGrabberCORE
{ {
// Port of ThreeTxtFileHelper/UpdateBlogPaths.cs. Reads .tumblr / .tmblrpriv metadata // Port of ThreeTxtFileHelper/UpdateBlogPaths.cs. Reads .tumblr / .tmblrpriv metadata
// files from a root\Index folder and populates Blogs.TTFolderPath in TL.db. // 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 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)) if (string.IsNullOrWhiteSpace(rootPath))
{ return new ScanResult { Outcome = ScanOutcome.NoRootConfigured };
Console.WriteLine("UpdateBlogPaths: rootPath is required.");
return 1;
}
DataAccess.EnsureTTFileHelperColumnsExist(); DataAccess.EnsureTTFileHelperColumnsExist();
string indexPath = Path.Combine(rootPath, "Index"); string indexPath = Path.Combine(rootPath, "Index");
if (!Directory.Exists(indexPath)) if (!Directory.Exists(indexPath))
{ {
Console.WriteLine($"Index folder not found at: {indexPath}"); return new ScanResult
return 1; {
Outcome = ScanOutcome.IndexFolderMissing,
RootPath = rootPath,
IndexPath = indexPath
};
} }
Console.WriteLine($"Scanning Index folder: {indexPath}");
var blogFiles = Directory.GetFiles(indexPath, "*.tumblr") var blogFiles = Directory.GetFiles(indexPath, "*.tumblr")
.Concat(Directory.GetFiles(indexPath, "*.tmblrpriv")) .Concat(Directory.GetFiles(indexPath, "*.tmblrpriv"))
.ToList(); .ToList();
if (verbose)
Console.WriteLine($"Found {blogFiles.Count} blog metadata files"); Console.WriteLine($"Found {blogFiles.Count} blog metadata files");
int updatedCount = 0; int updatedCount = 0;
@@ -55,6 +81,7 @@ namespace URLNotesGrabberCORE
if (DataAccess.SetBlogTTFolderPath(blogName, fileDownloadLocation)) if (DataAccess.SetBlogTTFolderPath(blogName, fileDownloadLocation))
{ {
updatedCount++; updatedCount++;
if (verbose)
Console.WriteLine($"Updated {blogName}: {fileDownloadLocation}"); Console.WriteLine($"Updated {blogName}: {fileDownloadLocation}");
} }
else if (DataAccess.BlogExists(blogName)) else if (DataAccess.BlogExists(blogName))
@@ -70,12 +97,14 @@ namespace URLNotesGrabberCORE
else else
{ {
noLocationCount++; noLocationCount++;
if (verbose)
Console.WriteLine($"Empty FileDownloadLocation in {blogFile}"); Console.WriteLine($"Empty FileDownloadLocation in {blogFile}");
} }
} }
else else
{ {
noLocationCount++; noLocationCount++;
if (verbose)
Console.WriteLine($"No FileDownloadLocation found in {blogFile}"); Console.WriteLine($"No FileDownloadLocation found in {blogFile}");
} }
} }
@@ -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($"\n========== UpdateBlogPaths summary ==========");
Console.WriteLine($"Metadata files: {blogFiles.Count}"); Console.WriteLine($"Metadata files: {result.MetadataFiles}");
Console.WriteLine($"TTFolderPath written: {updatedCount}"); Console.WriteLine($"TTFolderPath written: {result.Written}");
Console.WriteLine($"Already correct: {unchangedCount}"); Console.WriteLine($"Already correct: {result.Unchanged}");
Console.WriteLine($"No FileDownloadLocation: {noLocationCount}"); Console.WriteLine($"No FileDownloadLocation: {result.NoLocation}");
Console.WriteLine($"No matching blog row: {noRowCount}"); Console.WriteLine($"No matching blog row: {result.NoMatchingRow}");
Console.WriteLine($"Errors: {errorCount}"); Console.WriteLine($"Errors: {result.Errors}");
int stored = DataAccess.CountBlogsWithTTFolderPath(); Console.WriteLine($"\nBlogs now holding a TTFolderPath: {DataAccess.CountBlogsWithTTFolderPath()}");
Console.WriteLine($"\nBlogs now holding a TTFolderPath: {stored}");
return errorCount == 0 ? 0 : 2; return result.Errors == 0 ? 0 : 2;
} }
} }
} }