fix(library): podcasts list newest episode first
A Jellypod podcast listed its episodes alphabetically. The store pinned SortBy=SortName onto every drill-down, which overrode the order the channel plugin returns — and since Jellypod prefixes played episodes with "[Played]", the name sort also clumped every heard episode at the top. Which order a container's children take is domain knowledge, so it moves to Rust: the caller names the container (GetItemsOptions.parentKind) and default_listing_sort answers with the sort. A channel folder is PremiereDate descending, every other container keeps SortName ascending, and a caller naming no container still gets no SortBy, so the paths that rely on the server's own order keep it. An explicit sort always wins. ChannelFolderItem with is_folder now maps to MediaKind::ChannelFolder instead of collapsing into Folder — while both were Folder there was nothing to key the rule on. The offline leg of the cache/server race applies the same order, so the cached list no longer flashes in name order before the server's arrives. TRACES: UR-007 | DR-257 | UT-229, UT-230, UT-231
This commit is contained in:
@@ -72,7 +72,7 @@ pub fn kind_from_jellyfin(item_type: &str, is_folder: bool) -> MediaKind {
|
||||
// channel leaf (distinct kind so the UI can route it to playback).
|
||||
"ChannelFolderItem" => {
|
||||
if is_folder {
|
||||
MediaKind::Folder
|
||||
MediaKind::ChannelFolder
|
||||
} else {
|
||||
MediaKind::ChannelItem
|
||||
}
|
||||
@@ -142,11 +142,18 @@ mod tests {
|
||||
assert_eq!(kind_from_jellyfin("BoxSet", true), MediaKind::Folder);
|
||||
}
|
||||
|
||||
/// A channel container is not an ordinary folder. Jellyfin gives both the
|
||||
/// same item type, but only the channel one holds plugin content whose
|
||||
/// natural order is by release date — a podcast, for instance. Collapsing
|
||||
/// it into `Folder` left the repository with no way to tell the two apart,
|
||||
/// so every podcast listed alphabetically.
|
||||
///
|
||||
/// TRACES: UR-007 | DR-257 | UT-230
|
||||
#[test]
|
||||
fn channel_folder_item_disambiguates_on_is_folder() {
|
||||
assert_eq!(
|
||||
kind_from_jellyfin("ChannelFolderItem", true),
|
||||
MediaKind::Folder
|
||||
MediaKind::ChannelFolder
|
||||
);
|
||||
assert_eq!(
|
||||
kind_from_jellyfin("ChannelFolderItem", false),
|
||||
|
||||
@@ -48,6 +48,14 @@ pub enum MediaKind {
|
||||
/// seekable, unlike `LiveChannel`. Distinct from `Channel` (the container)
|
||||
/// and from `Other` so the UI can route it to playback.
|
||||
ChannelItem,
|
||||
/// A *container* inside a channel — a Jellyfin `ChannelFolderItem` that is
|
||||
/// itself a folder, e.g. one podcast within a podcast channel. Distinct
|
||||
/// from `Folder` because its children are plugin content with an order of
|
||||
/// their own (newest episode first), which a folder's name order silently
|
||||
/// overrode.
|
||||
///
|
||||
/// TRACES: UR-007 | DR-257
|
||||
ChannelFolder,
|
||||
/// A kind we do not model explicitly. Reached only for provider item types
|
||||
/// that map to nothing meaningful; consumers treat it like an opaque
|
||||
/// container. The mapping must be *total* — it never panics — so this is the
|
||||
|
||||
@@ -53,7 +53,11 @@ fn kind_rank(kind: MediaKind) -> u8 {
|
||||
// Top-level containers a user is most likely to be looking for.
|
||||
MediaKind::Series | MediaKind::Movie | MediaKind::Album | MediaKind::Artist => 0,
|
||||
// Sub-containers and standalone collections.
|
||||
MediaKind::Season | MediaKind::Playlist | MediaKind::Channel | MediaKind::Folder => 1,
|
||||
MediaKind::Season
|
||||
| MediaKind::Playlist
|
||||
| MediaKind::Channel
|
||||
| MediaKind::ChannelFolder
|
||||
| MediaKind::Folder => 1,
|
||||
// Leaves — an episode/track is a match *inside* something bigger.
|
||||
MediaKind::Episode | MediaKind::Track | MediaKind::LiveChannel | MediaKind::ChannelItem => {
|
||||
2
|
||||
|
||||
Reference in New Issue
Block a user