< 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: 587
Uncovered lines: 554
Coverable lines: 1141
Total lines: 2327
Line coverage: 51.4%
Branch coverage
48%
Covered branches: 313
Total branches: 648
Branch coverage: 48.3%
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%2296%
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(...)100%11100%
ApplyQueryPaging(...)87.5%8885.71%
ApplyQueryFilter(...)100%11100%
PrepareItemQuery(...)100%22100%
GetCount(...)100%210%
GetType(...)100%11100%
SaveImages(...)100%210%
SaveItems(...)100%11100%
UpdateOrInsertItems(...)84.61%272688.5%
RetrieveItem(...)50%4485.71%
Map(...)47.36%1297679.06%
Map(...)50%725079.46%
GetItemValueNames(...)100%44100%
TypeRequiresDeserialization(...)100%11100%
DeserialiseBaseItem(...)50%101088.88%
DeserialiseBaseItem(...)83.33%151272.72%
GetItemValues(...)0%210140%
PrepareFilterQuery(...)83.33%6680%
GetCleanValue(...)50%2266.66%
GetItemValuesToSave(...)50%4481.81%
Map(...)0%620%
Map(...)0%4260%
GetPathToSave(...)50%2266.66%
GetItemByNameTypesInQuery(...)100%1010100%
IsTypeInQuery(...)75%5466.66%
EnableGroupByPresentationUniqueKey(...)0%420200%
ApplyOrder(...)57.69%332678.26%
TranslateQuery(...)46.19%2579634239.85%

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 Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations;
 19using Jellyfin.Database.Implementations.Entities;
 20using Jellyfin.Database.Implementations.Enums;
 21using Jellyfin.Extensions;
 22using Jellyfin.Extensions.Json;
 23using Jellyfin.Server.Implementations.Extensions;
 24using MediaBrowser.Common;
 25using MediaBrowser.Controller;
 26using MediaBrowser.Controller.Channels;
 27using MediaBrowser.Controller.Configuration;
 28using MediaBrowser.Controller.Entities;
 29using MediaBrowser.Controller.Entities.Audio;
 30using MediaBrowser.Controller.Entities.TV;
 31using MediaBrowser.Controller.LiveTv;
 32using MediaBrowser.Controller.Persistence;
 33using MediaBrowser.Model.Dto;
 34using MediaBrowser.Model.Entities;
 35using MediaBrowser.Model.LiveTv;
 36using MediaBrowser.Model.Querying;
 37using Microsoft.EntityFrameworkCore;
 38using Microsoft.Extensions.Logging;
 39using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 40using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 41
 42namespace Jellyfin.Server.Implementations.Item;
 43
 44/*
 45    All queries in this class and all other nullable enabled EFCore repository classes will make liberal use of the null
 46    This is done as the code isn't actually executed client side, but only the expressions are interpret and the compile
 47    This is your only warning/message regarding this topic.
 48*/
 49
 50/// <summary>
 51/// Handles all storage logic for BaseItems.
 52/// </summary>
 53public sealed class BaseItemRepository
 54    : IItemRepository
 55{
 56    /// <summary>
 57    /// This holds all the types in the running assemblies
 58    /// so that we can de-serialize properly when we don't have strong types.
 59    /// </summary>
 160    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 61    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 62    private readonly IServerApplicationHost _appHost;
 63    private readonly IItemTypeLookup _itemTypeLookup;
 64    private readonly IServerConfigurationManager _serverConfigurationManager;
 65    private readonly ILogger<BaseItemRepository> _logger;
 66
 167    private static readonly IReadOnlyList<ItemValueType> _getAllArtistsValueTypes = [ItemValueType.Artist, ItemValueType
 168    private static readonly IReadOnlyList<ItemValueType> _getArtistValueTypes = [ItemValueType.Artist];
 169    private static readonly IReadOnlyList<ItemValueType> _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist];
 170    private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios];
 171    private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre];
 72
 73    /// <summary>
 74    /// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
 75    /// </summary>
 76    /// <param name="dbProvider">The db factory.</param>
 77    /// <param name="appHost">The Application host.</param>
 78    /// <param name="itemTypeLookup">The static type lookup.</param>
 79    /// <param name="serverConfigurationManager">The server Configuration manager.</param>
 80    /// <param name="logger">System logger.</param>
 81    public BaseItemRepository(
 82        IDbContextFactory<JellyfinDbContext> dbProvider,
 83        IServerApplicationHost appHost,
 84        IItemTypeLookup itemTypeLookup,
 85        IServerConfigurationManager serverConfigurationManager,
 86        ILogger<BaseItemRepository> logger)
 87    {
 2188        _dbProvider = dbProvider;
 2189        _appHost = appHost;
 2190        _itemTypeLookup = itemTypeLookup;
 2191        _serverConfigurationManager = serverConfigurationManager;
 2192        _logger = logger;
 2193    }
 94
 95    /// <inheritdoc />
 96    public void DeleteItem(Guid id)
 97    {
 298        if (id.IsEmpty())
 99        {
 0100            throw new ArgumentException("Guid can't be empty", nameof(id));
 101        }
 102
 2103        using var context = _dbProvider.CreateDbContext();
 2104        using var transaction = context.Database.BeginTransaction();
 2105        context.AncestorIds.Where(e => e.ItemId == id || e.ParentItemId == id).ExecuteDelete();
 2106        context.AttachmentStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2107        context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2108        context.BaseItemMetadataFields.Where(e => e.ItemId == id).ExecuteDelete();
 2109        context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete();
 2110        context.BaseItemTrailerTypes.Where(e => e.ItemId == id).ExecuteDelete();
 2111        context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
 2112        context.Chapters.Where(e => e.ItemId == id).ExecuteDelete();
 2113        context.CustomItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete();
 2114        context.ItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete();
 2115        context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete();
 2116        context.ItemValuesMap.Where(e => e.ItemId == id).ExecuteDelete();
 2117        context.KeyframeData.Where(e => e.ItemId == id).ExecuteDelete();
 2118        context.MediaSegments.Where(e => e.ItemId == id).ExecuteDelete();
 2119        context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2120        context.PeopleBaseItemMap.Where(e => e.ItemId == id).ExecuteDelete();
 2121        context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete();
 2122        context.TrickplayInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2123        context.SaveChanges();
 2124        transaction.Commit();
 4125    }
 126
 127    /// <inheritdoc />
 128    public void UpdateInheritedValues()
 129    {
 10130        using var context = _dbProvider.CreateDbContext();
 10131        using var transaction = context.Database.BeginTransaction();
 132
 10133        context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
 134        // ItemValue Inheritance is now correctly mapped via AncestorId on demand
 10135        context.SaveChanges();
 136
 10137        transaction.Commit();
 20138    }
 139
 140    /// <inheritdoc />
 141    public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
 142    {
 11143        ArgumentNullException.ThrowIfNull(filter);
 11144        PrepareFilterQuery(filter);
 145
 11146        using var context = _dbProvider.CreateDbContext();
 11147        return ApplyQueryFilter(context.BaseItems.AsNoTracking(), context, filter).Select(e => e.Id).ToArray();
 11148    }
 149
 150    /// <inheritdoc />
 151    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAllArtists(InternalItemsQuery filter)
 152    {
 0153        return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtis
 154    }
 155
 156    /// <inheritdoc />
 157    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetArtists(InternalItemsQuery filter)
 158    {
 0159        return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]);
 160    }
 161
 162    /// <inheritdoc />
 163    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
 164    {
 0165        return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArti
 166    }
 167
 168    /// <inheritdoc />
 169    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetStudios(InternalItemsQuery filter)
 170    {
 0171        return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]);
 172    }
 173
 174    /// <inheritdoc />
 175    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetGenres(InternalItemsQuery filter)
 176    {
 0177        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]);
 178    }
 179
 180    /// <inheritdoc />
 181    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
 182    {
 0183        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]);
 184    }
 185
 186    /// <inheritdoc />
 187    public IReadOnlyList<string> GetStudioNames()
 188    {
 11189        return GetItemValueNames(_getStudiosValueTypes, [], []);
 190    }
 191
 192    /// <inheritdoc />
 193    public IReadOnlyList<string> GetAllArtistNames()
 194    {
 11195        return GetItemValueNames(_getAllArtistsValueTypes, [], []);
 196    }
 197
 198    /// <inheritdoc />
 199    public IReadOnlyList<string> GetMusicGenreNames()
 200    {
 11201        return GetItemValueNames(
 11202            _getGenreValueTypes,
 11203            _itemTypeLookup.MusicGenreTypes,
 11204            []);
 205    }
 206
 207    /// <inheritdoc />
 208    public IReadOnlyList<string> GetGenreNames()
 209    {
 11210        return GetItemValueNames(
 11211            _getGenreValueTypes,
 11212            [],
 11213            _itemTypeLookup.MusicGenreTypes);
 214    }
 215
 216    /// <inheritdoc />
 217    public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
 218    {
 1219        ArgumentNullException.ThrowIfNull(filter);
 1220        if (!filter.EnableTotalRecordCount || (!filter.Limit.HasValue && (filter.StartIndex ?? 0) == 0))
 221        {
 1222            var returnList = GetItemList(filter);
 1223            return new QueryResult<BaseItemDto>(
 1224                filter.StartIndex,
 1225                returnList.Count,
 1226                returnList);
 227        }
 228
 0229        PrepareFilterQuery(filter);
 0230        var result = new QueryResult<BaseItemDto>();
 231
 0232        using var context = _dbProvider.CreateDbContext();
 233
 0234        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 235
 0236        dbQuery = TranslateQuery(dbQuery, context, filter);
 0237        if (filter.EnableTotalRecordCount)
 238        {
 0239            result.TotalRecordCount = dbQuery.Count();
 240        }
 241
 0242        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 0243        dbQuery = ApplyQueryPaging(dbQuery, filter);
 244
 0245        result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDe
 0246        result.StartIndex = filter.StartIndex ?? 0;
 0247        return result;
 0248    }
 249
 250    /// <inheritdoc />
 251    public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
 252    {
 285253        ArgumentNullException.ThrowIfNull(filter);
 285254        PrepareFilterQuery(filter);
 255
 285256        using var context = _dbProvider.CreateDbContext();
 284257        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 258
 284259        dbQuery = TranslateQuery(dbQuery, context, filter);
 260
 284261        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 284262        dbQuery = ApplyQueryPaging(dbQuery, filter);
 263
 284264        return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserializ
 284265    }
 266
 267    /// <inheritdoc/>
 268    public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType)
 269    {
 0270        ArgumentNullException.ThrowIfNull(filter);
 0271        PrepareFilterQuery(filter);
 272
 273        // Early exit if collection type is not tvshows or music
 0274        if (collectionType != CollectionType.tvshows && collectionType != CollectionType.music)
 275        {
 0276            return Array.Empty<BaseItem>();
 277        }
 278
 0279        using var context = _dbProvider.CreateDbContext();
 280
 281        // Subquery to group by SeriesNames/Album and get the max Date Created for each group.
 0282        var subquery = PrepareItemQuery(context, filter);
 0283        subquery = TranslateQuery(subquery, context, filter);
 0284        var subqueryGrouped = subquery.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
 0285            .Select(g => new
 0286            {
 0287                Key = g.Key,
 0288                MaxDateCreated = g.Max(a => a.DateCreated)
 0289            })
 0290            .OrderByDescending(g => g.MaxDateCreated)
 0291            .Select(g => g);
 292
 0293        if (filter.Limit.HasValue)
 294        {
 0295            subqueryGrouped = subqueryGrouped.Take(filter.Limit.Value);
 296        }
 297
 0298        filter.Limit = null;
 299
 0300        var mainquery = PrepareItemQuery(context, filter);
 0301        mainquery = TranslateQuery(mainquery, context, filter);
 0302        mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated));
 0303        mainquery = ApplyGroupingFilter(mainquery, filter);
 0304        mainquery = ApplyQueryPaging(mainquery, filter);
 305
 0306        return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserial
 0307    }
 308
 309    /// <inheritdoc />
 310    public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff)
 311    {
 0312        ArgumentNullException.ThrowIfNull(filter);
 0313        ArgumentNullException.ThrowIfNull(filter.User);
 314
 0315        using var context = _dbProvider.CreateDbContext();
 316
 0317        var query = context.BaseItems
 0318            .AsNoTracking()
 0319            .Where(i => filter.TopParentIds.Contains(i.TopParentId!.Value))
 0320            .Where(i => i.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode])
 0321            .Join(
 0322                context.UserData.AsNoTracking(),
 0323                i => new { UserId = filter.User.Id, ItemId = i.Id },
 0324                u => new { UserId = u.UserId, ItemId = u.ItemId },
 0325                (entity, data) => new { Item = entity, UserData = data })
 0326            .GroupBy(g => g.Item.SeriesPresentationUniqueKey)
 0327            .Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
 0328            .Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
 0329            .OrderByDescending(g => g.LastPlayedDate)
 0330            .Select(g => g.Key!);
 331
 0332        if (filter.Limit.HasValue)
 333        {
 0334            query = query.Take(filter.Limit.Value);
 335        }
 336
 0337        return query.ToArray();
 0338    }
 339
 340    private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter
 341    {
 342        // This whole block is needed to filter duplicate entries on request
 343        // for the time being it cannot be used because it would destroy the ordering
 344        // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions b
 345        // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
 346
 347        // var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
 348        // if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
 349        // {
 350        //     dbQuery = ApplyOrder(dbQuery, filter);
 351        //     dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e =
 352        // }
 353        // else if (enableGroupByPresentationUniqueKey)
 354        // {
 355        //     dbQuery = ApplyOrder(dbQuery, filter);
 356        //     dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First());
 357        // }
 358        // else if (filter.GroupBySeriesPresentationUniqueKey)
 359        // {
 360        //     dbQuery = ApplyOrder(dbQuery, filter);
 361        //     dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First());
 362        // }
 363        // else
 364        // {
 365        //     dbQuery = dbQuery.Distinct();
 366        //     dbQuery = ApplyOrder(dbQuery, filter);
 367        // }
 295368        dbQuery = dbQuery.Distinct();
 295369        dbQuery = ApplyOrder(dbQuery, filter);
 370
 295371        return dbQuery;
 372    }
 373
 374    private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
 375    {
 295376        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 377        {
 82378            var offset = filter.StartIndex ?? 0;
 379
 82380            if (offset > 0)
 381            {
 0382                dbQuery = dbQuery.Skip(offset);
 383            }
 384
 82385            if (filter.Limit.HasValue)
 386            {
 82387                dbQuery = dbQuery.Take(filter.Limit.Value);
 388            }
 389        }
 390
 295391        return dbQuery;
 392    }
 393
 394    private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, I
 395    {
 11396        dbQuery = TranslateQuery(dbQuery, context, filter);
 11397        dbQuery = ApplyOrder(dbQuery, filter);
 11398        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 11399        dbQuery = ApplyQueryPaging(dbQuery, filter);
 11400        return dbQuery;
 401    }
 402
 403    private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
 404    {
 500405        IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking();
 500406        dbQuery = dbQuery.AsSingleQuery()
 500407            .Include(e => e.TrailerTypes)
 500408            .Include(e => e.Provider)
 500409            .Include(e => e.LockedFields);
 410
 500411        if (filter.DtoOptions.EnableImages)
 412        {
 500413            dbQuery = dbQuery.Include(e => e.Images);
 414        }
 415
 500416        return dbQuery;
 417    }
 418
 419    /// <inheritdoc/>
 420    public int GetCount(InternalItemsQuery filter)
 421    {
 0422        ArgumentNullException.ThrowIfNull(filter);
 423        // Hack for right now since we currently don't support filtering out these duplicates within a query
 0424        PrepareFilterQuery(filter);
 425
 0426        using var context = _dbProvider.CreateDbContext();
 0427        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 428
 0429        return dbQuery.Count();
 0430    }
 431
 432#pragma warning disable CA1307 // Specify StringComparison for clarity
 433    /// <summary>
 434    /// Gets the type.
 435    /// </summary>
 436    /// <param name="typeName">Name of the type.</param>
 437    /// <returns>Type.</returns>
 438    /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
 439    private static Type? GetType(string typeName)
 440    {
 172441        ArgumentException.ThrowIfNullOrEmpty(typeName);
 442
 443        // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static 
 444        // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are 
 172445        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 172446            .Select(a => a.GetType(k))
 172447            .FirstOrDefault(t => t is not null));
 448    }
 449
 450    /// <inheritdoc  />
 451    public void SaveImages(BaseItemDto item)
 452    {
 0453        ArgumentNullException.ThrowIfNull(item);
 454
 0455        var images = item.ImageInfos.Select(e => Map(item.Id, e));
 0456        using var context = _dbProvider.CreateDbContext();
 0457        context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
 0458        context.BaseItemImageInfos.AddRange(images);
 0459        context.SaveChanges();
 0460    }
 461
 462    /// <inheritdoc  />
 463    public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 464    {
 89465        UpdateOrInsertItems(items, cancellationToken);
 89466    }
 467
 468    /// <inheritdoc cref="IItemRepository"/>
 469    public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 470    {
 89471        ArgumentNullException.ThrowIfNull(items);
 89472        cancellationToken.ThrowIfCancellationRequested();
 473
 89474        var tuples = new List<(BaseItemDto Item, List<Guid>? AncestorIds, BaseItemDto TopParent, IEnumerable<string> Use
 356475        foreach (var item in items.GroupBy(e => e.Id).Select(e => e.Last()))
 476        {
 89477            var ancestorIds = item.SupportsAncestors ?
 89478                item.GetAncestorIds().Distinct().ToList() :
 89479                null;
 480
 89481            var topParent = item.GetTopParent();
 482
 89483            var userdataKey = item.GetUserDataKeys();
 89484            var inheritedTags = item.GetInheritedTags();
 485
 89486            tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
 487        }
 488
 89489        using var context = _dbProvider.CreateDbContext();
 89490        using var transaction = context.Database.BeginTransaction();
 491
 89492        var ids = tuples.Select(f => f.Item.Id).ToArray();
 89493        var existingItems = context.BaseItems.Where(e => ids.Contains(e.Id)).Select(f => f.Id).ToArray();
 494
 356495        foreach (var item in tuples)
 496        {
 89497            var entity = Map(item.Item);
 498            // TODO: refactor this "inconsistency"
 89499            entity.TopParentId = item.TopParent?.Id;
 500
 89501            if (!existingItems.Any(e => e == entity.Id))
 502            {
 49503                context.BaseItems.Add(entity);
 504            }
 505            else
 506            {
 40507                context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 40508                context.BaseItems.Attach(entity).State = EntityState.Modified;
 509            }
 510        }
 511
 89512        context.SaveChanges();
 513
 89514        var itemValueMaps = tuples
 89515            .Select(e => (Item: e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
 89516            .ToArray();
 89517        var allListedItemValues = itemValueMaps
 89518            .SelectMany(f => f.Values)
 89519            .Distinct()
 89520            .ToArray();
 89521        var existingValues = context.ItemValues
 89522            .Select(e => new
 89523            {
 89524                item = e,
 89525                Key = e.Type + "+" + e.Value
 89526            })
 89527            .Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key))
 89528            .Select(e => e.item)
 89529            .ToArray();
 89530        var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).S
 89531        {
 89532            CleanValue = GetCleanValue(f.Value),
 89533            ItemValueId = Guid.NewGuid(),
 89534            Type = f.MagicNumber,
 89535            Value = f.Value
 89536        }).ToArray();
 89537        context.ItemValues.AddRange(missingItemValues);
 89538        context.SaveChanges();
 539
 89540        var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
 89541        var valueMap = itemValueMaps
 89542            .Select(f => (Item: f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.
 89543            .ToArray();
 544
 89545        var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList();
 546
 356547        foreach (var item in valueMap)
 548        {
 89549            var itemMappedValues = mappedValues.Where(e => e.ItemId == item.Item.Id).ToList();
 178550            foreach (var itemValue in item.Values)
 551            {
 0552                var existingItem = itemMappedValues.FirstOrDefault(f => f.ItemValueId == itemValue.ItemValueId);
 0553                if (existingItem is null)
 554                {
 0555                    context.ItemValuesMap.Add(new ItemValueMap()
 0556                    {
 0557                        Item = null!,
 0558                        ItemId = item.Item.Id,
 0559                        ItemValue = null!,
 0560                        ItemValueId = itemValue.ItemValueId
 0561                    });
 562                }
 563                else
 564                {
 565                    // map exists, remove from list so its been handled.
 0566                    itemMappedValues.Remove(existingItem);
 567                }
 568            }
 569
 570            // all still listed values are not in the new list so remove them.
 89571            context.ItemValuesMap.RemoveRange(itemMappedValues);
 572        }
 573
 89574        context.SaveChanges();
 575
 356576        foreach (var item in tuples)
 577        {
 89578            if (item.Item.SupportsAncestors && item.AncestorIds != null)
 579            {
 89580                var existingAncestorIds = context.AncestorIds.Where(e => e.ItemId == item.Item.Id).ToList();
 89581                var validAncestorIds = context.BaseItems.Where(e => item.AncestorIds.Contains(e.Id)).Select(f => f.Id).T
 194582                foreach (var ancestorId in validAncestorIds)
 583                {
 8584                    var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
 8585                    if (existingAncestorId is null)
 586                    {
 2587                        context.AncestorIds.Add(new AncestorId()
 2588                        {
 2589                            ParentItemId = ancestorId,
 2590                            ItemId = item.Item.Id,
 2591                            Item = null!,
 2592                            ParentItem = null!
 2593                        });
 594                    }
 595                    else
 596                    {
 6597                        existingAncestorIds.Remove(existingAncestorId);
 598                    }
 599                }
 600
 89601                context.AncestorIds.RemoveRange(existingAncestorIds);
 602            }
 603        }
 604
 89605        context.SaveChanges();
 89606        transaction.Commit();
 178607    }
 608
 609    /// <inheritdoc  />
 610    public BaseItemDto? RetrieveItem(Guid id)
 611    {
 216612        if (id.IsEmpty())
 613        {
 0614            throw new ArgumentException("Guid can't be empty", nameof(id));
 615        }
 616
 216617        using var context = _dbProvider.CreateDbContext();
 216618        var item = PrepareItemQuery(context, new()
 216619        {
 216620            DtoOptions = new()
 216621            {
 216622                EnableImages = true
 216623            }
 216624        }).FirstOrDefault(e => e.Id == id);
 216625        if (item is null)
 626        {
 216627            return null;
 628        }
 629
 0630        return DeserialiseBaseItem(item);
 216631    }
 632
 633    /// <summary>
 634    /// Maps a Entity to the DTO.
 635    /// </summary>
 636    /// <param name="entity">The entity.</param>
 637    /// <param name="dto">The dto base instance.</param>
 638    /// <param name="appHost">The Application server Host.</param>
 639    /// <returns>The dto to map.</returns>
 640    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 641    {
 86642        dto.Id = entity.Id;
 86643        dto.ParentId = entity.ParentId.GetValueOrDefault();
 86644        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 86645        dto.EndDate = entity.EndDate;
 86646        dto.CommunityRating = entity.CommunityRating;
 86647        dto.CustomRating = entity.CustomRating;
 86648        dto.IndexNumber = entity.IndexNumber;
 86649        dto.IsLocked = entity.IsLocked;
 86650        dto.Name = entity.Name;
 86651        dto.OfficialRating = entity.OfficialRating;
 86652        dto.Overview = entity.Overview;
 86653        dto.ParentIndexNumber = entity.ParentIndexNumber;
 86654        dto.PremiereDate = entity.PremiereDate;
 86655        dto.ProductionYear = entity.ProductionYear;
 86656        dto.SortName = entity.SortName;
 86657        dto.ForcedSortName = entity.ForcedSortName;
 86658        dto.RunTimeTicks = entity.RunTimeTicks;
 86659        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 86660        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 86661        dto.IsInMixedFolder = entity.IsInMixedFolder;
 86662        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 86663        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 86664        dto.CriticRating = entity.CriticRating;
 86665        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 86666        dto.OriginalTitle = entity.OriginalTitle;
 86667        dto.Album = entity.Album;
 86668        dto.LUFS = entity.LUFS;
 86669        dto.NormalizationGain = entity.NormalizationGain;
 86670        dto.IsVirtualItem = entity.IsVirtualItem;
 86671        dto.ExternalSeriesId = entity.ExternalSeriesId;
 86672        dto.Tagline = entity.Tagline;
 86673        dto.TotalBitrate = entity.TotalBitrate;
 86674        dto.ExternalId = entity.ExternalId;
 86675        dto.Size = entity.Size;
 86676        dto.Genres = entity.Genres?.Split('|') ?? [];
 86677        dto.DateCreated = entity.DateCreated.GetValueOrDefault();
 86678        dto.DateModified = entity.DateModified.GetValueOrDefault();
 86679        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 86680        dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
 86681        dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
 86682        dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ow
 86683        dto.Width = entity.Width.GetValueOrDefault();
 86684        dto.Height = entity.Height.GetValueOrDefault();
 86685        if (entity.Provider is not null)
 686        {
 86687            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 688        }
 689
 86690        if (entity.ExtraType is not null)
 691        {
 0692            dto.ExtraType = (ExtraType)entity.ExtraType;
 693        }
 694
 86695        if (entity.LockedFields is not null)
 696        {
 86697            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 698        }
 699
 86700        if (entity.Audio is not null)
 701        {
 0702            dto.Audio = (ProgramAudio)entity.Audio;
 703        }
 704
 86705        dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Par
 86706        dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
 86707        dto.Studios = entity.Studios?.Split('|') ?? [];
 86708        dto.Tags = entity.Tags?.Split('|') ?? [];
 709
 86710        if (dto is IHasProgramAttributes hasProgramAttributes)
 711        {
 0712            hasProgramAttributes.IsMovie = entity.IsMovie;
 0713            hasProgramAttributes.IsSeries = entity.IsSeries;
 0714            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0715            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 716        }
 717
 86718        if (dto is LiveTvChannel liveTvChannel)
 719        {
 0720            liveTvChannel.ServiceName = entity.ExternalServiceId;
 721        }
 722
 86723        if (dto is Trailer trailer)
 724        {
 0725            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 726        }
 727
 86728        if (dto is Video video)
 729        {
 0730            video.PrimaryVersionId = entity.PrimaryVersionId;
 731        }
 732
 86733        if (dto is IHasSeries hasSeriesName)
 734        {
 0735            hasSeriesName.SeriesName = entity.SeriesName;
 0736            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0737            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 738        }
 739
 86740        if (dto is Episode episode)
 741        {
 0742            episode.SeasonName = entity.SeasonName;
 0743            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 744        }
 745
 86746        if (dto is IHasArtist hasArtists)
 747        {
 0748            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 749        }
 750
 86751        if (dto is IHasAlbumArtist hasAlbumArtists)
 752        {
 0753            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 754        }
 755
 86756        if (dto is LiveTvProgram program)
 757        {
 0758            program.ShowId = entity.ShowId;
 759        }
 760
 86761        if (entity.Images is not null)
 762        {
 86763            dto.ImageInfos = entity.Images.Select(e => Map(e, appHost)).ToArray();
 764        }
 765
 766        // dto.Type = entity.Type;
 767        // dto.Data = entity.Data;
 768        // dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
 86769        if (dto is IHasStartDate hasStartDate)
 770        {
 0771            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 772        }
 773
 774        // Fields that are present in the DB but are never actually used
 775        // dto.UnratedType = entity.UnratedType;
 776        // dto.TopParentId = entity.TopParentId;
 777        // dto.CleanName = entity.CleanName;
 778        // dto.UserDataKey = entity.UserDataKey;
 779
 86780        if (dto is Folder folder)
 781        {
 86782            folder.DateLastMediaAdded = entity.DateLastMediaAdded;
 783        }
 784
 86785        return dto;
 786    }
 787
 788    /// <summary>
 789    /// Maps a Entity to the DTO.
 790    /// </summary>
 791    /// <param name="dto">The entity.</param>
 792    /// <returns>The dto to map.</returns>
 793    public BaseItemEntity Map(BaseItemDto dto)
 794    {
 89795        var dtoType = dto.GetType();
 89796        var entity = new BaseItemEntity()
 89797        {
 89798            Type = dtoType.ToString(),
 89799            Id = dto.Id
 89800        };
 801
 89802        if (TypeRequiresDeserialization(dtoType))
 803        {
 68804            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 805        }
 806
 89807        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 89808        entity.Path = GetPathToSave(dto.Path);
 89809        entity.EndDate = dto.EndDate;
 89810        entity.CommunityRating = dto.CommunityRating;
 89811        entity.CustomRating = dto.CustomRating;
 89812        entity.IndexNumber = dto.IndexNumber;
 89813        entity.IsLocked = dto.IsLocked;
 89814        entity.Name = dto.Name;
 89815        entity.CleanName = GetCleanValue(dto.Name);
 89816        entity.OfficialRating = dto.OfficialRating;
 89817        entity.Overview = dto.Overview;
 89818        entity.ParentIndexNumber = dto.ParentIndexNumber;
 89819        entity.PremiereDate = dto.PremiereDate;
 89820        entity.ProductionYear = dto.ProductionYear;
 89821        entity.SortName = dto.SortName;
 89822        entity.ForcedSortName = dto.ForcedSortName;
 89823        entity.RunTimeTicks = dto.RunTimeTicks;
 89824        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 89825        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 89826        entity.IsInMixedFolder = dto.IsInMixedFolder;
 89827        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 89828        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 89829        entity.CriticRating = dto.CriticRating;
 89830        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 89831        entity.OriginalTitle = dto.OriginalTitle;
 89832        entity.Album = dto.Album;
 89833        entity.LUFS = dto.LUFS;
 89834        entity.NormalizationGain = dto.NormalizationGain;
 89835        entity.IsVirtualItem = dto.IsVirtualItem;
 89836        entity.ExternalSeriesId = dto.ExternalSeriesId;
 89837        entity.Tagline = dto.Tagline;
 89838        entity.TotalBitrate = dto.TotalBitrate;
 89839        entity.ExternalId = dto.ExternalId;
 89840        entity.Size = dto.Size;
 89841        entity.Genres = string.Join('|', dto.Genres);
 89842        entity.DateCreated = dto.DateCreated;
 89843        entity.DateModified = dto.DateModified;
 89844        entity.ChannelId = dto.ChannelId;
 89845        entity.DateLastRefreshed = dto.DateLastRefreshed;
 89846        entity.DateLastSaved = dto.DateLastSaved;
 89847        entity.OwnerId = dto.OwnerId.ToString();
 89848        entity.Width = dto.Width;
 89849        entity.Height = dto.Height;
 89850        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 89851        {
 89852            Item = entity,
 89853            ProviderId = e.Key,
 89854            ProviderValue = e.Value
 89855        }).ToList();
 856
 89857        if (dto.Audio.HasValue)
 858        {
 0859            entity.Audio = (ProgramAudioEntity)dto.Audio;
 860        }
 861
 89862        if (dto.ExtraType.HasValue)
 863        {
 0864            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 865        }
 866
 89867        entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
 89868        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : n
 89869        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
 89870        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
 89871        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 89872            .Select(e => new BaseItemMetadataField()
 89873            {
 89874                Id = (int)e,
 89875                Item = entity,
 89876                ItemId = entity.Id
 89877            })
 89878            .ToArray() : null;
 879
 89880        if (dto is IHasProgramAttributes hasProgramAttributes)
 881        {
 0882            entity.IsMovie = hasProgramAttributes.IsMovie;
 0883            entity.IsSeries = hasProgramAttributes.IsSeries;
 0884            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0885            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 886        }
 887
 89888        if (dto is LiveTvChannel liveTvChannel)
 889        {
 0890            entity.ExternalServiceId = liveTvChannel.ServiceName;
 891        }
 892
 89893        if (dto is Video video)
 894        {
 0895            entity.PrimaryVersionId = video.PrimaryVersionId;
 896        }
 897
 89898        if (dto is IHasSeries hasSeriesName)
 899        {
 0900            entity.SeriesName = hasSeriesName.SeriesName;
 0901            entity.SeriesId = hasSeriesName.SeriesId;
 0902            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 903        }
 904
 89905        if (dto is Episode episode)
 906        {
 0907            entity.SeasonName = episode.SeasonName;
 0908            entity.SeasonId = episode.SeasonId;
 909        }
 910
 89911        if (dto is IHasArtist hasArtists)
 912        {
 0913            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
 914        }
 915
 89916        if (dto is IHasAlbumArtist hasAlbumArtists)
 917        {
 0918            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 919        }
 920
 89921        if (dto is LiveTvProgram program)
 922        {
 0923            entity.ShowId = program.ShowId;
 924        }
 925
 89926        if (dto.ImageInfos is not null)
 927        {
 89928            entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
 929        }
 930
 89931        if (dto is Trailer trailer)
 932        {
 0933            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0934            {
 0935                Id = (int)e,
 0936                Item = entity,
 0937                ItemId = entity.Id
 0938            }).ToArray() ?? [];
 939        }
 940
 941        // dto.Type = entity.Type;
 942        // dto.Data = entity.Data;
 89943        entity.MediaType = dto.MediaType.ToString();
 89944        if (dto is IHasStartDate hasStartDate)
 945        {
 0946            entity.StartDate = hasStartDate.StartDate;
 947        }
 948
 89949        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 950
 951        // Fields that are present in the DB but are never actually used
 952        // dto.UserDataKey = entity.UserDataKey;
 953
 89954        if (dto is Folder folder)
 955        {
 89956            entity.DateLastMediaAdded = folder.DateLastMediaAdded;
 89957            entity.IsFolder = folder.IsFolder;
 958        }
 959
 89960        return entity;
 961    }
 962
 963    private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes,
 964    {
 44965        using var context = _dbProvider.CreateDbContext();
 966
 44967        var query = context.ItemValuesMap
 44968            .AsNoTracking()
 44969            .Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.Type));
 44970        if (withItemTypes.Count > 0)
 971        {
 11972            query = query.Where(e => withItemTypes.Contains(e.Item.Type));
 973        }
 974
 44975        if (excludeItemTypes.Count > 0)
 976        {
 11977            query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
 978        }
 979
 980        // query = query.DistinctBy(e => e.CleanValue);
 44981        return query.Select(e => e.ItemValue)
 44982            .GroupBy(e => e.CleanValue)
 44983            .Select(e => e.First().Value)
 44984            .ToArray();
 44985    }
 986
 987    private static bool TypeRequiresDeserialization(Type type)
 988    {
 175989        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 990    }
 991
 992    private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
 993    {
 86994        ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity));
 86995        if (_serverConfigurationManager?.Configuration is null)
 996        {
 0997            throw new InvalidOperationException("Server Configuration manager or configuration is null");
 998        }
 999
 861000        var typeToSerialise = GetType(baseItemEntity.Type);
 861001        return BaseItemRepository.DeserialiseBaseItem(
 861002            baseItemEntity,
 861003            _logger,
 861004            _appHost,
 861005            skipDeserialization || (_serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeT
 1006    }
 1007
 1008    /// <summary>
 1009    /// Deserialises a BaseItemEntity and sets all properties.
 1010    /// </summary>
 1011    /// <param name="baseItemEntity">The DB entity.</param>
 1012    /// <param name="logger">Logger.</param>
 1013    /// <param name="appHost">The application server Host.</param>
 1014    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 1015    /// <returns>A mapped BaseItem.</returns>
 1016    /// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
 1017    public static BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost?
 1018    {
 861019        var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unknown type.
 861020        BaseItemDto? dto = null;
 861021        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 1022        {
 1023            try
 1024            {
 191025                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 191026            }
 01027            catch (JsonException ex)
 1028            {
 01029                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 01030            }
 1031        }
 1032
 861033        if (dto is null)
 1034        {
 671035            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 1036        }
 1037
 861038        return Map(baseItemEntity, dto, appHost);
 1039    }
 1040
 1041    private QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyLi
 1042    {
 01043        ArgumentNullException.ThrowIfNull(filter);
 1044
 01045        if (!filter.Limit.HasValue)
 1046        {
 01047            filter.EnableTotalRecordCount = false;
 1048        }
 1049
 01050        using var context = _dbProvider.CreateDbContext();
 1051
 01052        var innerQueryFilter = TranslateQuery(context.BaseItems, context, new InternalItemsQuery(filter.User)
 01053        {
 01054            ExcludeItemTypes = filter.ExcludeItemTypes,
 01055            IncludeItemTypes = filter.IncludeItemTypes,
 01056            MediaTypes = filter.MediaTypes,
 01057            AncestorIds = filter.AncestorIds,
 01058            ItemIds = filter.ItemIds,
 01059            TopParentIds = filter.TopParentIds,
 01060            ParentId = filter.ParentId,
 01061            IsAiring = filter.IsAiring,
 01062            IsMovie = filter.IsMovie,
 01063            IsSports = filter.IsSports,
 01064            IsKids = filter.IsKids,
 01065            IsNews = filter.IsNews,
 01066            IsSeries = filter.IsSeries
 01067        });
 1068
 01069        var innerQuery = PrepareItemQuery(context, filter)
 01070            .Where(e => e.Type == returnType)
 01071            .Where(e => context.ItemValues!
 01072                .Where(f => itemValueTypes.Contains(f.Type))
 01073                .Where(f => innerQueryFilter.Any(g => f.BaseItemsMap!.Any(w => w.ItemId == g.Id)))
 01074                .Select(f => f.CleanValue)
 01075                .Contains(e.CleanName));
 1076
 01077        var outerQueryFilter = new InternalItemsQuery(filter.User)
 01078        {
 01079            IsPlayed = filter.IsPlayed,
 01080            IsFavorite = filter.IsFavorite,
 01081            IsFavoriteOrLiked = filter.IsFavoriteOrLiked,
 01082            IsLiked = filter.IsLiked,
 01083            IsLocked = filter.IsLocked,
 01084            NameLessThan = filter.NameLessThan,
 01085            NameStartsWith = filter.NameStartsWith,
 01086            NameStartsWithOrGreater = filter.NameStartsWithOrGreater,
 01087            Tags = filter.Tags,
 01088            OfficialRatings = filter.OfficialRatings,
 01089            StudioIds = filter.StudioIds,
 01090            GenreIds = filter.GenreIds,
 01091            Genres = filter.Genres,
 01092            Years = filter.Years,
 01093            NameContains = filter.NameContains,
 01094            SearchTerm = filter.SearchTerm,
 01095            ExcludeItemIds = filter.ExcludeItemIds
 01096        };
 1097
 01098        var query = TranslateQuery(innerQuery, context, outerQueryFilter)
 01099            .GroupBy(e => e.PresentationUniqueKey);
 1100
 01101        var result = new QueryResult<(BaseItemDto, ItemCounts?)>();
 01102        if (filter.EnableTotalRecordCount)
 1103        {
 01104            result.TotalRecordCount = query.Count();
 1105        }
 1106
 01107        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 1108        {
 01109            var offset = filter.StartIndex ?? 0;
 1110
 01111            if (offset > 0)
 1112            {
 01113                query = query.Skip(offset);
 1114            }
 1115
 01116            if (filter.Limit.HasValue)
 1117            {
 01118                query = query.Take(filter.Limit.Value);
 1119            }
 1120        }
 1121
 01122        IQueryable<BaseItemEntity>? itemCountQuery = null;
 1123
 01124        if (filter.IncludeItemTypes.Length > 0)
 1125        {
 1126            // if we are to include more then one type, sub query those items beforehand.
 1127
 01128            var typeSubQuery = new InternalItemsQuery(filter.User)
 01129            {
 01130                ExcludeItemTypes = filter.ExcludeItemTypes,
 01131                IncludeItemTypes = filter.IncludeItemTypes,
 01132                MediaTypes = filter.MediaTypes,
 01133                AncestorIds = filter.AncestorIds,
 01134                ExcludeItemIds = filter.ExcludeItemIds,
 01135                ItemIds = filter.ItemIds,
 01136                TopParentIds = filter.TopParentIds,
 01137                ParentId = filter.ParentId,
 01138                IsPlayed = filter.IsPlayed
 01139            };
 1140
 01141            itemCountQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, typeSubQuery)
 01142                .Where(e => e.ItemValues!.Any(f => itemValueTypes!.Contains(f.ItemValue.Type)));
 1143
 01144            var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series];
 01145            var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
 01146            var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode];
 01147            var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum];
 01148            var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist];
 01149            var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio];
 01150            var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer];
 1151
 01152            var resultQuery = query.Select(e => new
 01153            {
 01154                item = e.AsQueryable()
 01155                        .Include(e => e.TrailerTypes)
 01156                        .Include(e => e.Provider)
 01157                        .Include(e => e.LockedFields)
 01158                        .Include(e => e.Images)
 01159                        .AsSingleQuery().First(),
 01160                // TODO: This is bad refactor!
 01161                itemCount = new ItemCounts()
 01162                {
 01163                    SeriesCount = itemCountQuery!.Count(f => f.Type == seriesTypeName),
 01164                    EpisodeCount = itemCountQuery!.Count(f => f.Type == episodeTypeName),
 01165                    MovieCount = itemCountQuery!.Count(f => f.Type == movieTypeName),
 01166                    AlbumCount = itemCountQuery!.Count(f => f.Type == musicAlbumTypeName),
 01167                    ArtistCount = itemCountQuery!.Count(f => f.Type == musicArtistTypeName),
 01168                    SongCount = itemCountQuery!.Count(f => f.Type == audioTypeName),
 01169                    TrailerCount = itemCountQuery!.Count(f => f.Type == trailerTypeName),
 01170                }
 01171            });
 1172
 01173            result.StartIndex = filter.StartIndex ?? 0;
 01174            result.Items =
 01175            [
 01176                .. resultQuery
 01177                    .AsEnumerable()
 01178                    .Where(e => e is not null)
 01179                    .Select(e =>
 01180                    {
 01181                        return (DeserialiseBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
 01182                    })
 01183            ];
 1184        }
 1185        else
 1186        {
 01187            result.StartIndex = filter.StartIndex ?? 0;
 01188            result.Items =
 01189            [
 01190                .. query
 01191                    .Select(e => e.First())
 01192                    .AsEnumerable()
 01193                    .Where(e => e is not null)
 01194                    .Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e =>
 01195                    {
 01196                        return (DeserialiseBaseItem(e, filter.SkipDeserialization), null);
 01197                    })
 01198            ];
 1199        }
 1200
 01201        return result;
 01202    }
 1203
 1204    private static void PrepareFilterQuery(InternalItemsQuery query)
 1205    {
 2961206        if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
 1207        {
 01208            query.Limit = query.Limit.Value + 4;
 1209        }
 1210
 2961211        if (query.IsResumable ?? false)
 1212        {
 11213            query.IsVirtualItem = false;
 1214        }
 2961215    }
 1216
 1217    private string GetCleanValue(string value)
 1218    {
 921219        if (string.IsNullOrWhiteSpace(value))
 1220        {
 01221            return value;
 1222        }
 1223
 921224        return value.RemoveDiacritics().ToLowerInvariant();
 1225    }
 1226
 1227    private List<(ItemValueType MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inherited
 1228    {
 891229        var list = new List<(ItemValueType, string)>();
 1230
 891231        if (item is IHasArtist hasArtist)
 1232        {
 01233            list.AddRange(hasArtist.Artists.Select(i => ((ItemValueType)0, i)));
 1234        }
 1235
 891236        if (item is IHasAlbumArtist hasAlbumArtist)
 1237        {
 01238            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (ItemValueType.AlbumArtist, i)));
 1239        }
 1240
 891241        list.AddRange(item.Genres.Select(i => (ItemValueType.Genre, i)));
 891242        list.AddRange(item.Studios.Select(i => (ItemValueType.Studios, i)));
 891243        list.AddRange(item.Tags.Select(i => (ItemValueType.Tags, i)));
 1244
 1245        // keywords was 5
 1246
 891247        list.AddRange(inheritedTags.Select(i => (ItemValueType.InheritedTags, i)));
 1248
 1249        // Remove all invalid values.
 891250        list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
 1251
 891252        return list;
 1253    }
 1254
 1255    private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
 1256    {
 01257        return new BaseItemImageInfo()
 01258        {
 01259            ItemId = baseItemId,
 01260            Id = Guid.NewGuid(),
 01261            Path = e.Path,
 01262            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 01263            DateModified = e.DateModified,
 01264            Height = e.Height,
 01265            Width = e.Width,
 01266            ImageType = (ImageInfoImageType)e.Type,
 01267            Item = null!
 01268        };
 1269    }
 1270
 1271    private static ItemImageInfo Map(BaseItemImageInfo e, IServerApplicationHost? appHost)
 1272    {
 01273        return new ItemImageInfo()
 01274        {
 01275            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 01276            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 01277            DateModified = e.DateModified,
 01278            Height = e.Height,
 01279            Width = e.Width,
 01280            Type = (ImageType)e.ImageType
 01281        };
 1282    }
 1283
 1284    private string? GetPathToSave(string path)
 1285    {
 891286        if (path is null)
 1287        {
 01288            return null;
 1289        }
 1290
 891291        return _appHost.ReverseVirtualPath(path);
 1292    }
 1293
 1294    private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
 1295    {
 141296        var list = new List<string>();
 1297
 141298        if (IsTypeInQuery(BaseItemKind.Person, query))
 1299        {
 11300            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!);
 1301        }
 1302
 141303        if (IsTypeInQuery(BaseItemKind.Genre, query))
 1304        {
 11305            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!);
 1306        }
 1307
 141308        if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
 1309        {
 11310            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!);
 1311        }
 1312
 141313        if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
 1314        {
 11315            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
 1316        }
 1317
 141318        if (IsTypeInQuery(BaseItemKind.Studio, query))
 1319        {
 11320            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!);
 1321        }
 1322
 141323        return list;
 1324    }
 1325
 1326    private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
 1327    {
 701328        if (query.ExcludeItemTypes.Contains(type))
 1329        {
 01330            return false;
 1331        }
 1332
 701333        return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
 1334    }
 1335
 1336    private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
 1337    {
 01338        if (!query.GroupByPresentationUniqueKey)
 1339        {
 01340            return false;
 1341        }
 1342
 01343        if (query.GroupBySeriesPresentationUniqueKey)
 1344        {
 01345            return false;
 1346        }
 1347
 01348        if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
 1349        {
 01350            return false;
 1351        }
 1352
 01353        if (query.User is null)
 1354        {
 01355            return false;
 1356        }
 1357
 01358        if (query.IncludeItemTypes.Length == 0)
 1359        {
 01360            return true;
 1361        }
 1362
 01363        return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
 01364            || query.IncludeItemTypes.Contains(BaseItemKind.Video)
 01365            || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01366            || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
 01367            || query.IncludeItemTypes.Contains(BaseItemKind.Series)
 01368            || query.IncludeItemTypes.Contains(BaseItemKind.Season);
 1369    }
 1370
 1371    private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter)
 1372    {
 3061373        var orderBy = filter.OrderBy;
 3061374        var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
 1375
 3061376        if (hasSearch)
 1377        {
 01378            orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
 1379        }
 3061380        else if (orderBy.Count == 0)
 1381        {
 2231382            return query.OrderBy(e => e.SortName);
 1383        }
 1384
 831385        IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
 1386
 831387        var firstOrdering = orderBy.FirstOrDefault();
 831388        if (firstOrdering != default)
 1389        {
 831390            var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
 831391            if (firstOrdering.SortOrder == SortOrder.Ascending)
 1392            {
 821393                orderedQuery = query.OrderBy(expression);
 1394            }
 1395            else
 1396            {
 11397                orderedQuery = query.OrderByDescending(expression);
 1398            }
 1399
 831400            if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
 1401            {
 01402                if (firstOrdering.SortOrder is SortOrder.Ascending)
 1403                {
 01404                    orderedQuery = orderedQuery.ThenBy(e => e.Name);
 1405                }
 1406                else
 1407                {
 01408                    orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
 1409                }
 1410            }
 1411        }
 1412
 2381413        foreach (var item in orderBy.Skip(1))
 1414        {
 361415            var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
 361416            if (item.SortOrder == SortOrder.Ascending)
 1417            {
 361418                orderedQuery = orderedQuery!.ThenBy(expression);
 1419            }
 1420            else
 1421            {
 01422                orderedQuery = orderedQuery!.ThenByDescending(expression);
 1423            }
 1424        }
 1425
 831426        return orderedQuery ?? query;
 1427    }
 1428
 1429    private IQueryable<BaseItemEntity> TranslateQuery(
 1430        IQueryable<BaseItemEntity> baseQuery,
 1431        JellyfinDbContext context,
 1432        InternalItemsQuery filter)
 1433    {
 1434        const int HDWidth = 1200;
 1435        const int UHDWidth = 3800;
 1436        const int UHDHeight = 2100;
 1437
 2951438        var minWidth = filter.MinWidth;
 2951439        var maxWidth = filter.MaxWidth;
 2951440        var now = DateTime.UtcNow;
 1441
 2951442        if (filter.IsHD.HasValue || filter.Is4K.HasValue)
 1443        {
 01444            bool includeSD = false;
 01445            bool includeHD = false;
 01446            bool include4K = false;
 1447
 01448            if (filter.IsHD.HasValue && !filter.IsHD.Value)
 1449            {
 01450                includeSD = true;
 1451            }
 1452
 01453            if (filter.IsHD.HasValue && filter.IsHD.Value)
 1454            {
 01455                includeHD = true;
 1456            }
 1457
 01458            if (filter.Is4K.HasValue && filter.Is4K.Value)
 1459            {
 01460                include4K = true;
 1461            }
 1462
 01463            baseQuery = baseQuery.Where(e =>
 01464                (includeSD && e.Width < HDWidth) ||
 01465                (includeHD && e.Width >= HDWidth && !(e.Width >= UHDWidth || e.Height >= UHDHeight)) ||
 01466                (include4K && (e.Width >= UHDWidth || e.Height >= UHDHeight)));
 1467        }
 1468
 2951469        if (minWidth.HasValue)
 1470        {
 01471            baseQuery = baseQuery.Where(e => e.Width >= minWidth);
 1472        }
 1473
 2951474        if (filter.MinHeight.HasValue)
 1475        {
 01476            baseQuery = baseQuery.Where(e => e.Height >= filter.MinHeight);
 1477        }
 1478
 2951479        if (maxWidth.HasValue)
 1480        {
 01481            baseQuery = baseQuery.Where(e => e.Width >= maxWidth);
 1482        }
 1483
 2951484        if (filter.MaxHeight.HasValue)
 1485        {
 01486            baseQuery = baseQuery.Where(e => e.Height <= filter.MaxHeight);
 1487        }
 1488
 2951489        if (filter.IsLocked.HasValue)
 1490        {
 331491            baseQuery = baseQuery.Where(e => e.IsLocked == filter.IsLocked);
 1492        }
 1493
 2951494        var tags = filter.Tags.ToList();
 2951495        var excludeTags = filter.ExcludeTags.ToList();
 1496
 2951497        if (filter.IsMovie == true)
 1498        {
 01499            if (filter.IncludeItemTypes.Length == 0
 01500                || filter.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01501                || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
 1502            {
 01503                baseQuery = baseQuery.Where(e => e.IsMovie);
 1504            }
 1505        }
 2951506        else if (filter.IsMovie.HasValue)
 1507        {
 01508            baseQuery = baseQuery.Where(e => e.IsMovie == filter.IsMovie);
 1509        }
 1510
 2951511        if (filter.IsSeries.HasValue)
 1512        {
 01513            baseQuery = baseQuery.Where(e => e.IsSeries == filter.IsSeries);
 1514        }
 1515
 2951516        if (filter.IsSports.HasValue)
 1517        {
 01518            if (filter.IsSports.Value)
 1519            {
 01520                tags.Add("Sports");
 1521            }
 1522            else
 1523            {
 01524                excludeTags.Add("Sports");
 1525            }
 1526        }
 1527
 2951528        if (filter.IsNews.HasValue)
 1529        {
 01530            if (filter.IsNews.Value)
 1531            {
 01532                tags.Add("News");
 1533            }
 1534            else
 1535            {
 01536                excludeTags.Add("News");
 1537            }
 1538        }
 1539
 2951540        if (filter.IsKids.HasValue)
 1541        {
 01542            if (filter.IsKids.Value)
 1543            {
 01544                tags.Add("Kids");
 1545            }
 1546            else
 1547            {
 01548                excludeTags.Add("Kids");
 1549            }
 1550        }
 1551
 2951552        if (!string.IsNullOrEmpty(filter.SearchTerm))
 1553        {
 01554            var searchTerm = filter.SearchTerm.ToLower();
 01555            baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && 
 1556        }
 1557
 2951558        if (filter.IsFolder.HasValue)
 1559        {
 211560            baseQuery = baseQuery.Where(e => e.IsFolder == filter.IsFolder);
 1561        }
 1562
 2951563        var includeTypes = filter.IncludeItemTypes;
 1564
 1565        // Only specify excluded types if no included types are specified
 2951566        if (filter.IncludeItemTypes.Length == 0)
 1567        {
 2121568            var excludeTypes = filter.ExcludeItemTypes;
 2121569            if (excludeTypes.Length == 1)
 1570            {
 01571                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
 1572                {
 01573                    baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
 1574                }
 1575            }
 2121576            else if (excludeTypes.Length > 1)
 1577            {
 01578                var excludeTypeName = new List<string>();
 01579                foreach (var excludeType in excludeTypes)
 1580                {
 01581                    if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
 1582                    {
 01583                        excludeTypeName.Add(baseItemKindName!);
 1584                    }
 1585                }
 1586
 01587                baseQuery = baseQuery.Where(e => !excludeTypeName.Contains(e.Type));
 1588            }
 1589        }
 1590        else
 1591        {
 831592            string[] types = includeTypes.Select(f => _itemTypeLookup.BaseItemKindNames.GetValueOrDefault(f)).Where(e =>
 831593            baseQuery = baseQuery.WhereOneOrMany(types, f => f.Type);
 1594        }
 1595
 2951596        if (filter.ChannelIds.Count > 0)
 1597        {
 01598            baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value));
 1599        }
 1600
 2951601        if (!filter.ParentId.IsEmpty())
 1602        {
 1431603            baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
 1604        }
 1605
 2951606        if (!string.IsNullOrWhiteSpace(filter.Path))
 1607        {
 01608            baseQuery = baseQuery.Where(e => e.Path == filter.Path);
 1609        }
 1610
 2951611        if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))
 1612        {
 01613            baseQuery = baseQuery.Where(e => e.PresentationUniqueKey == filter.PresentationUniqueKey);
 1614        }
 1615
 2951616        if (filter.MinCommunityRating.HasValue)
 1617        {
 01618            baseQuery = baseQuery.Where(e => e.CommunityRating >= filter.MinCommunityRating);
 1619        }
 1620
 2951621        if (filter.MinIndexNumber.HasValue)
 1622        {
 01623            baseQuery = baseQuery.Where(e => e.IndexNumber >= filter.MinIndexNumber);
 1624        }
 1625
 2951626        if (filter.MinParentAndIndexNumber.HasValue)
 1627        {
 01628            baseQuery = baseQuery
 01629                .Where(e => (e.ParentIndexNumber == filter.MinParentAndIndexNumber.Value.ParentIndexNumber && e.IndexNum
 1630        }
 1631
 2951632        if (filter.MinDateCreated.HasValue)
 1633        {
 01634            baseQuery = baseQuery.Where(e => e.DateCreated >= filter.MinDateCreated);
 1635        }
 1636
 2951637        if (filter.MinDateLastSaved.HasValue)
 1638        {
 01639            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSaved.Value
 1640        }
 1641
 2951642        if (filter.MinDateLastSavedForUser.HasValue)
 1643        {
 01644            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSavedForUse
 1645        }
 1646
 2951647        if (filter.IndexNumber.HasValue)
 1648        {
 01649            baseQuery = baseQuery.Where(e => e.IndexNumber == filter.IndexNumber.Value);
 1650        }
 1651
 2951652        if (filter.ParentIndexNumber.HasValue)
 1653        {
 01654            baseQuery = baseQuery.Where(e => e.ParentIndexNumber == filter.ParentIndexNumber.Value);
 1655        }
 1656
 2951657        if (filter.ParentIndexNumberNotEquals.HasValue)
 1658        {
 01659            baseQuery = baseQuery.Where(e => e.ParentIndexNumber != filter.ParentIndexNumberNotEquals.Value || e.ParentI
 1660        }
 1661
 2951662        var minEndDate = filter.MinEndDate;
 2951663        var maxEndDate = filter.MaxEndDate;
 1664
 2951665        if (filter.HasAired.HasValue)
 1666        {
 01667            if (filter.HasAired.Value)
 1668            {
 01669                maxEndDate = DateTime.UtcNow;
 1670            }
 1671            else
 1672            {
 01673                minEndDate = DateTime.UtcNow;
 1674            }
 1675        }
 1676
 2951677        if (minEndDate.HasValue)
 1678        {
 01679            baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
 1680        }
 1681
 2951682        if (maxEndDate.HasValue)
 1683        {
 01684            baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
 1685        }
 1686
 2951687        if (filter.MinStartDate.HasValue)
 1688        {
 01689            baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
 1690        }
 1691
 2951692        if (filter.MaxStartDate.HasValue)
 1693        {
 01694            baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
 1695        }
 1696
 2951697        if (filter.MinPremiereDate.HasValue)
 1698        {
 01699            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MinPremiereDate.Value);
 1700        }
 1701
 2951702        if (filter.MaxPremiereDate.HasValue)
 1703        {
 01704            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MaxPremiereDate.Value);
 1705        }
 1706
 2951707        if (filter.TrailerTypes.Length > 0)
 1708        {
 01709            var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray();
 01710            baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f)));
 1711        }
 1712
 2951713        if (filter.IsAiring.HasValue)
 1714        {
 01715            if (filter.IsAiring.Value)
 1716            {
 01717                baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
 1718            }
 1719            else
 1720            {
 01721                baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
 1722            }
 1723        }
 1724
 2951725        if (filter.PersonIds.Length > 0)
 1726        {
 01727            baseQuery = baseQuery
 01728                .Where(e =>
 01729                    context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).A
 01730                        .Any(f => f.ItemId == e.Id));
 1731        }
 1732
 2951733        if (!string.IsNullOrWhiteSpace(filter.Person))
 1734        {
 01735            baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person));
 1736        }
 1737
 2951738        if (!string.IsNullOrWhiteSpace(filter.MinSortName))
 1739        {
 1740            // this does not makes sense.
 1741            // baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName);
 1742            // whereClauses.Add("SortName>=@MinSortName");
 1743            // statement?.TryBind("@MinSortName", query.MinSortName);
 1744        }
 1745
 2951746        if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
 1747        {
 01748            baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
 1749        }
 1750
 2951751        if (!string.IsNullOrWhiteSpace(filter.ExternalId))
 1752        {
 01753            baseQuery = baseQuery.Where(e => e.ExternalId == filter.ExternalId);
 1754        }
 1755
 2951756        if (!string.IsNullOrWhiteSpace(filter.Name))
 1757        {
 31758            var cleanName = GetCleanValue(filter.Name);
 31759            baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
 1760        }
 1761
 1762        // These are the same, for now
 2951763        var nameContains = filter.NameContains;
 2951764        if (!string.IsNullOrWhiteSpace(nameContains))
 1765        {
 01766            baseQuery = baseQuery.Where(e =>
 01767                e.CleanName!.Contains(nameContains)
 01768                || e.OriginalTitle!.ToLower().Contains(nameContains!));
 1769        }
 1770
 2951771        if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
 1772        {
 01773            baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith) || e.Name!.StartsWith(filter.
 1774        }
 1775
 2951776        if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
 1777        {
 1778            // i hate this
 01779            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!
 1780        }
 1781
 2951782        if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
 1783        {
 1784            // i hate this
 01785            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDef
 1786        }
 1787
 2951788        if (filter.ImageTypes.Length > 0)
 1789        {
 821790            var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray();
 821791            baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f)));
 1792        }
 1793
 2951794        if (filter.IsLiked.HasValue)
 1795        {
 01796            baseQuery = baseQuery
 01797                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLike
 1798        }
 1799
 2951800        if (filter.IsFavoriteOrLiked.HasValue)
 1801        {
 01802            baseQuery = baseQuery
 01803                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1804        }
 1805
 2951806        if (filter.IsFavorite.HasValue)
 1807        {
 01808            baseQuery = baseQuery
 01809                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1810        }
 1811
 2951812        if (filter.IsPlayed.HasValue)
 1813        {
 1814            // We should probably figure this out for all folders, but for right now, this is the only place where we ne
 01815            if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series)
 1816            {
 01817                baseQuery = baseQuery.Where(e => context.BaseItems
 01818                    .Where(e => e.IsFolder == false && e.IsVirtualItem == false)
 01819                    .Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
 01820                    .Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
 1821            }
 1822            else
 1823            {
 01824                baseQuery = baseQuery
 01825                    .Select(e => new
 01826                    {
 01827                        IsPlayed = e.UserData!.Where(f => f.UserId == filter.User!.Id).Select(f => (bool?)f.Played).Firs
 01828                        Item = e
 01829                    })
 01830                    .Where(e => e.IsPlayed == filter.IsPlayed)
 01831                    .Select(f => f.Item);
 1832            }
 1833        }
 1834
 2951835        if (filter.IsResumable.HasValue)
 1836        {
 11837            if (filter.IsResumable.Value)
 1838            {
 11839                baseQuery = baseQuery
 11840                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks >
 1841            }
 1842            else
 1843            {
 01844                baseQuery = baseQuery
 01845                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks =
 1846            }
 1847        }
 1848
 2951849        if (filter.ArtistIds.Length > 0)
 1850        {
 01851            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ArtistIds);
 1852        }
 1853
 2951854        if (filter.AlbumArtistIds.Length > 0)
 1855        {
 01856            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.AlbumArtistIds);
 1857        }
 1858
 2951859        if (filter.ContributingArtistIds.Length > 0)
 1860        {
 01861            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ContributingArtistIds);
 1862        }
 1863
 2951864        if (filter.AlbumIds.Length > 0)
 1865        {
 01866            var subQuery = context.BaseItems.WhereOneOrMany(filter.AlbumIds, f => f.Id);
 01867            baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.Album));
 1868        }
 1869
 2951870        if (filter.ExcludeArtistIds.Length > 0)
 1871        {
 01872            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ExcludeArtistIds, true);
 1873        }
 1874
 2951875        if (filter.GenreIds.Count > 0)
 1876        {
 01877            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Genre, filter.GenreIds.ToArray());
 1878        }
 1879
 2951880        if (filter.Genres.Count > 0)
 1881        {
 01882            var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValue
 01883            baseQuery = baseQuery
 01884                    .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Genre).Any(clea
 1885        }
 1886
 2951887        if (tags.Count > 0)
 1888        {
 01889            var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMap, stri
 01890            baseQuery = baseQuery
 01891                    .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(clean
 1892        }
 1893
 2951894        if (excludeTags.Count > 0)
 1895        {
 01896            var cleanValues = excludeTags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMa
 01897            baseQuery = baseQuery
 01898                    .Where(e => !e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(clea
 1899        }
 1900
 2951901        if (filter.StudioIds.Length > 0)
 1902        {
 01903            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Studios, filter.StudioIds.ToArray());
 1904        }
 1905
 2951906        if (filter.OfficialRatings.Length > 0)
 1907        {
 01908            baseQuery = baseQuery
 01909                   .Where(e => filter.OfficialRatings.Contains(e.OfficialRating));
 1910        }
 1911
 2951912        Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null;
 2951913        if (filter.MinParentalRating != null)
 1914        {
 01915            var min = filter.MinParentalRating;
 01916            minParentalRatingFilter = e => e.InheritedParentalRatingValue >= min.Score || e.InheritedParentalRatingValue
 01917            if (min.SubScore != null)
 1918            {
 01919                minParentalRatingFilter = minParentalRatingFilter.And(e => e.InheritedParentalRatingValue >= min.SubScor
 1920            }
 1921        }
 1922
 2951923        Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null;
 2951924        if (filter.MaxParentalRating != null)
 1925        {
 331926            var max = filter.MaxParentalRating;
 331927            maxParentalRatingFilter = e => e.InheritedParentalRatingValue <= max.Score || e.InheritedParentalRatingValue
 331928            if (max.SubScore != null)
 1929            {
 01930                maxParentalRatingFilter = maxParentalRatingFilter.And(e => e.InheritedParentalRatingValue <= max.SubScor
 1931            }
 1932        }
 1933
 2951934        if (filter.HasParentalRating ?? false)
 1935        {
 01936            if (minParentalRatingFilter != null)
 1937            {
 01938                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1939            }
 1940
 01941            if (maxParentalRatingFilter != null)
 1942            {
 01943                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1944            }
 1945        }
 2951946        else if (filter.BlockUnratedItems.Length > 0)
 1947        {
 01948            var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
 01949            Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !u
 1950
 01951            if (minParentalRatingFilter != null && maxParentalRatingFilter != null)
 1952            {
 01953                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter)))
 1954            }
 01955            else if (minParentalRatingFilter != null)
 1956            {
 01957                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter));
 1958            }
 01959            else if (maxParentalRatingFilter != null)
 1960            {
 01961                baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter));
 1962            }
 1963            else
 1964            {
 01965                baseQuery = baseQuery.Where(unratedItemFilter);
 1966            }
 1967        }
 2951968        else if (minParentalRatingFilter != null || maxParentalRatingFilter != null)
 1969        {
 331970            if (minParentalRatingFilter != null)
 1971            {
 01972                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1973            }
 1974
 331975            if (maxParentalRatingFilter != null)
 1976            {
 331977                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1978            }
 1979        }
 2621980        else if (!filter.HasParentalRating ?? false)
 1981        {
 01982            baseQuery = baseQuery
 01983                .Where(e => e.InheritedParentalRatingValue == null);
 1984        }
 1985
 2951986        if (filter.HasOfficialRating.HasValue)
 1987        {
 01988            if (filter.HasOfficialRating.Value)
 1989            {
 01990                baseQuery = baseQuery
 01991                    .Where(e => e.OfficialRating != null && e.OfficialRating != string.Empty);
 1992            }
 1993            else
 1994            {
 01995                baseQuery = baseQuery
 01996                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty);
 1997            }
 1998        }
 1999
 2952000        if (filter.HasOverview.HasValue)
 2001        {
 02002            if (filter.HasOverview.Value)
 2003            {
 02004                baseQuery = baseQuery
 02005                    .Where(e => e.Overview != null && e.Overview != string.Empty);
 2006            }
 2007            else
 2008            {
 02009                baseQuery = baseQuery
 02010                    .Where(e => e.Overview == null || e.Overview == string.Empty);
 2011            }
 2012        }
 2013
 2952014        if (filter.HasOwnerId.HasValue)
 2015        {
 02016            if (filter.HasOwnerId.Value)
 2017            {
 02018                baseQuery = baseQuery
 02019                    .Where(e => e.OwnerId != null);
 2020            }
 2021            else
 2022            {
 02023                baseQuery = baseQuery
 02024                    .Where(e => e.OwnerId == null);
 2025            }
 2026        }
 2027
 2952028        if (!string.IsNullOrWhiteSpace(filter.HasNoAudioTrackWithLanguage))
 2029        {
 02030            baseQuery = baseQuery
 02031                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Audio && f.Language == filte
 2032        }
 2033
 2952034        if (!string.IsNullOrWhiteSpace(filter.HasNoInternalSubtitleTrackWithLanguage))
 2035        {
 02036            baseQuery = baseQuery
 02037                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && !f.IsExternal &&
 2038        }
 2039
 2952040        if (!string.IsNullOrWhiteSpace(filter.HasNoExternalSubtitleTrackWithLanguage))
 2041        {
 02042            baseQuery = baseQuery
 02043                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.IsExternal && 
 2044        }
 2045
 2952046        if (!string.IsNullOrWhiteSpace(filter.HasNoSubtitleTrackWithLanguage))
 2047        {
 02048            baseQuery = baseQuery
 02049                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.Language == fi
 2050        }
 2051
 2952052        if (filter.HasSubtitles.HasValue)
 2053        {
 02054            baseQuery = baseQuery
 02055                .Where(e => e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle) == filter.HasSubtit
 2056        }
 2057
 2952058        if (filter.HasChapterImages.HasValue)
 2059        {
 02060            baseQuery = baseQuery
 02061                .Where(e => e.Chapters!.Any(f => f.ImagePath != null) == filter.HasChapterImages.Value);
 2062        }
 2063
 2952064        if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
 2065        {
 112066            baseQuery = baseQuery
 112067                .Where(e => e.ParentId.HasValue && !context.BaseItems.Any(f => f.Id == e.ParentId.Value));
 2068        }
 2069
 2952070        if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
 2071        {
 112072            baseQuery = baseQuery
 112073                    .Where(e => !context.ItemValues.Where(f => _getAllArtistsValueTypes.Contains(f.Type)).Any(f => f.Val
 2074        }
 2075
 2952076        if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value)
 2077        {
 112078            baseQuery = baseQuery
 112079                    .Where(e => !context.ItemValues.Where(f => _getStudiosValueTypes.Contains(f.Type)).Any(f => f.Value 
 2080        }
 2081
 2952082        if (filter.IsDeadGenre.HasValue && filter.IsDeadGenre.Value)
 2083        {
 112084            baseQuery = baseQuery
 112085                    .Where(e => !context.ItemValues.Where(f => _getGenreValueTypes.Contains(f.Type)).Any(f => f.Value ==
 2086        }
 2087
 2952088        if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
 2089        {
 02090            baseQuery = baseQuery
 02091                .Where(e => !context.Peoples.Any(f => f.Name == e.Name));
 2092        }
 2093
 2952094        if (filter.Years.Length > 0)
 2095        {
 02096            baseQuery = baseQuery.WhereOneOrMany(filter.Years, e => e.ProductionYear!.Value);
 2097        }
 2098
 2952099        var isVirtualItem = filter.IsVirtualItem ?? filter.IsMissing;
 2952100        if (isVirtualItem.HasValue)
 2101        {
 222102            baseQuery = baseQuery
 222103                .Where(e => e.IsVirtualItem == isVirtualItem.Value);
 2104        }
 2105
 2952106        if (filter.IsSpecialSeason.HasValue)
 2107        {
 02108            if (filter.IsSpecialSeason.Value)
 2109            {
 02110                baseQuery = baseQuery
 02111                    .Where(e => e.IndexNumber == 0);
 2112            }
 2113            else
 2114            {
 02115                baseQuery = baseQuery
 02116                    .Where(e => e.IndexNumber != 0);
 2117            }
 2118        }
 2119
 2952120        if (filter.IsUnaired.HasValue)
 2121        {
 02122            if (filter.IsUnaired.Value)
 2123            {
 02124                baseQuery = baseQuery
 02125                    .Where(e => e.PremiereDate >= now);
 2126            }
 2127            else
 2128            {
 02129                baseQuery = baseQuery
 02130                    .Where(e => e.PremiereDate < now);
 2131            }
 2132        }
 2133
 2952134        if (filter.MediaTypes.Length > 0)
 2135        {
 212136            var mediaTypes = filter.MediaTypes.Select(f => f.ToString()).ToArray();
 212137            baseQuery = baseQuery.WhereOneOrMany(mediaTypes, e => e.MediaType);
 2138        }
 2139
 2952140        if (filter.ItemIds.Length > 0)
 2141        {
 02142            baseQuery = baseQuery.WhereOneOrMany(filter.ItemIds, e => e.Id);
 2143        }
 2144
 2952145        if (filter.ExcludeItemIds.Length > 0)
 2146        {
 02147            baseQuery = baseQuery
 02148                .Where(e => !filter.ItemIds.Contains(e.Id));
 2149        }
 2150
 2952151        if (filter.ExcludeProviderIds is not null && filter.ExcludeProviderIds.Count > 0)
 2152        {
 02153            baseQuery = baseQuery.Where(e => !e.Provider!.All(f => !filter.ExcludeProviderIds.All(w => f.ProviderId == w
 2154        }
 2155
 2952156        if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0)
 2157        {
 02158            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => !filter.HasAnyProviderId.Any(w => f.ProviderId == w.Ke
 2159        }
 2160
 2952161        if (filter.HasImdbId.HasValue)
 2162        {
 02163            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb"));
 2164        }
 2165
 2952166        if (filter.HasTmdbId.HasValue)
 2167        {
 02168            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb"));
 2169        }
 2170
 2952171        if (filter.HasTvdbId.HasValue)
 2172        {
 02173            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb"));
 2174        }
 2175
 2952176        var queryTopParentIds = filter.TopParentIds;
 2177
 2952178        if (queryTopParentIds.Length > 0)
 2179        {
 142180            var includedItemByNameTypes = GetItemByNameTypesInQuery(filter);
 142181            var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
 142182            if (enableItemsByName && includedItemByNameTypes.Count > 0)
 2183            {
 02184                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => 
 2185            }
 2186            else
 2187            {
 142188                baseQuery = baseQuery.WhereOneOrMany(queryTopParentIds, e => e.TopParentId!.Value);
 2189            }
 2190        }
 2191
 2952192        if (filter.AncestorIds.Length > 0)
 2193        {
 372194            baseQuery = baseQuery.Where(e => e.Parents!.Any(f => filter.AncestorIds.Contains(f.ParentItemId)));
 2195        }
 2196
 2952197        if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
 2198        {
 02199            baseQuery = baseQuery
 02200                .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqu
 2201        }
 2202
 2952203        if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
 2204        {
 02205            baseQuery = baseQuery
 02206                .Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
 2207        }
 2208
 2952209        if (filter.ExcludeInheritedTags.Length > 0)
 2210        {
 02211            baseQuery = baseQuery
 02212                .Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags)
 02213                    .Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
 2214        }
 2215
 2952216        if (filter.IncludeInheritedTags.Length > 0)
 2217        {
 2218            // Episodes do not store inherit tags from their parents in the database, and the tag may be still required 
 2219            // In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s 
 02220            if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
 2221            {
 02222                baseQuery = baseQuery
 02223                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags)
 02224                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
 02225                        ||
 02226                        (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value)!.Where(w 
 02227                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
 2228            }
 2229
 2230            // A playlist should be accessible to its owner regardless of allowed tags.
 02231            else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
 2232            {
 02233                baseQuery = baseQuery
 02234                    .Where(e =>
 02235                    e.Parents!
 02236                        .Any(f =>
 02237                            f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeIn
 02238                            || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
 2239                // d        ^^ this is stupid it hate this.
 2240            }
 2241            else
 2242            {
 02243                baseQuery = baseQuery
 02244                    .Where(e => e.Parents!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.
 2245            }
 2246        }
 2247
 2952248        if (filter.SeriesStatuses.Length > 0)
 2249        {
 02250            var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
 02251            baseQuery = baseQuery
 02252                .Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
 2253        }
 2254
 2952255        if (filter.BoxSetLibraryFolders.Length > 0)
 2256        {
 02257            var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).T
 02258            baseQuery = baseQuery
 02259                .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
 2260        }
 2261
 2952262        if (filter.VideoTypes.Length > 0)
 2263        {
 02264            var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"{e}\"");
 02265            baseQuery = baseQuery
 02266                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
 2267        }
 2268
 2952269        if (filter.Is3D.HasValue)
 2270        {
 02271            if (filter.Is3D.Value)
 2272            {
 02273                baseQuery = baseQuery
 02274                    .Where(e => e.Data!.Contains("Video3DFormat"));
 2275            }
 2276            else
 2277            {
 02278                baseQuery = baseQuery
 02279                    .Where(e => !e.Data!.Contains("Video3DFormat"));
 2280            }
 2281        }
 2282
 2952283        if (filter.IsPlaceHolder.HasValue)
 2284        {
 02285            if (filter.IsPlaceHolder.Value)
 2286            {
 02287                baseQuery = baseQuery
 02288                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
 2289            }
 2290            else
 2291            {
 02292                baseQuery = baseQuery
 02293                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
 2294            }
 2295        }
 2296
 2952297        if (filter.HasSpecialFeature.HasValue)
 2298        {
 02299            if (filter.HasSpecialFeature.Value)
 2300            {
 02301                baseQuery = baseQuery
 02302                    .Where(e => e.ExtraIds != null);
 2303            }
 2304            else
 2305            {
 02306                baseQuery = baseQuery
 02307                    .Where(e => e.ExtraIds == null);
 2308            }
 2309        }
 2310
 2952311        if (filter.HasTrailer.HasValue || filter.HasThemeSong.HasValue || filter.HasThemeVideo.HasValue)
 2312        {
 02313            if (filter.HasTrailer.GetValueOrDefault() || filter.HasThemeSong.GetValueOrDefault() || filter.HasThemeVideo
 2314            {
 02315                baseQuery = baseQuery
 02316                    .Where(e => e.ExtraIds != null);
 2317            }
 2318            else
 2319            {
 02320                baseQuery = baseQuery
 02321                    .Where(e => e.ExtraIds == null);
 2322            }
 2323        }
 2324
 2952325        return baseQuery;
 2326    }
 2327}

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.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(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)
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)
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)
DeserialiseBaseItem(Jellyfin.Database.Implementations.Entities.BaseItemEntity,System.Boolean)
DeserialiseBaseItem(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)