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
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
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
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
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
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