diff --git a/crates/qbz-core/src/core.rs b/crates/qbz-core/src/core.rs index f7b2337c7..c50e2fe8c 100644 --- a/crates/qbz-core/src/core.rs +++ b/crates/qbz-core/src/core.rs @@ -1419,6 +1419,22 @@ impl QbzCore { .map_err(CoreError::Api) } + /// Lightweight membership fetch: one playlist's track IDS only + /// (`playlist/get?extra=track_ids`). The membership hydrator's probe — + /// no Track objects, no pagination. + pub async fn get_playlist_track_ids( + &self, + playlist_id: u64, + ) -> Result { + let client = self.client.read().await; + let client = client.as_ref().ok_or(CoreError::NotInitialized)?; + + client + .get_playlist_track_ids(playlist_id) + .await + .map_err(CoreError::Api) + } + /// Check how many of `track_ids` are already in the Qobuz playlist /// `playlist_id`. Mirrors Tauri's `v2_check_playlist_duplicates` /// (commands_v2/playlists.rs): fetch the playlist's existing track ids and diff --git a/crates/qbz-library/src/playlist_membership.rs b/crates/qbz-library/src/playlist_membership.rs index 831821f27..52784ae83 100644 --- a/crates/qbz-library/src/playlist_membership.rs +++ b/crates/qbz-library/src/playlist_membership.rs @@ -34,10 +34,7 @@ pub enum PlaylistTrackRef { /// A library-projected row: a local file, or a Jellyfin/Subsonic track /// (their playlist storage identity is the projection). `path` is the /// `local_tracks.file_path` value local playlists key on. - Library { - track_id: i64, - path: Option, - }, + Library { track_id: i64, path: Option }, /// A Plex track by rating key. Plex { rating_key: String }, } @@ -467,7 +464,10 @@ mod tests { // The Qobuz playlist holds the library row and the Plex key (2 of 3); // the local playlist holds all three. assert_eq!(contained(&rows, ContainmentTarget::Qobuz(7)), Some(2)); - assert_eq!(contained(&rows, ContainmentTarget::Local(lp.clone())), Some(3)); + assert_eq!( + contained(&rows, ContainmentTarget::Local(lp.clone())), + Some(3) + ); assert_eq!(rows[0].target, ContainmentTarget::Local(lp)); } diff --git a/crates/qbz-library/src/qobuz_playlist_snapshot.rs b/crates/qbz-library/src/qobuz_playlist_snapshot.rs index b46d00bfd..feb103a3a 100644 --- a/crates/qbz-library/src/qobuz_playlist_snapshot.rs +++ b/crates/qbz-library/src/qobuz_playlist_snapshot.rs @@ -215,10 +215,7 @@ pub fn current_generation(conn: &Connection) -> Result { /// An EMPTY list is still recorded (a user can genuinely delete every /// playlist), but the caller must only invoke this for a fetch that /// succeeded — a network failure must never masquerade as an empty library. -pub fn record_authoritative_list( - conn: &Connection, - entries: &[AuthoritativeEntry], -) -> Result { +pub fn record_authoritative_list(conn: &Connection, entries: &[AuthoritativeEntry]) -> Result { let ts = now_ms(); conn.execute_batch("BEGIN IMMEDIATE")?; let result = (|| -> Result { @@ -620,9 +617,7 @@ pub fn track_ids(conn: &Connection, qobuz_playlist_id: u64) -> Result> WHERE qobuz_playlist_id = ?1 ORDER BY position", )?; let mut out = Vec::new(); - for r in stmt.query_map(params![qobuz_playlist_id as i64], |r| { - r.get::<_, i64>(0) - })? { + for r in stmt.query_map(params![qobuz_playlist_id as i64], |r| r.get::<_, i64>(0))? { out.push(r? as u64); } Ok(out) diff --git a/crates/qbz-models/src/types.rs b/crates/qbz-models/src/types.rs index 83b7d23a3..8d890761d 100644 --- a/crates/qbz-models/src/types.rs +++ b/crates/qbz-models/src/types.rs @@ -828,6 +828,10 @@ pub struct Playlist { pub image_rectangle_mini: Option>, pub slug: Option, pub users_count: Option, + /// API revision stamp (unix seconds). The membership index reads it as + /// change evidence when the API provides it; absent on some list shapes. + #[serde(default)] + pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -869,6 +873,9 @@ pub struct PlaylistWithTrackIds { pub images300: Option>, pub slug: Option, pub users_count: Option, + /// API revision stamp (unix seconds), when the response carries one. + #[serde(default)] + pub updated_at: Option, } /// Result of checking for duplicate tracks in a playlist