feat(source): wire My QBZ through the seam, and land the owner's container rules
Stage 2. The measured win: opening a 34-item collection now costs **2** library.db opens instead of 23-34 — the local source owns a pooled handle and hands out no guard, only `with(|db| ...)`, so a `!Send` `&LibraryDatabase` cannot cross an await either. Bug 1 from the seam design is dead by construction, not by care. The owner's rules, decided today, now live in one place instead of eight: - **Collection** takes albums, singles and EPs from ANY source — qobuz, folders, Plex, and whatever is added next. **Mixtape** takes albums, playlists and tracks. This was a single boolean (`restrict_to_mixtape`) that could express neither release types nor "collections accept local". - An unsupported combination must never be VISIBLE: the picker stops offering the impossible action, and the error stays a log-only safety net. Owner: "el bloqueo no debería ser visible nunca, más bien esto hay que ajustarlo en la UI". - **Ephemeral honours its name** — playable, queueable, and offerable to nothing else. - A row's source comes from the ROW. Eight QML sites built the add-to-mixtape payload with `"source": "qobuz"` written as a literal, so a Plex or local track was stored as Qobuz and afterwards could not play at all: the app asked the Qobuz API for an id that never existed there. `QobuzSource` reads the client through a lens on every call instead of holding a clone. That is not style: `qbz-core` REPLACES its client (core.rs:346 and :384, both on paths that run before `set_session`), so a cached clone goes stale and fails silently — the hazard the crate's own doc comment described. The lens is installed in `on_boot`, first of everything, and finding out why is the lesson of this commit. The gate was green — build, 142 tests, both QML audits, a clean offscreen smoke — and the feature was visibly broken: every Qobuz row showed "No results found", an empty Quality cell and a wrong "SINGLE" type, while the Plex rows resolved fine. The registry is a OnceLock, so whoever touches it first BUILDS it, and one built without the lens leaves Qobuz permanently detached. Only driving the real app surfaced it; the crate's error string named the cause exactly, which is the one thing that made it a ten-minute fix instead of an afternoon. cc
This commit is contained in:
20 files changed
+1574
-172
No files matched your search
@@ -962,6 +962,16 @@ Rectangle {
|
||||
// MyQBZ "Add to mixtape" — the HOST builds the
|
||||
// AddItem array (TrackRow does not know
|
||||
// itemType/source).
|
||||
//
|
||||
// SOURCE: this view never shows a local album.
|
||||
// `open_album` (main.rs:538-548) routes any id
|
||||
// that `is_local_feed_id("album", …)` accepts
|
||||
// — a Plex `plex:` key, a group key, a path —
|
||||
// to the LocalAlbum view instead, so what
|
||||
// reaches `album_qt::TrackRow` here is always
|
||||
// a `/album/get` response. `item.id` is a
|
||||
// Qobuz catalog id by construction of the
|
||||
// route, not by assumption at this call site.
|
||||
onMixtapeRequested: QbzMyQbzAdd.open(JSON.stringify([{
|
||||
"itemType": "track", "source": "qobuz",
|
||||
"sourceItemId": item.id, "title": item.title || "",
|
||||
|
||||
@@ -525,6 +525,14 @@ Rectangle {
|
||||
// MyQBZ "Add to mixtape" — the HOST builds the
|
||||
// AddItem array (TrackRow does not know
|
||||
// itemType/source).
|
||||
//
|
||||
// SOURCE: a label page is a Qobuz catalog surface.
|
||||
// These rows are the `/label/page` response's
|
||||
// `top_tracks` mapped to track rows
|
||||
// (label_qt.rs:280-287) — there is no local label
|
||||
// library and no non-Qobuz row can reach this
|
||||
// Repeater, so `modelData.id` is a Qobuz catalog
|
||||
// id by construction of the document.
|
||||
onMixtapeRequested: QbzMyQbzAdd.open(JSON.stringify([{
|
||||
"itemType": "track", "source": "qobuz",
|
||||
"sourceItemId": modelData.id,
|
||||
|
||||
@@ -1466,8 +1466,28 @@ Rectangle {
|
||||
// MyQBZ "Add to mixtape" — the HOST builds the
|
||||
// AddItem array (TrackRow does not know
|
||||
// itemType/source).
|
||||
//
|
||||
// The source comes off the ROW: every feed row
|
||||
// carries its own word (library_qt.rs:54 —
|
||||
// "qobuz" | "local" | "plex"), so a Plex or local
|
||||
// row can never be stored as a Qobuz id again.
|
||||
// "plex" is folded into "local" because the store
|
||||
// has no Plex source: `AddItem.source` is
|
||||
// "qobuz" | "local" and `source_from_str`
|
||||
// (myqbz_add_qt.rs:85-90) maps everything that is
|
||||
// not "local" to Qobuz — passing "plex" verbatim
|
||||
// would silently become "qobuz" again.
|
||||
//
|
||||
// Today this list only ever holds Qobuz rows (the
|
||||
// Tracks tab keeps `kind === "track" && group ===
|
||||
// "favorites"` and the local layer lands in group
|
||||
// "local"), so this is a no-op until that filter
|
||||
// changes — which is the point: it stays right
|
||||
// when it does.
|
||||
onMixtapeRequested: QbzMyQbzAdd.open(JSON.stringify([{
|
||||
"itemType": "track", "source": "qobuz",
|
||||
"itemType": "track",
|
||||
"source": item.source === "local" || item.source === "plex"
|
||||
? "local" : "qobuz",
|
||||
"sourceItemId": item.id, "title": item.title || "",
|
||||
"subtitle": item.artist || "", "artworkUrl": "",
|
||||
"year": null, "trackCount": null
|
||||
|
||||
@@ -285,6 +285,17 @@ Rectangle {
|
||||
}
|
||||
// MyQBZ "Add to mixtape" — the HOST builds the AddItem
|
||||
// array (TrackRow does not know itemType/source).
|
||||
//
|
||||
// SOURCE: these rows carry no source field, and they do
|
||||
// not need one — every one of the four mixes is built
|
||||
// from `qbz_models::Track` values that came back from the
|
||||
// Qobuz API (foryou_qt.rs:855-873: dynamic/suggest for
|
||||
// daily+weekly, `get_favorites("tracks")` for fav,
|
||||
// `get_playlist` for top), and the DailyQ/WeeklyQ seed
|
||||
// explicitly drops local / Plex / ephemeral recents
|
||||
// (foryou_qt.rs:774-776). `modelData.id` is therefore a
|
||||
// Qobuz catalog id by construction of the document, not
|
||||
// by assumption at this call site.
|
||||
onMixtapeRequested: QbzMyQbzAdd.open(JSON.stringify([{
|
||||
"itemType": "track", "source": "qobuz",
|
||||
"sourceItemId": modelData.id,
|
||||
|
||||
@@ -647,6 +647,16 @@ Rectangle {
|
||||
onRemoveRequested: QbzBridge.playlistRemoveTrack(item.playlistTrackId)
|
||||
// MyQBZ "Add to mixtape" — the HOST builds the AddItem
|
||||
// array (TrackRow does not know itemType/source).
|
||||
//
|
||||
// SOURCE: `PlaylistTrackRow` (playlist_qt.rs:47-78) has no
|
||||
// source field because this whole view has one source —
|
||||
// LOCAL playlists are out of scope everywhere in it
|
||||
// (playlist_qt.rs:23-25), the document is mapped from
|
||||
// Qobuz `Track` values, and `playlistTrackId` is the
|
||||
// Qobuz membership row id. `item.id` is a Qobuz catalog
|
||||
// id by construction of the view, not by assumption here.
|
||||
// If local playlists are ever ported, the row must start
|
||||
// carrying its source and this must read it.
|
||||
onMixtapeRequested: QbzMyQbzAdd.open(JSON.stringify([{
|
||||
"itemType": "track", "source": "qobuz",
|
||||
"sourceItemId": item.id, "title": item.title || "",
|
||||
|
||||
@@ -705,6 +705,17 @@ Rectangle {
|
||||
// MyQBZ "Add to mixtape" — the HOST builds the
|
||||
// AddItem array (TrackRow does not know
|
||||
// itemType/source).
|
||||
//
|
||||
// SOURCE: the results page has ONE source.
|
||||
// `search_qt::TrackRow` (:171) carries no
|
||||
// source word because every row is mapped from
|
||||
// a Qobuz `Track` (`map_track`, :320-336) and
|
||||
// the local "on this device" sections are not
|
||||
// ported (search_qt.rs:24-27) — the cortinilla
|
||||
// rows, which CAN be local, carry their own
|
||||
// `source` field and are a different document.
|
||||
// `item.id` is a Qobuz catalog id by
|
||||
// construction, not by assumption here.
|
||||
onMixtapeRequested: QbzMyQbzAdd.open(JSON.stringify([{
|
||||
"itemType": "track", "source": "qobuz",
|
||||
"sourceItemId": item.id, "title": item.title || "",
|
||||
|
||||
Reference in new issue
Block a user