You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Re-include older episodes when max_age is expanded
Increasing a feed's max_age now pulls in older episodes that were previously out of range, instead of silently
ignoring them. This required decoupling discovery depth from page_size and making episode re-inclusion work off
the database rather than the API window.
- Discovery depth is driven by max_age: a normal update still does a shallow scan (page_size most-recent
episodes), but when max_age is expanded beyond what was previously scanned, the YouTube builder performs a
one-time deep scan that pages back to the max_age cutoff. A per-feed ScannedThrough high-water mark gates this
so it costs nothing in steady state, and it is bounded by a 20-page backstop. Brand-new feeds stay shallow.
- Previously cleaned (soft-deleted) episodes that match the filters again are re-queued for download off the
database records directly, independent of the page_size API window. Only episodes that would survive the
keep_last cleanup are re-queued, to avoid a re-download/re-clean loop.
- Cleanup no longer clears an episode's title/description when soft-deleting it, so a re-included episode keeps
its metadata. For episodes cleaned by older versions (which wiped metadata), the title/description are
recovered from the current feed query.
- The feed build skips a downloaded episode with an empty title instead of aborting the whole feed, so one
malformed item can no longer take down the entire feed.
- A startup warning is logged when clean.keep_last exceeds page_size and no max_age is configured.
Currently implemented for YouTube.
Copy file name to clipboardExpand all lines: CLAUDE.md
+7-3Lines changed: 7 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,9 +38,12 @@ Understanding how episodes flow through the system:
38
38
39
39
### Discovery Phase
40
40
- Episodes are discovered during feed updates via platform APIs (YouTube Data API v3, Vimeo API, SoundCloud, Twitch)
41
-
-`updateFeed()` in `services/update/updater.go:99-154` queries the platform API
41
+
-`updateFeed()` in `services/update/updater.go:99-169` queries the platform API
42
42
- New episodes are stored in BadgerDB with status `EpisodeNew`
43
43
- Episodes matching feed URL are identified by provider-specific parsing in `pkg/builder/`
44
+
- **Discovery depth (`max_age`-driven deep scan)**: normal updates do a shallow scan (the `page_size` most-recent episodes). To discover **never-before-seen** older episodes after `max_age` is increased, the updater triggers a one-time deep scan that pages the channel back to the `max_age` cutoff instead of stopping at `page_size`. The decision lives in `discoveryWindow()` (`services/update/updater.go`): each feed record stores `ScannedThrough`, a high-water mark of the oldest publish date discovery has paged to; a deep scan runs only when the `max_age` cutoff is older than `ScannedThrough` (i.e. `max_age` was actually expanded), so it costs nothing in steady state. The cutoff is passed to the builder via `feed.Config.DiscoverSince` (runtime-only field); the YouTube builder honors it in `queryItemsSince()`, bounded by `maxDeepScanPages` (20) as a quota backstop. `nextScannedThrough()` advances the mark to the cutoff only once every matching in-range episode is downloaded/cleaned/errored, so multi-cycle catch-up (bounded by `page_size` downloads per cycle) isn't truncated by episode pruning. Brand-new feeds stay shallow (no surprise back-catalog download). Currently implemented for YouTube only.
45
+
- Previously cleaned episodes (`EpisodeCleaned`) that match the current filters again (e.g. after `max_age` is increased) are re-queued: status is reset to `EpisodeNew` (`resurrectEpisodes()` in `services/update/updater.go`). This works **off the database records directly, so it is independent of the `page_size` API window** — a cleaned episode no longer needs to be returned by the current feed query to be re-included. Cleaned records retain their metadata (see cleanup phase), so filters are evaluated against the stored title/description. Episodes cleaned by older podsync versions had their title/description wiped; for those the metadata is recovered from the current feed query (the deep-discovery scan brings in-range episodes back into the result with fresh metadata). An episode that still has no title — not in the DB and not in the feed query — is left cleaned until a later cycle surfaces it. When `keep_last` is configured, only episodes that would survive the next cleanup are re-queued (ranked by `PubDate` via `keepLastSurvivors()`), to avoid a re-download/re-clean loop.
46
+
- Feed XML build (`feed.Build()` in `pkg/feed/xml.go`) skips any downloaded episode with an empty title (logging a warning) instead of aborting the entire feed build for one malformed item. (Original podsync aborted the whole feed on any empty title/description.)
44
47
45
48
### Download Phase
46
49
-`fetchEpisodes()` iterates episodes with status `EpisodeNew` or `EpisodeError`
@@ -51,12 +54,13 @@ Understanding how episodes flow through the system:
51
54
- On failure: status set to `EpisodeError`, retry attempted next cycle
52
55
53
56
### Cleanup Phase (Important!)
54
-
-`cleanup()` in `updater.go:373-441` runs AFTER each successful update cycle
57
+
-`cleanup()` in `updater.go:456-524` runs AFTER each successful update cycle
55
58
-**Only triggered if `clean.keep_last` is configured** (global or per-feed)
56
59
- Keeps most recent N episodes by PubDate (descending order)
57
-
- Deleted episodes have status changed to `EpisodeCleaned` and title/description cleared
60
+
- Deleted episodes have status changed to `EpisodeCleaned`; their title/description are **retained** so the record can be resurrected later (e.g. after `max_age` is increased) without losing metadata. Cleaned episodes are excluded from the feed by status, so keeping metadata has no effect on output.
58
61
-**Files are deleted from storage but database records are retained**
59
62
- This is a soft-delete: episodes remain in the database forever
63
+
-**`keep_last` vs `page_size`**: a shallow update only sees `page_size` episodes, so on its own it can't fill a larger retention window. This is fine when `max_age` is set (the deep-discovery scan covers the look-back — see Discovery Phase) or when episodes were already discovered earlier. A startup warning is logged only when `clean.keep_last > page_size`**and** no `max_age` is configured (`applyDefaults()` in `cmd/podsync/config.go`).
60
64
61
65
### Episode Removal from Database
62
66
- Episodes are removed from the database only if they:
log.Warnf("deep scan for feed %q reached the %d-page cap before covering max_age; older episodes were not discovered (consider lowering max_age)", feed.ItemID, maxDeepScanPages)
0 commit comments