--output iterated all 156k active Blogs rows and printed a "does not exist or is not set" skip line for each, which is nearly every blog in the crawl registry -- only the few hundred downloaded locally ever have a folder. The signal was buried in six figures of noise. GetAllBlogsWithTTFolderPath now selects only active blogs carrying a non-empty path, so --output processes export targets and nothing else. When none exist it says so once, names the database it read, points at --updatepaths, and returns non-zero instead of reporting success. A stored path this machine cannot see is now reported separately from an unset one, with the path shown, because the two are fixed in different places. Paths are trimmed before Directory.Exists, which stray whitespace in a .tumblr FileDownloadLocation would otherwise defeat. Both writers counted optimistically. UpdateBlogPathsRunner printed its per-blog success line and incremented its total from the metadata file parsing, never checking whether the UPDATE matched a row; LegacyPostsDbImporter counted a blog as copied even when the legacy TTFolderPath was NULL. Either could report full success having written nothing -- which is consistent with TL.db holding zero populated paths across all 156,492 active blogs despite 20,679 posts having merged. SetBlogTTFolderPath now returns whether a row changed, and both callers report written / already-correct / no-matching-row separately. Verified against a throwaway database: no-paths case, export case (stale .txt rotated to .bak, per-PostType files, date-sorted), missing-folder case, --updatepaths honest counts, and an idempotent rerun reporting already-correct. Co-Authored-By: Claude Opus 5 <[email protected]>
157 lines
8.0 KiB
C#
157 lines
8.0 KiB
C#
using System.Data.SQLite;
|
|
|
|
namespace URLNotesGrabberCORE
|
|
{
|
|
// One-time migration: opens a legacy ThreeTxtFileHelper posts.db, copies its
|
|
// Blog + PostData rows into the merged TL.db via DataAccess.
|
|
// Conflict rule on (BlogName, PostId): ThreeTxtFileHelper wins on the 22 content
|
|
// columns + PostType + DateModified (handled inside UpsertPostFromTextFile).
|
|
// Engagement columns in TL.db (ByLikes, RootBlogName, RootURL, HasNotesGathered,
|
|
// NotFound, NotesGatheredDateTime) are preserved.
|
|
public static class LegacyPostsDbImporter
|
|
{
|
|
public static int Run(string legacyDbPath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(legacyDbPath))
|
|
{
|
|
Console.WriteLine("LegacyPostsDbImporter: path to legacy posts.db is required.");
|
|
return 1;
|
|
}
|
|
|
|
if (!File.Exists(legacyDbPath))
|
|
{
|
|
Console.WriteLine($"Legacy posts.db not found at: {legacyDbPath}");
|
|
return 1;
|
|
}
|
|
|
|
DataAccess.EnsureTTFileHelperColumnsExist();
|
|
|
|
Console.WriteLine($"Reading legacy posts.db: {legacyDbPath}");
|
|
|
|
int blogsCopied = 0;
|
|
int blogPathsWritten = 0;
|
|
int blogsWithoutPath = 0;
|
|
int postsUpserted = 0;
|
|
int errors = 0;
|
|
|
|
try
|
|
{
|
|
using var src = new SQLiteConnection("Data Source=" + legacyDbPath + ";Read Only=True;");
|
|
src.Open();
|
|
|
|
// 1) Copy Blogs (BlogName + TTFolderPath)
|
|
using (var cmd = new SQLiteCommand("SELECT BlogName, TTFolderPath FROM Blogs", src))
|
|
using (var reader = cmd.ExecuteReader())
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
string blogName = reader.IsDBNull(0) ? string.Empty : reader.GetString(0);
|
|
string? ttFolderPath = reader.IsDBNull(1) ? null : reader.GetString(1);
|
|
if (string.IsNullOrWhiteSpace(blogName)) continue;
|
|
try
|
|
{
|
|
// A legacy row whose TTFolderPath was already NULL copies nothing.
|
|
// Counting it as "copied" is what hid the fact that this import has
|
|
// never populated a single path.
|
|
if (string.IsNullOrWhiteSpace(ttFolderPath))
|
|
blogsWithoutPath++;
|
|
else if (DataAccess.SetBlogTTFolderPath(blogName, ttFolderPath.Trim()))
|
|
blogPathsWritten++;
|
|
blogsCopied++;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors++;
|
|
Console.WriteLine($" Blog copy failed for '{blogName}': {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
Console.WriteLine($" Blogs seen: {blogsCopied}, TTFolderPath written: {blogPathsWritten}, legacy rows with no path: {blogsWithoutPath}");
|
|
|
|
// 2) Copy Posts
|
|
try
|
|
{
|
|
DataAccess.EnableImportModePragmas();
|
|
DataAccess.BeginImportSession();
|
|
|
|
string sql = @"SELECT BlogName, PostId, ReblogUrl, Date, HasImage, PostUrl, Slug,
|
|
ReblogKey, ReblogName, Summary, Quote, Body, Tags, Link,
|
|
PhotoUrl, PhotoCaption, DownloadedFiles, AudioCaption,
|
|
Question, Answer, Title, PostType
|
|
FROM Posts";
|
|
|
|
using var cmd = new SQLiteCommand(sql, src);
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
{
|
|
try
|
|
{
|
|
string blogName = reader.IsDBNull(0) ? string.Empty : reader.GetString(0);
|
|
string postId = reader.IsDBNull(1) ? string.Empty : reader.GetString(1);
|
|
if (string.IsNullOrWhiteSpace(blogName) || string.IsNullOrWhiteSpace(postId)) continue;
|
|
|
|
string? hasImageRaw = reader.IsDBNull(4) ? null : reader.GetValue(4)?.ToString();
|
|
bool hasImage = !string.IsNullOrWhiteSpace(hasImageRaw)
|
|
&& (hasImageRaw.Equals("true", StringComparison.OrdinalIgnoreCase)
|
|
|| hasImageRaw == "1"
|
|
|| hasImageRaw.Equals("yes", StringComparison.OrdinalIgnoreCase));
|
|
|
|
DataAccess.UpsertPostFromTextFile(
|
|
blogName: blogName,
|
|
postID: postId,
|
|
reblogURL: reader.IsDBNull(2) ? null : reader.GetString(2),
|
|
postDate: reader.IsDBNull(3) ? null : reader.GetString(3),
|
|
postURL: reader.IsDBNull(5) ? null : reader.GetString(5),
|
|
slug: reader.IsDBNull(6) ? null : reader.GetString(6),
|
|
reblogKey: reader.IsDBNull(7) ? null : reader.GetString(7),
|
|
reblogName: reader.IsDBNull(8) ? null : reader.GetString(8),
|
|
summary: reader.IsDBNull(9) ? null : reader.GetString(9),
|
|
quote: reader.IsDBNull(10) ? null : reader.GetString(10),
|
|
body: reader.IsDBNull(11) ? null : reader.GetString(11),
|
|
tags: reader.IsDBNull(12) ? null : reader.GetString(12),
|
|
link: reader.IsDBNull(13) ? null : reader.GetString(13),
|
|
photoURL: reader.IsDBNull(14) ? null : reader.GetString(14),
|
|
photoCaption: reader.IsDBNull(15) ? null : reader.GetString(15),
|
|
downloadedFiles: reader.IsDBNull(16) ? null : reader.GetString(16),
|
|
audioCaption: reader.IsDBNull(17) ? null : reader.GetString(17),
|
|
question: reader.IsDBNull(18) ? null : reader.GetString(18),
|
|
answer: reader.IsDBNull(19) ? null : reader.GetString(19),
|
|
title: reader.IsDBNull(20) ? null : reader.GetString(20),
|
|
postType: reader.IsDBNull(21) ? null : reader.GetString(21),
|
|
hasImage: hasImage);
|
|
postsUpserted++;
|
|
if (postsUpserted % 500 == 0)
|
|
Console.WriteLine($" ... {postsUpserted} posts upserted");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors++;
|
|
if (errors < 20)
|
|
Console.WriteLine($" Post upsert error: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
DataAccess.EndImportSession();
|
|
DataAccess.RestoreImportModePragmas();
|
|
}
|
|
|
|
Console.WriteLine($" Posts upserted: {postsUpserted}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Fatal error reading legacy posts.db: {ex.Message}");
|
|
return 1;
|
|
}
|
|
|
|
Console.WriteLine($"\n========== Legacy import summary ==========");
|
|
Console.WriteLine($"Blogs seen: {blogsCopied}");
|
|
Console.WriteLine($"Paths written: {blogPathsWritten} (legacy rows with no path: {blogsWithoutPath})");
|
|
Console.WriteLine($"Posts upserted: {postsUpserted}");
|
|
Console.WriteLine($"Errors: {errors}");
|
|
return errors == 0 ? 0 : 2;
|
|
}
|
|
}
|
|
}
|