aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-09-06 17:19:36 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-09-06 17:20:18 +0200
commitfd016cfb13266dc67f0ce5982e6d4965d8fbd273 (patch)
treeec7f964fb42906ef5dfce858548e17aef36f47fa /Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
parentf898c35b9668318c1200bc6bc1659ffed47ee798 (diff)
Share played state across alternate versions
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs30
1 files changed, 20 insertions, 10 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
index d635b38df5..a745c3309f 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
@@ -38,22 +38,32 @@ public sealed partial class BaseItemRepository
// 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;
+ // Folders (Series, Seasons, BoxSets, albums, ...) carry no played state of their own and count
+ // as played once no descendant is left unplayed.
+ var unplayedLeafItems = GetAccessFilteredLeafItemsQuery(context, user)
+ .Where(BuildLeafIsPlayedFilter(context, user.Id).Not());
+
+ return IsFolderFilter.And(BuildHasDescendantFilter(context, unplayedLeafItems).Not())
+ .Or(IsFolderFilter.Not().And(BuildLeafIsPlayedFilter(context, user.Id)));
+ }
- // Leaf items carry their own played state.
+ private static Expression<Func<BaseItemEntity, bool>> BuildLeafIsPlayedFilter(JellyfinDbContext context, Guid userId)
+ {
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));
+ // The primaries of every version group holding a played row, whichever version carries it.
+ var playedGroupIds = context.BaseItems
+ .Where(v => v.PrimaryVersionId != null
+ && context.UserData.Any(ud => ud.UserId == userId
+ && ud.Played
+ && (ud.ItemId == v.Id || ud.ItemId == v.PrimaryVersionId)))
+ .Select(v => v.PrimaryVersionId!.Value);
- return IsFolderFilter.And(BuildHasDescendantFilter(context, unplayedLeafItems).Not())
- .Or(IsFolderFilter.Not().And(e => playedItemIds.Contains(e.Id)));
+ return e => playedItemIds.Contains(e.Id)
+ || playedGroupIds.Contains(e.Id)
+ || (e.PrimaryVersionId != null && playedGroupIds.Contains(e.PrimaryVersionId.Value));
}
// "und" is the language filters' stand-in for a track that declares no language at all.