fix(catalog): show a new album once in Recently Added, not once per track
Recently Added listed every newly-added track individually, so importing a 14-track album filled the whole row with that one album and buried everything else. Both code paths that build the row had the same symptom from separate causes: - Online: Jellyfin's /Items/Latest defaults to GroupItems=false, returning each new leaf on its own. Send GroupItems=true so the server collapses children into the container that was added. - Offline: the downloaded-items CTE deliberately matches leaves *and* their container (right for browsing, wrong here), so a downloaded album returned the album plus each of its tracks. Drop a leaf only when its own container is in the same result. Items with no container (movies, standalone tracks) are unaffected in both paths. The online URL is extracted into build_latest_items_endpoint so it can be asserted without an HTTP server, matching build_favorites_endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -670,6 +670,26 @@ fn build_get_items_endpoint(
|
||||
endpoint
|
||||
}
|
||||
|
||||
/// Build the Jellyfin endpoint for a "recently added" listing.
|
||||
///
|
||||
/// `GroupItems=true` is the load-bearing parameter: Jellyfin defaults it to
|
||||
/// `false`, which returns each newly-added *leaf* separately, so importing one
|
||||
/// 14-track album pushed 14 rows into "recently added" and buried everything
|
||||
/// else. With grouping on, the server collapses children into the container
|
||||
/// that was added — an album appears once, while movies (which have no such
|
||||
/// container) are unaffected.
|
||||
///
|
||||
/// Pulled out of `get_latest_items` so the query can be asserted without an
|
||||
/// HTTP server, matching `build_favorites_endpoint`.
|
||||
fn build_latest_items_endpoint(user_id: &str, parent_id: &str, limit: Option<usize>) -> String {
|
||||
format!(
|
||||
"/Users/{}/Items/Latest?ParentId={}&Limit={}&GroupItems=true&Fields=BackdropImageTags,ParentBackdropImageTags,UserData",
|
||||
user_id,
|
||||
parent_id,
|
||||
limit.unwrap_or(16)
|
||||
)
|
||||
}
|
||||
|
||||
/// Build the Jellyfin endpoint for a favourites listing.
|
||||
///
|
||||
/// Pulled out of `get_favorites` so the query can be asserted without an HTTP
|
||||
@@ -908,11 +928,7 @@ impl MediaRepository for OnlineRepository {
|
||||
parent_id: &str,
|
||||
limit: Option<usize>,
|
||||
) -> Result<Vec<MediaItem>, RepoError> {
|
||||
let limit_str = limit.unwrap_or(16);
|
||||
let endpoint = format!(
|
||||
"/Users/{}/Items/Latest?ParentId={}&Limit={}&Fields=BackdropImageTags,ParentBackdropImageTags,UserData",
|
||||
self.user_id, parent_id, limit_str
|
||||
);
|
||||
let endpoint = build_latest_items_endpoint(&self.user_id, parent_id, limit);
|
||||
|
||||
let items: Vec<JellyfinItem> = self.get_json(&endpoint).await?;
|
||||
Ok(items
|
||||
@@ -2706,6 +2722,25 @@ mod tests {
|
||||
assert!(!off.contains("Filters=IsFavorite"));
|
||||
}
|
||||
|
||||
/// A newly-added album must arrive as one entry, not one per track.
|
||||
///
|
||||
/// Jellyfin's `/Items/Latest` defaults to `GroupItems=false`, which returns
|
||||
/// every new Audio track individually — so ripping a 14-track album filled
|
||||
/// the whole "recently added" row with that one album. `GroupItems=true`
|
||||
/// makes the server collapse children into their parent container.
|
||||
#[test]
|
||||
fn test_latest_items_endpoint_groups_children_into_containers() {
|
||||
let endpoint = build_latest_items_endpoint("u1", "lib-1", Some(16));
|
||||
|
||||
assert!(
|
||||
endpoint.contains("GroupItems=true"),
|
||||
"latest items must be grouped so an album counts once, got: {}",
|
||||
endpoint
|
||||
);
|
||||
assert!(endpoint.contains("ParentId=lib-1"));
|
||||
assert!(endpoint.contains("Limit=16"));
|
||||
}
|
||||
|
||||
/// UT-099 — a Jellyfin item's `UserData` reaches `MediaItem.user_data`.
|
||||
///
|
||||
/// Before DR-113 this mapping was hardcoded to `None`, so nothing outside
|
||||
|
||||
Reference in New Issue
Block a user