feat(slint): source-aware Plex artwork on now-playing bar, queue panel, MPRIS (port slice 4)
A Plex queue track's artwork was file://-prefixed onto its raw /library/ thumb path, breaking the cover on all three surfaces. Approach (b) from the funnel review: keep the raw path in QueueTrack.artwork_url (token never persisted) and resolve to a fresh tokenized PlexThumb from live plex_settings creds at each surface. - qbz-models: additive QueueTrack::artwork_ref_with_plex(base_url, token) — a raw /library or /photo path + creds => ArtworkRef::PlexThumb, else falls back to artwork_ref() (http/file:// unchanged). 4 regression tests. - playback.rs: local_queue_track keeps the raw path for Plex rows (no file:// poison); refresh_now_playing_meta uses artwork_ref_with_plex -> fixes the now-playing bar, MPRIS (to_mpris_url), and the desktop notification. - queue.rs: load_artwork threads creds + a PlexThumb branch for the queue panel. - Local/Qobuz/ephemeral artwork byte-for-byte unchanged. cc
This commit is contained in:
3 files changed
+146
-18
No files matched your search
@@ -204,6 +204,31 @@ impl QueueTrack {
|
||||
ArtworkRef::LocalFile(raw.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// A uniform reference to this track's cover art, Plex-aware.
|
||||
///
|
||||
/// Same value-driven classification as [`artwork_ref`](Self::artwork_ref),
|
||||
/// except a bare server-relative Plex thumb path (`/library/...` or
|
||||
/// `/photo/...`) is resolved to [`ArtworkRef::PlexThumb`] using the supplied
|
||||
/// current credentials — so the now-playing bar, queue panel, and MPRIS get
|
||||
/// the tokenized cover instead of a `file://`-poisoned local-read miss.
|
||||
///
|
||||
/// `qbz-models` stays credential-free: the caller fetches the live
|
||||
/// `{base_url, token}` (from the Slint Plex settings store) and threads them
|
||||
/// in. When creds are missing, or the path is not a Plex thumb path, this
|
||||
/// falls back to [`artwork_ref`](Self::artwork_ref).
|
||||
pub fn artwork_ref_with_plex(&self, base_url: &str, token: &str) -> ArtworkRef {
|
||||
let raw = self.artwork_url.as_deref().unwrap_or("");
|
||||
let is_plex_path = raw.starts_with("/library/") || raw.starts_with("/photo/");
|
||||
if is_plex_path && !base_url.is_empty() && !token.is_empty() {
|
||||
return ArtworkRef::PlexThumb {
|
||||
base_url: base_url.to_string(),
|
||||
token: token.to_string(),
|
||||
path: raw.to_string(),
|
||||
};
|
||||
}
|
||||
self.artwork_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -296,5 +321,79 @@ mod tests {
|
||||
track_with(Some("local"), Some("file:///home/u/cover.jpg")).artwork_ref(),
|
||||
ArtworkRef::LocalFile("/home/u/cover.jpg".into())
|
||||
);
|
||||
// A raw Plex thumb path stays LocalFile here (no creds at the model
|
||||
// layer); PlexThumb is produced only by `artwork_ref_with_plex`.
|
||||
assert_eq!(
|
||||
track_with(Some("plex"), Some("/library/metadata/42/thumb/1")).artwork_ref(),
|
||||
ArtworkRef::LocalFile("/library/metadata/42/thumb/1".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artwork_ref_with_plex_resolves_library_path() {
|
||||
// Plex thumb path + creds → PlexThumb (the now-playing/queue/MPRIS path).
|
||||
assert_eq!(
|
||||
track_with(Some("plex"), Some("/library/metadata/42/thumb/1"))
|
||||
.artwork_ref_with_plex("http://plex.local:32400", "tok"),
|
||||
ArtworkRef::PlexThumb {
|
||||
base_url: "http://plex.local:32400".into(),
|
||||
token: "tok".into(),
|
||||
path: "/library/metadata/42/thumb/1".into(),
|
||||
}
|
||||
);
|
||||
// `/photo/...` transcode paths classify the same way.
|
||||
assert_eq!(
|
||||
track_with(Some("plex"), Some("/photo/:/transcode?url=x"))
|
||||
.artwork_ref_with_plex("http://plex.local:32400", "tok"),
|
||||
ArtworkRef::PlexThumb {
|
||||
base_url: "http://plex.local:32400".into(),
|
||||
token: "tok".into(),
|
||||
path: "/photo/:/transcode?url=x".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artwork_ref_with_plex_falls_back_without_creds_or_non_plex() {
|
||||
// Missing creds → no PlexThumb; falls back to LocalFile (the raw path).
|
||||
assert_eq!(
|
||||
track_with(Some("plex"), Some("/library/metadata/42/thumb/1"))
|
||||
.artwork_ref_with_plex("", ""),
|
||||
ArtworkRef::LocalFile("/library/metadata/42/thumb/1".into())
|
||||
);
|
||||
// Non-Plex value is untouched: http stays Remote even with creds.
|
||||
assert_eq!(
|
||||
track_with(Some("qobuz"), Some("https://x/cover.jpg"))
|
||||
.artwork_ref_with_plex("http://plex.local:32400", "tok"),
|
||||
ArtworkRef::Remote("https://x/cover.jpg".into())
|
||||
);
|
||||
// A local file:// path stays LocalFile, never mistaken for Plex.
|
||||
assert_eq!(
|
||||
track_with(Some("local"), Some("file:///home/u/cover.jpg"))
|
||||
.artwork_ref_with_plex("http://plex.local:32400", "tok"),
|
||||
ArtworkRef::LocalFile("/home/u/cover.jpg".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plex_thumb_to_mpris_url_tokenizes() {
|
||||
let no_query = ArtworkRef::PlexThumb {
|
||||
base_url: "http://plex.local:32400".into(),
|
||||
token: "tok".into(),
|
||||
path: "/library/metadata/42/thumb/1".into(),
|
||||
};
|
||||
assert_eq!(
|
||||
no_query.to_mpris_url().as_deref(),
|
||||
Some("http://plex.local:32400/library/metadata/42/thumb/1?X-Plex-Token=tok")
|
||||
);
|
||||
let with_query = ArtworkRef::PlexThumb {
|
||||
base_url: "http://plex.local:32400".into(),
|
||||
token: "tok".into(),
|
||||
path: "/photo/:/transcode?url=x".into(),
|
||||
};
|
||||
assert_eq!(
|
||||
with_query.to_mpris_url().as_deref(),
|
||||
Some("http://plex.local:32400/photo/:/transcode?url=x&X-Plex-Token=tok")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user