Retention follows per-user read and starred
reap_candidates still read entries.read/flagged, which nothing writes since read state moved to entry_state -- so starring no longer protected a file and the read-first ordering was dead. One file serves every subscriber, so anyone starring it keeps it, and it counts as read only once everyone subscribed has read it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
32
src/db.rs
32
src/db.rs
@@ -466,23 +466,31 @@ pub struct Candidate {
|
||||
}
|
||||
|
||||
impl Db {
|
||||
/// Files on disk, flagged ones excluded, read before unread and oldest first within
|
||||
/// each group.
|
||||
/// Files on disk that may be deleted to get back under quota: starred by nobody,
|
||||
/// with the ones everybody has finished going first, oldest first within each group.
|
||||
///
|
||||
/// The Python intended `read = 1 AND flagged = 0` but never achieved it (a missing
|
||||
/// plistlib import and an `EntreiesData` typo meant the filter always threw). Requiring
|
||||
/// `read = 1` outright would be just as dead here, since nothing marks episodes read
|
||||
/// until a UI exists -- so `flagged` is the keep-forever marker, and `read` only decides
|
||||
/// what goes first.
|
||||
/// One file serves every subscriber, so both tests are about all of them: **anyone**
|
||||
/// starring it keeps it, and it only counts as read when **everyone** subscribed has
|
||||
/// read it. A file whose feed nobody subscribes to has no one left to keep it, so it
|
||||
/// sorts with the read ones.
|
||||
///
|
||||
/// (The Python intended `read = 1 AND flagged = 0` but never achieved it -- a missing
|
||||
/// plistlib import and an `EntreiesData` typo meant the filter always threw.)
|
||||
pub fn reap_candidates(&self) -> Result<Vec<Candidate>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT e.id, e.url, e.path, e.bytes_done,
|
||||
coalesce(e.downloaded_at, 0), coalesce(n.read, 0), coalesce(n.flagged, 0)
|
||||
"SELECT e.id, e.url, e.path, e.bytes_done, coalesce(e.downloaded_at, 0),
|
||||
CASE WHEN coalesce(readers.n, 0) >= coalesce(subs.n, 0) THEN 1 ELSE 0 END
|
||||
FROM enclosures e
|
||||
LEFT JOIN entries n ON n.feed_id = e.feed_id AND n.guid = e.guid
|
||||
WHERE e.path IS NOT NULL AND coalesce(n.flagged, 0) = 0
|
||||
ORDER BY coalesce(n.read, 0) DESC, coalesce(e.downloaded_at, 0) ASC, e.id ASC",
|
||||
LEFT JOIN (SELECT feed_id, count(*) n FROM subscriptions GROUP BY feed_id) subs
|
||||
ON subs.feed_id = e.feed_id
|
||||
LEFT JOIN (SELECT feed_id, guid, count(*) n FROM entry_state
|
||||
WHERE read = 1 GROUP BY feed_id, guid) readers
|
||||
ON readers.feed_id = e.feed_id AND readers.guid = e.guid
|
||||
WHERE e.path IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM entry_state s
|
||||
WHERE s.feed_id = e.feed_id AND s.guid = e.guid AND s.flagged = 1)
|
||||
ORDER BY 6 DESC, coalesce(e.downloaded_at, 0) ASC, e.id ASC",
|
||||
)?;
|
||||
let rows = stmt
|
||||
.query_map([], |r| {
|
||||
|
||||
Reference in New Issue
Block a user