From f41957fd2f5a8aa2055fe834a10281ee70617900 Mon Sep 17 00:00:00 2001 From: jim Date: Sun, 23 Aug 2026 04:34:45 -0500 Subject: [PATCH] feat(collect): let --force ignore the re-collect cooldown --collect 1 returned an empty worklist whenever the periodic re-queue branch was still inside its 3-day window, with no way to ask for the re-collect early. GetPosts now composes that age predicate conditionally, and the existing global --force flag - already "ignore cooldown" for --likes - drives it. Only the age gate drops: NotFound = 0, the IsActive filter and the blog scoping still apply. The flag is a no-op for mode 0, which re-collects every post regardless, and says so rather than pretending to act. Co-Authored-By: Claude Opus 5 --- .github/copilot-instructions.md | 2 +- URLNotesGrabberCORE/DataAccess.cs | 12 ++++++++++-- URLNotesGrabberCORE/Program.cs | 19 +++++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a98095c..09e49db 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -31,7 +31,7 @@ dotnet run -- --test [blogname] [postID] # Test API for specific post - `--test [blogname] [postID]`: Test API note collection - `--posts`: Export post blogs to file - `--blogs`: Export blog list to file -- `--collect [0|1] [datetime] [blogname]`: Collect notes for posts in DB. Optional `blogname` restricts the run to one blog (exact match), e.g. `--collect 1 zomb-eh` +- `--collect [0|1] [datetime] [blogname]`: Collect notes for posts in DB. Optional `blogname` restricts the run to one blog (exact match), e.g. `--collect 1 zomb-eh`. Add `--force` to ignore the periodic re-collect cooldown so already-collected posts are re-queued immediately (mode 1 only) - `--blogsR`: Export reply blogs to file - `--blogsO [start] [stop]`: Export blogs within range diff --git a/URLNotesGrabberCORE/DataAccess.cs b/URLNotesGrabberCORE/DataAccess.cs index c662444..474c0cb 100644 --- a/URLNotesGrabberCORE/DataAccess.cs +++ b/URLNotesGrabberCORE/DataAccess.cs @@ -858,9 +858,10 @@ namespace URLNotesGrabberCORE /// /// /// + /// Drops the age gate on the periodic re-queue branch (--force). /// /// blogName, postID, lastNoteTimestamp, notesGatheredTimestamp - public static List> GetPosts(bool withoutNotesOnly = false, DateTime? beforeDate = null, string? blogName = null, string? DBPath = null) + public static List> GetPosts(bool withoutNotesOnly = false, DateTime? beforeDate = null, string? blogName = null, bool ignoreRefreshCooldown = false, string? DBPath = null) { DBPath ??= GetDefaultDbPath(); using SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath); @@ -895,6 +896,13 @@ namespace URLNotesGrabberCORE // scoped, so it contributes its rows when the filter names zomb-eh and nothing otherwise. // That keeps a filtered worklist a strict subset of the unfiltered one -- "--collect 1 X" // returns exactly the rows "--collect 1" would have returned for X. + // + // --force drops the age gate only. NotFound = 0 and the IsActive/blog scoping above still + // apply: the flag is "re-collect early", not "collect rows every other path excludes". + string refreshCooldownClause = ignoreRefreshCooldown + ? string.Empty + : " AND NotesGatheredDateTime < unixepoch('now', 'localtime', '-3 days')" + Environment.NewLine; + string refreshBranch = "" + Environment.NewLine + " UNION " + Environment.NewLine + @@ -909,7 +917,7 @@ namespace URLNotesGrabberCORE " FROM PostsWithCount" + Environment.NewLine + " WHERE BlogName = 'zomb-eh'" + Environment.NewLine + " AND NotFound = 0" + Environment.NewLine + - " AND NotesGatheredDateTime < unixepoch('now', 'localtime', '-3 days')" + Environment.NewLine; + refreshCooldownClause; sql = "WITH PostsWithCount AS" + Environment.NewLine + "(" + Environment.NewLine + diff --git a/URLNotesGrabberCORE/Program.cs b/URLNotesGrabberCORE/Program.cs index 1c81814..27149f8 100644 --- a/URLNotesGrabberCORE/Program.cs +++ b/URLNotesGrabberCORE/Program.cs @@ -322,7 +322,12 @@ namespace URLNotesGrabberCORE managedCollectRun = true; } - exitCode = CollectNotes(settings.GetValue("PathOutput"), withoutNotesOnly, beforeDate, managedCollectRun, collectBlogName).GetAwaiter().GetResult(); + if (forceIgnoreCooldown) + Console.WriteLine(withoutNotesOnly + ? "--force: ignoring the periodic re-collect cooldown; already-collected posts in scope are re-queued now" + : "--force: no effect in mode 0 - a full re-check already re-collects every post"); + + exitCode = CollectNotes(settings.GetValue("PathOutput"), withoutNotesOnly, beforeDate, managedCollectRun, collectBlogName, forceIgnoreCooldown).GetAwaiter().GetResult(); break; case "--blogsR": //collect notes from all posts @@ -454,7 +459,7 @@ namespace URLNotesGrabberCORE Console.WriteLine("--blogs\t For each Blog in DB, write blogname to file"); - Console.WriteLine("--collect [0|1] [datetime] [blogname]\t Collect Notes from API. 1=only posts without notes. 0=full re-check of all posts: a single resumable pass (interrupt & relaunch to resume; stops when complete, retrigger for a new pass). Optional datetime overrides the cutoff and runs as a one-off (bypasses resume tracking). Optional blogname restricts the run to that blog (exact, case-sensitive match) and also runs as a one-off; e.g. \"--collect 1 zomb-eh\". datetime and blogname may be given in either order - use --blog=name if a blog name would otherwise parse as a date."); + Console.WriteLine("--collect [0|1] [datetime] [blogname]\t Collect Notes from API. 1=only posts without notes. 0=full re-check of all posts: a single resumable pass (interrupt & relaunch to resume; stops when complete, retrigger for a new pass). Optional datetime overrides the cutoff and runs as a one-off (bypasses resume tracking). Optional blogname restricts the run to that blog (exact, case-sensitive match) and also runs as a one-off; e.g. \"--collect 1 zomb-eh\". datetime and blogname may be given in either order - use --blog=name if a blog name would otherwise parse as a date. Add --force to ignore the periodic re-collect cooldown and re-queue already-collected posts immediately (mode 1 only)."); Console.WriteLine("--blogsR\t For each Note that is a REPLY, write blogname to file "); @@ -466,7 +471,7 @@ namespace URLNotesGrabberCORE Console.WriteLine("--likes\t Fetch likes: initial backfill for new blogs, incremental refresh for blogs past cooldown. Optional blog name forces single-blog run."); - Console.WriteLine("--force\t (with --likes) Ignore cooldown and refresh every fully-backfilled blog"); + Console.WriteLine("--force\t Ignore refresh cooldowns: with --likes, refresh every fully-backfilled blog; with --collect 1, re-queue already-collected posts without waiting out their cooldown"); Console.WriteLine("--urldump\t Scan all posts' text columns and extract suspected URLs to configured file"); @@ -1345,15 +1350,17 @@ if (shouldInsert) // blipping on one post. Past this, skipping post-by-post would just hammer a closed door. const int MaxConsecutiveTransient = 10; - static async Task CollectNotes(string outPath, bool withoutNotesOnly = true, DateTime? beforeDate = null, bool managedRun = false, string? blogName = null) + static async Task CollectNotes(string outPath, bool withoutNotesOnly = true, DateTime? beforeDate = null, bool managedRun = false, string? blogName = null, bool ignoreRefreshCooldown = false) { - List> posts = DataAccess.GetPosts(withoutNotesOnly, beforeDate, blogName); + List> posts = DataAccess.GetPosts(withoutNotesOnly, beforeDate, blogName, ignoreRefreshCooldown); if (posts.Count == 0 && !string.IsNullOrWhiteSpace(blogName)) { // BlogName is matched exactly, so a typo or a case mismatch looks identical to "nothing // left to collect". Say so rather than reporting a silent, instant success. Console.WriteLine($"No posts to collect for blog '{blogName}'. Either it is fully collected, or the name does not match a stored blog (the match is case-sensitive)."); + if (withoutNotesOnly && !ignoreRefreshCooldown) + Console.WriteLine("Already-collected posts are re-queued only once their cooldown elapses; add --force to re-collect them now."); return 0; } @@ -1451,7 +1458,7 @@ if (shouldInsert) } // Re-fetch the updated list after processing the current post - posts = DataAccess.GetPosts(withoutNotesOnly, beforeDate, blogName); + posts = DataAccess.GetPosts(withoutNotesOnly, beforeDate, blogName, ignoreRefreshCooldown); } }