diff options
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs | 150 |
1 files changed, 115 insertions, 35 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs index e4fd3204e1..8e917f6951 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.EntityFrameworkCore; using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem; @@ -81,6 +83,40 @@ public sealed partial class BaseItemRepository _itemTypeLookup.MusicGenreTypes); } + /// <inheritdoc /> + public IReadOnlyList<string> GetMediaStreamLanguages(InternalItemsQuery filter, MediaStreamType mediaStreamType) + { + ArgumentNullException.ThrowIfNull(filter); + + using var context = _dbProvider.CreateDbContext(); + + return TranslateQuery( + context.BaseItems.Include(e => e.MediaStreams).Where(e => e.Id != EF.Constant(PlaceholderId)), + context, + new InternalItemsQuery(filter.User) + { + IncludeOwnedItems = filter.IncludeOwnedItems, + ExcludeItemTypes = filter.ExcludeItemTypes, + IncludeItemTypes = filter.IncludeItemTypes, + MediaTypes = filter.MediaTypes, + AncestorIds = filter.AncestorIds, + ItemIds = filter.ItemIds, + TopParentIds = filter.TopParentIds, + ParentId = filter.ParentId, + IsAiring = filter.IsAiring, + IsMovie = filter.IsMovie, + IsSports = filter.IsSports, + IsKids = filter.IsKids, + IsNews = filter.IsNews, + IsSeries = filter.IsSeries + }) + .SelectMany(e => e.MediaStreams!) + .Where(e => e.StreamType == (MediaStreamTypeEntity)mediaStreamType) + .Select(s => string.IsNullOrEmpty(s.Language) ? "und" : s.Language) // und = undetermined + .Distinct() + .ToArray(); + } + private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes) { using var context = _dbProvider.CreateDbContext(); @@ -132,21 +168,16 @@ public sealed partial class BaseItemRepository IsSeries = filter.IsSeries }); - // Keep this as an IQueryable sub-select. Materializing to a list would inline one - // bound parameter per CleanValue and hit SQLite's variable cap on libraries with - // high-cardinality value types (e.g. tens of thousands of artists). - var matchingCleanValues = context.ItemValuesMap - .Where(ivm => itemValueTypes.Contains(ivm.ItemValue.Type)) - .Join( - innerQueryFilter, - ivm => ivm.ItemId, - g => g.Id, - (ivm, g) => ivm.ItemValue.CleanValue) - .Distinct(); - var innerQuery = PrepareItemQuery(context, filter) .Where(e => e.Type == returnType) - .Where(e => matchingCleanValues.Contains(e.CleanName!)); + .Where(e => context.ItemValuesMap + .Where(ivm => itemValueTypes.Contains(ivm.ItemValue.Type) && ivm.ItemValue.CleanValue == e.CleanName) + .Join( + innerQueryFilter, + ivm => ivm.ItemId, + g => g.Id, + (ivm, g) => ivm.ItemId) + .Any()); var outerQueryFilter = new InternalItemsQuery(filter.User) { @@ -169,22 +200,42 @@ public sealed partial class BaseItemRepository ExcludeItemIds = filter.ExcludeItemIds }; - // Collapse rows that share a PresentationUniqueKey (e.g. alternate versions) by picking - // the lowest Id per group. Keep as an IQueryable sub-select so paging is applied AFTER - // ApplyOrder runs the caller's actual sort. + // Collapse rows that share a PresentationUniqueKey (e.g. alternate versions) into one + // representative id per group, then materialize the representative ids once. var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter); - var representativeIds = masterQuery - .GroupBy(e => e.PresentationUniqueKey) - .Select(g => g.Min(e => e.Id)); + var isMusicArtist = returnType == _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]; + List<Guid> representativeIds; + if (isMusicArtist) + { + // For MusicArtist, prefer the entity from a library the user can actually access. + // Materialize to prevent correlated per-group first-row queries which hurt performance. + var topParentIds = filter.TopParentIds; + representativeIds = masterQuery + .Select(e => new { e.Id, e.PresentationUniqueKey, e.TopParentId }) + .AsEnumerable() + .GroupBy(e => e.PresentationUniqueKey) + .Select(g => g + .OrderBy(e => topParentIds.Contains(e.TopParentId ?? Guid.Empty) ? 0 : 1) + .ThenBy(e => e.Id) + .First().Id) + .ToList(); + } + else + { + representativeIds = masterQuery + .GroupBy(e => e.PresentationUniqueKey) + .Select(g => g.Min(e => e.Id)) + .ToList(); + } var result = new QueryResult<(BaseItemDto, ItemCounts?)>(); if (filter.EnableTotalRecordCount) { - result.TotalRecordCount = representativeIds.Count(); + result.TotalRecordCount = representativeIds.Count; } var query = ApplyNavigations( - context.BaseItems.AsNoTracking().AsSingleQuery().Where(e => representativeIds.Contains(e.Id)), + context.BaseItems.AsNoTracking().AsSingleQuery().WhereOneOrMany(representativeIds, e => e.Id), filter); query = ApplyOrder(query, filter, context); @@ -265,8 +316,8 @@ public sealed partial class BaseItemRepository var itemIds = itemCountQuery.Select(e => e.Id); // Rewrite query to avoid SelectMany on navigation properties (which requires SQL APPLY, not supported on SQLite) - // Instead, start from ItemValueMaps and join with BaseItems - return context.ItemValuesMap + // Instead, start from ItemValueMaps and join with BaseItems. + var rawCounts = context.ItemValuesMap .Where(ivm => itemValueTypes.Contains(ivm.ItemValue.Type)) .Where(ivm => itemIds.Contains(ivm.ItemId)) .Join( @@ -276,18 +327,47 @@ public sealed partial class BaseItemRepository (ivm, e) => new { CleanName = ivm.ItemValue.CleanValue, e.Type }) .GroupBy(x => new { x.CleanName, x.Type }) .Select(g => new { g.Key.CleanName, g.Key.Type, Count = g.Count() }) - .GroupBy(x => x.CleanName) - .ToDictionary( - g => g.Key, - g => new ItemCounts + .AsEnumerable(); + + var countsByCleanName = new Dictionary<string, ItemCounts>(); + foreach (var group in rawCounts.GroupBy(x => x.CleanName)) + { + var counts = new ItemCounts(); + foreach (var row in group) + { + if (row.Type == seriesTypeName) + { + counts.SeriesCount += row.Count; + } + else if (row.Type == episodeTypeName) + { + counts.EpisodeCount += row.Count; + } + else if (row.Type == movieTypeName) { - SeriesCount = g.Where(x => x.Type == seriesTypeName).Sum(x => x.Count), - EpisodeCount = g.Where(x => x.Type == episodeTypeName).Sum(x => x.Count), - MovieCount = g.Where(x => x.Type == movieTypeName).Sum(x => x.Count), - AlbumCount = g.Where(x => x.Type == musicAlbumTypeName).Sum(x => x.Count), - ArtistCount = g.Where(x => x.Type == musicArtistTypeName).Sum(x => x.Count), - SongCount = g.Where(x => x.Type == audioTypeName).Sum(x => x.Count), - TrailerCount = g.Where(x => x.Type == trailerTypeName).Sum(x => x.Count), - }); + counts.MovieCount += row.Count; + } + else if (row.Type == musicAlbumTypeName) + { + counts.AlbumCount += row.Count; + } + else if (row.Type == musicArtistTypeName) + { + counts.ArtistCount += row.Count; + } + else if (row.Type == audioTypeName) + { + counts.SongCount += row.Count; + } + else if (row.Type == trailerTypeName) + { + counts.TrailerCount += row.Count; + } + } + + countsByCleanName[group.Key] = counts; + } + + return countsByCleanName; } } |
