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]>
165 lines
6.5 KiB
C#
165 lines
6.5 KiB
C#
using System.Text.Json;
|
|
|
|
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 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))
|
|
return new ScanResult { Outcome = ScanOutcome.NoRootConfigured };
|
|
|
|
DataAccess.EnsureTTFileHelperColumnsExist();
|
|
|
|
string indexPath = Path.Combine(rootPath, "Index");
|
|
if (!Directory.Exists(indexPath))
|
|
{
|
|
return new ScanResult
|
|
{
|
|
Outcome = ScanOutcome.IndexFolderMissing,
|
|
RootPath = rootPath,
|
|
IndexPath = indexPath
|
|
};
|
|
}
|
|
|
|
var blogFiles = Directory.GetFiles(indexPath, "*.tumblr")
|
|
.Concat(Directory.GetFiles(indexPath, "*.tmblrpriv"))
|
|
.ToList();
|
|
|
|
if (verbose)
|
|
Console.WriteLine($"Found {blogFiles.Count} blog metadata files");
|
|
|
|
int updatedCount = 0;
|
|
int unchangedCount = 0;
|
|
int noLocationCount = 0;
|
|
int noRowCount = 0;
|
|
int errorCount = 0;
|
|
|
|
foreach (var blogFile in blogFiles)
|
|
{
|
|
try
|
|
{
|
|
string blogName = Path.GetFileNameWithoutExtension(blogFile);
|
|
string jsonContent = File.ReadAllText(blogFile);
|
|
using JsonDocument doc = JsonDocument.Parse(jsonContent);
|
|
JsonElement root = doc.RootElement;
|
|
|
|
if (root.TryGetProperty("FileDownloadLocation", out JsonElement locationElement))
|
|
{
|
|
string? fileDownloadLocation = locationElement.GetString()?.Trim();
|
|
if (!string.IsNullOrWhiteSpace(fileDownloadLocation))
|
|
{
|
|
// Report the database's answer, not the fact that the file parsed.
|
|
if (DataAccess.SetBlogTTFolderPath(blogName, fileDownloadLocation))
|
|
{
|
|
updatedCount++;
|
|
if (verbose)
|
|
Console.WriteLine($"Updated {blogName}: {fileDownloadLocation}");
|
|
}
|
|
else if (DataAccess.BlogExists(blogName))
|
|
{
|
|
unchangedCount++;
|
|
}
|
|
else
|
|
{
|
|
noRowCount++;
|
|
Console.WriteLine($"No Blogs row named '{blogName}' -- path not stored (name may differ in case)");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
noLocationCount++;
|
|
if (verbose)
|
|
Console.WriteLine($"Empty FileDownloadLocation in {blogFile}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
noLocationCount++;
|
|
if (verbose)
|
|
Console.WriteLine($"No FileDownloadLocation found in {blogFile}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorCount++;
|
|
Console.WriteLine($"Error processing {blogFile}: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
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: {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}");
|
|
|
|
Console.WriteLine($"\nBlogs now holding a TTFolderPath: {DataAccess.CountBlogsWithTTFolderPath()}");
|
|
|
|
return result.Errors == 0 ? 0 : 2;
|
|
}
|
|
}
|
|
}
|