soulsync/core/library
BoulderBadgeDad 2fb142dded jellyfin scan: page the bulk fetch so the no-progress watchdog can't false-stall
Discord (DXP4800 NAS, 7148 tracks): library updates kept dying with "Update appears stuck — no
progress for 300s (last phase: Fetching all tracks in bulk...)". not actually hung.

root cause: the bulk track/album fetch used a single 10000-item page, so a whole library came
back in ONE request that emitted NO progress while in flight. the watchdog (database_update_health)
kills a job with no progress for 300s — so on a slow server that one silent request tripped it even
though it was alive, not stuck. raising the timeout cap only buys the silent request more rope; a
bigger library or slower disk just needs a higher number. the per-batch progress line also only ran
when there was a NEXT page, so a sub-page-size library reported nothing at all.

fix: extract a pure paginate_all_items seam (core/library/bulk_paginate.py) that pages in 1000s and
reports progress after EVERY page — so the watchdog is fed on a cadence set by page size, not library
size, and can't starve mid-fetch no matter how big the library. both Jellyfin bulk loops (tracks +
albums, same defect) now route through it. preserves the failure-shrink resilience (halve to a floor,
then give up). does NOT change what's fetched — same query, fields, items.

note: changes nothing about WHICH tracks come back; only how they're paged + that every page reports.
keep the raised cap on dev as a margin — this is the actual fix. Plex/Navidrome don't share the
pattern (checked). 9 seam tests incl. the watchdog-feed invariant (progress count scales with
N/page_size, never one call for the whole library) + the sub-page regression + failure-shrink.
467 jellyfin/library tests green, ruff clean.
2026-06-28 13:41:38 -07:00
..
__init__.py Lift _execute_retag to core/library/retag.py 2026-04-29 09:03:42 -07:00
artist_image.py Write artist.jpg to artist folder so Navidrome shows real photos 2026-05-13 11:48:09 -07:00
bulk_paginate.py jellyfin scan: page the bulk fetch so the no-progress watchdog can't false-stall 2026-06-28 13:41:38 -07:00
direct_id.py Manual match: paste a MusicBrainz ID/URL to match directly (Ashh) 2026-06-07 13:04:17 -07:00
duplicate_cleaner.py Surface silent exceptions across remaining modules — ~70 sites 2026-05-07 10:28:58 -07:00
duplicate_keep.py Fix: duplicate detector kept lossy over lossless (rank format first) 2026-06-01 12:49:34 -07:00
embedded_id_reconcile.py Auto-reconcile embedded IDs for new tracks on library scans 2026-06-05 18:31:11 -07:00
existing_album_folder.py Downloads: reuse an album's existing folder so batches don't split it (#829) 2026-06-09 13:47:25 -07:00
expired_cleanup.py Expired Download Cleaner: retention-based cleanup of watchlist/playlist downloads (Boulder) 2026-06-07 22:06:56 -07:00
file_tags.py Add per-download Audit Trail modal to Library History 2026-05-13 09:50:24 -07:00
manual_library_match.py Manual library match: accept non-numeric library track ids (#754) 2026-05-31 09:11:46 -07:00
missing_track_import.py #917: 'I have this' reuses the album's existing folder year instead of dropping it 2026-06-23 16:18:00 -07:00
path_resolve.py Delete: resolve the real on-disk file when DB metadata uses curly quotes (#833) 2026-06-09 22:28:53 -07:00
path_resolver.py fix(path-resolve): CWD-independent base dirs + quality-scan resolve diagnostic 2026-06-15 14:39:18 +02:00
redownload.py #915: redownload pulls full album_data from the primary source for iTunes/Deezer too 2026-06-23 19:21:50 -07:00
reorganize_tag_source.py Reorganize: optional embedded-tag mode (closes #592) 2026-05-15 07:56:18 -07:00
residual_files.py #891: clear dead folders left with only cover images / .lrc sidecars 2026-06-18 20:07:41 -07:00
retag_planner.py Library re-tag (1/3): pure planner — match source tracklist + per-field tag diff 2026-06-04 08:50:40 -07:00
service_search.py Manual match: paste a MusicBrainz ID/URL to match directly (Ashh) 2026-06-07 13:04:17 -07:00
stale_guard.py Orphan detector: hard-bail on a mass-orphan flood instead of warn-only 2026-06-12 08:15:23 -07:00
standalone_scan.py #904: guard standalone Deep Scan against relocating a desynced library 2026-06-22 17:53:49 -07:00
track_identity.py Final silent-exception sweep + ruff S110 lint guardrail — ~45 sites 2026-05-07 11:16:06 -07:00