Commit Graph
6 Commits
Author SHA1 Message Date
vicrodh 75b8eee1c6 feat(media): add Jellyfin Quick Connect and harden remote connections
Add cancellable Quick Connect pairing and shared connection guards for Jellyfin and Subsonic. Bound connection attempts, validate library access before saving credentials, and report per-provider connection and synchronization status.

cx
2026-09-09 17:12:45 -06:00
vicrodh 472706f84e fix(settings): harden migration and accessibility
Apply interface scaling through Qt at startup, complete keyboard navigation, move account migration into setup and progress modals, and defer Subsonic sync while the server scans.

cx
2026-09-02 14:33:08 -06:00
vicrodh e7135a424a feat(media-servers): capture the recording MBID and ISRC from Jellyfin and Subsonic
Jellyfin requests Fields=ProviderIds on both listing passes and maps
ProviderIds.MusicBrainzTrack (the recording id) into JellyfinTrack;
Subsonic reads OpenSubsonic musicBrainzId and the first isrc. Both land
in two additive remote_cache_tracks columns (isrc, recording_mbid), kept
across upserts with COALESCE so a known id is never overwritten by a
server that stops sending it, and travel to the queue through
qbz-source's remote constructor.

cc
2026-08-28 20:31:05 -06:00
vicrodh d244213a64 fix(library): harden cross-source catalog data
Preserve per-disc and collection artwork, multi-value genres and remote metadata across Local, Plex, Jellyfin and Subsonic.

Apply source, format and quality filters before pagination, reject invalid CUE rows, and keep Qt library surfaces aligned with the selected physical version.

cx
2026-08-25 18:26:06 -06:00
vicrodh 6005510bf7 feat(library): sync Subsonic pages by generation
Project Search3 and per-album results into atomic source-generation pages, authorizing prune only after a complete enumeration.

Reject malformed authoritative responses, invalidate late writes across cancellation and profile changes, and sanitize secret-bearing transport errors.

cx
2026-08-23 14:37:45 -06:00
vicrodh 0f41c44dac feat(subsonic): the protocol crate, with the HTTP-200 trap enforced in code
Stage 5's second protocol crate. One client for Navidrome, Gonic,
Airsonic, Astiga and Ampache — which is why the owner scoped this as
"Subsonic, not just Navidrome". Only Navidrome 0.63.2 was on the bench,
so the places servers are likely to differ are named rather than
smoothed over.

The dangerous property of this protocol is that it reports FAILURE with
HTTP 200. `error_for_status()` passes on a refusal. Three defences, all
in code rather than in a reviewer's memory:

- `parse_envelope` is the only way a body becomes a payload, and it
  checks `subsonic-response.status` first. It also accepts XML on the
  failure path, because the measured no-credential getCoverArt reply came
  back as XML DESPITE `f=json` — a JSON-only parser would report "not
  understood" for a perfectly clear "missing parameter: 'u'".
- `looks_like_audio` exists because a refused stream is a ~200-byte JSON
  blob served as the audio body. That is the one way this feature could
  reach the protected decoder with garbage.
- code 40 and 70 become their own variants, so a caller can act on
  "re-authenticate" without matching on prose.

Two mapping decisions worth stating: `duration` is SECONDS on the wire
and milliseconds in QBZ, and `bitDepth` is **0** for a lossy track where
Jellyfin reports null — both sentinels fold to None so downstream sees
one vocabulary rather than two.

The salt is FIXED per install, not rolled per request. Rolling looks
more careful and would make every cover URL unique per request, which
re-downloads every cover on every pass since the artwork cache keys on
the URL. It buys nothing: the password is not transmitted either way.
The consequence is recorded where it matters — a cover URL carries
credentials, so callers must key their cache on the opaque coverArt id.

`detect_sweep_mode` ASKS rather than checks the server's name: search3
with an empty query is 14 requests for this library and getAlbumList2 +
getAlbum is 675, but the fast path is the behaviour most likely to
differ on a server that was never tested, and probing one page costs
~100 ms.

14 unit tests + 6 live. The live suite runs in PARALLEL by design —
Subsonic auth is stateless, so unlike Jellyfin nothing here can revoke
anything else's session. Against Navidrome 0.63.2, three consecutive
runs, 6/6 each:

  ping ......... navidrome 0.63.2, openSubsonic=true
  wrong pass ... Unauthorized, and the raw request really answered 200
  sweep ........ Search3 detected; page 0 = 500 rows, 344 bit depths
  per-album .... fallback returns 500 album ids, 17 tracks in the first
  stream ....... 206 audio/flac, Content-Range total == the API's own
                 reported file size, so nothing was re-encoded
  cover ........ authed image/webp 69 KB; unauthed HTTP 200
                 application/xml 251 B, rejected by the guard

cc
2026-08-20 07:43:46 -06:00