Stage 1 of the seam the owner asked for: "son vistas súper híbridas donde se mezcla Qobuz/Local(Folders/Plex) ... hay que hacer una solución agnóstica porque no tardamos en meter a Jellyfin y quizás Navidrome". Nothing is wired yet — this lands the crate, its three source implementations and its tests. `qbz-qt` and `qbz` are untouched, so this commit changes no behaviour. The problem it exists to end, measured rather than assumed: 129 source-branching sites across 30 files in qbz-qt alone, SEVEN distinct item->tracks resolvers none of which can reach the others, FIVE non-interchangeable artwork pipelines over one correct taxonomy, and six incompatible source vocabularies — three of which cannot express Plex at all, including the PERSISTED one (`AlbumSource` is `Qobuz|Local`). Every hybrid view re-wires the per-source handling by hand, which is why something is always missing. One `Source` trait covering the four concerns together — tracks, metadata and origin, artwork, playback — because splitting them is exactly what lets a view forget one. A `SourceRegistry` with claim-priority ordering (Qobuz, Plex, then Local; Plex-before-Local is load-bearing). `RegistryResolver` implements `qbz_mixtape::ItemResolver`, so that crate and the Slint glue stay byte- untouched and keep compiling. The three measured bugs die structurally, not by discipline: - The local DB was reopened PER CALL, so a My QBZ collection logged one "Opening library database" per item (34 measured live). `LocalSource` owns a pooled handle, and it hands out no guard — only `with(|db| ...)` — so a `!Send` `&LibraryDatabase` provably cannot cross an await. `qbz-mixtape`'s sync-resolver constraint becomes an API property instead of a comment. - A `plex:<hash>` album key reached an API that wants a rating key. `MediaRef` has no public constructor: only `registry.claim` mints one, and `PlexSource::claim` rejects that shape in track position. The survey also found a second, undiscovered face of the same bug — "Go to album" on a Plex track row opens an empty page. - Local rows in a hybrid list rendered placeholder discs. Traced to its real mechanism: those items are stored with `artwork_url: None` and the only backfill is behind a Qobuz-client gate, so offline or pre-login the art is on disk and never shown. `Source::artwork` is one call per row whatever the source, and keeps the cheap/cold split the local path already had. Adding Jellyfin is one new file, four diff lines inside this crate, and one line in `auth_qt.rs` — and the design states this only holds BECAUSE artwork sits on the trait. 36 tests. The id tests derive their fixtures from `PLEX_TRACK_ID_FLOOR` instead of transcribing them, which is not pedantry: the survey's own id table — the artefact meant to be the definitive reference — paired a namespaced id with the wrong rating key, and the transcribed test caught it on the first run. Design and adversarial review (6 BLOCKER / 10 MAJOR / 7 MINOR, all corrected): qbz-nix-docs/qt-frontend/2026-07-30-source-seam/. cc
29 lines
1.2 KiB
TOML
29 lines
1.2 KiB
TOML
[package]
|
|
name = "qbz-source"
|
|
description = "The source-agnostic seam: one trait + one registry over Qobuz, local files and Plex"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
|
|
[dependencies]
|
|
# QueueTrack, mixtape::*, plex_thumb_url. No internal deps of its own.
|
|
qbz-models = { path = "../qbz-models" }
|
|
# QobuzClient (Clone) — the catalog arm.
|
|
qbz-qobuz = { path = "../qbz-qobuz" }
|
|
# LibraryDatabase / LocalTrack / thumbnails — the local arm.
|
|
qbz-library = { path = "../qbz-library" }
|
|
# Plex cache reads + plex_resolve_track_media — the Plex arm.
|
|
qbz-plex = { path = "../qbz-plex" }
|
|
# `ItemResolver` — this crate IMPLEMENTS it (mixtape_adapter.rs). qbz-mixtape is
|
|
# NOT modified and NOT absorbed; the dependency is one-directional so the `qbz`
|
|
# (Slint) crate keeps compiling untouched.
|
|
qbz-mixtape = { path = "../qbz-mixtape" }
|
|
|
|
async-trait = { workspace = true }
|
|
log = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
|
|
# ABSENT ON PURPOSE (design §8): qbz-audio, qbz-player (the PROTECTED backend is
|
|
# not linked, so it cannot be reached even by accident), qbz-core, qbz-app,
|
|
# tokio (this crate never spawns), reqwest, qbz-i18n, qbz-cache, rusqlite.
|