diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-03-13 22:46:42 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-03-13 22:46:42 +0100 |
| commit | 27d54c5b1c96a9b81daa772ad18207204e9ce00c (patch) | |
| tree | 5e3566091549cb79f5d741d38de8b9dd52585279 /Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs | |
| parent | d218303b934bba879ce3c0521b2c761b68cb7833 (diff) | |
Fix IsResumable and IsPlayed filter
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs | 76 |
1 files changed, 61 insertions, 15 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs index f3c9f2adbd..f7f48278db 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs @@ -446,20 +446,29 @@ public sealed partial class BaseItemRepository // We should probably figure this out for all folders, but for right now, this is the only place where we need it if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series) { - // Get played series IDs by joining episodes to UserData via SeriesId (Guid foreign key). - // Don't filter episodes by TopParentIds here - the series will be filtered by baseQuery anyway. - // This allows the materialized list to be reused across library-scoped queries. - var playedSeriesIdList = context.BaseItems + var userId = filter.User!.Id; + var seriesWithEpisodes = context.BaseItems .Where(e => !e.IsFolder && !e.IsVirtualItem && e.SeriesId.HasValue) - .Join( - context.UserData.Where(ud => ud.UserId == filter.User!.Id && ud.Played), - episode => episode.Id, - ud => ud.ItemId, - (episode, ud) => episode.SeriesId!.Value) + .Select(e => e.SeriesId!.Value) + .Distinct(); + + var seriesWithUnplayedEpisodes = context.BaseItems + .Where(e => !e.IsFolder && !e.IsVirtualItem && e.SeriesId.HasValue + && !e.UserData!.Any(ud => ud.UserId == userId && ud.Played)) + .Select(e => e.SeriesId!.Value) .Distinct(); var isPlayed = filter.IsPlayed.Value; - baseQuery = baseQuery.Where(s => playedSeriesIdList.Contains(s.Id) == isPlayed); + if (isPlayed) + { + baseQuery = baseQuery.Where(s => + seriesWithEpisodes.Contains(s.Id) && !seriesWithUnplayedEpisodes.Contains(s.Id)); + } + else + { + baseQuery = baseQuery.Where(s => + !seriesWithEpisodes.Contains(s.Id) || seriesWithUnplayedEpisodes.Contains(s.Id)); + } } else if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.BoxSet) { @@ -484,11 +493,48 @@ public sealed partial class BaseItemRepository if (filter.IsResumable.HasValue) { - var resumableItemIds = context.UserData - .Where(ud => ud.UserId == filter.User!.Id && ud.PlaybackPositionTicks > 0) - .Select(ud => ud.ItemId); - var isResumable = filter.IsResumable.Value; - baseQuery = baseQuery.Where(e => resumableItemIds.Contains(e.Id) == isResumable); + if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series) + { + var userId = filter.User!.Id; + + // Series with at least one in-progress episode. + var seriesWithInProgressEpisodes = context.BaseItems + .Where(e => !e.IsFolder && !e.IsVirtualItem && e.SeriesId.HasValue + && e.UserData!.Any(ud => ud.UserId == userId && ud.PlaybackPositionTicks > 0)) + .Select(e => e.SeriesId!.Value) + .Distinct(); + + // Series with at least one played episode. + var seriesWithPlayedEpisodes = context.BaseItems + .Where(e => !e.IsFolder && !e.IsVirtualItem && e.SeriesId.HasValue + && e.UserData!.Any(ud => ud.UserId == userId && ud.Played)) + .Select(e => e.SeriesId!.Value) + .Distinct(); + + // Series with at least one unplayed episode. + var seriesWithUnplayedEpisodes = context.BaseItems + .Where(e => !e.IsFolder && !e.IsVirtualItem && e.SeriesId.HasValue + && !e.UserData!.Any(ud => ud.UserId == userId && ud.Played)) + .Select(e => e.SeriesId!.Value) + .Distinct(); + + var isResumable = filter.IsResumable.Value; + + // A series is resumable if it has an in-progress episode, + // or if it has both played and unplayed episodes (partially watched). + baseQuery = baseQuery.Where(s => + (seriesWithInProgressEpisodes.Contains(s.Id) + || (seriesWithPlayedEpisodes.Contains(s.Id) && seriesWithUnplayedEpisodes.Contains(s.Id))) + == isResumable); + } + else + { + var resumableItemIds = context.UserData + .Where(ud => ud.UserId == filter.User!.Id && ud.PlaybackPositionTicks > 0) + .Select(ud => ud.ItemId); + var isResumable = filter.IsResumable.Value; + baseQuery = baseQuery.Where(e => resumableItemIds.Contains(e.Id) == isResumable); + } } if (filter.ArtistIds.Length > 0) |
