aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-03-07 20:12:42 +0100
committerShadowghost <Ghost_of_Stone@web.de>2026-03-07 20:12:42 +0100
commit077fa89717957f871b172ca4b2dc4a178efd3bc5 (patch)
tree1c2be0089b3c33cda1ed96bde4b76a715a845df7 /Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs
parent268f23f39ac18e783156b91b575ee6a105b6937c (diff)
Split BaseItemRepository and IItemRepository
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.ByName.cs293
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;
+ }
+}