Almost no blog names a category the Directory can use, so a feed can
carry one of its own in config.toml, set by an admin in the feed's
settings and used when the feed names none. The feed's own iTunes
category still wins. The field offers the categories the Directory
already shows, so a blog about games joins Games rather than starting a
second chip. Setting it on a feed from an OPML promotes it to config, as
any other shared setting does.
Closes#10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Works through TODO.md from the 2026-09-12 over-engineering audit. Drops the
entries.read/flagged/position columns (migrate() removes them from older
databases), migrate_opml_children, the legacy interval_mins key, the
contrib/ systemd units, test-only Db wrappers, a duplicate token generator,
redundant logbuf visitors, unused page state and CSS, and the infer, dirs
and tokio-stream dependencies. The icon is served once as /icon.png instead
of inlined four times, taking about 94 KB off the two pages.
The adoption's subscription half was not dead: it gives a fresh install's
first admin the config's feeds. It stays as adopt_catalogue, now tested.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TAC7sLVqfKmY6rsTLXzNgk
Blog feeds put each article's header image in an <enclosure>, so a text
feed read as a podcast full of episodes: 149 images, 74 MB across 11
feeds. media_types defaults to audio and video, with a per-feed override.
Such enclosures stay listed and stay downloadable by hand; the row names
what it is rather than saying "skipped". An unknown type is allowed, since
the real type is only known after downloading, and a torrent is allowed as
a container judged once unpacked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
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
Splits the forbidden set: / \ | : were separating words, so they become
"-"; ? * < > " ' just go. Runs of dashes and spaces collapse to " - "
when the run held whitespace and to a bare "-" when it did not, so
"Show | Series" reads "Show - Series" while "AC/DC" stays "AC-DC".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
A real feed titled with pipe separators produced a folder named
"Get in the Trunk Anthology Series Delta Green": removing a forbidden
character left the gap around it. Runs of whitespace now collapse, and
control characters map to a space rather than vanishing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
librqbit replaces the vendored BitTorrent 4.2.1 tree. Torrents download
in place because seeding serves the files it downloaded, so the planned
stage-then-move would have broken it. The stall budget now also covers
magnet metadata resolution, which otherwise never returns against a dead
swarm and wedged the scan.
Adds add/rm/import/export, tracing setup, systemd units and README.
A successful swarm download is unverified: no reachable peers here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
Streaming download with progress, content sniffing that replaces the
long-dead detectFileType(), HTML-body rejection, and placement by rename
from an incomplete dir on the same filesystem.
Filters are applied once at discovery and recorded in enclosures.state,
so the download queue is the table rather than the parse result and
max_new_per_check defers work instead of dropping it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe