aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs105
1 files changed, 87 insertions, 18 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
index 1ff8d8f863..c9e08b1b5d 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs
@@ -49,6 +49,7 @@ public sealed partial class BaseItemRepository
dbQuery = TranslateQuery(dbQuery, context, filter);
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
+ dbQuery = ApplyAdjacencyFilter(context, dbQuery, filter);
if (filter.EnableTotalRecordCount)
{
@@ -75,6 +76,7 @@ public sealed partial class BaseItemRepository
dbQuery = TranslateQuery(dbQuery, context, filter);
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
+ dbQuery = ApplyAdjacencyFilter(context, dbQuery, filter);
dbQuery = ApplyQueryPaging(dbQuery, filter);
var hasRandomSort = filter.OrderBy.Any(e => e.OrderBy == ItemSortBy.Random);
@@ -108,7 +110,7 @@ public sealed partial class BaseItemRepository
PrepareFilterQuery(filter);
// Early exit if collection type is not supported
- if (collectionType is not CollectionType.movies and not CollectionType.tvshows and not CollectionType.music)
+ if (collectionType is not CollectionType.movies and not CollectionType.tvshows and not CollectionType.music and not CollectionType.unknown)
{
return [];
}
@@ -119,30 +121,27 @@ public sealed partial class BaseItemRepository
var baseQuery = PrepareItemQuery(context, filter);
baseQuery = TranslateQuery(baseQuery, context, filter);
- if (collectionType == CollectionType.tvshows)
+ if (collectionType is CollectionType.tvshows)
{
return GetLatestTvShowItems(context, baseQuery, filter, limit);
}
if (collectionType is CollectionType.movies)
{
- // Pick, per PresentationUniqueKey, the newest item; return the newest `limit` of those.
- // Build up until limit by streaming through results and deduplicating on the fly.
- var orderedIds = baseQuery
- .Where(e => e.PresentationUniqueKey != null)
- .OrderByDescending(e => e.DateCreated)
- .ThenByDescending(e => e.Id)
- .Select(e => new { e.Id, e.PresentationUniqueKey });
-
- // DistinctBy and Take are lazy, so enumeration stops as soon as limit distinct keys are read.
- var firstIds = orderedIds
- .AsEnumerable()
- .DistinctBy(row => row.PresentationUniqueKey)
- .Select(row => row.Id)
+ return GetLatestMovieItems(context, baseQuery, filter, limit);
+ }
+
+ if (collectionType is CollectionType.unknown)
+ {
+ var moviesQuery = baseQuery.Where(e => e.SeriesName == null);
+ var latestMovies = GetLatestMovieItems(context, moviesQuery, filter, limit);
+ var latestShows = GetLatestTvShowItems(context, baseQuery, filter, limit);
+
+ return latestMovies.Concat(latestShows)
+ .OrderByDescending(dto => dto.DateCreated)
+ .ThenByDescending(dto => dto.Id)
.Take(limit ?? int.MaxValue)
.ToList();
-
- return LoadLatestByIds(context, firstIds, filter);
}
var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum]!;
@@ -167,7 +166,10 @@ public sealed partial class BaseItemRepository
.Where(album => albumIdsWithMatchingTrack.Contains(album.Id));
}
- var orderedAlbums = topAlbumsQuery
+ // The album is what gets returned, and neither branch above reads it through the
+ // user's filters, so its own parental restrictions have to be applied here: a
+ // matching track does not make an album the user may not see visible.
+ var orderedAlbums = ApplyParentalRestrictions(context, topAlbumsQuery, filter)
.OrderByDescending(album => album.DateCreated)
.ThenByDescending(album => album.Id);
@@ -221,6 +223,39 @@ public sealed partial class BaseItemRepository
}
/// <summary>
+ /// Gets the latest movies, deduplicated so each movie only appears once.
+ /// </summary>
+ /// <param name="context">The database context.</param>
+ /// <param name="baseQuery">The query to pull movies from, with filters already applied.</param>
+ /// <param name="filter">The original query filter, used when loading the final items.</param>
+ /// <param name="limit">How many items to return.</param>
+ /// <returns>The latest movies, newest first.</returns>
+ private IReadOnlyList<BaseItemDto> GetLatestMovieItems(
+ JellyfinDbContext context,
+ IQueryable<BaseItemEntity> baseQuery,
+ InternalItemsQuery filter,
+ int? limit)
+ {
+ // Pick, per PresentationUniqueKey, the newest item; return the newest `limit` of those.
+ // Build up until limit by streaming through results and deduplicating on the fly.
+ var orderedIds = baseQuery
+ .Where(e => e.PresentationUniqueKey != null)
+ .OrderByDescending(e => e.DateCreated)
+ .ThenByDescending(e => e.Id)
+ .Select(e => new { e.Id, e.PresentationUniqueKey });
+
+ // DistinctBy and Take are lazy, so enumeration stops as soon as limit distinct keys are read.
+ var firstIds = orderedIds
+ .AsEnumerable()
+ .DistinctBy(row => row.PresentationUniqueKey)
+ .Select(row => row.Id)
+ .Take(limit ?? int.MaxValue)
+ .ToList();
+
+ return LoadLatestByIds(context, firstIds, filter);
+ }
+
+ /// <summary>
/// Gets the latest TV show items with smart Season/Series container selection.
/// </summary>
/// <remarks>
@@ -420,6 +455,40 @@ public sealed partial class BaseItemRepository
seriesResults.Add((seasonId, seriesId, maxDate, mostRecentEpisodeId));
}
+ // Step 5b: A container is what gets returned, so it has to pass the user's access
+ // filters on its own - a matching episode does not make a Season or Series the user
+ // may not see visible. Containers that don't pass are replaced by their episode.
+ if (RequiresParentalRestrictions(filter) && entitiesToFetch.Count > 0)
+ {
+ var allowedContainerIds = ApplyParentalRestrictions(
+ context,
+ context.BaseItems.AsNoTracking().Where(e => entitiesToFetch.Contains(e.Id)),
+ filter)
+ .Select(e => e.Id)
+ .ToHashSet();
+
+ for (var i = 0; i < seriesResults.Count; i++)
+ {
+ var (seasonId, seriesId, maxDate, mostRecentEpisodeId) = seriesResults[i];
+ if (seasonId.HasValue && !allowedContainerIds.Contains(seasonId.Value))
+ {
+ seasonId = null;
+ }
+
+ if (seriesId.HasValue && !allowedContainerIds.Contains(seriesId.Value))
+ {
+ seriesId = null;
+ }
+
+ if (seasonId is null && seriesId is null)
+ {
+ entitiesToFetch.Add(mostRecentEpisodeId);
+ }
+
+ seriesResults[i] = (seasonId, seriesId, maxDate, mostRecentEpisodeId);
+ }
+ }
+
// Step 6: Fetch the Season/Series entities we decided to return
var entities = entitiesToFetch.Count > 0
? ApplyNavigations(