< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Item.BaseItemRepository
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
Line coverage
51%
Covered lines: 664
Uncovered lines: 622
Coverable lines: 1286
Total lines: 2582
Line coverage: 51.6%
Branch coverage
48%
Covered branches: 351
Total branches: 718
Branch coverage: 48.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
DeleteItem(...)50%6697.56%
UpdateInheritedValues()100%11100%
GetItemIdsList(...)100%11100%
GetAllArtists(...)100%210%
GetArtists(...)100%210%
GetAlbumArtists(...)100%210%
GetStudios(...)100%210%
GetGenres(...)100%210%
GetMusicGenres(...)100%210%
GetStudioNames()100%11100%
GetAllArtistNames()100%11100%
GetMusicGenreNames()100%11100%
GetGenreNames()100%11100%
GetItems(...)50%26835%
GetItemList(...)100%11100%
GetLatestItemList(...)0%4260%
GetNextUpSeriesKeys(...)0%620%
ApplyGroupingFilter(...)75%9871.42%
ApplyNavigations(...)100%22100%
ApplyQueryPaging(...)87.5%8885.71%
ApplyQueryFilter(...)100%11100%
PrepareItemQuery(...)100%11100%
GetCount(...)100%210%
GetItemCounts(...)0%420200%
GetType(...)100%11100%
SaveImages(...)0%620%
SaveItems(...)100%11100%
UpdateOrInsertItems(...)85.71%292889.69%
RetrieveItem(...)50%4490%
Map(...)48.78%1428279.31%
Map(...)53.22%956279.46%
GetItemValueNames(...)100%44100%
TypeRequiresDeserialization(...)100%11100%
DeserializeBaseItem(...)50%101088.88%
DeserializeBaseItem(...)83.33%151272.72%
GetItemValues(...)0%210140%
PrepareFilterQuery(...)83.33%6680%
GetCleanValue(...)50%2266.66%
GetItemValuesToSave(...)50%4481.81%
Map(...)0%620%
Map(...)0%7280%
GetPathToSave(...)50%2266.66%
GetItemByNameTypesInQuery(...)100%1010100%
IsTypeInQuery(...)75%5466.66%
EnableGroupByPresentationUniqueKey(...)65%212087.5%
ApplyOrder(...)57.69%332678.26%
TranslateQuery(...)45.37%2800334638.64%
GetIsPlayed(...)0%620%
TraverseHirachyDown(...)50%8884.21%
FindArtists(...)100%210%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2// Do not enforce that because EFCore cannot deal with cultures well.
 3#pragma warning disable CA1304 // Specify CultureInfo
 4#pragma warning disable CA1311 // Specify a culture or use an invariant version
 5#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string compari
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Collections.Generic;
 10using System.Globalization;
 11using System.Linq;
 12using System.Linq.Expressions;
 13using System.Reflection;
 14using System.Text;
 15using System.Text.Json;
 16using System.Threading;
 17using System.Threading.Tasks;
 18using Jellyfin.Data.Enums;
 19using Jellyfin.Database.Implementations;
 20using Jellyfin.Database.Implementations.Entities;
 21using Jellyfin.Database.Implementations.Enums;
 22using Jellyfin.Extensions;
 23using Jellyfin.Extensions.Json;
 24using Jellyfin.Server.Implementations.Extensions;
 25using MediaBrowser.Common;
 26using MediaBrowser.Controller;
 27using MediaBrowser.Controller.Channels;
 28using MediaBrowser.Controller.Configuration;
 29using MediaBrowser.Controller.Entities;
 30using MediaBrowser.Controller.Entities.Audio;
 31using MediaBrowser.Controller.Entities.TV;
 32using MediaBrowser.Controller.LiveTv;
 33using MediaBrowser.Controller.Persistence;
 34using MediaBrowser.Model.Dto;
 35using MediaBrowser.Model.Entities;
 36using MediaBrowser.Model.LiveTv;
 37using MediaBrowser.Model.Querying;
 38using Microsoft.EntityFrameworkCore;
 39using Microsoft.Extensions.Logging;
 40using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 41using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 42
 43namespace Jellyfin.Server.Implementations.Item;
 44
 45/*
 46    All queries in this class and all other nullable enabled EFCore repository classes will make liberal use of the null
 47    This is done as the code isn't actually executed client side, but only the expressions are interpret and the compile
 48    This is your only warning/message regarding this topic.
 49*/
 50
 51/// <summary>
 52/// Handles all storage logic for BaseItems.
 53/// </summary>
 54public sealed class BaseItemRepository
 55    : IItemRepository
 56{
 57    /// <summary>
 58    /// Gets the placeholder id for UserData detached items.
 59    /// </summary>
 160    public static readonly Guid PlaceholderId = Guid.Parse("00000000-0000-0000-0000-000000000001");
 61
 62    /// <summary>
 63    /// This holds all the types in the running assemblies
 64    /// so that we can de-serialize properly when we don't have strong types.
 65    /// </summary>
 166    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 67    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 68    private readonly IServerApplicationHost _appHost;
 69    private readonly IItemTypeLookup _itemTypeLookup;
 70    private readonly IServerConfigurationManager _serverConfigurationManager;
 71    private readonly ILogger<BaseItemRepository> _logger;
 72
 173    private static readonly IReadOnlyList<ItemValueType> _getAllArtistsValueTypes = [ItemValueType.Artist, ItemValueType
 174    private static readonly IReadOnlyList<ItemValueType> _getArtistValueTypes = [ItemValueType.Artist];
 175    private static readonly IReadOnlyList<ItemValueType> _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist];
 176    private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios];
 177    private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre];
 178    private static readonly IReadOnlyList<char> SearchWildcardTerms = ['%', '_', '[', ']', '^'];
 79
 80    /// <summary>
 81    /// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
 82    /// </summary>
 83    /// <param name="dbProvider">The db factory.</param>
 84    /// <param name="appHost">The Application host.</param>
 85    /// <param name="itemTypeLookup">The static type lookup.</param>
 86    /// <param name="serverConfigurationManager">The server Configuration manager.</param>
 87    /// <param name="logger">System logger.</param>
 88    public BaseItemRepository(
 89        IDbContextFactory<JellyfinDbContext> dbProvider,
 90        IServerApplicationHost appHost,
 91        IItemTypeLookup itemTypeLookup,
 92        IServerConfigurationManager serverConfigurationManager,
 93        ILogger<BaseItemRepository> logger)
 94    {
 2195        _dbProvider = dbProvider;
 2196        _appHost = appHost;
 2197        _itemTypeLookup = itemTypeLookup;
 2198        _serverConfigurationManager = serverConfigurationManager;
 2199        _logger = logger;
 21100    }
 101
 102    /// <inheritdoc />
 103    public void DeleteItem(params IReadOnlyList<Guid> ids)
 104    {
 2105        if (ids is null || ids.Count == 0 || ids.Any(f => f.Equals(PlaceholderId)))
 106        {
 0107            throw new ArgumentException("Guid can't be empty or the placeholder id.", nameof(ids));
 108        }
 109
 2110        using var context = _dbProvider.CreateDbContext();
 2111        using var transaction = context.Database.BeginTransaction();
 112
 2113        var date = (DateTime?)DateTime.UtcNow;
 114
 2115        var relatedItems = ids.SelectMany(f => TraverseHirachyDown(f, context)).ToArray();
 116
 117        // Remove any UserData entries for the placeholder item that would conflict with the UserData
 118        // being detached from the item being deleted. This is necessary because, during an update,
 119        // UserData may be reattached to a new entry, but some entries can be left behind.
 120        // Ensures there are no duplicate UserId/CustomDataKey combinations for the placeholder.
 2121        context.UserData
 2122            .Join(
 2123                context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId),
 2124                placeholder => new { placeholder.UserId, placeholder.CustomDataKey },
 2125                userData => new { userData.UserId, userData.CustomDataKey },
 2126                (placeholder, userData) => placeholder)
 2127            .Where(e => e.ItemId == PlaceholderId)
 2128            .ExecuteDelete();
 129
 130        // Detach all user watch data
 2131        context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId)
 2132            .ExecuteUpdate(e => e
 2133                .SetProperty(f => f.RetentionDate, date)
 2134                .SetProperty(f => f.ItemId, PlaceholderId));
 135
 2136        context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2137        context.AncestorIds.WhereOneOrMany(relatedItems, e => e.ParentItemId).ExecuteDelete();
 2138        context.AttachmentStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2139        context.BaseItemImageInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2140        context.BaseItemMetadataFields.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2141        context.BaseItemProviders.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2142        context.BaseItemTrailerTypes.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2143        context.BaseItems.WhereOneOrMany(relatedItems, e => e.Id).ExecuteDelete();
 2144        context.Chapters.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2145        context.CustomItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2146        context.ItemDisplayPreferences.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2147        context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete();
 2148        context.ItemValuesMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2149        context.KeyframeData.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2150        context.MediaSegments.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2151        context.MediaStreamInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2152        var query = context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).Select(f => f.PeopleId).Distin
 2153        context.PeopleBaseItemMap.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2154        context.Peoples.WhereOneOrMany(query, e => e.Id).Where(e => e.BaseItems!.Count == 0).ExecuteDelete();
 2155        context.TrickplayInfos.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete();
 2156        context.SaveChanges();
 2157        transaction.Commit();
 4158    }
 159
 160    /// <inheritdoc />
 161    public void UpdateInheritedValues()
 162    {
 16163        using var context = _dbProvider.CreateDbContext();
 16164        using var transaction = context.Database.BeginTransaction();
 165
 16166        context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
 167        // ItemValue Inheritance is now correctly mapped via AncestorId on demand
 16168        context.SaveChanges();
 169
 16170        transaction.Commit();
 32171    }
 172
 173    /// <inheritdoc />
 174    public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
 175    {
 17176        ArgumentNullException.ThrowIfNull(filter);
 17177        PrepareFilterQuery(filter);
 178
 17179        using var context = _dbProvider.CreateDbContext();
 17180        return ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context
 17181    }
 182
 183    /// <inheritdoc />
 184    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAllArtists(InternalItemsQuery filter)
 185    {
 0186        return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtis
 187    }
 188
 189    /// <inheritdoc />
 190    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetArtists(InternalItemsQuery filter)
 191    {
 0192        return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]);
 193    }
 194
 195    /// <inheritdoc />
 196    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
 197    {
 0198        return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArti
 199    }
 200
 201    /// <inheritdoc />
 202    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetStudios(InternalItemsQuery filter)
 203    {
 0204        return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]);
 205    }
 206
 207    /// <inheritdoc />
 208    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetGenres(InternalItemsQuery filter)
 209    {
 0210        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]);
 211    }
 212
 213    /// <inheritdoc />
 214    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
 215    {
 0216        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]);
 217    }
 218
 219    /// <inheritdoc />
 220    public IReadOnlyList<string> GetStudioNames()
 221    {
 17222        return GetItemValueNames(_getStudiosValueTypes, [], []);
 223    }
 224
 225    /// <inheritdoc />
 226    public IReadOnlyList<string> GetAllArtistNames()
 227    {
 17228        return GetItemValueNames(_getAllArtistsValueTypes, [], []);
 229    }
 230
 231    /// <inheritdoc />
 232    public IReadOnlyList<string> GetMusicGenreNames()
 233    {
 17234        return GetItemValueNames(
 17235            _getGenreValueTypes,
 17236            _itemTypeLookup.MusicGenreTypes,
 17237            []);
 238    }
 239
 240    /// <inheritdoc />
 241    public IReadOnlyList<string> GetGenreNames()
 242    {
 17243        return GetItemValueNames(
 17244            _getGenreValueTypes,
 17245            [],
 17246            _itemTypeLookup.MusicGenreTypes);
 247    }
 248
 249    /// <inheritdoc />
 250    public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
 251    {
 1252        ArgumentNullException.ThrowIfNull(filter);
 1253        if (!filter.EnableTotalRecordCount || (!filter.Limit.HasValue && (filter.StartIndex ?? 0) == 0))
 254        {
 1255            var returnList = GetItemList(filter);
 1256            return new QueryResult<BaseItemDto>(
 1257                filter.StartIndex,
 1258                returnList.Count,
 1259                returnList);
 260        }
 261
 0262        PrepareFilterQuery(filter);
 0263        var result = new QueryResult<BaseItemDto>();
 264
 0265        using var context = _dbProvider.CreateDbContext();
 266
 0267        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 268
 0269        dbQuery = TranslateQuery(dbQuery, context, filter);
 0270        dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
 271
 0272        if (filter.EnableTotalRecordCount)
 273        {
 0274            result.TotalRecordCount = dbQuery.Count();
 275        }
 276
 0277        dbQuery = ApplyQueryPaging(dbQuery, filter);
 278
 0279        result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDe
 0280        result.StartIndex = filter.StartIndex ?? 0;
 0281        return result;
 0282    }
 283
 284    /// <inheritdoc />
 285    public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
 286    {
 307287        ArgumentNullException.ThrowIfNull(filter);
 307288        PrepareFilterQuery(filter);
 289
 307290        using var context = _dbProvider.CreateDbContext();
 307291        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 292
 307293        dbQuery = TranslateQuery(dbQuery, context, filter);
 294
 307295        dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
 307296        dbQuery = ApplyQueryPaging(dbQuery, filter);
 297
 307298        return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserializ
 307299    }
 300
 301    /// <inheritdoc/>
 302    public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType)
 303    {
 0304        ArgumentNullException.ThrowIfNull(filter);
 0305        PrepareFilterQuery(filter);
 306
 307        // Early exit if collection type is not tvshows or music
 0308        if (collectionType != CollectionType.tvshows && collectionType != CollectionType.music)
 309        {
 0310            return Array.Empty<BaseItem>();
 311        }
 312
 0313        using var context = _dbProvider.CreateDbContext();
 314
 315        // Subquery to group by SeriesNames/Album and get the max Date Created for each group.
 0316        var subquery = PrepareItemQuery(context, filter);
 0317        subquery = TranslateQuery(subquery, context, filter);
 0318        var subqueryGrouped = subquery.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
 0319            .Select(g => new
 0320            {
 0321                Key = g.Key,
 0322                MaxDateCreated = g.Max(a => a.DateCreated)
 0323            })
 0324            .OrderByDescending(g => g.MaxDateCreated)
 0325            .Select(g => g);
 326
 0327        if (filter.Limit.HasValue)
 328        {
 0329            subqueryGrouped = subqueryGrouped.Take(filter.Limit.Value);
 330        }
 331
 0332        filter.Limit = null;
 333
 0334        var mainquery = PrepareItemQuery(context, filter);
 0335        mainquery = TranslateQuery(mainquery, context, filter);
 0336        mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated));
 0337        mainquery = ApplyGroupingFilter(context, mainquery, filter);
 0338        mainquery = ApplyQueryPaging(mainquery, filter);
 339
 0340        return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserial
 0341    }
 342
 343    /// <inheritdoc />
 344    public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff)
 345    {
 0346        ArgumentNullException.ThrowIfNull(filter);
 0347        ArgumentNullException.ThrowIfNull(filter.User);
 348
 0349        using var context = _dbProvider.CreateDbContext();
 350
 0351        var query = context.BaseItems
 0352            .AsNoTracking()
 0353            .Where(i => filter.TopParentIds.Contains(i.TopParentId!.Value))
 0354            .Where(i => i.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode])
 0355            .Join(
 0356                context.UserData.AsNoTracking().Where(e => e.ItemId != EF.Constant(PlaceholderId)),
 0357                i => new { UserId = filter.User.Id, ItemId = i.Id },
 0358                u => new { UserId = u.UserId, ItemId = u.ItemId },
 0359                (entity, data) => new { Item = entity, UserData = data })
 0360            .GroupBy(g => g.Item.SeriesPresentationUniqueKey)
 0361            .Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
 0362            .Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
 0363            .OrderByDescending(g => g.LastPlayedDate)
 0364            .Select(g => g.Key!);
 365
 0366        if (filter.Limit.HasValue)
 367        {
 0368            query = query.Take(filter.Limit.Value);
 369        }
 370
 0371        return query.ToArray();
 0372    }
 373
 374    private IQueryable<BaseItemEntity> ApplyGroupingFilter(JellyfinDbContext context, IQueryable<BaseItemEntity> dbQuery
 375    {
 376        // This whole block is needed to filter duplicate entries on request
 377        // for the time being it cannot be used because it would destroy the ordering
 378        // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions b
 379        // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
 380
 324381        var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
 324382        if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
 383        {
 0384            var tempQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(
 0385            dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
 386        }
 324387        else if (enableGroupByPresentationUniqueKey)
 388        {
 1389            var tempQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e => e!
 1390            dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
 391        }
 323392        else if (filter.GroupBySeriesPresentationUniqueKey)
 393        {
 0394            var tempQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.FirstOrDefault()).Select(e
 0395            dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
 396        }
 397        else
 398        {
 323399            dbQuery = dbQuery.Distinct();
 400        }
 401
 324402        dbQuery = ApplyOrder(dbQuery, filter);
 403
 324404        dbQuery = ApplyNavigations(dbQuery, filter);
 405
 324406        return dbQuery;
 407    }
 408
 409    private static IQueryable<BaseItemEntity> ApplyNavigations(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery fi
 410    {
 324411        dbQuery = dbQuery.Include(e => e.TrailerTypes)
 324412           .Include(e => e.Provider)
 324413           .Include(e => e.LockedFields)
 324414           .Include(e => e.UserData);
 415
 324416        if (filter.DtoOptions.EnableImages)
 417        {
 324418            dbQuery = dbQuery.Include(e => e.Images);
 419        }
 420
 324421        return dbQuery;
 422    }
 423
 424    private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
 425    {
 324426        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 427        {
 106428            var offset = filter.StartIndex ?? 0;
 429
 106430            if (offset > 0)
 431            {
 0432                dbQuery = dbQuery.Skip(offset);
 433            }
 434
 106435            if (filter.Limit.HasValue)
 436            {
 106437                dbQuery = dbQuery.Take(filter.Limit.Value);
 438            }
 439        }
 440
 324441        return dbQuery;
 442    }
 443
 444    private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, I
 445    {
 17446        dbQuery = TranslateQuery(dbQuery, context, filter);
 17447        dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
 17448        dbQuery = ApplyQueryPaging(dbQuery, filter);
 17449        return dbQuery;
 450    }
 451
 452    private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
 453    {
 393454        IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking();
 393455        dbQuery = dbQuery.AsSingleQuery();
 456
 393457        return dbQuery;
 458    }
 459
 460    /// <inheritdoc/>
 461    public int GetCount(InternalItemsQuery filter)
 462    {
 0463        ArgumentNullException.ThrowIfNull(filter);
 464        // Hack for right now since we currently don't support filtering out these duplicates within a query
 0465        PrepareFilterQuery(filter);
 466
 0467        using var context = _dbProvider.CreateDbContext();
 0468        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 469
 0470        return dbQuery.Count();
 0471    }
 472
 473    /// <inheritdoc />
 474    public ItemCounts GetItemCounts(InternalItemsQuery filter)
 475    {
 0476        ArgumentNullException.ThrowIfNull(filter);
 477        // Hack for right now since we currently don't support filtering out these duplicates within a query
 0478        PrepareFilterQuery(filter);
 479
 0480        using var context = _dbProvider.CreateDbContext();
 0481        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 482
 0483        var counts = dbQuery
 0484            .GroupBy(x => x.Type)
 0485            .Select(x => new { x.Key, Count = x.Count() })
 0486            .ToArray();
 487
 0488        var lookup = _itemTypeLookup.BaseItemKindNames;
 0489        var result = new ItemCounts();
 0490        foreach (var count in counts)
 491        {
 0492            if (string.Equals(count.Key, lookup[BaseItemKind.MusicAlbum], StringComparison.Ordinal))
 493            {
 0494                result.AlbumCount = count.Count;
 495            }
 0496            else if (string.Equals(count.Key, lookup[BaseItemKind.MusicArtist], StringComparison.Ordinal))
 497            {
 0498                result.ArtistCount = count.Count;
 499            }
 0500            else if (string.Equals(count.Key, lookup[BaseItemKind.Episode], StringComparison.Ordinal))
 501            {
 0502                result.EpisodeCount = count.Count;
 503            }
 0504            else if (string.Equals(count.Key, lookup[BaseItemKind.Movie], StringComparison.Ordinal))
 505            {
 0506                result.MovieCount = count.Count;
 507            }
 0508            else if (string.Equals(count.Key, lookup[BaseItemKind.MusicVideo], StringComparison.Ordinal))
 509            {
 0510                result.MusicVideoCount = count.Count;
 511            }
 0512            else if (string.Equals(count.Key, lookup[BaseItemKind.LiveTvProgram], StringComparison.Ordinal))
 513            {
 0514                result.ProgramCount = count.Count;
 515            }
 0516            else if (string.Equals(count.Key, lookup[BaseItemKind.Series], StringComparison.Ordinal))
 517            {
 0518                result.SeriesCount = count.Count;
 519            }
 0520            else if (string.Equals(count.Key, lookup[BaseItemKind.Audio], StringComparison.Ordinal))
 521            {
 0522                result.SongCount = count.Count;
 523            }
 0524            else if (string.Equals(count.Key, lookup[BaseItemKind.Trailer], StringComparison.Ordinal))
 525            {
 0526                result.TrailerCount = count.Count;
 527            }
 528        }
 529
 0530        return result;
 0531    }
 532
 533#pragma warning disable CA1307 // Specify StringComparison for clarity
 534    /// <summary>
 535    /// Gets the type.
 536    /// </summary>
 537    /// <param name="typeName">Name of the type.</param>
 538    /// <returns>Type.</returns>
 539    /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
 540    private static Type? GetType(string typeName)
 541    {
 104542        ArgumentException.ThrowIfNullOrEmpty(typeName);
 543
 544        // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static 
 545        // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are 
 104546        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 104547            .Select(a => a.GetType(k))
 104548            .FirstOrDefault(t => t is not null));
 549    }
 550
 551    /// <inheritdoc  />
 552    public void SaveImages(BaseItemDto item)
 553    {
 0554        ArgumentNullException.ThrowIfNull(item);
 555
 0556        var images = item.ImageInfos.Select(e => Map(item.Id, e));
 0557        using var context = _dbProvider.CreateDbContext();
 558
 0559        if (!context.BaseItems.Any(bi => bi.Id == item.Id))
 560        {
 0561            _logger.LogWarning("Unable to save ImageInfo for non existing BaseItem");
 0562            return;
 563        }
 564
 0565        context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
 0566        context.BaseItemImageInfos.AddRange(images);
 0567        context.SaveChanges();
 0568    }
 569
 570    /// <inheritdoc  />
 571    public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 572    {
 114573        UpdateOrInsertItems(items, cancellationToken);
 114574    }
 575
 576    /// <inheritdoc cref="IItemRepository"/>
 577    public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 578    {
 114579        ArgumentNullException.ThrowIfNull(items);
 114580        cancellationToken.ThrowIfCancellationRequested();
 581
 114582        var tuples = new List<(BaseItemDto Item, List<Guid>? AncestorIds, BaseItemDto TopParent, IEnumerable<string> Use
 456583        foreach (var item in items.GroupBy(e => e.Id).Select(e => e.Last()).Where(e => e.Id != PlaceholderId))
 584        {
 114585            var ancestorIds = item.SupportsAncestors ?
 114586                item.GetAncestorIds().Distinct().ToList() :
 114587                null;
 588
 114589            var topParent = item.GetTopParent();
 590
 114591            var userdataKey = item.GetUserDataKeys();
 114592            var inheritedTags = item.GetInheritedTags();
 593
 114594            tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
 595        }
 596
 114597        using var context = _dbProvider.CreateDbContext();
 114598        using var transaction = context.Database.BeginTransaction();
 599
 114600        var ids = tuples.Select(f => f.Item.Id).ToArray();
 114601        var existingItems = context.BaseItems.Where(e => ids.Contains(e.Id)).Select(f => f.Id).ToArray();
 114602        var newItems = tuples.Where(e => !existingItems.Contains(e.Item.Id)).ToArray();
 603
 456604        foreach (var item in tuples)
 605        {
 114606            var entity = Map(item.Item);
 607            // TODO: refactor this "inconsistency"
 114608            entity.TopParentId = item.TopParent?.Id;
 609
 114610            if (!existingItems.Any(e => e == entity.Id))
 611            {
 59612                context.BaseItems.Add(entity);
 613            }
 614            else
 615            {
 55616                context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 55617                context.BaseItems.Attach(entity).State = EntityState.Modified;
 618            }
 619        }
 620
 114621        context.SaveChanges();
 622
 346623        foreach (var item in newItems)
 624        {
 625            // reattach old userData entries
 59626            var userKeys = item.UserDataKey.ToArray();
 59627            var retentionDate = (DateTime?)null;
 59628            context.UserData
 59629                .Where(e => e.ItemId == PlaceholderId)
 59630                .Where(e => userKeys.Contains(e.CustomDataKey))
 59631                .ExecuteUpdate(e => e
 59632                    .SetProperty(f => f.ItemId, item.Item.Id)
 59633                    .SetProperty(f => f.RetentionDate, retentionDate));
 634        }
 635
 114636        var itemValueMaps = tuples
 114637            .Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
 114638            .ToArray();
 114639        var allListedItemValues = itemValueMaps
 114640            .SelectMany(f => f.Values)
 114641            .Distinct()
 114642            .ToArray();
 114643        var existingValues = context.ItemValues
 114644            .Select(e => new
 114645            {
 114646                item = e,
 114647                Key = e.Type + "+" + e.Value
 114648            })
 114649            .Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key))
 114650            .Select(e => e.item)
 114651            .ToArray();
 114652        var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).S
 114653        {
 114654            CleanValue = GetCleanValue(f.Value),
 114655            ItemValueId = Guid.NewGuid(),
 114656            Type = f.MagicNumber,
 114657            Value = f.Value
 114658        }).ToArray();
 114659        context.ItemValues.AddRange(missingItemValues);
 114660        context.SaveChanges();
 661
 114662        var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
 114663        var valueMap = itemValueMaps
 114664            .Select(f => (f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type =
 114665            .ToArray();
 666
 114667        var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList();
 668
 456669        foreach (var item in valueMap)
 670        {
 114671            var itemMappedValues = mappedValues.Where(e => e.ItemId == item.Item.Id).ToList();
 228672            foreach (var itemValue in item.Values)
 673            {
 0674                var existingItem = itemMappedValues.FirstOrDefault(f => f.ItemValueId == itemValue.ItemValueId);
 0675                if (existingItem is null)
 676                {
 0677                    context.ItemValuesMap.Add(new ItemValueMap()
 0678                    {
 0679                        Item = null!,
 0680                        ItemId = item.Item.Id,
 0681                        ItemValue = null!,
 0682                        ItemValueId = itemValue.ItemValueId
 0683                    });
 684                }
 685                else
 686                {
 687                    // map exists, remove from list so its been handled.
 0688                    itemMappedValues.Remove(existingItem);
 689                }
 690            }
 691
 692            // all still listed values are not in the new list so remove them.
 114693            context.ItemValuesMap.RemoveRange(itemMappedValues);
 694        }
 695
 114696        context.SaveChanges();
 697
 456698        foreach (var item in tuples)
 699        {
 114700            if (item.Item.SupportsAncestors && item.AncestorIds != null)
 701            {
 114702                var existingAncestorIds = context.AncestorIds.Where(e => e.ItemId == item.Item.Id).ToList();
 114703                var validAncestorIds = context.BaseItems.Where(e => item.AncestorIds.Contains(e.Id)).Select(f => f.Id).T
 282704                foreach (var ancestorId in validAncestorIds)
 705                {
 27706                    var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
 27707                    if (existingAncestorId is null)
 708                    {
 23709                        context.AncestorIds.Add(new AncestorId()
 23710                        {
 23711                            ParentItemId = ancestorId,
 23712                            ItemId = item.Item.Id,
 23713                            Item = null!,
 23714                            ParentItem = null!
 23715                        });
 716                    }
 717                    else
 718                    {
 4719                        existingAncestorIds.Remove(existingAncestorId);
 720                    }
 721                }
 722
 114723                context.AncestorIds.RemoveRange(existingAncestorIds);
 724            }
 725        }
 726
 114727        context.SaveChanges();
 114728        transaction.Commit();
 228729    }
 730
 731    /// <inheritdoc  />
 732    public BaseItemDto? RetrieveItem(Guid id)
 733    {
 86734        if (id.IsEmpty())
 735        {
 0736            throw new ArgumentException("Guid can't be empty", nameof(id));
 737        }
 738
 86739        using var context = _dbProvider.CreateDbContext();
 86740        var dbQuery = PrepareItemQuery(context, new()
 86741        {
 86742            DtoOptions = new()
 86743            {
 86744                EnableImages = true
 86745            }
 86746        });
 86747        dbQuery = dbQuery.Include(e => e.TrailerTypes)
 86748            .Include(e => e.Provider)
 86749            .Include(e => e.LockedFields)
 86750            .Include(e => e.UserData)
 86751            .Include(e => e.Images);
 752
 86753        var item = dbQuery.FirstOrDefault(e => e.Id == id);
 86754        if (item is null)
 755        {
 86756            return null;
 757        }
 758
 0759        return DeserializeBaseItem(item);
 86760    }
 761
 762    /// <summary>
 763    /// Maps a Entity to the DTO.
 764    /// </summary>
 765    /// <param name="entity">The entity.</param>
 766    /// <param name="dto">The dto base instance.</param>
 767    /// <param name="appHost">The Application server Host.</param>
 768    /// <param name="logger">The applogger.</param>
 769    /// <returns>The dto to map.</returns>
 770    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost, ILogger logge
 771    {
 52772        dto.Id = entity.Id;
 52773        dto.ParentId = entity.ParentId.GetValueOrDefault();
 52774        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 52775        dto.EndDate = entity.EndDate;
 52776        dto.CommunityRating = entity.CommunityRating;
 52777        dto.CustomRating = entity.CustomRating;
 52778        dto.IndexNumber = entity.IndexNumber;
 52779        dto.IsLocked = entity.IsLocked;
 52780        dto.Name = entity.Name;
 52781        dto.OfficialRating = entity.OfficialRating;
 52782        dto.Overview = entity.Overview;
 52783        dto.ParentIndexNumber = entity.ParentIndexNumber;
 52784        dto.PremiereDate = entity.PremiereDate;
 52785        dto.ProductionYear = entity.ProductionYear;
 52786        dto.SortName = entity.SortName;
 52787        dto.ForcedSortName = entity.ForcedSortName;
 52788        dto.RunTimeTicks = entity.RunTimeTicks;
 52789        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 52790        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 52791        dto.IsInMixedFolder = entity.IsInMixedFolder;
 52792        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 52793        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 52794        dto.CriticRating = entity.CriticRating;
 52795        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 52796        dto.OriginalTitle = entity.OriginalTitle;
 52797        dto.Album = entity.Album;
 52798        dto.LUFS = entity.LUFS;
 52799        dto.NormalizationGain = entity.NormalizationGain;
 52800        dto.IsVirtualItem = entity.IsVirtualItem;
 52801        dto.ExternalSeriesId = entity.ExternalSeriesId;
 52802        dto.Tagline = entity.Tagline;
 52803        dto.TotalBitrate = entity.TotalBitrate;
 52804        dto.ExternalId = entity.ExternalId;
 52805        dto.Size = entity.Size;
 52806        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 52807        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 52808        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 52809        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 52810        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 52811        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 52812        dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ow
 52813        dto.Width = entity.Width.GetValueOrDefault();
 52814        dto.Height = entity.Height.GetValueOrDefault();
 52815        dto.UserData = entity.UserData;
 816
 52817        if (entity.Provider is not null)
 818        {
 52819            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 820        }
 821
 52822        if (entity.ExtraType is not null)
 823        {
 0824            dto.ExtraType = (ExtraType)entity.ExtraType;
 825        }
 826
 52827        if (entity.LockedFields is not null)
 828        {
 52829            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 830        }
 831
 52832        if (entity.Audio is not null)
 833        {
 0834            dto.Audio = (ProgramAudio)entity.Audio;
 835        }
 836
 52837        dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Par
 52838        dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
 52839        dto.Studios = entity.Studios?.Split('|') ?? [];
 52840        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 841
 52842        if (dto is IHasProgramAttributes hasProgramAttributes)
 843        {
 0844            hasProgramAttributes.IsMovie = entity.IsMovie;
 0845            hasProgramAttributes.IsSeries = entity.IsSeries;
 0846            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0847            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 848        }
 849
 52850        if (dto is LiveTvChannel liveTvChannel)
 851        {
 0852            liveTvChannel.ServiceName = entity.ExternalServiceId;
 853        }
 854
 52855        if (dto is Trailer trailer)
 856        {
 0857            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 858        }
 859
 52860        if (dto is Video video)
 861        {
 0862            video.PrimaryVersionId = entity.PrimaryVersionId;
 863        }
 864
 52865        if (dto is IHasSeries hasSeriesName)
 866        {
 0867            hasSeriesName.SeriesName = entity.SeriesName;
 0868            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0869            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 870        }
 871
 52872        if (dto is Episode episode)
 873        {
 0874            episode.SeasonName = entity.SeasonName;
 0875            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 876        }
 877
 52878        if (dto is IHasArtist hasArtists)
 879        {
 0880            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 881        }
 882
 52883        if (dto is IHasAlbumArtist hasAlbumArtists)
 884        {
 0885            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 886        }
 887
 52888        if (dto is LiveTvProgram program)
 889        {
 0890            program.ShowId = entity.ShowId;
 891        }
 892
 52893        if (entity.Images is not null)
 894        {
 52895            dto.ImageInfos = entity.Images.Select(e => Map(e, appHost)).ToArray();
 896        }
 897
 898        // dto.Type = entity.Type;
 899        // dto.Data = entity.Data;
 900        // dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
 52901        if (dto is IHasStartDate hasStartDate)
 902        {
 0903            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 904        }
 905
 906        // Fields that are present in the DB but are never actually used
 907        // dto.UnratedType = entity.UnratedType;
 908        // dto.TopParentId = entity.TopParentId;
 909        // dto.CleanName = entity.CleanName;
 910        // dto.UserDataKey = entity.UserDataKey;
 911
 52912        if (dto is Folder folder)
 913        {
 52914            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 915        }
 916
 52917        return dto;
 918    }
 919
 920    /// <summary>
 921    /// Maps a Entity to the DTO.
 922    /// </summary>
 923    /// <param name="dto">The entity.</param>
 924    /// <returns>The dto to map.</returns>
 925    public BaseItemEntity Map(BaseItemDto dto)
 926    {
 114927        var dtoType = dto.GetType();
 114928        var entity = new BaseItemEntity()
 114929        {
 114930            Type = dtoType.ToString(),
 114931            Id = dto.Id
 114932        };
 933
 114934        if (TypeRequiresDeserialization(dtoType))
 935        {
 93936            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 937        }
 938
 114939        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 114940        entity.Path = GetPathToSave(dto.Path);
 114941        entity.EndDate = dto.EndDate;
 114942        entity.CommunityRating = dto.CommunityRating;
 114943        entity.CustomRating = dto.CustomRating;
 114944        entity.IndexNumber = dto.IndexNumber;
 114945        entity.IsLocked = dto.IsLocked;
 114946        entity.Name = dto.Name;
 114947        entity.CleanName = GetCleanValue(dto.Name);
 114948        entity.OfficialRating = dto.OfficialRating;
 114949        entity.Overview = dto.Overview;
 114950        entity.ParentIndexNumber = dto.ParentIndexNumber;
 114951        entity.PremiereDate = dto.PremiereDate;
 114952        entity.ProductionYear = dto.ProductionYear;
 114953        entity.SortName = dto.SortName;
 114954        entity.ForcedSortName = dto.ForcedSortName;
 114955        entity.RunTimeTicks = dto.RunTimeTicks;
 114956        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 114957        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 114958        entity.IsInMixedFolder = dto.IsInMixedFolder;
 114959        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 114960        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 114961        entity.CriticRating = dto.CriticRating;
 114962        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 114963        entity.OriginalTitle = dto.OriginalTitle;
 114964        entity.Album = dto.Album;
 114965        entity.LUFS = dto.LUFS;
 114966        entity.NormalizationGain = dto.NormalizationGain;
 114967        entity.IsVirtualItem = dto.IsVirtualItem;
 114968        entity.ExternalSeriesId = dto.ExternalSeriesId;
 114969        entity.Tagline = dto.Tagline;
 114970        entity.TotalBitrate = dto.TotalBitrate;
 114971        entity.ExternalId = dto.ExternalId;
 114972        entity.Size = dto.Size;
 114973        entity.Genres = string.Join('|', dto.Genres);
 114974        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 114975        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 114976        entity.ChannelId = dto.ChannelId;
 114977        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 114978        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 114979        entity.OwnerId = dto.OwnerId.ToString();
 114980        entity.Width = dto.Width;
 114981        entity.Height = dto.Height;
 114982        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 114983        {
 114984            Item = entity,
 114985            ProviderId = e.Key,
 114986            ProviderValue = e.Value
 114987        }).ToList();
 988
 114989        if (dto.Audio.HasValue)
 990        {
 0991            entity.Audio = (ProgramAudioEntity)dto.Audio;
 992        }
 993
 114994        if (dto.ExtraType.HasValue)
 995        {
 0996            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 997        }
 998
 114999        entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
 1141000        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : n
 1141001        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
 1141002        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
 1141003        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 1141004            .Select(e => new BaseItemMetadataField()
 1141005            {
 1141006                Id = (int)e,
 1141007                Item = entity,
 1141008                ItemId = entity.Id
 1141009            })
 1141010            .ToArray() : null;
 1011
 1141012        if (dto is IHasProgramAttributes hasProgramAttributes)
 1013        {
 01014            entity.IsMovie = hasProgramAttributes.IsMovie;
 01015            entity.IsSeries = hasProgramAttributes.IsSeries;
 01016            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 01017            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 1018        }
 1019
 1141020        if (dto is LiveTvChannel liveTvChannel)
 1021        {
 01022            entity.ExternalServiceId = liveTvChannel.ServiceName;
 1023        }
 1024
 1141025        if (dto is Video video)
 1026        {
 01027            entity.PrimaryVersionId = video.PrimaryVersionId;
 1028        }
 1029
 1141030        if (dto is IHasSeries hasSeriesName)
 1031        {
 01032            entity.SeriesName = hasSeriesName.SeriesName;
 01033            entity.SeriesId = hasSeriesName.SeriesId;
 01034            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 1035        }
 1036
 1141037        if (dto is Episode episode)
 1038        {
 01039            entity.SeasonName = episode.SeasonName;
 01040            entity.SeasonId = episode.SeasonId;
 1041        }
 1042
 1141043        if (dto is IHasArtist hasArtists)
 1044        {
 01045            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
 1046        }
 1047
 1141048        if (dto is IHasAlbumArtist hasAlbumArtists)
 1049        {
 01050            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 1051        }
 1052
 1141053        if (dto is LiveTvProgram program)
 1054        {
 01055            entity.ShowId = program.ShowId;
 1056        }
 1057
 1141058        if (dto.ImageInfos is not null)
 1059        {
 1141060            entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
 1061        }
 1062
 1141063        if (dto is Trailer trailer)
 1064        {
 01065            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 01066            {
 01067                Id = (int)e,
 01068                Item = entity,
 01069                ItemId = entity.Id
 01070            }).ToArray() ?? [];
 1071        }
 1072
 1073        // dto.Type = entity.Type;
 1074        // dto.Data = entity.Data;
 1141075        entity.MediaType = dto.MediaType.ToString();
 1141076        if (dto is IHasStartDate hasStartDate)
 1077        {
 01078            entity.StartDate = hasStartDate.StartDate;
 1079        }
 1080
 1141081        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 1082
 1083        // Fields that are present in the DB but are never actually used
 1084        // dto.UserDataKey = entity.UserDataKey;
 1085
 1141086        if (dto is Folder folder)
 1087        {
 1141088            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 1141089            entity.IsFolder = folder.IsFolder;
 1090        }
 1091
 1141092        return entity;
 1093    }
 1094
 1095    private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes,
 1096    {
 681097        using var context = _dbProvider.CreateDbContext();
 1098
 681099        var query = context.ItemValuesMap
 681100            .AsNoTracking()
 681101            .Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.Type));
 681102        if (withItemTypes.Count > 0)
 1103        {
 171104            query = query.Where(e => withItemTypes.Contains(e.Item.Type));
 1105        }
 1106
 681107        if (excludeItemTypes.Count > 0)
 1108        {
 171109            query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
 1110        }
 1111
 1112        // query = query.DistinctBy(e => e.CleanValue);
 681113        return query.Select(e => e.ItemValue)
 681114            .GroupBy(e => e.CleanValue)
 681115            .Select(e => e.First().Value)
 681116            .ToArray();
 681117    }
 1118
 1119    private static bool TypeRequiresDeserialization(Type type)
 1120    {
 1661121        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 1122    }
 1123
 1124    private BaseItemDto DeserializeBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
 1125    {
 521126        ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity));
 521127        if (_serverConfigurationManager?.Configuration is null)
 1128        {
 01129            throw new InvalidOperationException("Server Configuration manager or configuration is null");
 1130        }
 1131
 521132        var typeToSerialise = GetType(baseItemEntity.Type);
 521133        return BaseItemRepository.DeserializeBaseItem(
 521134            baseItemEntity,
 521135            _logger,
 521136            _appHost,
 521137            skipDeserialization || (_serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeT
 1138    }
 1139
 1140    /// <summary>
 1141    /// Deserializes a BaseItemEntity and sets all properties.
 1142    /// </summary>
 1143    /// <param name="baseItemEntity">The DB entity.</param>
 1144    /// <param name="logger">Logger.</param>
 1145    /// <param name="appHost">The application server Host.</param>
 1146    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 1147    /// <returns>A mapped BaseItem.</returns>
 1148    /// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
 1149    public static BaseItemDto DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost?
 1150    {
 521151        var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialize unknown type.
 521152        BaseItemDto? dto = null;
 521153        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 1154        {
 1155            try
 1156            {
 121157                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 121158            }
 01159            catch (JsonException ex)
 1160            {
 01161                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 01162            }
 1163        }
 1164
 521165        if (dto is null)
 1166        {
 401167            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 1168        }
 1169
 521170        return Map(baseItemEntity, dto, appHost, logger);
 1171    }
 1172
 1173    private QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyLi
 1174    {
 01175        ArgumentNullException.ThrowIfNull(filter);
 1176
 01177        if (!filter.Limit.HasValue)
 1178        {
 01179            filter.EnableTotalRecordCount = false;
 1180        }
 1181
 01182        using var context = _dbProvider.CreateDbContext();
 1183
 01184        var innerQueryFilter = TranslateQuery(context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)), context,
 01185        {
 01186            ExcludeItemTypes = filter.ExcludeItemTypes,
 01187            IncludeItemTypes = filter.IncludeItemTypes,
 01188            MediaTypes = filter.MediaTypes,
 01189            AncestorIds = filter.AncestorIds,
 01190            ItemIds = filter.ItemIds,
 01191            TopParentIds = filter.TopParentIds,
 01192            ParentId = filter.ParentId,
 01193            IsAiring = filter.IsAiring,
 01194            IsMovie = filter.IsMovie,
 01195            IsSports = filter.IsSports,
 01196            IsKids = filter.IsKids,
 01197            IsNews = filter.IsNews,
 01198            IsSeries = filter.IsSeries
 01199        });
 1200
 01201        var itemValuesQuery = context.ItemValues
 01202            .Where(f => itemValueTypes.Contains(f.Type))
 01203            .SelectMany(f => f.BaseItemsMap!, (f, w) => new { f, w })
 01204            .Join(
 01205                innerQueryFilter,
 01206                fw => fw.w.ItemId,
 01207                g => g.Id,
 01208                (fw, g) => fw.f.CleanValue);
 1209
 01210        var innerQuery = PrepareItemQuery(context, filter)
 01211            .Where(e => e.Type == returnType)
 01212            .Where(e => itemValuesQuery.Contains(e.CleanName));
 1213
 01214        var outerQueryFilter = new InternalItemsQuery(filter.User)
 01215        {
 01216            IsPlayed = filter.IsPlayed,
 01217            IsFavorite = filter.IsFavorite,
 01218            IsFavoriteOrLiked = filter.IsFavoriteOrLiked,
 01219            IsLiked = filter.IsLiked,
 01220            IsLocked = filter.IsLocked,
 01221            NameLessThan = filter.NameLessThan,
 01222            NameStartsWith = filter.NameStartsWith,
 01223            NameStartsWithOrGreater = filter.NameStartsWithOrGreater,
 01224            Tags = filter.Tags,
 01225            OfficialRatings = filter.OfficialRatings,
 01226            StudioIds = filter.StudioIds,
 01227            GenreIds = filter.GenreIds,
 01228            Genres = filter.Genres,
 01229            Years = filter.Years,
 01230            NameContains = filter.NameContains,
 01231            SearchTerm = filter.SearchTerm,
 01232            ExcludeItemIds = filter.ExcludeItemIds
 01233        };
 1234
 01235        var masterQuery = TranslateQuery(innerQuery, context, outerQueryFilter)
 01236            .GroupBy(e => e.PresentationUniqueKey)
 01237            .Select(e => e.FirstOrDefault())
 01238            .Select(e => e!.Id);
 1239
 01240        var query = context.BaseItems
 01241            .Include(e => e.TrailerTypes)
 01242            .Include(e => e.Provider)
 01243            .Include(e => e.LockedFields)
 01244            .Include(e => e.Images)
 01245            .AsSingleQuery()
 01246            .Where(e => masterQuery.Contains(e.Id));
 1247
 01248        query = ApplyOrder(query, filter);
 1249
 01250        var result = new QueryResult<(BaseItemDto, ItemCounts?)>();
 01251        if (filter.EnableTotalRecordCount)
 1252        {
 01253            result.TotalRecordCount = query.Count();
 1254        }
 1255
 01256        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 1257        {
 01258            var offset = filter.StartIndex ?? 0;
 1259
 01260            if (offset > 0)
 1261            {
 01262                query = query.Skip(offset);
 1263            }
 1264
 01265            if (filter.Limit.HasValue)
 1266            {
 01267                query = query.Take(filter.Limit.Value);
 1268            }
 1269        }
 1270
 01271        IQueryable<BaseItemEntity>? itemCountQuery = null;
 1272
 01273        if (filter.IncludeItemTypes.Length > 0)
 1274        {
 1275            // if we are to include more then one type, sub query those items beforehand.
 1276
 01277            var typeSubQuery = new InternalItemsQuery(filter.User)
 01278            {
 01279                ExcludeItemTypes = filter.ExcludeItemTypes,
 01280                IncludeItemTypes = filter.IncludeItemTypes,
 01281                MediaTypes = filter.MediaTypes,
 01282                AncestorIds = filter.AncestorIds,
 01283                ExcludeItemIds = filter.ExcludeItemIds,
 01284                ItemIds = filter.ItemIds,
 01285                TopParentIds = filter.TopParentIds,
 01286                ParentId = filter.ParentId,
 01287                IsPlayed = filter.IsPlayed
 01288            };
 1289
 01290            itemCountQuery = TranslateQuery(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderI
 01291                .Where(e => e.ItemValues!.Any(f => itemValueTypes!.Contains(f.ItemValue.Type)));
 1292
 01293            var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series];
 01294            var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
 01295            var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode];
 01296            var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum];
 01297            var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist];
 01298            var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio];
 01299            var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer];
 1300
 01301            var resultQuery = query.Select(e => new
 01302            {
 01303                item = e,
 01304                // TODO: This is bad refactor!
 01305                itemCount = new ItemCounts()
 01306                {
 01307                    SeriesCount = itemCountQuery!.Count(f => f.Type == seriesTypeName),
 01308                    EpisodeCount = itemCountQuery!.Count(f => f.Type == episodeTypeName),
 01309                    MovieCount = itemCountQuery!.Count(f => f.Type == movieTypeName),
 01310                    AlbumCount = itemCountQuery!.Count(f => f.Type == musicAlbumTypeName),
 01311                    ArtistCount = itemCountQuery!.Count(f => f.Type == musicArtistTypeName),
 01312                    SongCount = itemCountQuery!.Count(f => f.Type == audioTypeName),
 01313                    TrailerCount = itemCountQuery!.Count(f => f.Type == trailerTypeName),
 01314                }
 01315            });
 1316
 01317            result.StartIndex = filter.StartIndex ?? 0;
 01318            result.Items =
 01319            [
 01320                .. resultQuery
 01321                    .AsEnumerable()
 01322                    .Where(e => e is not null)
 01323                    .Select(e =>
 01324                    {
 01325                        return (DeserializeBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
 01326                    })
 01327            ];
 1328        }
 1329        else
 1330        {
 01331            result.StartIndex = filter.StartIndex ?? 0;
 01332            result.Items =
 01333            [
 01334                .. query
 01335                    .AsEnumerable()
 01336                    .Where(e => e is not null)
 01337                    .Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e =>
 01338                    {
 01339                        return (DeserializeBaseItem(e, filter.SkipDeserialization), null);
 01340                    })
 01341            ];
 1342        }
 1343
 01344        return result;
 01345    }
 1346
 1347    private static void PrepareFilterQuery(InternalItemsQuery query)
 1348    {
 3241349        if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
 1350        {
 01351            query.Limit = query.Limit.Value + 4;
 1352        }
 1353
 3241354        if (query.IsResumable ?? false)
 1355        {
 11356            query.IsVirtualItem = false;
 1357        }
 3241358    }
 1359
 1360    private string GetCleanValue(string value)
 1361    {
 1171362        if (string.IsNullOrWhiteSpace(value))
 1363        {
 01364            return value;
 1365        }
 1366
 1171367        return value.RemoveDiacritics().ToLowerInvariant();
 1368    }
 1369
 1370    private List<(ItemValueType MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inherited
 1371    {
 1141372        var list = new List<(ItemValueType, string)>();
 1373
 1141374        if (item is IHasArtist hasArtist)
 1375        {
 01376            list.AddRange(hasArtist.Artists.Select(i => ((ItemValueType)0, i)));
 1377        }
 1378
 1141379        if (item is IHasAlbumArtist hasAlbumArtist)
 1380        {
 01381            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (ItemValueType.AlbumArtist, i)));
 1382        }
 1383
 1141384        list.AddRange(item.Genres.Select(i => (ItemValueType.Genre, i)));
 1141385        list.AddRange(item.Studios.Select(i => (ItemValueType.Studios, i)));
 1141386        list.AddRange(item.Tags.Select(i => (ItemValueType.Tags, i)));
 1387
 1388        // keywords was 5
 1389
 1141390        list.AddRange(inheritedTags.Select(i => (ItemValueType.InheritedTags, i)));
 1391
 1392        // Remove all invalid values.
 1141393        list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
 1394
 1141395        return list;
 1396    }
 1397
 1398    private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
 1399    {
 01400        return new BaseItemImageInfo()
 01401        {
 01402            ItemId = baseItemId,
 01403            Id = Guid.NewGuid(),
 01404            Path = e.Path,
 01405            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 01406            DateModified = e.DateModified,
 01407            Height = e.Height,
 01408            Width = e.Width,
 01409            ImageType = (ImageInfoImageType)e.Type,
 01410            Item = null!
 01411        };
 1412    }
 1413
 1414    private static ItemImageInfo Map(BaseItemImageInfo e, IServerApplicationHost? appHost)
 1415    {
 01416        return new ItemImageInfo()
 01417        {
 01418            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 01419            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 01420            DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
 01421            Height = e.Height,
 01422            Width = e.Width,
 01423            Type = (ImageType)e.ImageType
 01424        };
 1425    }
 1426
 1427    private string? GetPathToSave(string path)
 1428    {
 1141429        if (path is null)
 1430        {
 01431            return null;
 1432        }
 1433
 1141434        return _appHost.ReverseVirtualPath(path);
 1435    }
 1436
 1437    private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
 1438    {
 131439        var list = new List<string>();
 1440
 131441        if (IsTypeInQuery(BaseItemKind.Person, query))
 1442        {
 11443            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!);
 1444        }
 1445
 131446        if (IsTypeInQuery(BaseItemKind.Genre, query))
 1447        {
 11448            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!);
 1449        }
 1450
 131451        if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
 1452        {
 11453            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!);
 1454        }
 1455
 131456        if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
 1457        {
 11458            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
 1459        }
 1460
 131461        if (IsTypeInQuery(BaseItemKind.Studio, query))
 1462        {
 11463            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!);
 1464        }
 1465
 131466        return list;
 1467    }
 1468
 1469    private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
 1470    {
 651471        if (query.ExcludeItemTypes.Contains(type))
 1472        {
 01473            return false;
 1474        }
 1475
 651476        return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
 1477    }
 1478
 1479    private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
 1480    {
 3241481        if (!query.GroupByPresentationUniqueKey)
 1482        {
 1241483            return false;
 1484        }
 1485
 2001486        if (query.GroupBySeriesPresentationUniqueKey)
 1487        {
 01488            return false;
 1489        }
 1490
 2001491        if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
 1492        {
 01493            return false;
 1494        }
 1495
 2001496        if (query.User is null)
 1497        {
 1981498            return false;
 1499        }
 1500
 21501        if (query.IncludeItemTypes.Length == 0)
 1502        {
 11503            return true;
 1504        }
 1505
 11506        return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
 11507            || query.IncludeItemTypes.Contains(BaseItemKind.Video)
 11508            || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
 11509            || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
 11510            || query.IncludeItemTypes.Contains(BaseItemKind.Series)
 11511            || query.IncludeItemTypes.Contains(BaseItemKind.Season);
 1512    }
 1513
 1514    private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter)
 1515    {
 3241516        var orderBy = filter.OrderBy;
 3241517        var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
 1518
 3241519        if (hasSearch)
 1520        {
 01521            orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
 1522        }
 3241523        else if (orderBy.Count == 0)
 1524        {
 2171525            return query.OrderBy(e => e.SortName);
 1526        }
 1527
 1071528        IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
 1529
 1071530        var firstOrdering = orderBy.FirstOrDefault();
 1071531        if (firstOrdering != default)
 1532        {
 1071533            var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
 1071534            if (firstOrdering.SortOrder == SortOrder.Ascending)
 1535            {
 1061536                orderedQuery = query.OrderBy(expression);
 1537            }
 1538            else
 1539            {
 11540                orderedQuery = query.OrderByDescending(expression);
 1541            }
 1542
 1071543            if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
 1544            {
 01545                if (firstOrdering.SortOrder is SortOrder.Ascending)
 1546                {
 01547                    orderedQuery = orderedQuery.ThenBy(e => e.Name);
 1548                }
 1549                else
 1550                {
 01551                    orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
 1552                }
 1553            }
 1554        }
 1555
 3001556        foreach (var item in orderBy.Skip(1))
 1557        {
 431558            var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
 431559            if (item.SortOrder == SortOrder.Ascending)
 1560            {
 431561                orderedQuery = orderedQuery!.ThenBy(expression);
 1562            }
 1563            else
 1564            {
 01565                orderedQuery = orderedQuery!.ThenByDescending(expression);
 1566            }
 1567        }
 1568
 1071569        return orderedQuery ?? query;
 1570    }
 1571
 1572    private IQueryable<BaseItemEntity> TranslateQuery(
 1573        IQueryable<BaseItemEntity> baseQuery,
 1574        JellyfinDbContext context,
 1575        InternalItemsQuery filter)
 1576    {
 1577        const int HDWidth = 1200;
 1578        const int UHDWidth = 3800;
 1579        const int UHDHeight = 2100;
 1580
 3241581        var minWidth = filter.MinWidth;
 3241582        var maxWidth = filter.MaxWidth;
 3241583        var now = DateTime.UtcNow;
 1584
 3241585        if (filter.IsHD.HasValue || filter.Is4K.HasValue)
 1586        {
 01587            bool includeSD = false;
 01588            bool includeHD = false;
 01589            bool include4K = false;
 1590
 01591            if (filter.IsHD.HasValue && !filter.IsHD.Value)
 1592            {
 01593                includeSD = true;
 1594            }
 1595
 01596            if (filter.IsHD.HasValue && filter.IsHD.Value)
 1597            {
 01598                includeHD = true;
 1599            }
 1600
 01601            if (filter.Is4K.HasValue && filter.Is4K.Value)
 1602            {
 01603                include4K = true;
 1604            }
 1605
 01606            baseQuery = baseQuery.Where(e =>
 01607                (includeSD && e.Width < HDWidth) ||
 01608                (includeHD && e.Width >= HDWidth && !(e.Width >= UHDWidth || e.Height >= UHDHeight)) ||
 01609                (include4K && (e.Width >= UHDWidth || e.Height >= UHDHeight)));
 1610        }
 1611
 3241612        if (minWidth.HasValue)
 1613        {
 01614            baseQuery = baseQuery.Where(e => e.Width >= minWidth);
 1615        }
 1616
 3241617        if (filter.MinHeight.HasValue)
 1618        {
 01619            baseQuery = baseQuery.Where(e => e.Height >= filter.MinHeight);
 1620        }
 1621
 3241622        if (maxWidth.HasValue)
 1623        {
 01624            baseQuery = baseQuery.Where(e => e.Width <= maxWidth);
 1625        }
 1626
 3241627        if (filter.MaxHeight.HasValue)
 1628        {
 01629            baseQuery = baseQuery.Where(e => e.Height <= filter.MaxHeight);
 1630        }
 1631
 3241632        if (filter.IsLocked.HasValue)
 1633        {
 511634            baseQuery = baseQuery.Where(e => e.IsLocked == filter.IsLocked);
 1635        }
 1636
 3241637        var tags = filter.Tags.ToList();
 3241638        var excludeTags = filter.ExcludeTags.ToList();
 1639
 3241640        if (filter.IsMovie == true)
 1641        {
 01642            if (filter.IncludeItemTypes.Length == 0
 01643                || filter.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01644                || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
 1645            {
 01646                baseQuery = baseQuery.Where(e => e.IsMovie);
 1647            }
 1648        }
 3241649        else if (filter.IsMovie.HasValue)
 1650        {
 01651            baseQuery = baseQuery.Where(e => e.IsMovie == filter.IsMovie);
 1652        }
 1653
 3241654        if (filter.IsSeries.HasValue)
 1655        {
 01656            baseQuery = baseQuery.Where(e => e.IsSeries == filter.IsSeries);
 1657        }
 1658
 3241659        if (filter.IsSports.HasValue)
 1660        {
 01661            if (filter.IsSports.Value)
 1662            {
 01663                tags.Add("Sports");
 1664            }
 1665            else
 1666            {
 01667                excludeTags.Add("Sports");
 1668            }
 1669        }
 1670
 3241671        if (filter.IsNews.HasValue)
 1672        {
 01673            if (filter.IsNews.Value)
 1674            {
 01675                tags.Add("News");
 1676            }
 1677            else
 1678            {
 01679                excludeTags.Add("News");
 1680            }
 1681        }
 1682
 3241683        if (filter.IsKids.HasValue)
 1684        {
 01685            if (filter.IsKids.Value)
 1686            {
 01687                tags.Add("Kids");
 1688            }
 1689            else
 1690            {
 01691                excludeTags.Add("Kids");
 1692            }
 1693        }
 1694
 3241695        if (!string.IsNullOrEmpty(filter.SearchTerm))
 1696        {
 01697            var searchTerm = filter.SearchTerm.ToLower();
 01698            if (SearchWildcardTerms.Any(f => searchTerm.Contains(f)))
 1699            {
 01700                searchTerm = $"%{searchTerm.Trim('%')}%";
 01701                baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName!.ToLower(), searchTerm) || (e.OriginalTit
 1702            }
 1703            else
 1704            {
 01705                baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null
 1706            }
 1707        }
 1708
 3241709        if (filter.IsFolder.HasValue)
 1710        {
 211711            baseQuery = baseQuery.Where(e => e.IsFolder == filter.IsFolder);
 1712        }
 1713
 3241714        var includeTypes = filter.IncludeItemTypes;
 1715
 1716        // Only specify excluded types if no included types are specified
 3241717        if (filter.IncludeItemTypes.Length == 0)
 1718        {
 2061719            var excludeTypes = filter.ExcludeItemTypes;
 2061720            if (excludeTypes.Length == 1)
 1721            {
 01722                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
 1723                {
 01724                    baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
 1725                }
 1726            }
 2061727            else if (excludeTypes.Length > 1)
 1728            {
 01729                var excludeTypeName = new List<string>();
 01730                foreach (var excludeType in excludeTypes)
 1731                {
 01732                    if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
 1733                    {
 01734                        excludeTypeName.Add(baseItemKindName!);
 1735                    }
 1736                }
 1737
 01738                baseQuery = baseQuery.Where(e => !excludeTypeName.Contains(e.Type));
 1739            }
 1740        }
 1741        else
 1742        {
 1181743            string[] types = includeTypes.Select(f => _itemTypeLookup.BaseItemKindNames.GetValueOrDefault(f)).Where(e =>
 1181744            baseQuery = baseQuery.WhereOneOrMany(types, f => f.Type);
 1745        }
 1746
 3241747        if (filter.ChannelIds.Count > 0)
 1748        {
 01749            baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value));
 1750        }
 1751
 3241752        if (!filter.ParentId.IsEmpty())
 1753        {
 1241754            baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
 1755        }
 1756
 3241757        if (!string.IsNullOrWhiteSpace(filter.Path))
 1758        {
 01759            baseQuery = baseQuery.Where(e => e.Path == filter.Path);
 1760        }
 1761
 3241762        if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))
 1763        {
 01764            baseQuery = baseQuery.Where(e => e.PresentationUniqueKey == filter.PresentationUniqueKey);
 1765        }
 1766
 3241767        if (filter.MinCommunityRating.HasValue)
 1768        {
 01769            baseQuery = baseQuery.Where(e => e.CommunityRating >= filter.MinCommunityRating);
 1770        }
 1771
 3241772        if (filter.MinIndexNumber.HasValue)
 1773        {
 01774            baseQuery = baseQuery.Where(e => e.IndexNumber >= filter.MinIndexNumber);
 1775        }
 1776
 3241777        if (filter.MinParentAndIndexNumber.HasValue)
 1778        {
 01779            baseQuery = baseQuery
 01780                .Where(e => (e.ParentIndexNumber == filter.MinParentAndIndexNumber.Value.ParentIndexNumber && e.IndexNum
 1781        }
 1782
 3241783        if (filter.MinDateCreated.HasValue)
 1784        {
 01785            baseQuery = baseQuery.Where(e => e.DateCreated >= filter.MinDateCreated);
 1786        }
 1787
 3241788        if (filter.MinDateLastSaved.HasValue)
 1789        {
 01790            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSaved.Value
 1791        }
 1792
 3241793        if (filter.MinDateLastSavedForUser.HasValue)
 1794        {
 01795            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSavedForUse
 1796        }
 1797
 3241798        if (filter.IndexNumber.HasValue)
 1799        {
 01800            baseQuery = baseQuery.Where(e => e.IndexNumber == filter.IndexNumber.Value);
 1801        }
 1802
 3241803        if (filter.ParentIndexNumber.HasValue)
 1804        {
 01805            baseQuery = baseQuery.Where(e => e.ParentIndexNumber == filter.ParentIndexNumber.Value);
 1806        }
 1807
 3241808        if (filter.ParentIndexNumberNotEquals.HasValue)
 1809        {
 01810            baseQuery = baseQuery.Where(e => e.ParentIndexNumber != filter.ParentIndexNumberNotEquals.Value || e.ParentI
 1811        }
 1812
 3241813        var minEndDate = filter.MinEndDate;
 3241814        var maxEndDate = filter.MaxEndDate;
 1815
 3241816        if (filter.HasAired.HasValue)
 1817        {
 01818            if (filter.HasAired.Value)
 1819            {
 01820                maxEndDate = DateTime.UtcNow;
 1821            }
 1822            else
 1823            {
 01824                minEndDate = DateTime.UtcNow;
 1825            }
 1826        }
 1827
 3241828        if (minEndDate.HasValue)
 1829        {
 01830            baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
 1831        }
 1832
 3241833        if (maxEndDate.HasValue)
 1834        {
 01835            baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
 1836        }
 1837
 3241838        if (filter.MinStartDate.HasValue)
 1839        {
 01840            baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
 1841        }
 1842
 3241843        if (filter.MaxStartDate.HasValue)
 1844        {
 01845            baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
 1846        }
 1847
 3241848        if (filter.MinPremiereDate.HasValue)
 1849        {
 01850            baseQuery = baseQuery.Where(e => e.PremiereDate >= filter.MinPremiereDate.Value);
 1851        }
 1852
 3241853        if (filter.MaxPremiereDate.HasValue)
 1854        {
 01855            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MaxPremiereDate.Value);
 1856        }
 1857
 3241858        if (filter.TrailerTypes.Length > 0)
 1859        {
 01860            var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray();
 01861            baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f)));
 1862        }
 1863
 3241864        if (filter.IsAiring.HasValue)
 1865        {
 01866            if (filter.IsAiring.Value)
 1867            {
 01868                baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
 1869            }
 1870            else
 1871            {
 01872                baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
 1873            }
 1874        }
 1875
 3241876        if (filter.PersonIds.Length > 0)
 1877        {
 01878            var peopleEntityIds = context.BaseItems
 01879                .WhereOneOrMany(filter.PersonIds, b => b.Id)
 01880                .Join(
 01881                    context.Peoples,
 01882                    b => b.Name,
 01883                    p => p.Name,
 01884                    (b, p) => p.Id);
 1885
 01886            baseQuery = baseQuery
 01887                .Where(e => context.PeopleBaseItemMap
 01888                    .Any(m => m.ItemId == e.Id && peopleEntityIds.Contains(m.PeopleId)));
 1889        }
 1890
 3241891        if (!string.IsNullOrWhiteSpace(filter.Person))
 1892        {
 01893            baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person));
 1894        }
 1895
 3241896        if (!string.IsNullOrWhiteSpace(filter.MinSortName))
 1897        {
 1898            // this does not makes sense.
 1899            // baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName);
 1900            // whereClauses.Add("SortName>=@MinSortName");
 1901            // statement?.TryBind("@MinSortName", query.MinSortName);
 1902        }
 1903
 3241904        if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
 1905        {
 01906            baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
 1907        }
 1908
 3241909        if (!string.IsNullOrWhiteSpace(filter.ExternalId))
 1910        {
 01911            baseQuery = baseQuery.Where(e => e.ExternalId == filter.ExternalId);
 1912        }
 1913
 3241914        if (!string.IsNullOrWhiteSpace(filter.Name))
 1915        {
 31916            var cleanName = GetCleanValue(filter.Name);
 31917            baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
 1918        }
 1919
 1920        // These are the same, for now
 3241921        var nameContains = filter.NameContains;
 3241922        if (!string.IsNullOrWhiteSpace(nameContains))
 1923        {
 01924            if (SearchWildcardTerms.Any(f => nameContains.Contains(f)))
 1925            {
 01926                nameContains = $"%{nameContains.Trim('%')}%";
 01927                baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName, nameContains) || EF.Functions.Like(e.Ori
 1928            }
 1929            else
 1930            {
 01931                baseQuery = baseQuery.Where(e =>
 01932                                    e.CleanName!.Contains(nameContains)
 01933                                    || e.OriginalTitle!.ToLower().Contains(nameContains!));
 1934            }
 1935        }
 1936
 3241937        if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
 1938        {
 01939            baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith));
 1940        }
 1941
 3241942        if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
 1943        {
 1944            // i hate this
 01945            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!
 1946        }
 1947
 3241948        if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
 1949        {
 1950            // i hate this
 01951            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDef
 1952        }
 1953
 3241954        if (filter.ImageTypes.Length > 0)
 1955        {
 1061956            var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray();
 1061957            baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f)));
 1958        }
 1959
 3241960        if (filter.IsLiked.HasValue)
 1961        {
 01962            baseQuery = baseQuery
 01963                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLike
 1964        }
 1965
 3241966        if (filter.IsFavoriteOrLiked.HasValue)
 1967        {
 01968            baseQuery = baseQuery
 01969                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1970        }
 1971
 3241972        if (filter.IsFavorite.HasValue)
 1973        {
 01974            baseQuery = baseQuery
 01975                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1976        }
 1977
 3241978        if (filter.IsPlayed.HasValue)
 1979        {
 1980            // We should probably figure this out for all folders, but for right now, this is the only place where we ne
 01981            if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series)
 1982            {
 01983                baseQuery = baseQuery.Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId))
 01984                    .Where(e => e.IsFolder == false && e.IsVirtualItem == false)
 01985                    .Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
 01986                    .Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
 1987            }
 1988            else
 1989            {
 01990                baseQuery = baseQuery
 01991                    .Select(e => new
 01992                    {
 01993                        IsPlayed = e.UserData!.Where(f => f.UserId == filter.User!.Id).Select(f => (bool?)f.Played).Firs
 01994                        Item = e
 01995                    })
 01996                    .Where(e => e.IsPlayed == filter.IsPlayed)
 01997                    .Select(f => f.Item);
 1998            }
 1999        }
 2000
 3242001        if (filter.IsResumable.HasValue)
 2002        {
 12003            if (filter.IsResumable.Value)
 2004            {
 12005                baseQuery = baseQuery
 12006                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks >
 2007            }
 2008            else
 2009            {
 02010                baseQuery = baseQuery
 02011                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks =
 2012            }
 2013        }
 2014
 3242015        if (filter.ArtistIds.Length > 0)
 2016        {
 02017            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ArtistIds);
 2018        }
 2019
 3242020        if (filter.AlbumArtistIds.Length > 0)
 2021        {
 02022            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.AlbumArtist, filter.AlbumArtistIds);
 2023        }
 2024
 3242025        if (filter.ContributingArtistIds.Length > 0)
 2026        {
 02027            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ContributingArtistIds);
 2028        }
 2029
 3242030        if (filter.AlbumIds.Length > 0)
 2031        {
 02032            var subQuery = context.BaseItems.WhereOneOrMany(filter.AlbumIds, f => f.Id);
 02033            baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.Album));
 2034        }
 2035
 3242036        if (filter.ExcludeArtistIds.Length > 0)
 2037        {
 02038            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ExcludeArtistIds, true);
 2039        }
 2040
 3242041        if (filter.GenreIds.Count > 0)
 2042        {
 02043            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Genre, filter.GenreIds.ToArray());
 2044        }
 2045
 3242046        if (filter.Genres.Count > 0)
 2047        {
 02048            var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValue
 02049            baseQuery = baseQuery
 02050                    .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Genre).Any(clea
 2051        }
 2052
 3242053        if (tags.Count > 0)
 2054        {
 02055            var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMap, stri
 02056            baseQuery = baseQuery
 02057                    .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(clean
 2058        }
 2059
 3242060        if (excludeTags.Count > 0)
 2061        {
 02062            var cleanValues = excludeTags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMa
 02063            baseQuery = baseQuery
 02064                    .Where(e => !e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(clea
 2065        }
 2066
 3242067        if (filter.StudioIds.Length > 0)
 2068        {
 02069            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Studios, filter.StudioIds.ToArray());
 2070        }
 2071
 3242072        if (filter.OfficialRatings.Length > 0)
 2073        {
 02074            baseQuery = baseQuery
 02075                   .Where(e => filter.OfficialRatings.Contains(e.OfficialRating));
 2076        }
 2077
 3242078        Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null;
 3242079        if (filter.MinParentalRating != null)
 2080        {
 02081            var min = filter.MinParentalRating;
 02082            var minScore = min.Score;
 02083            var minSubScore = min.SubScore ?? 0;
 2084
 02085            minParentalRatingFilter = e =>
 02086                e.InheritedParentalRatingValue == null ||
 02087                e.InheritedParentalRatingValue > minScore ||
 02088                (e.InheritedParentalRatingValue == minScore && (e.InheritedParentalRatingSubValue ?? 0) >= minSubScore);
 2089        }
 2090
 3242091        Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null;
 3242092        if (filter.MaxParentalRating != null)
 2093        {
 512094            var max = filter.MaxParentalRating;
 512095            var maxScore = max.Score;
 512096            var maxSubScore = max.SubScore ?? 0;
 2097
 512098            maxParentalRatingFilter = e =>
 512099                e.InheritedParentalRatingValue == null ||
 512100                e.InheritedParentalRatingValue < maxScore ||
 512101                (e.InheritedParentalRatingValue == maxScore && (e.InheritedParentalRatingSubValue ?? 0) <= maxSubScore);
 2102        }
 2103
 3242104        if (filter.HasParentalRating ?? false)
 2105        {
 02106            if (minParentalRatingFilter != null)
 2107            {
 02108                baseQuery = baseQuery.Where(minParentalRatingFilter);
 2109            }
 2110
 02111            if (maxParentalRatingFilter != null)
 2112            {
 02113                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 2114            }
 2115        }
 3242116        else if (filter.BlockUnratedItems.Length > 0)
 2117        {
 02118            var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
 02119            Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !u
 2120
 02121            if (minParentalRatingFilter != null && maxParentalRatingFilter != null)
 2122            {
 02123                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter)))
 2124            }
 02125            else if (minParentalRatingFilter != null)
 2126            {
 02127                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter));
 2128            }
 02129            else if (maxParentalRatingFilter != null)
 2130            {
 02131                baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter));
 2132            }
 2133            else
 2134            {
 02135                baseQuery = baseQuery.Where(unratedItemFilter);
 2136            }
 2137        }
 3242138        else if (minParentalRatingFilter != null || maxParentalRatingFilter != null)
 2139        {
 512140            if (minParentalRatingFilter != null)
 2141            {
 02142                baseQuery = baseQuery.Where(minParentalRatingFilter);
 2143            }
 2144
 512145            if (maxParentalRatingFilter != null)
 2146            {
 512147                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 2148            }
 2149        }
 2732150        else if (!filter.HasParentalRating ?? false)
 2151        {
 02152            baseQuery = baseQuery
 02153                .Where(e => e.InheritedParentalRatingValue == null);
 2154        }
 2155
 3242156        if (filter.HasOfficialRating.HasValue)
 2157        {
 02158            if (filter.HasOfficialRating.Value)
 2159            {
 02160                baseQuery = baseQuery
 02161                    .Where(e => e.OfficialRating != null && e.OfficialRating != string.Empty);
 2162            }
 2163            else
 2164            {
 02165                baseQuery = baseQuery
 02166                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty);
 2167            }
 2168        }
 2169
 3242170        if (filter.HasOverview.HasValue)
 2171        {
 02172            if (filter.HasOverview.Value)
 2173            {
 02174                baseQuery = baseQuery
 02175                    .Where(e => e.Overview != null && e.Overview != string.Empty);
 2176            }
 2177            else
 2178            {
 02179                baseQuery = baseQuery
 02180                    .Where(e => e.Overview == null || e.Overview == string.Empty);
 2181            }
 2182        }
 2183
 3242184        if (filter.HasOwnerId.HasValue)
 2185        {
 02186            if (filter.HasOwnerId.Value)
 2187            {
 02188                baseQuery = baseQuery
 02189                    .Where(e => e.OwnerId != null);
 2190            }
 2191            else
 2192            {
 02193                baseQuery = baseQuery
 02194                    .Where(e => e.OwnerId == null);
 2195            }
 2196        }
 2197
 3242198        if (!string.IsNullOrWhiteSpace(filter.HasNoAudioTrackWithLanguage))
 2199        {
 02200            baseQuery = baseQuery
 02201                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Audio && f.Language == filte
 2202        }
 2203
 3242204        if (!string.IsNullOrWhiteSpace(filter.HasNoInternalSubtitleTrackWithLanguage))
 2205        {
 02206            baseQuery = baseQuery
 02207                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && !f.IsExternal &&
 2208        }
 2209
 3242210        if (!string.IsNullOrWhiteSpace(filter.HasNoExternalSubtitleTrackWithLanguage))
 2211        {
 02212            baseQuery = baseQuery
 02213                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.IsExternal && 
 2214        }
 2215
 3242216        if (!string.IsNullOrWhiteSpace(filter.HasNoSubtitleTrackWithLanguage))
 2217        {
 02218            baseQuery = baseQuery
 02219                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.Language == fi
 2220        }
 2221
 3242222        if (filter.HasSubtitles.HasValue)
 2223        {
 02224            baseQuery = baseQuery
 02225                .Where(e => e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle) == filter.HasSubtit
 2226        }
 2227
 3242228        if (filter.HasChapterImages.HasValue)
 2229        {
 02230            baseQuery = baseQuery
 02231                .Where(e => e.Chapters!.Any(f => f.ImagePath != null) == filter.HasChapterImages.Value);
 2232        }
 2233
 3242234        if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
 2235        {
 172236            baseQuery = baseQuery
 172237                .Where(e => e.ParentId.HasValue && !context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)).Any
 2238        }
 2239
 3242240        if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
 2241        {
 172242            baseQuery = baseQuery
 172243                    .Where(e => !context.ItemValues.Where(f => _getAllArtistsValueTypes.Contains(f.Type)).Any(f => f.Val
 2244        }
 2245
 3242246        if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value)
 2247        {
 172248            baseQuery = baseQuery
 172249                    .Where(e => !context.ItemValues.Where(f => _getStudiosValueTypes.Contains(f.Type)).Any(f => f.Value 
 2250        }
 2251
 3242252        if (filter.IsDeadGenre.HasValue && filter.IsDeadGenre.Value)
 2253        {
 172254            baseQuery = baseQuery
 172255                    .Where(e => !context.ItemValues.Where(f => _getGenreValueTypes.Contains(f.Type)).Any(f => f.Value ==
 2256        }
 2257
 3242258        if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
 2259        {
 02260            baseQuery = baseQuery
 02261                .Where(e => !context.Peoples.Any(f => f.Name == e.Name));
 2262        }
 2263
 3242264        if (filter.Years.Length > 0)
 2265        {
 02266            baseQuery = baseQuery.WhereOneOrMany(filter.Years, e => e.ProductionYear!.Value);
 2267        }
 2268
 3242269        var isVirtualItem = filter.IsVirtualItem ?? filter.IsMissing;
 3242270        if (isVirtualItem.HasValue)
 2271        {
 222272            baseQuery = baseQuery
 222273                .Where(e => e.IsVirtualItem == isVirtualItem.Value);
 2274        }
 2275
 3242276        if (filter.IsSpecialSeason.HasValue)
 2277        {
 02278            if (filter.IsSpecialSeason.Value)
 2279            {
 02280                baseQuery = baseQuery
 02281                    .Where(e => e.IndexNumber == 0);
 2282            }
 2283            else
 2284            {
 02285                baseQuery = baseQuery
 02286                    .Where(e => e.IndexNumber != 0);
 2287            }
 2288        }
 2289
 3242290        if (filter.IsUnaired.HasValue)
 2291        {
 02292            if (filter.IsUnaired.Value)
 2293            {
 02294                baseQuery = baseQuery
 02295                    .Where(e => e.PremiereDate >= now);
 2296            }
 2297            else
 2298            {
 02299                baseQuery = baseQuery
 02300                    .Where(e => e.PremiereDate < now);
 2301            }
 2302        }
 2303
 3242304        if (filter.MediaTypes.Length > 0)
 2305        {
 212306            var mediaTypes = filter.MediaTypes.Select(f => f.ToString()).ToArray();
 212307            baseQuery = baseQuery.WhereOneOrMany(mediaTypes, e => e.MediaType);
 2308        }
 2309
 3242310        if (filter.ItemIds.Length > 0)
 2311        {
 02312            baseQuery = baseQuery.WhereOneOrMany(filter.ItemIds, e => e.Id);
 2313        }
 2314
 3242315        if (filter.ExcludeItemIds.Length > 0)
 2316        {
 02317            baseQuery = baseQuery
 02318                .Where(e => !filter.ExcludeItemIds.Contains(e.Id));
 2319        }
 2320
 3242321        if (filter.ExcludeProviderIds is not null && filter.ExcludeProviderIds.Count > 0)
 2322        {
 02323            var exclude = filter.ExcludeProviderIds.Select(e => $"{e.Key}:{e.Value}").ToArray();
 02324            baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.All(f => !ex
 2325        }
 2326
 3242327        if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0)
 2328        {
 2329            // Allow setting a null or empty value to get all items that have the specified provider set.
 02330            var includeAny = filter.HasAnyProviderId.Where(e => string.IsNullOrEmpty(e.Value)).Select(e => e.Key).ToArra
 02331            if (includeAny.Length > 0)
 2332            {
 02333                baseQuery = baseQuery.Where(e => e.Provider!.Any(f => includeAny.Contains(f.ProviderId)));
 2334            }
 2335
 02336            var includeSelected = filter.HasAnyProviderId.Where(e => !string.IsNullOrEmpty(e.Value)).Select(e => $"{e.Ke
 02337            if (includeSelected.Length > 0)
 2338            {
 02339                baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f =>
 2340            }
 2341        }
 2342
 3242343        if (filter.HasImdbId.HasValue)
 2344        {
 02345            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb"));
 2346        }
 2347
 3242348        if (filter.HasTmdbId.HasValue)
 2349        {
 02350            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb"));
 2351        }
 2352
 3242353        if (filter.HasTvdbId.HasValue)
 2354        {
 02355            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb"));
 2356        }
 2357
 3242358        var queryTopParentIds = filter.TopParentIds;
 2359
 3242360        if (queryTopParentIds.Length > 0)
 2361        {
 132362            var includedItemByNameTypes = GetItemByNameTypesInQuery(filter);
 132363            var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
 132364            if (enableItemsByName && includedItemByNameTypes.Count > 0)
 2365            {
 02366                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => 
 2367            }
 2368            else
 2369            {
 132370                baseQuery = baseQuery.WhereOneOrMany(queryTopParentIds, e => e.TopParentId!.Value);
 2371            }
 2372        }
 2373
 3242374        if (filter.AncestorIds.Length > 0)
 2375        {
 442376            baseQuery = baseQuery.Where(e => e.Parents!.Any(f => filter.AncestorIds.Contains(f.ParentItemId)));
 2377        }
 2378
 3242379        if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
 2380        {
 02381            baseQuery = baseQuery
 02382                .Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)).Where(f => f.PresentationUn
 2383        }
 2384
 3242385        if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
 2386        {
 02387            baseQuery = baseQuery
 02388                .Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
 2389        }
 2390
 3242391        if (filter.ExcludeInheritedTags.Length > 0)
 2392        {
 02393            baseQuery = baseQuery
 02394                .Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags || w.ItemValue.Typ
 02395                .Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
 2396        }
 2397
 3242398        if (filter.IncludeInheritedTags.Length > 0)
 2399        {
 2400            // Episodes do not store inherit tags from their parents in the database, and the tag may be still required 
 2401            // In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s 
 02402            if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
 2403            {
 02404                baseQuery = baseQuery
 02405                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.
 02406                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
 02407                        ||
 02408                        (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value && (w.Item
 02409                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
 2410            }
 2411
 2412            // A playlist should be accessible to its owner regardless of allowed tags.
 02413            else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
 2414            {
 02415                baseQuery = baseQuery
 02416                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.
 02417                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
 02418                        || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""));
 2419                // d        ^^ this is stupid it hate this.
 2420            }
 2421            else
 2422            {
 02423                baseQuery = baseQuery
 02424                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.
 02425                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)));
 2426            }
 2427        }
 2428
 3242429        if (filter.SeriesStatuses.Length > 0)
 2430        {
 02431            var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
 02432            baseQuery = baseQuery
 02433                .Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
 2434        }
 2435
 3242436        if (filter.BoxSetLibraryFolders.Length > 0)
 2437        {
 02438            var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).T
 02439            baseQuery = baseQuery
 02440                .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
 2441        }
 2442
 3242443        if (filter.VideoTypes.Length > 0)
 2444        {
 02445            var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"{e}\"");
 02446            baseQuery = baseQuery
 02447                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
 2448        }
 2449
 3242450        if (filter.Is3D.HasValue)
 2451        {
 02452            if (filter.Is3D.Value)
 2453            {
 02454                baseQuery = baseQuery
 02455                    .Where(e => e.Data!.Contains("Video3DFormat"));
 2456            }
 2457            else
 2458            {
 02459                baseQuery = baseQuery
 02460                    .Where(e => !e.Data!.Contains("Video3DFormat"));
 2461            }
 2462        }
 2463
 3242464        if (filter.IsPlaceHolder.HasValue)
 2465        {
 02466            if (filter.IsPlaceHolder.Value)
 2467            {
 02468                baseQuery = baseQuery
 02469                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
 2470            }
 2471            else
 2472            {
 02473                baseQuery = baseQuery
 02474                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
 2475            }
 2476        }
 2477
 3242478        if (filter.HasSpecialFeature.HasValue)
 2479        {
 02480            if (filter.HasSpecialFeature.Value)
 2481            {
 02482                baseQuery = baseQuery
 02483                    .Where(e => e.ExtraIds != null);
 2484            }
 2485            else
 2486            {
 02487                baseQuery = baseQuery
 02488                    .Where(e => e.ExtraIds == null);
 2489            }
 2490        }
 2491
 3242492        if (filter.HasTrailer.HasValue || filter.HasThemeSong.HasValue || filter.HasThemeVideo.HasValue)
 2493        {
 02494            if (filter.HasTrailer.GetValueOrDefault() || filter.HasThemeSong.GetValueOrDefault() || filter.HasThemeVideo
 2495            {
 02496                baseQuery = baseQuery
 02497                    .Where(e => e.ExtraIds != null);
 2498            }
 2499            else
 2500            {
 02501                baseQuery = baseQuery
 02502                    .Where(e => e.ExtraIds == null);
 2503            }
 2504        }
 2505
 3242506        return baseQuery;
 2507    }
 2508
 2509    /// <inheritdoc/>
 2510    public async Task<bool> ItemExistsAsync(Guid id)
 2511    {
 2512        var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 2513        await using (dbContext.ConfigureAwait(false))
 2514        {
 2515            return await dbContext.BaseItems.AnyAsync(f => f.Id == id).ConfigureAwait(false);
 2516        }
 2517    }
 2518
 2519    /// <inheritdoc/>
 2520    public bool GetIsPlayed(User user, Guid id, bool recursive)
 2521    {
 02522        using var dbContext = _dbProvider.CreateDbContext();
 2523
 02524        if (recursive)
 2525        {
 02526            var folderList = TraverseHirachyDown(id, dbContext, item => (item.IsFolder || item.IsVirtualItem));
 2527
 02528            return dbContext.BaseItems
 02529                    .Where(e => folderList.Contains(e.ParentId!.Value) && !e.IsFolder && !e.IsVirtualItem)
 02530                    .All(f => f.UserData!.Any(e => e.UserId == user.Id && e.Played));
 2531        }
 2532
 02533        return dbContext.BaseItems.Where(e => e.ParentId == id).All(f => f.UserData!.Any(e => e.UserId == user.Id && e.P
 02534    }
 2535
 2536    private static HashSet<Guid> TraverseHirachyDown(Guid parentId, JellyfinDbContext dbContext, Expression<Func<BaseIte
 2537    {
 22538        var folderStack = new HashSet<Guid>()
 22539            {
 22540                parentId
 22541            };
 22542        var folderList = new HashSet<Guid>()
 22543            {
 22544                parentId
 22545            };
 2546
 42547        while (folderStack.Count != 0)
 2548        {
 22549            var items = folderStack.ToArray();
 22550            folderStack.Clear();
 22551            var query = dbContext.BaseItems
 22552                .WhereOneOrMany(items, e => e.ParentId!.Value);
 2553
 22554            if (filter != null)
 2555            {
 02556                query = query.Where(filter);
 2557            }
 2558
 42559            foreach (var item in query.Select(e => e.Id).ToArray())
 2560            {
 02561                if (folderList.Add(item))
 2562                {
 02563                    folderStack.Add(item);
 2564                }
 2565            }
 2566        }
 2567
 22568        return folderList;
 2569    }
 2570
 2571    /// <inheritdoc/>
 2572    public IReadOnlyDictionary<string, MusicArtist[]> FindArtists(IReadOnlyList<string> artistNames)
 2573    {
 02574        using var dbContext = _dbProvider.CreateDbContext();
 2575
 02576        var artists = dbContext.BaseItems.Where(e => e.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtis
 02577            .Where(e => artistNames.Contains(e.Name))
 02578            .ToArray();
 2579
 02580        return artists.GroupBy(e => e.Name).ToDictionary(e => e.Key!, e => e.Select(f => DeserializeBaseItem(f)).Cast<Mu
 02581    }
 2582}

Methods/Properties

.cctor()
.ctor(Microsoft.EntityFrameworkCore.IDbContextFactory`1<Jellyfin.Database.Implementations.JellyfinDbContext>,MediaBrowser.Controller.IServerApplicationHost,MediaBrowser.Controller.Persistence.IItemTypeLookup,MediaBrowser.Controller.Configuration.IServerConfigurationManager,Microsoft.Extensions.Logging.ILogger`1<Jellyfin.Server.Implementations.Item.BaseItemRepository>)
DeleteItem(System.Collections.Generic.IReadOnlyList`1<System.Guid>)
UpdateInheritedValues()
GetItemIdsList(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetAllArtists(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetArtists(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetAlbumArtists(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetStudios(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetGenres(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetMusicGenres(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetStudioNames()
GetAllArtistNames()
GetMusicGenreNames()
GetGenreNames()
GetItems(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetItemList(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetLatestItemList(MediaBrowser.Controller.Entities.InternalItemsQuery,Jellyfin.Data.Enums.CollectionType)
GetNextUpSeriesKeys(MediaBrowser.Controller.Entities.InternalItemsQuery,System.DateTime)
ApplyGroupingFilter(Jellyfin.Database.Implementations.JellyfinDbContext,System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,MediaBrowser.Controller.Entities.InternalItemsQuery)
ApplyNavigations(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,MediaBrowser.Controller.Entities.InternalItemsQuery)
ApplyQueryPaging(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,MediaBrowser.Controller.Entities.InternalItemsQuery)
ApplyQueryFilter(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,MediaBrowser.Controller.Entities.InternalItemsQuery)
PrepareItemQuery(Jellyfin.Database.Implementations.JellyfinDbContext,MediaBrowser.Controller.Entities.InternalItemsQuery)
GetCount(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetItemCounts(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetType(System.String)
SaveImages(MediaBrowser.Controller.Entities.BaseItem)
SaveItems(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,System.Threading.CancellationToken)
UpdateOrInsertItems(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,System.Threading.CancellationToken)
RetrieveItem(System.Guid)
Map(Jellyfin.Database.Implementations.Entities.BaseItemEntity,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.IServerApplicationHost,Microsoft.Extensions.Logging.ILogger)
Map(MediaBrowser.Controller.Entities.BaseItem)
GetItemValueNames(System.Collections.Generic.IReadOnlyList`1<Jellyfin.Database.Implementations.Entities.ItemValueType>,System.Collections.Generic.IReadOnlyList`1<System.String>,System.Collections.Generic.IReadOnlyList`1<System.String>)
TypeRequiresDeserialization(System.Type)
DeserializeBaseItem(Jellyfin.Database.Implementations.Entities.BaseItemEntity,System.Boolean)
DeserializeBaseItem(Jellyfin.Database.Implementations.Entities.BaseItemEntity,Microsoft.Extensions.Logging.ILogger,MediaBrowser.Controller.IServerApplicationHost,System.Boolean)
GetItemValues(MediaBrowser.Controller.Entities.InternalItemsQuery,System.Collections.Generic.IReadOnlyList`1<Jellyfin.Database.Implementations.Entities.ItemValueType>,System.String)
PrepareFilterQuery(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetCleanValue(System.String)
GetItemValuesToSave(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<System.String>)
Map(System.Guid,MediaBrowser.Controller.Entities.ItemImageInfo)
Map(Jellyfin.Database.Implementations.Entities.BaseItemImageInfo,MediaBrowser.Controller.IServerApplicationHost)
GetPathToSave(System.String)
GetItemByNameTypesInQuery(MediaBrowser.Controller.Entities.InternalItemsQuery)
IsTypeInQuery(Jellyfin.Data.Enums.BaseItemKind,MediaBrowser.Controller.Entities.InternalItemsQuery)
EnableGroupByPresentationUniqueKey(MediaBrowser.Controller.Entities.InternalItemsQuery)
ApplyOrder(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,MediaBrowser.Controller.Entities.InternalItemsQuery)
TranslateQuery(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,MediaBrowser.Controller.Entities.InternalItemsQuery)
GetIsPlayed(Jellyfin.Database.Implementations.Entities.User,System.Guid,System.Boolean)
TraverseHirachyDown(System.Guid,Jellyfin.Database.Implementations.JellyfinDbContext,System.Linq.Expressions.Expression`1<System.Func`2<Jellyfin.Database.Implementations.Entities.BaseItemEntity,System.Boolean>>)
FindArtists(System.Collections.Generic.IReadOnlyList`1<System.String>)