Pruning respects a star from anyone

prune_entries still guarded on entries.flagged, which nothing writes
since read state moved to entry_state -- so starring a text item with no
file would not have saved it from the age sweep. It follows the reaper's
rule now, and takes orphaned read state with whatever it deletes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
2026-09-11 02:41:12 +00:00
parent 686851b448
commit c47c224372
2 changed files with 26 additions and 11 deletions

View File

@@ -536,14 +536,27 @@ impl Db {
pub fn prune_entries(&self, older_than: i64) -> Result<usize> {
let conn = self.conn.lock().unwrap();
let n = conn.execute(
"DELETE FROM entries WHERE flagged = 0
AND coalesce(published, first_seen) < ?1
AND NOT EXISTS (
SELECT 1 FROM enclosures e
WHERE e.feed_id = entries.feed_id AND e.guid = entries.guid
AND e.path IS NOT NULL)",
"DELETE FROM entries
WHERE coalesce(published, first_seen) < ?1
AND NOT EXISTS (
SELECT 1 FROM enclosures e
WHERE e.feed_id = entries.feed_id AND e.guid = entries.guid
AND e.path IS NOT NULL)
-- Starred by anyone keeps it, the same rule the reaper follows.
AND NOT EXISTS (
SELECT 1 FROM entry_state s
WHERE s.feed_id = entries.feed_id AND s.guid = entries.guid
AND s.flagged = 1)",
[older_than],
)?;
// Whatever went takes everyone's read state with it, rather than leaving rows
// pointing at an item that no longer exists.
conn.execute(
"DELETE FROM entry_state WHERE NOT EXISTS (
SELECT 1 FROM entries e
WHERE e.feed_id = entry_state.feed_id AND e.guid = entry_state.guid)",
[],
)?;
Ok(n)
}
}