38d7ba80fe
* feat: auto-download new items from subscribed OPDS catalogs Add opt-in auto-download per OPDS catalog. When enabled, new publications are downloaded on app startup and pull-to-refresh. Dedup via Atom <id>. - Extend foliate-js parser to surface entry id/updated fields - Add subscription state to OPDSCatalog type - New headless opdsSyncService with navigation feed crawling - Auto-download toggle in CatalogManager UI - Wire sync into startup and pull-to-refresh hooks - 16 new test cases Built with an AI code agent. Closes #3836 * chore: point foliate-js submodule to fork with OPDS id/updated fields CI needs to fetch the foliate-js commit that adds id and updated field extraction from OPDS feeds. Point submodule URL to our fork where the commit is pushed. * feat(opds): rearchitect auto-download with two-phase OPDS sync Replaces the monolithic opdsSyncService from #3837 with a separated discovery + acquisition pipeline. Subscription state lives in per-catalog JSON files under Data/opds-subscriptions/, decoupled from catalog config. Discovery (services/opds/feedChecker.ts) resolves a catalog URL down to its "by newest" feed via three detection tiers — rel="http://opds- spec.org/sort/new" (Calibre / Calibre-Web), title heuristics ("Newest", "Recently Added", "Latest"), and href patterns (?sort_order=release_date for Project Gutenberg, /new-releases for Standard Ebooks). It then walks only that feed plus rel=next pagination, never the full navigation tree. When no by-newest feed is found, the catalog is skipped with a warning instead of silently mirroring everything. Acquisition link selection filters to safe rels (acquisition / acquisition/open-access — drops buy / borrow / subscribe / sample / indirect), prefers open-access when both rels are present, then ranks by format tier: 0 Advanced EPUB / EPUB 3 (link title, .epub3 href, version=3.x) 1 plain EPUB 2 MOBI / AZW / AZW3 3 PDF / CBZ 4 anything else Within a tier, ties resolve by feed order. When the upstream omits or mis-declares (octet-stream) the link's media type, format is inferred from href extension and link title. Entries that appear in both feed.publications and a group are deduped within the same batch. Orchestration (services/opds/autoDownload.ts) runs catalogs sequentially — per-catalog concurrency already caps parallel downloads at 3, so a parallel fan-out across catalogs would be N×3 simultaneous requests on cellular. Pending and retry items are deduped by entryId, and entries still inside their exponential-backoff window are skipped instead of re-attempted (avoids appending duplicate failedEntries records). Filenames come from the last path segment, capped at 200 chars, with a try/catch around decodeURIComponent for malformed %-sequences. Hook (hooks/useOPDSSubscriptions.ts): startup, 5-minute interval, and 'check-opds-subscriptions' event triggers. Toggling auto-download on fires the event so the sync runs immediately. Newly imported books are queued for cloud upload via transferManager when the user is logged in and settings.autoUpload is on, mirroring the manual OPDS download path. 'opds-sync-complete' is dispatched after every sync so listening UI can refresh without polling. loadSubscriptionState self-heals duplicate failedEntries from older state files. The submodule URL is reverted to readest/foliate-js (the changes needed for OPDS id/updated extraction are already in main), and OPDSCatalog is trimmed to only the autoDownload field. Verbose [OPDS]-prefixed debug logs cover the whole pipeline: feed fetch → resolve → discovery → per-item download (with magic-byte dump of the saved file) → import → cloud upload queueing. 34 new tests across opds-feed-checker, opds-auto-download, opds-subscription-state, opds-types. Closes #3836. Supersedes #3837. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Johannes Mauerer <johannes@mauerer.info> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>