diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-03-07 20:12:42 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-03-07 20:12:42 +0100 |
| commit | 077fa89717957f871b172ca4b2dc4a178efd3bc5 (patch) | |
| tree | 1c2be0089b3c33cda1ed96bde4b76a715a845df7 /Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs | |
| parent | 268f23f39ac18e783156b91b575ee6a105b6937c (diff) | |
Split BaseItemRepository and IItemRepository
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs new file mode 100644 index 0000000000..0a8f8627b4 --- /dev/null +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs @@ -0,0 +1,293 @@ +#pragma warning disable RS0030 // Do not use banned APIs + +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.EntityFrameworkCore; +using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem; + +namespace Jellyfin.Server.Implementations.Item; + +public sealed partial class BaseItemRepository +{ + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAllArtists(InternalItemsQuery filter) + { + return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetArtists(InternalItemsQuery filter) + { + return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAlbumArtists(InternalItemsQuery filter) + { + return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetStudios(InternalItemsQuery filter) + { + return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetGenres(InternalItemsQuery filter) + { + return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]); + } + + /// <inheritdoc /> + public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetMusicGenres(InternalItemsQuery filter) + { + return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetStudioNames() + { + return GetItemValueNames(_getStudiosValueTypes, [], []); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetAllArtistNames() + { + return GetItemValueNames(_getAllArtistsValueTypes, [], []); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetMusicGenreNames() + { + return GetItemValueNames( + _getGenreValueTypes, + _itemTypeLookup.MusicGenreTypes, + []); + } + + /// <inheritdoc /> + public IReadOnlyList<string> GetGenreNames() + { + return GetItemValueNames( + _getGenreValueTypes, + [], + _itemTypeLookup.MusicGenreTypes); + } + + private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes) + { + using var context = _dbProvider.CreateDbContext(); + + var query = context.ItemValuesMap + .AsNoTracking() + .Where(e => itemValueTypes.Any(w => w == e.ItemValue.Type)); + if (withItemTypes.Count > 0) + { + query = query.Where(e => withItemTypes.Contains(e.Item.Type)); + } + + if (excludeItemTypes.Count > 0) + { + query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type)); + } + + // query = query.DistinctBy(e => e.CleanValue); + return query.Select(e => e.ItemValue) + .GroupBy(e => e.CleanValue) + .Select(e => e.OrderBy(v => v.Value).First().Value) + .ToArray(); + } + + private QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyList<ItemValueType> itemValueTypes, string returnType) + { + ArgumentNullException.ThrowIfNull(filter); + + if (!filter.Limit.HasValue) + { + filter.EnableTotalRecordCount = false; + } + + using var context = _dbProvider.CreateDbContext(); + + var innerQueryFilter = TranslateQuery(context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)), context, new InternalItemsQuery(filter.User) + { + 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 + }); + var itemValuesQuery = context.ItemValuesMap + .Where(ivm => itemValueTypes.Contains(ivm.ItemValue.Type)) + .Join( + innerQueryFilter, + ivm => ivm.ItemId, + g => g.Id, + (ivm, g) => ivm.ItemValue.CleanValue); + + var innerQuery = PrepareItemQuery(context, filter) + .Where(e => e.Type == returnType) + .Where(e => itemValuesQuery.Contains(e.CleanName)); + + var outerQueryFilter = new InternalItemsQuery(filter.User) + { + IsPlayed = filter.IsPlayed, + IsFavorite = filter.IsFavorite, + IsFavoriteOrLiked = filter.IsFavoriteOrLiked, + IsLiked = filter.IsLiked, + IsLocked = filter.IsLocked, + NameLessThan = filter.NameLessThan, + NameStartsWith = filter.NameStartsWith, + NameStartsWithOrGreater = filter.NameStartsWithOrGreater, + Tags = filter.Tags, + OfficialRatings = filter.OfficialRatings, + StudioIds = filter.StudioIds, + GenreIds = filter.GenreIds, + Genres = filter.Genres, + Years = filter.Years, + NameContains = filter.NameContains, + SearchTerm = filter.SearchTerm, + ExcludeItemIds = filter.ExcludeItemIds + }; + + var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter) + .GroupBy(e => e.PresentationUniqueKey) + .Select(e => e.OrderBy(x => x.Id).FirstOrDefault()) + .Select(e => e!.Id); + + var query = context.BaseItems + .Include(e => e.TrailerTypes) + .Include(e => e.Provider) + .Include(e => e.LockedFields) + .Include(e => e.Images) + .Include(e => e.LinkedChildEntities) + .AsSingleQuery() + .Where(e => masterQuery.Contains(e.Id)); + + query = ApplyOrder(query, filter, context); + + var result = new QueryResult<(BaseItemDto, ItemCounts?)>(); + if (filter.EnableTotalRecordCount) + { + result.TotalRecordCount = query.Count(); + } + + if (filter.Limit.HasValue || filter.StartIndex.HasValue) + { + var offset = filter.StartIndex ?? 0; + + if (offset > 0) + { + query = query.Skip(offset); + } + + if (filter.Limit.HasValue) + { + query = query.Take(filter.Limit.Value); + } + } + + if (filter.IncludeItemTypes.Length > 0) + { + var typeSubQuery = new InternalItemsQuery(filter.User) + { + ExcludeItemTypes = filter.ExcludeItemTypes, + IncludeItemTypes = filter.IncludeItemTypes, + MediaTypes = filter.MediaTypes, + AncestorIds = filter.AncestorIds, + ExcludeItemIds = filter.ExcludeItemIds, + ItemIds = filter.ItemIds, + TopParentIds = filter.TopParentIds, + ParentId = filter.ParentId, + IsPlayed = filter.IsPlayed + }; + + var itemCountQuery = TranslateQuery(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, typeSubQuery) + .Where(e => e.ItemValues!.Any(f => itemValueTypes!.Contains(f.ItemValue.Type))); + + var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series]; + var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie]; + var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode]; + var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum]; + var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]; + var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio]; + var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer]; + + // Get the IDs from itemCountQuery to use in the join + 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 + var countsByCleanName = context.ItemValuesMap + .Where(ivm => itemValueTypes.Contains(ivm.ItemValue.Type)) + .Where(ivm => itemIds.Contains(ivm.ItemId)) + .Join( + context.BaseItems, + ivm => ivm.ItemId, + e => e.Id, + (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 + { + 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), + }); + + result.StartIndex = filter.StartIndex ?? 0; + result.Items = + [ + .. query + .AsEnumerable() + .Where(e => e is not null) + .Select(e => + { + var item = DeserializeBaseItem(e, filter.SkipDeserialization); + countsByCleanName.TryGetValue(e.CleanName ?? string.Empty, out var itemCount); + return (item, itemCount); + }) + .Where(x => x.item is not null) + .Select(x => (x.item!, x.itemCount)) + ]; + } + else + { + result.StartIndex = filter.StartIndex ?? 0; + result.Items = + [ + .. query + .AsEnumerable() + .Where(e => e != null) + .Select(e => DeserializeBaseItem(e, filter.SkipDeserialization)) + .Where(item => item != null) + .Select(item => (item!, (ItemCounts?)null)) + ]; + } + + return result; + } +} |
