Subscribe to an OPML, not just import one
A feed whose body sniffs as OPML is treated as a subscription list and re-read on every scan, as iPodderX did. Listed feeds become real config entries grouped under it, inherit its settings, land in one nested folder, and are scanned in the same run. When a feed leaves the OPML: removed if nothing was downloaded, kept and flagged otherwise, so a downloaded file is never orphaned. folder_for sanitized the whole folder string and would have flattened the nesting; each segment is sanitized separately now, and a traversal still cannot escape the download directory. Db::memory() also runs migrate(), which it did not, so a migration-only column passed tests while missing in production. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
30
PROGRESS.md
30
PROGRESS.md
@@ -56,6 +56,36 @@ and until now nothing set them.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-10 — Subscribing to an OPML, not just importing one
|
||||
|
||||
Asked whether this version could do what iPodderX did: subscribe to an OPML and get a folder of the
|
||||
feeds inside it. It could not — `ipx import` was one-shot from a file and nothing ever re-read it.
|
||||
The original checked for a URL ending in `.opml` and re-fetched it on every scan
|
||||
(`iPXClass.py:34-40`).
|
||||
|
||||
Now: a feed whose body sniffs as OPML is a subscription list. Sniffing the body rather than the URL
|
||||
extension also catches an OPML served from a `.rss` or extension-less URL, which the original missed.
|
||||
Every scan re-reads it; listed feeds become real config entries with `group = "<opml-id>"` inheriting
|
||||
the parent's settings, land in one nested folder, and are scanned in the same run rather than waiting
|
||||
an interval.
|
||||
|
||||
Removal policy, Ray's rule: **a downloaded file is never orphaned.** Gone from the OPML with nothing
|
||||
downloaded -> unsubscribed and removed. Gone but with downloads -> kept and flagged `orphaned`, with
|
||||
the UI saying why. Verified end to end on a local fixture: the feed with 1 download was kept and
|
||||
flagged, the one with 0 was removed, the file untouched.
|
||||
|
||||
`folder_for` had to change: it sanitized the whole folder string, so `sub/Show` would have collapsed
|
||||
into one directory. Each path segment is sanitized separately now, with a test that `../../etc/Show`
|
||||
cannot climb out of the download directory.
|
||||
|
||||
Also: `Db::memory()` now runs `migrate()` like a real open. It did not, so a column added only in the
|
||||
migration passed the tests while being absent in production — which is exactly backwards.
|
||||
|
||||
Live result: Ray's `lists.opml.org` subscription expanded into 82 child feeds, all grouped, 1431
|
||||
entries, no feed errors, worker still responsive mid-scan.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-10 — Log view in the app, and Docker
|
||||
|
||||
**Log view.** A ring buffer (2000 lines) fed by a `tracing` layer, exposed at `/api/logs` with a
|
||||
|
||||
Reference in New Issue
Block a user