2 files changed
+31
-2
No files matched your search
@@ -5,12 +5,23 @@ edition = "2021"
|
||||
description = "Mixtapes & Collections backend for QBZ — schema, repo, shuffle, enqueue (headless, frontend-agnostic)"
|
||||
authors = ["blitzkriegfc"]
|
||||
|
||||
[features]
|
||||
default = ["local", "plex"]
|
||||
# Resolving Mixtape items that live in the on-disk library (`qbz-library`).
|
||||
local = ["dep:qbz-library"]
|
||||
# Resolving `plex:`-prefixed album keys against the Plex cache. Reached through
|
||||
# `resolve_local_album`, so it builds on `local`.
|
||||
plex = ["local", "dep:qbz-plex"]
|
||||
|
||||
[dependencies]
|
||||
# Shared QBZ crates (the real models + resolvers, not src-tauri shims)
|
||||
qbz-models = { path = "../qbz-models" }
|
||||
qbz-qobuz = { path = "../qbz-qobuz" }
|
||||
qbz-library = { path = "../qbz-library" }
|
||||
qbz-plex = { path = "../qbz-plex" }
|
||||
# Optional so the schema/repo/shuffle half of this crate is reusable where there
|
||||
# are no local files and no LAN peer to reach — a phone, for one. Both default
|
||||
# on, so the desktop build is unchanged.
|
||||
qbz-library = { path = "../qbz-library", optional = true }
|
||||
qbz-plex = { path = "../qbz-plex", optional = true }
|
||||
|
||||
# Database
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
|
||||
@@ -340,6 +340,7 @@ pub async fn resolve_qobuz_playlist(
|
||||
/// Routes the `plex:`-prefixed keys to the Plex cache; everything else is a
|
||||
/// true local album resolved against the passed `&LibraryDatabase`. Both
|
||||
/// branches are synchronous — no `&LibraryDatabase` is held across an `.await`.
|
||||
#[cfg(feature = "local")]
|
||||
pub fn resolve_local_album(
|
||||
db: &qbz_library::LibraryDatabase,
|
||||
group_key: &str,
|
||||
@@ -348,13 +349,23 @@ pub fn resolve_local_album(
|
||||
// rows live in the Plex cache DB (plex_cache_tracks), not local_tracks,
|
||||
// so route them to the Plex cache fetcher instead of db.get_album_tracks.
|
||||
if group_key.starts_with("plex:") {
|
||||
#[cfg(feature = "plex")]
|
||||
return resolve_plex_album_tracks(group_key);
|
||||
// Refused rather than looked up in local_tracks: a Plex key is not a
|
||||
// local album group, and resolving it there would silently return the
|
||||
// wrong tracks or none at all.
|
||||
#[cfg(not(feature = "plex"))]
|
||||
return Err(format!(
|
||||
"plex album {} cannot be resolved: this build has no Plex support",
|
||||
group_key
|
||||
));
|
||||
}
|
||||
|
||||
resolve_local_album_tracks(db, group_key)
|
||||
}
|
||||
|
||||
/// Resolve a true local album group (no `plex:` prefix) against the library DB.
|
||||
#[cfg(feature = "local")]
|
||||
pub fn resolve_local_album_tracks(
|
||||
db: &qbz_library::LibraryDatabase,
|
||||
group_key: &str,
|
||||
@@ -371,6 +382,7 @@ pub fn resolve_local_album_tracks(
|
||||
}
|
||||
|
||||
/// Resolve a `plex:`-prefixed album key against the shared Plex cache.
|
||||
#[cfg(feature = "plex")]
|
||||
pub fn resolve_plex_album_tracks(group_key: &str) -> Result<Vec<CoreQueueTrack>, String> {
|
||||
let tracks = qbz_plex::plex_cache_get_album_tracks(group_key.to_string())
|
||||
.map_err(|e| format!("plex cache get_album_tracks({}) failed: {}", group_key, e))?;
|
||||
@@ -388,6 +400,7 @@ pub fn resolve_plex_album_tracks(group_key: &str) -> Result<Vec<CoreQueueTrack>,
|
||||
|
||||
// ── Local track (synchronous) ──
|
||||
|
||||
#[cfg(feature = "local")]
|
||||
pub fn resolve_local_track(
|
||||
db: &qbz_library::LibraryDatabase,
|
||||
track_id: i64,
|
||||
@@ -412,6 +425,7 @@ pub fn resolve_local_track(
|
||||
/// - Album → [`resolve_local_album`] (handles the `plex:` prefix internally)
|
||||
/// - Track → parse `source_item_id` to `i64` (`invalid local track id`) → [`resolve_local_track`]
|
||||
/// - Playlist → hard error `local playlists not supported in this release`
|
||||
#[cfg(feature = "local")]
|
||||
pub fn resolve_local_item(
|
||||
db: &qbz_library::LibraryDatabase,
|
||||
item: &MixtapeCollectionItem,
|
||||
@@ -492,6 +506,7 @@ pub fn track_to_queue_track_from_api(track: &ApiTrack) -> CoreQueueTrack {
|
||||
/// (`v2_plex_play_track` with `ratingKey: String(track.id)`) resolves to the
|
||||
/// same Plex object. source="plex" lets playback route to the Plex branch
|
||||
/// instead of the local-file branch.
|
||||
#[cfg(feature = "plex")]
|
||||
pub fn plex_cached_track_to_queue_track(track: &qbz_plex::PlexCachedTrack) -> CoreQueueTrack {
|
||||
let id: u64 = track.rating_key.parse().unwrap_or(track.id);
|
||||
let sample_rate_khz = if track.sample_rate > 0 {
|
||||
@@ -529,6 +544,7 @@ pub fn plex_cached_track_to_queue_track(track: &qbz_plex::PlexCachedTrack) -> Co
|
||||
/// Map a `LocalTrack` to a `CoreQueueTrack`.
|
||||
/// `is_local = true`, `source = "local"`, `sample_rate` is converted from Hz
|
||||
/// to kHz to match the Qobuz convention used elsewhere in the queue display.
|
||||
#[cfg(feature = "local")]
|
||||
pub fn local_track_to_queue_track(track: &qbz_library::LocalTrack) -> CoreQueueTrack {
|
||||
// Artwork: local tracks store a file path; expose it as a `file://` URL
|
||||
// so the frontend's <img> can load it. Falls back to None when absent.
|
||||
@@ -641,6 +657,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "local")]
|
||||
#[test]
|
||||
fn resolve_local_item_playlist_is_unsupported() {
|
||||
// The (Playlist, Local) hard error is a load-bearing contract (spec §5.4/§10);
|
||||
@@ -651,6 +668,7 @@ mod tests {
|
||||
assert_eq!(err, "local playlists not supported in this release");
|
||||
}
|
||||
|
||||
#[cfg(feature = "local")]
|
||||
#[test]
|
||||
fn resolve_local_item_track_rejects_non_numeric_id() {
|
||||
let db = qbz_library::LibraryDatabase::open(std::path::Path::new(":memory:")).unwrap();
|
||||
|
||||
Reference in new issue
Block a user