diff options
| author | Piotr Niełacny <piotr.nielacny@gmail.com> | 2026-08-25 15:19:38 +0200 |
|---|---|---|
| committer | Piotr Niełacny <piotr.nielacny@gmail.com> | 2026-08-25 15:19:38 +0200 |
| commit | 5e621d0e3f2102177210f162e72a5d73378063fb (patch) | |
| tree | 0dc45a48ce637dde27d1c118d798eeae8ea092ca /Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs | |
| parent | 422b2bb3d9193bfe0813d3d654919151eb355d82 (diff) | |
Order IsPlayed and IsUnplayed by the played state the filter reports
Ordering mapped both keys to the item's own stored UserData row. Folders do not
have one: a series, season or box set counts as played when no descendant is
left unplayed, which is what the isPlayed filter and the DTO both report. A
mixed library therefore sorted every series and box set into the unplayed group,
and a query could filter and sort by two different notions of "played".
Extract the filter's predicate into BuildIsPlayedFilter and route both sort keys
through it so the two cannot drift apart again.
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs | 38 |
1 files changed, 22 insertions, 16 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs index 623c1ea0ab..1e30f0164e 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs @@ -35,6 +35,27 @@ public sealed partial class BaseItemRepository // instance across several lambdas, and this filter is combined into a tree more than once. private static Expression<Func<BaseItemEntity, bool>> IsFolderFilter => e => e.IsFolder; + // Shared by the isPlayed filter and the IsPlayed/IsUnplayed ordering so the two cannot disagree. + private Expression<Func<BaseItemEntity, bool>> BuildIsPlayedFilter(JellyfinDbContext context, User user) + { + var userId = user.Id; + + // Leaf items carry their own played state. + var playedItemIds = context.UserData + .Where(ud => ud.UserId == userId && ud.Played) + .Select(ud => ud.ItemId); + + // Folders (Series, Seasons, BoxSets, albums, ...) have none and count as played once no + // descendant is left unplayed, matching what the DTO reports for them. This has to key off + // the item itself rather than off the requested item types: tag and collection listings mix + // folders and leaf items in a single query. + var unplayedLeafItems = GetAccessFilteredLeafItemsQuery(context, user) + .Where(e => !e.UserData!.Any(ud => ud.UserId == userId && ud.Played)); + + return IsFolderFilter.And(BuildHasDescendantFilter(context, unplayedLeafItems).Not()) + .Or(IsFolderFilter.Not().And(e => playedItemIds.Contains(e.Id))); + } + // "und" is the language filters' stand-in for a track that declares no language at all. private static string NormalizeLanguage(string language) => string.Equals(language, "und", StringComparison.OrdinalIgnoreCase) ? "und" : language; @@ -523,22 +544,7 @@ public sealed partial class BaseItemRepository if (filter.IsPlayed.HasValue) { - var userId = filter.User!.Id; - - // Leaf items carry their own played state. - var playedItemIds = context.UserData - .Where(ud => ud.UserId == userId && ud.Played) - .Select(ud => ud.ItemId); - - // Folders (Series, Seasons, BoxSets, albums, ...) have none and count as played once no - // descendant is left unplayed, matching what the DTO reports for them. This has to key off - // the item itself rather than off the requested item types: tag and collection listings mix - // folders and leaf items in a single query. - var unplayedLeafItems = GetAccessFilteredLeafItemsQuery(context, filter.User!) - .Where(e => !e.UserData!.Any(ud => ud.UserId == userId && ud.Played)); - - var isPlayedFilter = IsFolderFilter.And(BuildHasDescendantFilter(context, unplayedLeafItems).Not()) - .Or(IsFolderFilter.Not().And(e => playedItemIds.Contains(e.Id))); + var isPlayedFilter = BuildIsPlayedFilter(context, filter.User!); baseQuery = baseQuery.Where(filter.IsPlayed.Value ? isPlayedFilter : isPlayedFilter.Not()); } |
