< 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
52%
Covered lines: 603
Uncovered lines: 556
Coverable lines: 1159
Total lines: 2367
Line coverage: 52%
Branch coverage
48%
Covered branches: 328
Total branches: 672
Branch coverage: 48.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
DeleteItem(...)50%4496.66%
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(...)85.71%292889.69%
RetrieveItem(...)50%4485.71%
Map(...)48.78%1448279.06%
Map(...)53.22%956279.46%
GetItemValueNames(...)100%44100%
TypeRequiresDeserialization(...)100%11100%
DeserializeBaseItem(...)50%101088.88%
DeserializeBaseItem(...)83.33%151272.72%
GetItemValues(...)0%210140%
PrepareFilterQuery(...)83.33%6680%
GetCleanValue(...)50%2266.66%
GetItemValuesToSave(...)50%4481.81%
Map(...)0%620%
Map(...)0%7280%
GetPathToSave(...)50%2266.66%
GetItemByNameTypesInQuery(...)100%1010100%
IsTypeInQuery(...)75%5466.66%
EnableGroupByPresentationUniqueKey(...)0%420200%
ApplyOrder(...)57.69%332678.26%
TranslateQuery(...)46.19%2605134239.65%

File(s)

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

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2// Do not enforce that because EFCore cannot deal with cultures well.
 3#pragma warning disable CA1304 // Specify CultureInfo
 4#pragma warning disable CA1311 // Specify a culture or use an invariant version
 5#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string compari
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Collections.Generic;
 10using System.Globalization;
 11using System.Linq;
 12using System.Linq.Expressions;
 13using System.Reflection;
 14using System.Text;
 15using System.Text.Json;
 16using System.Threading;
 17using System.Threading.Tasks;
 18using Jellyfin.Data.Enums;
 19using Jellyfin.Database.Implementations;
 20using Jellyfin.Database.Implementations.Entities;
 21using Jellyfin.Database.Implementations.Enums;
 22using Jellyfin.Extensions;
 23using Jellyfin.Extensions.Json;
 24using Jellyfin.Server.Implementations.Extensions;
 25using MediaBrowser.Common;
 26using MediaBrowser.Controller;
 27using MediaBrowser.Controller.Channels;
 28using MediaBrowser.Controller.Configuration;
 29using MediaBrowser.Controller.Entities;
 30using MediaBrowser.Controller.Entities.Audio;
 31using MediaBrowser.Controller.Entities.TV;
 32using MediaBrowser.Controller.LiveTv;
 33using MediaBrowser.Controller.Persistence;
 34using MediaBrowser.Model.Dto;
 35using MediaBrowser.Model.Entities;
 36using MediaBrowser.Model.LiveTv;
 37using MediaBrowser.Model.Querying;
 38using Microsoft.EntityFrameworkCore;
 39using Microsoft.Extensions.Logging;
 40using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 41using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 42
 43namespace Jellyfin.Server.Implementations.Item;
 44
 45/*
 46    All queries in this class and all other nullable enabled EFCore repository classes will make liberal use of the null
 47    This is done as the code isn't actually executed client side, but only the expressions are interpret and the compile
 48    This is your only warning/message regarding this topic.
 49*/
 50
 51/// <summary>
 52/// Handles all storage logic for BaseItems.
 53/// </summary>
 54public sealed class BaseItemRepository
 55    : IItemRepository
 56{
 57    /// <summary>
 58    /// Gets the placeholder id for UserData detached items.
 59    /// </summary>
 160    public static readonly Guid PlaceholderId = Guid.Parse("00000000-0000-0000-0000-000000000001");
 61
 62    /// <summary>
 63    /// This holds all the types in the running assemblies
 64    /// so that we can de-serialize properly when we don't have strong types.
 65    /// </summary>
 166    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 67    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 68    private readonly IServerApplicationHost _appHost;
 69    private readonly IItemTypeLookup _itemTypeLookup;
 70    private readonly IServerConfigurationManager _serverConfigurationManager;
 71    private readonly ILogger<BaseItemRepository> _logger;
 72
 173    private static readonly IReadOnlyList<ItemValueType> _getAllArtistsValueTypes = [ItemValueType.Artist, ItemValueType
 174    private static readonly IReadOnlyList<ItemValueType> _getArtistValueTypes = [ItemValueType.Artist];
 175    private static readonly IReadOnlyList<ItemValueType> _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist];
 176    private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios];
 177    private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre];
 78
 79    /// <summary>
 80    /// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
 81    /// </summary>
 82    /// <param name="dbProvider">The db factory.</param>
 83    /// <param name="appHost">The Application host.</param>
 84    /// <param name="itemTypeLookup">The static type lookup.</param>
 85    /// <param name="serverConfigurationManager">The server Configuration manager.</param>
 86    /// <param name="logger">System logger.</param>
 87    public BaseItemRepository(
 88        IDbContextFactory<JellyfinDbContext> dbProvider,
 89        IServerApplicationHost appHost,
 90        IItemTypeLookup itemTypeLookup,
 91        IServerConfigurationManager serverConfigurationManager,
 92        ILogger<BaseItemRepository> logger)
 93    {
 2194        _dbProvider = dbProvider;
 2195        _appHost = appHost;
 2196        _itemTypeLookup = itemTypeLookup;
 2197        _serverConfigurationManager = serverConfigurationManager;
 2198        _logger = logger;
 2199    }
 100
 101    /// <inheritdoc />
 102    public void DeleteItem(Guid id)
 103    {
 3104        if (id.IsEmpty() || id.Equals(PlaceholderId))
 105        {
 0106            throw new ArgumentException("Guid can't be empty or the placeholder id.", nameof(id));
 107        }
 108
 3109        using var context = _dbProvider.CreateDbContext();
 3110        using var transaction = context.Database.BeginTransaction();
 111
 3112        var date = (DateTime?)DateTime.UtcNow;
 113        // Detach all user watch data
 3114        context.UserData.Where(e => e.ItemId == id)
 3115            .ExecuteUpdate(e => e
 3116                .SetProperty(f => f.RetentionDate, date)
 3117                .SetProperty(f => f.ItemId, PlaceholderId));
 118
 3119        context.AncestorIds.Where(e => e.ItemId == id || e.ParentItemId == id).ExecuteDelete();
 3120        context.AttachmentStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 3121        context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete();
 3122        context.BaseItemMetadataFields.Where(e => e.ItemId == id).ExecuteDelete();
 3123        context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete();
 3124        context.BaseItemTrailerTypes.Where(e => e.ItemId == id).ExecuteDelete();
 3125        context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
 3126        context.Chapters.Where(e => e.ItemId == id).ExecuteDelete();
 3127        context.CustomItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete();
 3128        context.ItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete();
 3129        context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete();
 3130        context.ItemValuesMap.Where(e => e.ItemId == id).ExecuteDelete();
 3131        context.KeyframeData.Where(e => e.ItemId == id).ExecuteDelete();
 3132        context.MediaSegments.Where(e => e.ItemId == id).ExecuteDelete();
 3133        context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 3134        context.PeopleBaseItemMap.Where(e => e.ItemId == id).ExecuteDelete();
 3135        context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete();
 3136        context.TrickplayInfos.Where(e => e.ItemId == id).ExecuteDelete();
 3137        context.SaveChanges();
 3138        transaction.Commit();
 6139    }
 140
 141    /// <inheritdoc />
 142    public void UpdateInheritedValues()
 143    {
 10144        using var context = _dbProvider.CreateDbContext();
 10145        using var transaction = context.Database.BeginTransaction();
 146
 10147        context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
 148        // ItemValue Inheritance is now correctly mapped via AncestorId on demand
 10149        context.SaveChanges();
 150
 10151        transaction.Commit();
 20152    }
 153
 154    /// <inheritdoc />
 155    public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
 156    {
 12157        ArgumentNullException.ThrowIfNull(filter);
 12158        PrepareFilterQuery(filter);
 159
 12160        using var context = _dbProvider.CreateDbContext();
 12161        return ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context
 12162    }
 163
 164    /// <inheritdoc />
 165    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAllArtists(InternalItemsQuery filter)
 166    {
 0167        return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtis
 168    }
 169
 170    /// <inheritdoc />
 171    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetArtists(InternalItemsQuery filter)
 172    {
 0173        return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]);
 174    }
 175
 176    /// <inheritdoc />
 177    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
 178    {
 0179        return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArti
 180    }
 181
 182    /// <inheritdoc />
 183    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetStudios(InternalItemsQuery filter)
 184    {
 0185        return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]);
 186    }
 187
 188    /// <inheritdoc />
 189    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetGenres(InternalItemsQuery filter)
 190    {
 0191        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]);
 192    }
 193
 194    /// <inheritdoc />
 195    public QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
 196    {
 0197        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]);
 198    }
 199
 200    /// <inheritdoc />
 201    public IReadOnlyList<string> GetStudioNames()
 202    {
 12203        return GetItemValueNames(_getStudiosValueTypes, [], []);
 204    }
 205
 206    /// <inheritdoc />
 207    public IReadOnlyList<string> GetAllArtistNames()
 208    {
 12209        return GetItemValueNames(_getAllArtistsValueTypes, [], []);
 210    }
 211
 212    /// <inheritdoc />
 213    public IReadOnlyList<string> GetMusicGenreNames()
 214    {
 12215        return GetItemValueNames(
 12216            _getGenreValueTypes,
 12217            _itemTypeLookup.MusicGenreTypes,
 12218            []);
 219    }
 220
 221    /// <inheritdoc />
 222    public IReadOnlyList<string> GetGenreNames()
 223    {
 12224        return GetItemValueNames(
 12225            _getGenreValueTypes,
 12226            [],
 12227            _itemTypeLookup.MusicGenreTypes);
 228    }
 229
 230    /// <inheritdoc />
 231    public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
 232    {
 1233        ArgumentNullException.ThrowIfNull(filter);
 1234        if (!filter.EnableTotalRecordCount || (!filter.Limit.HasValue && (filter.StartIndex ?? 0) == 0))
 235        {
 1236            var returnList = GetItemList(filter);
 1237            return new QueryResult<BaseItemDto>(
 1238                filter.StartIndex,
 1239                returnList.Count,
 1240                returnList);
 241        }
 242
 0243        PrepareFilterQuery(filter);
 0244        var result = new QueryResult<BaseItemDto>();
 245
 0246        using var context = _dbProvider.CreateDbContext();
 247
 0248        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 249
 0250        dbQuery = TranslateQuery(dbQuery, context, filter);
 0251        if (filter.EnableTotalRecordCount)
 252        {
 0253            result.TotalRecordCount = dbQuery.Count();
 254        }
 255
 0256        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 0257        dbQuery = ApplyQueryPaging(dbQuery, filter);
 258
 0259        result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDe
 0260        result.StartIndex = filter.StartIndex ?? 0;
 0261        return result;
 0262    }
 263
 264    /// <inheritdoc />
 265    public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
 266    {
 307267        ArgumentNullException.ThrowIfNull(filter);
 307268        PrepareFilterQuery(filter);
 269
 307270        using var context = _dbProvider.CreateDbContext();
 307271        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 272
 307273        dbQuery = TranslateQuery(dbQuery, context, filter);
 274
 307275        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 307276        dbQuery = ApplyQueryPaging(dbQuery, filter);
 277
 307278        return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserializ
 307279    }
 280
 281    /// <inheritdoc/>
 282    public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType)
 283    {
 0284        ArgumentNullException.ThrowIfNull(filter);
 0285        PrepareFilterQuery(filter);
 286
 287        // Early exit if collection type is not tvshows or music
 0288        if (collectionType != CollectionType.tvshows && collectionType != CollectionType.music)
 289        {
 0290            return Array.Empty<BaseItem>();
 291        }
 292
 0293        using var context = _dbProvider.CreateDbContext();
 294
 295        // Subquery to group by SeriesNames/Album and get the max Date Created for each group.
 0296        var subquery = PrepareItemQuery(context, filter);
 0297        subquery = TranslateQuery(subquery, context, filter);
 0298        var subqueryGrouped = subquery.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
 0299            .Select(g => new
 0300            {
 0301                Key = g.Key,
 0302                MaxDateCreated = g.Max(a => a.DateCreated)
 0303            })
 0304            .OrderByDescending(g => g.MaxDateCreated)
 0305            .Select(g => g);
 306
 0307        if (filter.Limit.HasValue)
 308        {
 0309            subqueryGrouped = subqueryGrouped.Take(filter.Limit.Value);
 310        }
 311
 0312        filter.Limit = null;
 313
 0314        var mainquery = PrepareItemQuery(context, filter);
 0315        mainquery = TranslateQuery(mainquery, context, filter);
 0316        mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated));
 0317        mainquery = ApplyGroupingFilter(mainquery, filter);
 0318        mainquery = ApplyQueryPaging(mainquery, filter);
 319
 0320        return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserial
 0321    }
 322
 323    /// <inheritdoc />
 324    public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff)
 325    {
 0326        ArgumentNullException.ThrowIfNull(filter);
 0327        ArgumentNullException.ThrowIfNull(filter.User);
 328
 0329        using var context = _dbProvider.CreateDbContext();
 330
 0331        var query = context.BaseItems
 0332            .AsNoTracking()
 0333            .Where(i => filter.TopParentIds.Contains(i.TopParentId!.Value))
 0334            .Where(i => i.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode])
 0335            .Join(
 0336                context.UserData.AsNoTracking().Where(e => e.ItemId != EF.Constant(PlaceholderId)),
 0337                i => new { UserId = filter.User.Id, ItemId = i.Id },
 0338                u => new { UserId = u.UserId, ItemId = u.ItemId },
 0339                (entity, data) => new { Item = entity, UserData = data })
 0340            .GroupBy(g => g.Item.SeriesPresentationUniqueKey)
 0341            .Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
 0342            .Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
 0343            .OrderByDescending(g => g.LastPlayedDate)
 0344            .Select(g => g.Key!);
 345
 0346        if (filter.Limit.HasValue)
 347        {
 0348            query = query.Take(filter.Limit.Value);
 349        }
 350
 0351        return query.ToArray();
 0352    }
 353
 354    private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter
 355    {
 356        // This whole block is needed to filter duplicate entries on request
 357        // for the time being it cannot be used because it would destroy the ordering
 358        // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions b
 359        // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
 360
 361        // var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
 362        // if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
 363        // {
 364        //     dbQuery = ApplyOrder(dbQuery, filter);
 365        //     dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e =
 366        // }
 367        // else if (enableGroupByPresentationUniqueKey)
 368        // {
 369        //     dbQuery = ApplyOrder(dbQuery, filter);
 370        //     dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First());
 371        // }
 372        // else if (filter.GroupBySeriesPresentationUniqueKey)
 373        // {
 374        //     dbQuery = ApplyOrder(dbQuery, filter);
 375        //     dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First());
 376        // }
 377        // else
 378        // {
 379        //     dbQuery = dbQuery.Distinct();
 380        //     dbQuery = ApplyOrder(dbQuery, filter);
 381        // }
 319382        dbQuery = dbQuery.Distinct();
 319383        dbQuery = ApplyOrder(dbQuery, filter);
 384
 319385        return dbQuery;
 386    }
 387
 388    private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
 389    {
 319390        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 391        {
 89392            var offset = filter.StartIndex ?? 0;
 393
 89394            if (offset > 0)
 395            {
 0396                dbQuery = dbQuery.Skip(offset);
 397            }
 398
 89399            if (filter.Limit.HasValue)
 400            {
 89401                dbQuery = dbQuery.Take(filter.Limit.Value);
 402            }
 403        }
 404
 319405        return dbQuery;
 406    }
 407
 408    private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, I
 409    {
 12410        dbQuery = TranslateQuery(dbQuery, context, filter);
 12411        dbQuery = ApplyOrder(dbQuery, filter);
 12412        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 12413        dbQuery = ApplyQueryPaging(dbQuery, filter);
 12414        return dbQuery;
 415    }
 416
 417    private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
 418    {
 523419        IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking();
 523420        dbQuery = dbQuery.AsSingleQuery()
 523421            .Include(e => e.TrailerTypes)
 523422            .Include(e => e.Provider)
 523423            .Include(e => e.LockedFields);
 424
 523425        if (filter.DtoOptions.EnableImages)
 426        {
 523427            dbQuery = dbQuery.Include(e => e.Images);
 428        }
 429
 523430        return dbQuery;
 431    }
 432
 433    /// <inheritdoc/>
 434    public int GetCount(InternalItemsQuery filter)
 435    {
 0436        ArgumentNullException.ThrowIfNull(filter);
 437        // Hack for right now since we currently don't support filtering out these duplicates within a query
 0438        PrepareFilterQuery(filter);
 439
 0440        using var context = _dbProvider.CreateDbContext();
 0441        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 442
 0443        return dbQuery.Count();
 0444    }
 445
 446#pragma warning disable CA1307 // Specify StringComparison for clarity
 447    /// <summary>
 448    /// Gets the type.
 449    /// </summary>
 450    /// <param name="typeName">Name of the type.</param>
 451    /// <returns>Type.</returns>
 452    /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
 453    private static Type? GetType(string typeName)
 454    {
 188455        ArgumentException.ThrowIfNullOrEmpty(typeName);
 456
 457        // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static 
 458        // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are 
 188459        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 188460            .Select(a => a.GetType(k))
 188461            .FirstOrDefault(t => t is not null));
 462    }
 463
 464    /// <inheritdoc  />
 465    public void SaveImages(BaseItemDto item)
 466    {
 0467        ArgumentNullException.ThrowIfNull(item);
 468
 0469        var images = item.ImageInfos.Select(e => Map(item.Id, e));
 0470        using var context = _dbProvider.CreateDbContext();
 0471        context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
 0472        context.BaseItemImageInfos.AddRange(images);
 0473        context.SaveChanges();
 0474    }
 475
 476    /// <inheritdoc  />
 477    public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 478    {
 85479        UpdateOrInsertItems(items, cancellationToken);
 85480    }
 481
 482    /// <inheritdoc cref="IItemRepository"/>
 483    public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 484    {
 85485        ArgumentNullException.ThrowIfNull(items);
 85486        cancellationToken.ThrowIfCancellationRequested();
 487
 85488        var tuples = new List<(BaseItemDto Item, List<Guid>? AncestorIds, BaseItemDto TopParent, IEnumerable<string> Use
 340489        foreach (var item in items.GroupBy(e => e.Id).Select(e => e.Last()).Where(e => e.Id != PlaceholderId))
 490        {
 85491            var ancestorIds = item.SupportsAncestors ?
 85492                item.GetAncestorIds().Distinct().ToList() :
 85493                null;
 494
 85495            var topParent = item.GetTopParent();
 496
 85497            var userdataKey = item.GetUserDataKeys();
 85498            var inheritedTags = item.GetInheritedTags();
 499
 85500            tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
 501        }
 502
 85503        using var context = _dbProvider.CreateDbContext();
 85504        using var transaction = context.Database.BeginTransaction();
 505
 85506        var ids = tuples.Select(f => f.Item.Id).ToArray();
 85507        var existingItems = context.BaseItems.Where(e => ids.Contains(e.Id)).Select(f => f.Id).ToArray();
 85508        var newItems = tuples.Where(e => !existingItems.Contains(e.Item.Id)).ToArray();
 509
 340510        foreach (var item in tuples)
 511        {
 85512            var entity = Map(item.Item);
 513            // TODO: refactor this "inconsistency"
 85514            entity.TopParentId = item.TopParent?.Id;
 515
 85516            if (!existingItems.Any(e => e == entity.Id))
 517            {
 49518                context.BaseItems.Add(entity);
 519            }
 520            else
 521            {
 36522                context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 36523                context.BaseItems.Attach(entity).State = EntityState.Modified;
 524            }
 525        }
 526
 85527        context.SaveChanges();
 528
 268529        foreach (var item in newItems)
 530        {
 531            // reattach old userData entries
 49532            var userKeys = item.UserDataKey.ToArray();
 49533            var retentionDate = (DateTime?)null;
 49534            context.UserData
 49535                .Where(e => e.ItemId == PlaceholderId)
 49536                .Where(e => userKeys.Contains(e.CustomDataKey))
 49537                .ExecuteUpdate(e => e
 49538                    .SetProperty(f => f.ItemId, item.Item.Id)
 49539                    .SetProperty(f => f.RetentionDate, retentionDate));
 540        }
 541
 85542        var itemValueMaps = tuples
 85543            .Select(e => (e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
 85544            .ToArray();
 85545        var allListedItemValues = itemValueMaps
 85546            .SelectMany(f => f.Values)
 85547            .Distinct()
 85548            .ToArray();
 85549        var existingValues = context.ItemValues
 85550            .Select(e => new
 85551            {
 85552                item = e,
 85553                Key = e.Type + "+" + e.Value
 85554            })
 85555            .Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key))
 85556            .Select(e => e.item)
 85557            .ToArray();
 85558        var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).S
 85559        {
 85560            CleanValue = GetCleanValue(f.Value),
 85561            ItemValueId = Guid.NewGuid(),
 85562            Type = f.MagicNumber,
 85563            Value = f.Value
 85564        }).ToArray();
 85565        context.ItemValues.AddRange(missingItemValues);
 85566        context.SaveChanges();
 567
 85568        var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
 85569        var valueMap = itemValueMaps
 85570            .Select(f => (f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type =
 85571            .ToArray();
 572
 85573        var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList();
 574
 340575        foreach (var item in valueMap)
 576        {
 85577            var itemMappedValues = mappedValues.Where(e => e.ItemId == item.Item.Id).ToList();
 170578            foreach (var itemValue in item.Values)
 579            {
 0580                var existingItem = itemMappedValues.FirstOrDefault(f => f.ItemValueId == itemValue.ItemValueId);
 0581                if (existingItem is null)
 582                {
 0583                    context.ItemValuesMap.Add(new ItemValueMap()
 0584                    {
 0585                        Item = null!,
 0586                        ItemId = item.Item.Id,
 0587                        ItemValue = null!,
 0588                        ItemValueId = itemValue.ItemValueId
 0589                    });
 590                }
 591                else
 592                {
 593                    // map exists, remove from list so its been handled.
 0594                    itemMappedValues.Remove(existingItem);
 595                }
 596            }
 597
 598            // all still listed values are not in the new list so remove them.
 85599            context.ItemValuesMap.RemoveRange(itemMappedValues);
 600        }
 601
 85602        context.SaveChanges();
 603
 340604        foreach (var item in tuples)
 605        {
 85606            if (item.Item.SupportsAncestors && item.AncestorIds != null)
 607            {
 85608                var existingAncestorIds = context.AncestorIds.Where(e => e.ItemId == item.Item.Id).ToList();
 85609                var validAncestorIds = context.BaseItems.Where(e => item.AncestorIds.Contains(e.Id)).Select(f => f.Id).T
 182610                foreach (var ancestorId in validAncestorIds)
 611                {
 6612                    var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
 6613                    if (existingAncestorId is null)
 614                    {
 2615                        context.AncestorIds.Add(new AncestorId()
 2616                        {
 2617                            ParentItemId = ancestorId,
 2618                            ItemId = item.Item.Id,
 2619                            Item = null!,
 2620                            ParentItem = null!
 2621                        });
 622                    }
 623                    else
 624                    {
 4625                        existingAncestorIds.Remove(existingAncestorId);
 626                    }
 627                }
 628
 85629                context.AncestorIds.RemoveRange(existingAncestorIds);
 630            }
 631        }
 632
 85633        context.SaveChanges();
 85634        transaction.Commit();
 170635    }
 636
 637    /// <inheritdoc  />
 638    public BaseItemDto? RetrieveItem(Guid id)
 639    {
 216640        if (id.IsEmpty())
 641        {
 0642            throw new ArgumentException("Guid can't be empty", nameof(id));
 643        }
 644
 216645        using var context = _dbProvider.CreateDbContext();
 216646        var item = PrepareItemQuery(context, new()
 216647        {
 216648            DtoOptions = new()
 216649            {
 216650                EnableImages = true
 216651            }
 216652        }).FirstOrDefault(e => e.Id == id);
 216653        if (item is null)
 654        {
 216655            return null;
 656        }
 657
 0658        return DeserializeBaseItem(item);
 216659    }
 660
 661    /// <summary>
 662    /// Maps a Entity to the DTO.
 663    /// </summary>
 664    /// <param name="entity">The entity.</param>
 665    /// <param name="dto">The dto base instance.</param>
 666    /// <param name="appHost">The Application server Host.</param>
 667    /// <returns>The dto to map.</returns>
 668    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 669    {
 94670        dto.Id = entity.Id;
 94671        dto.ParentId = entity.ParentId.GetValueOrDefault();
 94672        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 94673        dto.EndDate = entity.EndDate;
 94674        dto.CommunityRating = entity.CommunityRating;
 94675        dto.CustomRating = entity.CustomRating;
 94676        dto.IndexNumber = entity.IndexNumber;
 94677        dto.IsLocked = entity.IsLocked;
 94678        dto.Name = entity.Name;
 94679        dto.OfficialRating = entity.OfficialRating;
 94680        dto.Overview = entity.Overview;
 94681        dto.ParentIndexNumber = entity.ParentIndexNumber;
 94682        dto.PremiereDate = entity.PremiereDate;
 94683        dto.ProductionYear = entity.ProductionYear;
 94684        dto.SortName = entity.SortName;
 94685        dto.ForcedSortName = entity.ForcedSortName;
 94686        dto.RunTimeTicks = entity.RunTimeTicks;
 94687        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 94688        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 94689        dto.IsInMixedFolder = entity.IsInMixedFolder;
 94690        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 94691        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 94692        dto.CriticRating = entity.CriticRating;
 94693        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 94694        dto.OriginalTitle = entity.OriginalTitle;
 94695        dto.Album = entity.Album;
 94696        dto.LUFS = entity.LUFS;
 94697        dto.NormalizationGain = entity.NormalizationGain;
 94698        dto.IsVirtualItem = entity.IsVirtualItem;
 94699        dto.ExternalSeriesId = entity.ExternalSeriesId;
 94700        dto.Tagline = entity.Tagline;
 94701        dto.TotalBitrate = entity.TotalBitrate;
 94702        dto.ExternalId = entity.ExternalId;
 94703        dto.Size = entity.Size;
 94704        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 94705        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 94706        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 94707        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 94708        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 94709        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 94710        dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ow
 94711        dto.Width = entity.Width.GetValueOrDefault();
 94712        dto.Height = entity.Height.GetValueOrDefault();
 94713        if (entity.Provider is not null)
 714        {
 94715            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 716        }
 717
 94718        if (entity.ExtraType is not null)
 719        {
 0720            dto.ExtraType = (ExtraType)entity.ExtraType;
 721        }
 722
 94723        if (entity.LockedFields is not null)
 724        {
 94725            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 726        }
 727
 94728        if (entity.Audio is not null)
 729        {
 0730            dto.Audio = (ProgramAudio)entity.Audio;
 731        }
 732
 94733        dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Par
 94734        dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
 94735        dto.Studios = entity.Studios?.Split('|') ?? [];
 94736        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 737
 94738        if (dto is IHasProgramAttributes hasProgramAttributes)
 739        {
 0740            hasProgramAttributes.IsMovie = entity.IsMovie;
 0741            hasProgramAttributes.IsSeries = entity.IsSeries;
 0742            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0743            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 744        }
 745
 94746        if (dto is LiveTvChannel liveTvChannel)
 747        {
 0748            liveTvChannel.ServiceName = entity.ExternalServiceId;
 749        }
 750
 94751        if (dto is Trailer trailer)
 752        {
 0753            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 754        }
 755
 94756        if (dto is Video video)
 757        {
 0758            video.PrimaryVersionId = entity.PrimaryVersionId;
 759        }
 760
 94761        if (dto is IHasSeries hasSeriesName)
 762        {
 0763            hasSeriesName.SeriesName = entity.SeriesName;
 0764            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0765            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 766        }
 767
 94768        if (dto is Episode episode)
 769        {
 0770            episode.SeasonName = entity.SeasonName;
 0771            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 772        }
 773
 94774        if (dto is IHasArtist hasArtists)
 775        {
 0776            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 777        }
 778
 94779        if (dto is IHasAlbumArtist hasAlbumArtists)
 780        {
 0781            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 782        }
 783
 94784        if (dto is LiveTvProgram program)
 785        {
 0786            program.ShowId = entity.ShowId;
 787        }
 788
 94789        if (entity.Images is not null)
 790        {
 94791            dto.ImageInfos = entity.Images.Select(e => Map(e, appHost)).ToArray();
 792        }
 793
 794        // dto.Type = entity.Type;
 795        // dto.Data = entity.Data;
 796        // dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
 94797        if (dto is IHasStartDate hasStartDate)
 798        {
 0799            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 800        }
 801
 802        // Fields that are present in the DB but are never actually used
 803        // dto.UnratedType = entity.UnratedType;
 804        // dto.TopParentId = entity.TopParentId;
 805        // dto.CleanName = entity.CleanName;
 806        // dto.UserDataKey = entity.UserDataKey;
 807
 94808        if (dto is Folder folder)
 809        {
 94810            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 811        }
 812
 94813        return dto;
 814    }
 815
 816    /// <summary>
 817    /// Maps a Entity to the DTO.
 818    /// </summary>
 819    /// <param name="dto">The entity.</param>
 820    /// <returns>The dto to map.</returns>
 821    public BaseItemEntity Map(BaseItemDto dto)
 822    {
 85823        var dtoType = dto.GetType();
 85824        var entity = new BaseItemEntity()
 85825        {
 85826            Type = dtoType.ToString(),
 85827            Id = dto.Id
 85828        };
 829
 85830        if (TypeRequiresDeserialization(dtoType))
 831        {
 64832            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 833        }
 834
 85835        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 85836        entity.Path = GetPathToSave(dto.Path);
 85837        entity.EndDate = dto.EndDate;
 85838        entity.CommunityRating = dto.CommunityRating;
 85839        entity.CustomRating = dto.CustomRating;
 85840        entity.IndexNumber = dto.IndexNumber;
 85841        entity.IsLocked = dto.IsLocked;
 85842        entity.Name = dto.Name;
 85843        entity.CleanName = GetCleanValue(dto.Name);
 85844        entity.OfficialRating = dto.OfficialRating;
 85845        entity.Overview = dto.Overview;
 85846        entity.ParentIndexNumber = dto.ParentIndexNumber;
 85847        entity.PremiereDate = dto.PremiereDate;
 85848        entity.ProductionYear = dto.ProductionYear;
 85849        entity.SortName = dto.SortName;
 85850        entity.ForcedSortName = dto.ForcedSortName;
 85851        entity.RunTimeTicks = dto.RunTimeTicks;
 85852        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 85853        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 85854        entity.IsInMixedFolder = dto.IsInMixedFolder;
 85855        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 85856        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 85857        entity.CriticRating = dto.CriticRating;
 85858        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 85859        entity.OriginalTitle = dto.OriginalTitle;
 85860        entity.Album = dto.Album;
 85861        entity.LUFS = dto.LUFS;
 85862        entity.NormalizationGain = dto.NormalizationGain;
 85863        entity.IsVirtualItem = dto.IsVirtualItem;
 85864        entity.ExternalSeriesId = dto.ExternalSeriesId;
 85865        entity.Tagline = dto.Tagline;
 85866        entity.TotalBitrate = dto.TotalBitrate;
 85867        entity.ExternalId = dto.ExternalId;
 85868        entity.Size = dto.Size;
 85869        entity.Genres = string.Join('|', dto.Genres);
 85870        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 85871        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 85872        entity.ChannelId = dto.ChannelId;
 85873        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 85874        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 85875        entity.OwnerId = dto.OwnerId.ToString();
 85876        entity.Width = dto.Width;
 85877        entity.Height = dto.Height;
 85878        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 85879        {
 85880            Item = entity,
 85881            ProviderId = e.Key,
 85882            ProviderValue = e.Value
 85883        }).ToList();
 884
 85885        if (dto.Audio.HasValue)
 886        {
 0887            entity.Audio = (ProgramAudioEntity)dto.Audio;
 888        }
 889
 85890        if (dto.ExtraType.HasValue)
 891        {
 0892            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 893        }
 894
 85895        entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
 85896        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : n
 85897        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
 85898        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
 85899        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 85900            .Select(e => new BaseItemMetadataField()
 85901            {
 85902                Id = (int)e,
 85903                Item = entity,
 85904                ItemId = entity.Id
 85905            })
 85906            .ToArray() : null;
 907
 85908        if (dto is IHasProgramAttributes hasProgramAttributes)
 909        {
 0910            entity.IsMovie = hasProgramAttributes.IsMovie;
 0911            entity.IsSeries = hasProgramAttributes.IsSeries;
 0912            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0913            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 914        }
 915
 85916        if (dto is LiveTvChannel liveTvChannel)
 917        {
 0918            entity.ExternalServiceId = liveTvChannel.ServiceName;
 919        }
 920
 85921        if (dto is Video video)
 922        {
 0923            entity.PrimaryVersionId = video.PrimaryVersionId;
 924        }
 925
 85926        if (dto is IHasSeries hasSeriesName)
 927        {
 0928            entity.SeriesName = hasSeriesName.SeriesName;
 0929            entity.SeriesId = hasSeriesName.SeriesId;
 0930            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 931        }
 932
 85933        if (dto is Episode episode)
 934        {
 0935            entity.SeasonName = episode.SeasonName;
 0936            entity.SeasonId = episode.SeasonId;
 937        }
 938
 85939        if (dto is IHasArtist hasArtists)
 940        {
 0941            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
 942        }
 943
 85944        if (dto is IHasAlbumArtist hasAlbumArtists)
 945        {
 0946            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 947        }
 948
 85949        if (dto is LiveTvProgram program)
 950        {
 0951            entity.ShowId = program.ShowId;
 952        }
 953
 85954        if (dto.ImageInfos is not null)
 955        {
 85956            entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
 957        }
 958
 85959        if (dto is Trailer trailer)
 960        {
 0961            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0962            {
 0963                Id = (int)e,
 0964                Item = entity,
 0965                ItemId = entity.Id
 0966            }).ToArray() ?? [];
 967        }
 968
 969        // dto.Type = entity.Type;
 970        // dto.Data = entity.Data;
 85971        entity.MediaType = dto.MediaType.ToString();
 85972        if (dto is IHasStartDate hasStartDate)
 973        {
 0974            entity.StartDate = hasStartDate.StartDate;
 975        }
 976
 85977        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 978
 979        // Fields that are present in the DB but are never actually used
 980        // dto.UserDataKey = entity.UserDataKey;
 981
 85982        if (dto is Folder folder)
 983        {
 85984            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 85985            entity.IsFolder = folder.IsFolder;
 986        }
 987
 85988        return entity;
 989    }
 990
 991    private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes,
 992    {
 48993        using var context = _dbProvider.CreateDbContext();
 994
 48995        var query = context.ItemValuesMap
 48996            .AsNoTracking()
 48997            .Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.Type));
 48998        if (withItemTypes.Count > 0)
 999        {
 121000            query = query.Where(e => withItemTypes.Contains(e.Item.Type));
 1001        }
 1002
 481003        if (excludeItemTypes.Count > 0)
 1004        {
 121005            query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
 1006        }
 1007
 1008        // query = query.DistinctBy(e => e.CleanValue);
 481009        return query.Select(e => e.ItemValue)
 481010            .GroupBy(e => e.CleanValue)
 481011            .Select(e => e.First().Value)
 481012            .ToArray();
 481013    }
 1014
 1015    private static bool TypeRequiresDeserialization(Type type)
 1016    {
 1791017        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 1018    }
 1019
 1020    private BaseItemDto DeserializeBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
 1021    {
 941022        ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity));
 941023        if (_serverConfigurationManager?.Configuration is null)
 1024        {
 01025            throw new InvalidOperationException("Server Configuration manager or configuration is null");
 1026        }
 1027
 941028        var typeToSerialise = GetType(baseItemEntity.Type);
 941029        return BaseItemRepository.DeserializeBaseItem(
 941030            baseItemEntity,
 941031            _logger,
 941032            _appHost,
 941033            skipDeserialization || (_serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeT
 1034    }
 1035
 1036    /// <summary>
 1037    /// Deserializes a BaseItemEntity and sets all properties.
 1038    /// </summary>
 1039    /// <param name="baseItemEntity">The DB entity.</param>
 1040    /// <param name="logger">Logger.</param>
 1041    /// <param name="appHost">The application server Host.</param>
 1042    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 1043    /// <returns>A mapped BaseItem.</returns>
 1044    /// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
 1045    public static BaseItemDto DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost?
 1046    {
 941047        var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialize unknown type.
 941048        BaseItemDto? dto = null;
 941049        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 1050        {
 1051            try
 1052            {
 251053                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 251054            }
 01055            catch (JsonException ex)
 1056            {
 01057                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 01058            }
 1059        }
 1060
 941061        if (dto is null)
 1062        {
 691063            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 1064        }
 1065
 941066        return Map(baseItemEntity, dto, appHost);
 1067    }
 1068
 1069    private QueryResult<(BaseItemDto Item, ItemCounts? ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyLi
 1070    {
 01071        ArgumentNullException.ThrowIfNull(filter);
 1072
 01073        if (!filter.Limit.HasValue)
 1074        {
 01075            filter.EnableTotalRecordCount = false;
 1076        }
 1077
 01078        using var context = _dbProvider.CreateDbContext();
 1079
 01080        var innerQueryFilter = TranslateQuery(context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)), context,
 01081        {
 01082            ExcludeItemTypes = filter.ExcludeItemTypes,
 01083            IncludeItemTypes = filter.IncludeItemTypes,
 01084            MediaTypes = filter.MediaTypes,
 01085            AncestorIds = filter.AncestorIds,
 01086            ItemIds = filter.ItemIds,
 01087            TopParentIds = filter.TopParentIds,
 01088            ParentId = filter.ParentId,
 01089            IsAiring = filter.IsAiring,
 01090            IsMovie = filter.IsMovie,
 01091            IsSports = filter.IsSports,
 01092            IsKids = filter.IsKids,
 01093            IsNews = filter.IsNews,
 01094            IsSeries = filter.IsSeries
 01095        });
 1096
 01097        var innerQuery = PrepareItemQuery(context, filter)
 01098            .Where(e => e.Type == returnType)
 01099            .Where(e => context.ItemValues!
 01100                .Where(f => itemValueTypes.Contains(f.Type))
 01101                .Where(f => innerQueryFilter.Any(g => f.BaseItemsMap!.Any(w => w.ItemId == g.Id)))
 01102                .Select(f => f.CleanValue)
 01103                .Contains(e.CleanName));
 1104
 01105        var outerQueryFilter = new InternalItemsQuery(filter.User)
 01106        {
 01107            IsPlayed = filter.IsPlayed,
 01108            IsFavorite = filter.IsFavorite,
 01109            IsFavoriteOrLiked = filter.IsFavoriteOrLiked,
 01110            IsLiked = filter.IsLiked,
 01111            IsLocked = filter.IsLocked,
 01112            NameLessThan = filter.NameLessThan,
 01113            NameStartsWith = filter.NameStartsWith,
 01114            NameStartsWithOrGreater = filter.NameStartsWithOrGreater,
 01115            Tags = filter.Tags,
 01116            OfficialRatings = filter.OfficialRatings,
 01117            StudioIds = filter.StudioIds,
 01118            GenreIds = filter.GenreIds,
 01119            Genres = filter.Genres,
 01120            Years = filter.Years,
 01121            NameContains = filter.NameContains,
 01122            SearchTerm = filter.SearchTerm,
 01123            ExcludeItemIds = filter.ExcludeItemIds
 01124        };
 1125
 01126        var query = TranslateQuery(innerQuery, context, outerQueryFilter)
 01127            .GroupBy(e => e.PresentationUniqueKey);
 1128
 01129        var result = new QueryResult<(BaseItemDto, ItemCounts?)>();
 01130        if (filter.EnableTotalRecordCount)
 1131        {
 01132            result.TotalRecordCount = query.Count();
 1133        }
 1134
 01135        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 1136        {
 01137            var offset = filter.StartIndex ?? 0;
 1138
 01139            if (offset > 0)
 1140            {
 01141                query = query.Skip(offset);
 1142            }
 1143
 01144            if (filter.Limit.HasValue)
 1145            {
 01146                query = query.Take(filter.Limit.Value);
 1147            }
 1148        }
 1149
 01150        IQueryable<BaseItemEntity>? itemCountQuery = null;
 1151
 01152        if (filter.IncludeItemTypes.Length > 0)
 1153        {
 1154            // if we are to include more then one type, sub query those items beforehand.
 1155
 01156            var typeSubQuery = new InternalItemsQuery(filter.User)
 01157            {
 01158                ExcludeItemTypes = filter.ExcludeItemTypes,
 01159                IncludeItemTypes = filter.IncludeItemTypes,
 01160                MediaTypes = filter.MediaTypes,
 01161                AncestorIds = filter.AncestorIds,
 01162                ExcludeItemIds = filter.ExcludeItemIds,
 01163                ItemIds = filter.ItemIds,
 01164                TopParentIds = filter.TopParentIds,
 01165                ParentId = filter.ParentId,
 01166                IsPlayed = filter.IsPlayed
 01167            };
 1168
 01169            itemCountQuery = TranslateQuery(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderI
 01170                .Where(e => e.ItemValues!.Any(f => itemValueTypes!.Contains(f.ItemValue.Type)));
 1171
 01172            var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series];
 01173            var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
 01174            var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode];
 01175            var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum];
 01176            var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist];
 01177            var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio];
 01178            var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer];
 1179
 01180            var resultQuery = query.Select(e => new
 01181            {
 01182                item = e.AsQueryable()
 01183                        .Include(e => e.TrailerTypes)
 01184                        .Include(e => e.Provider)
 01185                        .Include(e => e.LockedFields)
 01186                        .Include(e => e.Images)
 01187                        .AsSingleQuery().First(),
 01188                // TODO: This is bad refactor!
 01189                itemCount = new ItemCounts()
 01190                {
 01191                    SeriesCount = itemCountQuery!.Count(f => f.Type == seriesTypeName),
 01192                    EpisodeCount = itemCountQuery!.Count(f => f.Type == episodeTypeName),
 01193                    MovieCount = itemCountQuery!.Count(f => f.Type == movieTypeName),
 01194                    AlbumCount = itemCountQuery!.Count(f => f.Type == musicAlbumTypeName),
 01195                    ArtistCount = itemCountQuery!.Count(f => f.Type == musicArtistTypeName),
 01196                    SongCount = itemCountQuery!.Count(f => f.Type == audioTypeName),
 01197                    TrailerCount = itemCountQuery!.Count(f => f.Type == trailerTypeName),
 01198                }
 01199            });
 1200
 01201            result.StartIndex = filter.StartIndex ?? 0;
 01202            result.Items =
 01203            [
 01204                .. resultQuery
 01205                    .AsEnumerable()
 01206                    .Where(e => e is not null)
 01207                    .Select(e =>
 01208                    {
 01209                        return (DeserializeBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
 01210                    })
 01211            ];
 1212        }
 1213        else
 1214        {
 01215            result.StartIndex = filter.StartIndex ?? 0;
 01216            result.Items =
 01217            [
 01218                .. query
 01219                    .Select(e => e.First())
 01220                    .AsEnumerable()
 01221                    .Where(e => e is not null)
 01222                    .Select<BaseItemEntity, (BaseItemDto, ItemCounts?)>(e =>
 01223                    {
 01224                        return (DeserializeBaseItem(e, filter.SkipDeserialization), null);
 01225                    })
 01226            ];
 1227        }
 1228
 01229        return result;
 01230    }
 1231
 1232    private static void PrepareFilterQuery(InternalItemsQuery query)
 1233    {
 3191234        if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
 1235        {
 01236            query.Limit = query.Limit.Value + 4;
 1237        }
 1238
 3191239        if (query.IsResumable ?? false)
 1240        {
 11241            query.IsVirtualItem = false;
 1242        }
 3191243    }
 1244
 1245    private string GetCleanValue(string value)
 1246    {
 881247        if (string.IsNullOrWhiteSpace(value))
 1248        {
 01249            return value;
 1250        }
 1251
 881252        return value.RemoveDiacritics().ToLowerInvariant();
 1253    }
 1254
 1255    private List<(ItemValueType MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inherited
 1256    {
 851257        var list = new List<(ItemValueType, string)>();
 1258
 851259        if (item is IHasArtist hasArtist)
 1260        {
 01261            list.AddRange(hasArtist.Artists.Select(i => ((ItemValueType)0, i)));
 1262        }
 1263
 851264        if (item is IHasAlbumArtist hasAlbumArtist)
 1265        {
 01266            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (ItemValueType.AlbumArtist, i)));
 1267        }
 1268
 851269        list.AddRange(item.Genres.Select(i => (ItemValueType.Genre, i)));
 851270        list.AddRange(item.Studios.Select(i => (ItemValueType.Studios, i)));
 851271        list.AddRange(item.Tags.Select(i => (ItemValueType.Tags, i)));
 1272
 1273        // keywords was 5
 1274
 851275        list.AddRange(inheritedTags.Select(i => (ItemValueType.InheritedTags, i)));
 1276
 1277        // Remove all invalid values.
 851278        list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
 1279
 851280        return list;
 1281    }
 1282
 1283    private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
 1284    {
 01285        return new BaseItemImageInfo()
 01286        {
 01287            ItemId = baseItemId,
 01288            Id = Guid.NewGuid(),
 01289            Path = e.Path,
 01290            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 01291            DateModified = e.DateModified,
 01292            Height = e.Height,
 01293            Width = e.Width,
 01294            ImageType = (ImageInfoImageType)e.Type,
 01295            Item = null!
 01296        };
 1297    }
 1298
 1299    private static ItemImageInfo Map(BaseItemImageInfo e, IServerApplicationHost? appHost)
 1300    {
 01301        return new ItemImageInfo()
 01302        {
 01303            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 01304            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 01305            DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
 01306            Height = e.Height,
 01307            Width = e.Width,
 01308            Type = (ImageType)e.ImageType
 01309        };
 1310    }
 1311
 1312    private string? GetPathToSave(string path)
 1313    {
 851314        if (path is null)
 1315        {
 01316            return null;
 1317        }
 1318
 851319        return _appHost.ReverseVirtualPath(path);
 1320    }
 1321
 1322    private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
 1323    {
 161324        var list = new List<string>();
 1325
 161326        if (IsTypeInQuery(BaseItemKind.Person, query))
 1327        {
 11328            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!);
 1329        }
 1330
 161331        if (IsTypeInQuery(BaseItemKind.Genre, query))
 1332        {
 11333            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!);
 1334        }
 1335
 161336        if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
 1337        {
 11338            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!);
 1339        }
 1340
 161341        if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
 1342        {
 11343            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
 1344        }
 1345
 161346        if (IsTypeInQuery(BaseItemKind.Studio, query))
 1347        {
 11348            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!);
 1349        }
 1350
 161351        return list;
 1352    }
 1353
 1354    private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
 1355    {
 801356        if (query.ExcludeItemTypes.Contains(type))
 1357        {
 01358            return false;
 1359        }
 1360
 801361        return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
 1362    }
 1363
 1364    private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
 1365    {
 01366        if (!query.GroupByPresentationUniqueKey)
 1367        {
 01368            return false;
 1369        }
 1370
 01371        if (query.GroupBySeriesPresentationUniqueKey)
 1372        {
 01373            return false;
 1374        }
 1375
 01376        if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
 1377        {
 01378            return false;
 1379        }
 1380
 01381        if (query.User is null)
 1382        {
 01383            return false;
 1384        }
 1385
 01386        if (query.IncludeItemTypes.Length == 0)
 1387        {
 01388            return true;
 1389        }
 1390
 01391        return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
 01392            || query.IncludeItemTypes.Contains(BaseItemKind.Video)
 01393            || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01394            || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
 01395            || query.IncludeItemTypes.Contains(BaseItemKind.Series)
 01396            || query.IncludeItemTypes.Contains(BaseItemKind.Season);
 1397    }
 1398
 1399    private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter)
 1400    {
 3311401        var orderBy = filter.OrderBy;
 3311402        var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
 1403
 3311404        if (hasSearch)
 1405        {
 01406            orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
 1407        }
 3311408        else if (orderBy.Count == 0)
 1409        {
 2411410            return query.OrderBy(e => e.SortName);
 1411        }
 1412
 901413        IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
 1414
 901415        var firstOrdering = orderBy.FirstOrDefault();
 901416        if (firstOrdering != default)
 1417        {
 901418            var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
 901419            if (firstOrdering.SortOrder == SortOrder.Ascending)
 1420            {
 891421                orderedQuery = query.OrderBy(expression);
 1422            }
 1423            else
 1424            {
 11425                orderedQuery = query.OrderByDescending(expression);
 1426            }
 1427
 901428            if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
 1429            {
 01430                if (firstOrdering.SortOrder is SortOrder.Ascending)
 1431                {
 01432                    orderedQuery = orderedQuery.ThenBy(e => e.Name);
 1433                }
 1434                else
 1435                {
 01436                    orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
 1437                }
 1438            }
 1439        }
 1440
 2561441        foreach (var item in orderBy.Skip(1))
 1442        {
 381443            var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
 381444            if (item.SortOrder == SortOrder.Ascending)
 1445            {
 381446                orderedQuery = orderedQuery!.ThenBy(expression);
 1447            }
 1448            else
 1449            {
 01450                orderedQuery = orderedQuery!.ThenByDescending(expression);
 1451            }
 1452        }
 1453
 901454        return orderedQuery ?? query;
 1455    }
 1456
 1457    private IQueryable<BaseItemEntity> TranslateQuery(
 1458        IQueryable<BaseItemEntity> baseQuery,
 1459        JellyfinDbContext context,
 1460        InternalItemsQuery filter)
 1461    {
 1462        const int HDWidth = 1200;
 1463        const int UHDWidth = 3800;
 1464        const int UHDHeight = 2100;
 1465
 3191466        var minWidth = filter.MinWidth;
 3191467        var maxWidth = filter.MaxWidth;
 3191468        var now = DateTime.UtcNow;
 1469
 3191470        if (filter.IsHD.HasValue || filter.Is4K.HasValue)
 1471        {
 01472            bool includeSD = false;
 01473            bool includeHD = false;
 01474            bool include4K = false;
 1475
 01476            if (filter.IsHD.HasValue && !filter.IsHD.Value)
 1477            {
 01478                includeSD = true;
 1479            }
 1480
 01481            if (filter.IsHD.HasValue && filter.IsHD.Value)
 1482            {
 01483                includeHD = true;
 1484            }
 1485
 01486            if (filter.Is4K.HasValue && filter.Is4K.Value)
 1487            {
 01488                include4K = true;
 1489            }
 1490
 01491            baseQuery = baseQuery.Where(e =>
 01492                (includeSD && e.Width < HDWidth) ||
 01493                (includeHD && e.Width >= HDWidth && !(e.Width >= UHDWidth || e.Height >= UHDHeight)) ||
 01494                (include4K && (e.Width >= UHDWidth || e.Height >= UHDHeight)));
 1495        }
 1496
 3191497        if (minWidth.HasValue)
 1498        {
 01499            baseQuery = baseQuery.Where(e => e.Width >= minWidth);
 1500        }
 1501
 3191502        if (filter.MinHeight.HasValue)
 1503        {
 01504            baseQuery = baseQuery.Where(e => e.Height >= filter.MinHeight);
 1505        }
 1506
 3191507        if (maxWidth.HasValue)
 1508        {
 01509            baseQuery = baseQuery.Where(e => e.Width <= maxWidth);
 1510        }
 1511
 3191512        if (filter.MaxHeight.HasValue)
 1513        {
 01514            baseQuery = baseQuery.Where(e => e.Height <= filter.MaxHeight);
 1515        }
 1516
 3191517        if (filter.IsLocked.HasValue)
 1518        {
 361519            baseQuery = baseQuery.Where(e => e.IsLocked == filter.IsLocked);
 1520        }
 1521
 3191522        var tags = filter.Tags.ToList();
 3191523        var excludeTags = filter.ExcludeTags.ToList();
 1524
 3191525        if (filter.IsMovie == true)
 1526        {
 01527            if (filter.IncludeItemTypes.Length == 0
 01528                || filter.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01529                || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
 1530            {
 01531                baseQuery = baseQuery.Where(e => e.IsMovie);
 1532            }
 1533        }
 3191534        else if (filter.IsMovie.HasValue)
 1535        {
 01536            baseQuery = baseQuery.Where(e => e.IsMovie == filter.IsMovie);
 1537        }
 1538
 3191539        if (filter.IsSeries.HasValue)
 1540        {
 01541            baseQuery = baseQuery.Where(e => e.IsSeries == filter.IsSeries);
 1542        }
 1543
 3191544        if (filter.IsSports.HasValue)
 1545        {
 01546            if (filter.IsSports.Value)
 1547            {
 01548                tags.Add("Sports");
 1549            }
 1550            else
 1551            {
 01552                excludeTags.Add("Sports");
 1553            }
 1554        }
 1555
 3191556        if (filter.IsNews.HasValue)
 1557        {
 01558            if (filter.IsNews.Value)
 1559            {
 01560                tags.Add("News");
 1561            }
 1562            else
 1563            {
 01564                excludeTags.Add("News");
 1565            }
 1566        }
 1567
 3191568        if (filter.IsKids.HasValue)
 1569        {
 01570            if (filter.IsKids.Value)
 1571            {
 01572                tags.Add("Kids");
 1573            }
 1574            else
 1575            {
 01576                excludeTags.Add("Kids");
 1577            }
 1578        }
 1579
 3191580        if (!string.IsNullOrEmpty(filter.SearchTerm))
 1581        {
 01582            var searchTerm = filter.SearchTerm.ToLower();
 01583            baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && 
 1584        }
 1585
 3191586        if (filter.IsFolder.HasValue)
 1587        {
 211588            baseQuery = baseQuery.Where(e => e.IsFolder == filter.IsFolder);
 1589        }
 1590
 3191591        var includeTypes = filter.IncludeItemTypes;
 1592
 1593        // Only specify excluded types if no included types are specified
 3191594        if (filter.IncludeItemTypes.Length == 0)
 1595        {
 2281596            var excludeTypes = filter.ExcludeItemTypes;
 2281597            if (excludeTypes.Length == 1)
 1598            {
 01599                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
 1600                {
 01601                    baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
 1602                }
 1603            }
 2281604            else if (excludeTypes.Length > 1)
 1605            {
 01606                var excludeTypeName = new List<string>();
 01607                foreach (var excludeType in excludeTypes)
 1608                {
 01609                    if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
 1610                    {
 01611                        excludeTypeName.Add(baseItemKindName!);
 1612                    }
 1613                }
 1614
 01615                baseQuery = baseQuery.Where(e => !excludeTypeName.Contains(e.Type));
 1616            }
 1617        }
 1618        else
 1619        {
 911620            string[] types = includeTypes.Select(f => _itemTypeLookup.BaseItemKindNames.GetValueOrDefault(f)).Where(e =>
 911621            baseQuery = baseQuery.WhereOneOrMany(types, f => f.Type);
 1622        }
 1623
 3191624        if (filter.ChannelIds.Count > 0)
 1625        {
 01626            baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value));
 1627        }
 1628
 3191629        if (!filter.ParentId.IsEmpty())
 1630        {
 1561631            baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
 1632        }
 1633
 3191634        if (!string.IsNullOrWhiteSpace(filter.Path))
 1635        {
 01636            baseQuery = baseQuery.Where(e => e.Path == filter.Path);
 1637        }
 1638
 3191639        if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))
 1640        {
 01641            baseQuery = baseQuery.Where(e => e.PresentationUniqueKey == filter.PresentationUniqueKey);
 1642        }
 1643
 3191644        if (filter.MinCommunityRating.HasValue)
 1645        {
 01646            baseQuery = baseQuery.Where(e => e.CommunityRating >= filter.MinCommunityRating);
 1647        }
 1648
 3191649        if (filter.MinIndexNumber.HasValue)
 1650        {
 01651            baseQuery = baseQuery.Where(e => e.IndexNumber >= filter.MinIndexNumber);
 1652        }
 1653
 3191654        if (filter.MinParentAndIndexNumber.HasValue)
 1655        {
 01656            baseQuery = baseQuery
 01657                .Where(e => (e.ParentIndexNumber == filter.MinParentAndIndexNumber.Value.ParentIndexNumber && e.IndexNum
 1658        }
 1659
 3191660        if (filter.MinDateCreated.HasValue)
 1661        {
 01662            baseQuery = baseQuery.Where(e => e.DateCreated >= filter.MinDateCreated);
 1663        }
 1664
 3191665        if (filter.MinDateLastSaved.HasValue)
 1666        {
 01667            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSaved.Value
 1668        }
 1669
 3191670        if (filter.MinDateLastSavedForUser.HasValue)
 1671        {
 01672            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSavedForUse
 1673        }
 1674
 3191675        if (filter.IndexNumber.HasValue)
 1676        {
 01677            baseQuery = baseQuery.Where(e => e.IndexNumber == filter.IndexNumber.Value);
 1678        }
 1679
 3191680        if (filter.ParentIndexNumber.HasValue)
 1681        {
 01682            baseQuery = baseQuery.Where(e => e.ParentIndexNumber == filter.ParentIndexNumber.Value);
 1683        }
 1684
 3191685        if (filter.ParentIndexNumberNotEquals.HasValue)
 1686        {
 01687            baseQuery = baseQuery.Where(e => e.ParentIndexNumber != filter.ParentIndexNumberNotEquals.Value || e.ParentI
 1688        }
 1689
 3191690        var minEndDate = filter.MinEndDate;
 3191691        var maxEndDate = filter.MaxEndDate;
 1692
 3191693        if (filter.HasAired.HasValue)
 1694        {
 01695            if (filter.HasAired.Value)
 1696            {
 01697                maxEndDate = DateTime.UtcNow;
 1698            }
 1699            else
 1700            {
 01701                minEndDate = DateTime.UtcNow;
 1702            }
 1703        }
 1704
 3191705        if (minEndDate.HasValue)
 1706        {
 01707            baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
 1708        }
 1709
 3191710        if (maxEndDate.HasValue)
 1711        {
 01712            baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
 1713        }
 1714
 3191715        if (filter.MinStartDate.HasValue)
 1716        {
 01717            baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
 1718        }
 1719
 3191720        if (filter.MaxStartDate.HasValue)
 1721        {
 01722            baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
 1723        }
 1724
 3191725        if (filter.MinPremiereDate.HasValue)
 1726        {
 01727            baseQuery = baseQuery.Where(e => e.PremiereDate >= filter.MinPremiereDate.Value);
 1728        }
 1729
 3191730        if (filter.MaxPremiereDate.HasValue)
 1731        {
 01732            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MaxPremiereDate.Value);
 1733        }
 1734
 3191735        if (filter.TrailerTypes.Length > 0)
 1736        {
 01737            var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray();
 01738            baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f)));
 1739        }
 1740
 3191741        if (filter.IsAiring.HasValue)
 1742        {
 01743            if (filter.IsAiring.Value)
 1744            {
 01745                baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
 1746            }
 1747            else
 1748            {
 01749                baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
 1750            }
 1751        }
 1752
 3191753        if (filter.PersonIds.Length > 0)
 1754        {
 01755            baseQuery = baseQuery
 01756                .Where(e =>
 01757                    context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).A
 01758                        .Any(f => f.ItemId == e.Id));
 1759        }
 1760
 3191761        if (!string.IsNullOrWhiteSpace(filter.Person))
 1762        {
 01763            baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person));
 1764        }
 1765
 3191766        if (!string.IsNullOrWhiteSpace(filter.MinSortName))
 1767        {
 1768            // this does not makes sense.
 1769            // baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName);
 1770            // whereClauses.Add("SortName>=@MinSortName");
 1771            // statement?.TryBind("@MinSortName", query.MinSortName);
 1772        }
 1773
 3191774        if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
 1775        {
 01776            baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
 1777        }
 1778
 3191779        if (!string.IsNullOrWhiteSpace(filter.ExternalId))
 1780        {
 01781            baseQuery = baseQuery.Where(e => e.ExternalId == filter.ExternalId);
 1782        }
 1783
 3191784        if (!string.IsNullOrWhiteSpace(filter.Name))
 1785        {
 31786            var cleanName = GetCleanValue(filter.Name);
 31787            baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
 1788        }
 1789
 1790        // These are the same, for now
 3191791        var nameContains = filter.NameContains;
 3191792        if (!string.IsNullOrWhiteSpace(nameContains))
 1793        {
 01794            baseQuery = baseQuery.Where(e =>
 01795                e.CleanName!.Contains(nameContains)
 01796                || e.OriginalTitle!.ToLower().Contains(nameContains!));
 1797        }
 1798
 3191799        if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
 1800        {
 01801            baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith) || e.Name!.StartsWith(filter.
 1802        }
 1803
 3191804        if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
 1805        {
 1806            // i hate this
 01807            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!
 1808        }
 1809
 3191810        if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
 1811        {
 1812            // i hate this
 01813            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDef
 1814        }
 1815
 3191816        if (filter.ImageTypes.Length > 0)
 1817        {
 891818            var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray();
 891819            baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f)));
 1820        }
 1821
 3191822        if (filter.IsLiked.HasValue)
 1823        {
 01824            baseQuery = baseQuery
 01825                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLike
 1826        }
 1827
 3191828        if (filter.IsFavoriteOrLiked.HasValue)
 1829        {
 01830            baseQuery = baseQuery
 01831                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1832        }
 1833
 3191834        if (filter.IsFavorite.HasValue)
 1835        {
 01836            baseQuery = baseQuery
 01837                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1838        }
 1839
 3191840        if (filter.IsPlayed.HasValue)
 1841        {
 1842            // We should probably figure this out for all folders, but for right now, this is the only place where we ne
 01843            if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series)
 1844            {
 01845                baseQuery = baseQuery.Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId))
 01846                    .Where(e => e.IsFolder == false && e.IsVirtualItem == false)
 01847                    .Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
 01848                    .Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
 1849            }
 1850            else
 1851            {
 01852                baseQuery = baseQuery
 01853                    .Select(e => new
 01854                    {
 01855                        IsPlayed = e.UserData!.Where(f => f.UserId == filter.User!.Id).Select(f => (bool?)f.Played).Firs
 01856                        Item = e
 01857                    })
 01858                    .Where(e => e.IsPlayed == filter.IsPlayed)
 01859                    .Select(f => f.Item);
 1860            }
 1861        }
 1862
 3191863        if (filter.IsResumable.HasValue)
 1864        {
 11865            if (filter.IsResumable.Value)
 1866            {
 11867                baseQuery = baseQuery
 11868                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks >
 1869            }
 1870            else
 1871            {
 01872                baseQuery = baseQuery
 01873                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks =
 1874            }
 1875        }
 1876
 3191877        if (filter.ArtistIds.Length > 0)
 1878        {
 01879            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ArtistIds);
 1880        }
 1881
 3191882        if (filter.AlbumArtistIds.Length > 0)
 1883        {
 01884            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.AlbumArtistIds);
 1885        }
 1886
 3191887        if (filter.ContributingArtistIds.Length > 0)
 1888        {
 01889            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ContributingArtistIds);
 1890        }
 1891
 3191892        if (filter.AlbumIds.Length > 0)
 1893        {
 01894            var subQuery = context.BaseItems.WhereOneOrMany(filter.AlbumIds, f => f.Id);
 01895            baseQuery = baseQuery.Where(e => subQuery.Any(f => f.Name == e.Album));
 1896        }
 1897
 3191898        if (filter.ExcludeArtistIds.Length > 0)
 1899        {
 01900            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Artist, filter.ExcludeArtistIds, true);
 1901        }
 1902
 3191903        if (filter.GenreIds.Count > 0)
 1904        {
 01905            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Genre, filter.GenreIds.ToArray());
 1906        }
 1907
 3191908        if (filter.Genres.Count > 0)
 1909        {
 01910            var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValue
 01911            baseQuery = baseQuery
 01912                    .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Genre).Any(clea
 1913        }
 1914
 3191915        if (tags.Count > 0)
 1916        {
 01917            var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMap, stri
 01918            baseQuery = baseQuery
 01919                    .Where(e => e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(clean
 1920        }
 1921
 3191922        if (excludeTags.Count > 0)
 1923        {
 01924            var cleanValues = excludeTags.Select(e => GetCleanValue(e)).ToArray().OneOrManyExpressionBuilder<ItemValueMa
 01925            baseQuery = baseQuery
 01926                    .Where(e => !e.ItemValues!.AsQueryable().Where(f => f.ItemValue.Type == ItemValueType.Tags).Any(clea
 1927        }
 1928
 3191929        if (filter.StudioIds.Length > 0)
 1930        {
 01931            baseQuery = baseQuery.WhereReferencedItem(context, ItemValueType.Studios, filter.StudioIds.ToArray());
 1932        }
 1933
 3191934        if (filter.OfficialRatings.Length > 0)
 1935        {
 01936            baseQuery = baseQuery
 01937                   .Where(e => filter.OfficialRatings.Contains(e.OfficialRating));
 1938        }
 1939
 3191940        Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null;
 3191941        if (filter.MinParentalRating != null)
 1942        {
 01943            var min = filter.MinParentalRating;
 01944            minParentalRatingFilter = e => e.InheritedParentalRatingValue >= min.Score || e.InheritedParentalRatingValue
 01945            if (min.SubScore != null)
 1946            {
 01947                minParentalRatingFilter = minParentalRatingFilter.And(e => e.InheritedParentalRatingValue >= min.SubScor
 1948            }
 1949        }
 1950
 3191951        Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null;
 3191952        if (filter.MaxParentalRating != null)
 1953        {
 361954            var max = filter.MaxParentalRating;
 361955            maxParentalRatingFilter = e => e.InheritedParentalRatingValue <= max.Score || e.InheritedParentalRatingValue
 361956            if (max.SubScore != null)
 1957            {
 01958                maxParentalRatingFilter = maxParentalRatingFilter.And(e => e.InheritedParentalRatingValue <= max.SubScor
 1959            }
 1960        }
 1961
 3191962        if (filter.HasParentalRating ?? false)
 1963        {
 01964            if (minParentalRatingFilter != null)
 1965            {
 01966                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1967            }
 1968
 01969            if (maxParentalRatingFilter != null)
 1970            {
 01971                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1972            }
 1973        }
 3191974        else if (filter.BlockUnratedItems.Length > 0)
 1975        {
 01976            var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
 01977            Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !u
 1978
 01979            if (minParentalRatingFilter != null && maxParentalRatingFilter != null)
 1980            {
 01981                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter)))
 1982            }
 01983            else if (minParentalRatingFilter != null)
 1984            {
 01985                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter));
 1986            }
 01987            else if (maxParentalRatingFilter != null)
 1988            {
 01989                baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter));
 1990            }
 1991            else
 1992            {
 01993                baseQuery = baseQuery.Where(unratedItemFilter);
 1994            }
 1995        }
 3191996        else if (minParentalRatingFilter != null || maxParentalRatingFilter != null)
 1997        {
 361998            if (minParentalRatingFilter != null)
 1999            {
 02000                baseQuery = baseQuery.Where(minParentalRatingFilter);
 2001            }
 2002
 362003            if (maxParentalRatingFilter != null)
 2004            {
 362005                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 2006            }
 2007        }
 2832008        else if (!filter.HasParentalRating ?? false)
 2009        {
 02010            baseQuery = baseQuery
 02011                .Where(e => e.InheritedParentalRatingValue == null);
 2012        }
 2013
 3192014        if (filter.HasOfficialRating.HasValue)
 2015        {
 02016            if (filter.HasOfficialRating.Value)
 2017            {
 02018                baseQuery = baseQuery
 02019                    .Where(e => e.OfficialRating != null && e.OfficialRating != string.Empty);
 2020            }
 2021            else
 2022            {
 02023                baseQuery = baseQuery
 02024                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty);
 2025            }
 2026        }
 2027
 3192028        if (filter.HasOverview.HasValue)
 2029        {
 02030            if (filter.HasOverview.Value)
 2031            {
 02032                baseQuery = baseQuery
 02033                    .Where(e => e.Overview != null && e.Overview != string.Empty);
 2034            }
 2035            else
 2036            {
 02037                baseQuery = baseQuery
 02038                    .Where(e => e.Overview == null || e.Overview == string.Empty);
 2039            }
 2040        }
 2041
 3192042        if (filter.HasOwnerId.HasValue)
 2043        {
 02044            if (filter.HasOwnerId.Value)
 2045            {
 02046                baseQuery = baseQuery
 02047                    .Where(e => e.OwnerId != null);
 2048            }
 2049            else
 2050            {
 02051                baseQuery = baseQuery
 02052                    .Where(e => e.OwnerId == null);
 2053            }
 2054        }
 2055
 3192056        if (!string.IsNullOrWhiteSpace(filter.HasNoAudioTrackWithLanguage))
 2057        {
 02058            baseQuery = baseQuery
 02059                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Audio && f.Language == filte
 2060        }
 2061
 3192062        if (!string.IsNullOrWhiteSpace(filter.HasNoInternalSubtitleTrackWithLanguage))
 2063        {
 02064            baseQuery = baseQuery
 02065                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && !f.IsExternal &&
 2066        }
 2067
 3192068        if (!string.IsNullOrWhiteSpace(filter.HasNoExternalSubtitleTrackWithLanguage))
 2069        {
 02070            baseQuery = baseQuery
 02071                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.IsExternal && 
 2072        }
 2073
 3192074        if (!string.IsNullOrWhiteSpace(filter.HasNoSubtitleTrackWithLanguage))
 2075        {
 02076            baseQuery = baseQuery
 02077                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.Language == fi
 2078        }
 2079
 3192080        if (filter.HasSubtitles.HasValue)
 2081        {
 02082            baseQuery = baseQuery
 02083                .Where(e => e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle) == filter.HasSubtit
 2084        }
 2085
 3192086        if (filter.HasChapterImages.HasValue)
 2087        {
 02088            baseQuery = baseQuery
 02089                .Where(e => e.Chapters!.Any(f => f.ImagePath != null) == filter.HasChapterImages.Value);
 2090        }
 2091
 3192092        if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
 2093        {
 122094            baseQuery = baseQuery
 122095                .Where(e => e.ParentId.HasValue && !context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)).Any
 2096        }
 2097
 3192098        if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
 2099        {
 122100            baseQuery = baseQuery
 122101                    .Where(e => !context.ItemValues.Where(f => _getAllArtistsValueTypes.Contains(f.Type)).Any(f => f.Val
 2102        }
 2103
 3192104        if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value)
 2105        {
 122106            baseQuery = baseQuery
 122107                    .Where(e => !context.ItemValues.Where(f => _getStudiosValueTypes.Contains(f.Type)).Any(f => f.Value 
 2108        }
 2109
 3192110        if (filter.IsDeadGenre.HasValue && filter.IsDeadGenre.Value)
 2111        {
 122112            baseQuery = baseQuery
 122113                    .Where(e => !context.ItemValues.Where(f => _getGenreValueTypes.Contains(f.Type)).Any(f => f.Value ==
 2114        }
 2115
 3192116        if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
 2117        {
 02118            baseQuery = baseQuery
 02119                .Where(e => !context.Peoples.Any(f => f.Name == e.Name));
 2120        }
 2121
 3192122        if (filter.Years.Length > 0)
 2123        {
 02124            baseQuery = baseQuery.WhereOneOrMany(filter.Years, e => e.ProductionYear!.Value);
 2125        }
 2126
 3192127        var isVirtualItem = filter.IsVirtualItem ?? filter.IsMissing;
 3192128        if (isVirtualItem.HasValue)
 2129        {
 222130            baseQuery = baseQuery
 222131                .Where(e => e.IsVirtualItem == isVirtualItem.Value);
 2132        }
 2133
 3192134        if (filter.IsSpecialSeason.HasValue)
 2135        {
 02136            if (filter.IsSpecialSeason.Value)
 2137            {
 02138                baseQuery = baseQuery
 02139                    .Where(e => e.IndexNumber == 0);
 2140            }
 2141            else
 2142            {
 02143                baseQuery = baseQuery
 02144                    .Where(e => e.IndexNumber != 0);
 2145            }
 2146        }
 2147
 3192148        if (filter.IsUnaired.HasValue)
 2149        {
 02150            if (filter.IsUnaired.Value)
 2151            {
 02152                baseQuery = baseQuery
 02153                    .Where(e => e.PremiereDate >= now);
 2154            }
 2155            else
 2156            {
 02157                baseQuery = baseQuery
 02158                    .Where(e => e.PremiereDate < now);
 2159            }
 2160        }
 2161
 3192162        if (filter.MediaTypes.Length > 0)
 2163        {
 212164            var mediaTypes = filter.MediaTypes.Select(f => f.ToString()).ToArray();
 212165            baseQuery = baseQuery.WhereOneOrMany(mediaTypes, e => e.MediaType);
 2166        }
 2167
 3192168        if (filter.ItemIds.Length > 0)
 2169        {
 02170            baseQuery = baseQuery.WhereOneOrMany(filter.ItemIds, e => e.Id);
 2171        }
 2172
 3192173        if (filter.ExcludeItemIds.Length > 0)
 2174        {
 02175            baseQuery = baseQuery
 02176                .Where(e => !filter.ExcludeItemIds.Contains(e.Id));
 2177        }
 2178
 3192179        if (filter.ExcludeProviderIds is not null && filter.ExcludeProviderIds.Count > 0)
 2180        {
 02181            var exclude = filter.ExcludeProviderIds.Select(e => $"{e.Key}:{e.Value}").ToArray();
 02182            baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.All(f => !ex
 2183        }
 2184
 3192185        if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0)
 2186        {
 02187            var include = filter.HasAnyProviderId.Select(e => $"{e.Key}:{e.Value}").ToArray();
 02188            baseQuery = baseQuery.Where(e => e.Provider!.Select(f => f.ProviderId + ":" + f.ProviderValue)!.Any(f => inc
 2189        }
 2190
 3192191        if (filter.HasImdbId.HasValue)
 2192        {
 02193            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb"));
 2194        }
 2195
 3192196        if (filter.HasTmdbId.HasValue)
 2197        {
 02198            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb"));
 2199        }
 2200
 3192201        if (filter.HasTvdbId.HasValue)
 2202        {
 02203            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb"));
 2204        }
 2205
 3192206        var queryTopParentIds = filter.TopParentIds;
 2207
 3192208        if (queryTopParentIds.Length > 0)
 2209        {
 162210            var includedItemByNameTypes = GetItemByNameTypesInQuery(filter);
 162211            var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
 162212            if (enableItemsByName && includedItemByNameTypes.Count > 0)
 2213            {
 02214                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => 
 2215            }
 2216            else
 2217            {
 162218                baseQuery = baseQuery.WhereOneOrMany(queryTopParentIds, e => e.TopParentId!.Value);
 2219            }
 2220        }
 2221
 3192222        if (filter.AncestorIds.Length > 0)
 2223        {
 392224            baseQuery = baseQuery.Where(e => e.Parents!.Any(f => filter.AncestorIds.Contains(f.ParentItemId)));
 2225        }
 2226
 3192227        if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
 2228        {
 02229            baseQuery = baseQuery
 02230                .Where(e => context.BaseItems.Where(e => e.Id != EF.Constant(PlaceholderId)).Where(f => f.PresentationUn
 2231        }
 2232
 3192233        if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
 2234        {
 02235            baseQuery = baseQuery
 02236                .Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
 2237        }
 2238
 3192239        if (filter.ExcludeInheritedTags.Length > 0)
 2240        {
 02241            baseQuery = baseQuery
 02242                .Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags)
 02243                    .Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
 2244        }
 2245
 3192246        if (filter.IncludeInheritedTags.Length > 0)
 2247        {
 2248            // Episodes do not store inherit tags from their parents in the database, and the tag may be still required 
 2249            // In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s 
 02250            if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
 2251            {
 02252                baseQuery = baseQuery
 02253                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags)
 02254                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
 02255                        ||
 02256                        (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value)!.Where(w 
 02257                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
 2258            }
 2259
 2260            // A playlist should be accessible to its owner regardless of allowed tags.
 02261            else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
 2262            {
 02263                baseQuery = baseQuery
 02264                    .Where(e =>
 02265                    e.Parents!
 02266                        .Any(f =>
 02267                            f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeIn
 02268                            || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
 2269                // d        ^^ this is stupid it hate this.
 2270            }
 2271            else
 2272            {
 02273                baseQuery = baseQuery
 02274                    .Where(e => e.Parents!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.
 2275            }
 2276        }
 2277
 3192278        if (filter.SeriesStatuses.Length > 0)
 2279        {
 02280            var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
 02281            baseQuery = baseQuery
 02282                .Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
 2283        }
 2284
 3192285        if (filter.BoxSetLibraryFolders.Length > 0)
 2286        {
 02287            var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).T
 02288            baseQuery = baseQuery
 02289                .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
 2290        }
 2291
 3192292        if (filter.VideoTypes.Length > 0)
 2293        {
 02294            var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"{e}\"");
 02295            baseQuery = baseQuery
 02296                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
 2297        }
 2298
 3192299        if (filter.Is3D.HasValue)
 2300        {
 02301            if (filter.Is3D.Value)
 2302            {
 02303                baseQuery = baseQuery
 02304                    .Where(e => e.Data!.Contains("Video3DFormat"));
 2305            }
 2306            else
 2307            {
 02308                baseQuery = baseQuery
 02309                    .Where(e => !e.Data!.Contains("Video3DFormat"));
 2310            }
 2311        }
 2312
 3192313        if (filter.IsPlaceHolder.HasValue)
 2314        {
 02315            if (filter.IsPlaceHolder.Value)
 2316            {
 02317                baseQuery = baseQuery
 02318                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
 2319            }
 2320            else
 2321            {
 02322                baseQuery = baseQuery
 02323                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
 2324            }
 2325        }
 2326
 3192327        if (filter.HasSpecialFeature.HasValue)
 2328        {
 02329            if (filter.HasSpecialFeature.Value)
 2330            {
 02331                baseQuery = baseQuery
 02332                    .Where(e => e.ExtraIds != null);
 2333            }
 2334            else
 2335            {
 02336                baseQuery = baseQuery
 02337                    .Where(e => e.ExtraIds == null);
 2338            }
 2339        }
 2340
 3192341        if (filter.HasTrailer.HasValue || filter.HasThemeSong.HasValue || filter.HasThemeVideo.HasValue)
 2342        {
 02343            if (filter.HasTrailer.GetValueOrDefault() || filter.HasThemeSong.GetValueOrDefault() || filter.HasThemeVideo
 2344            {
 02345                baseQuery = baseQuery
 02346                    .Where(e => e.ExtraIds != null);
 2347            }
 2348            else
 2349            {
 02350                baseQuery = baseQuery
 02351                    .Where(e => e.ExtraIds == null);
 2352            }
 2353        }
 2354
 3192355        return baseQuery;
 2356    }
 2357
 2358    /// <inheritdoc/>
 2359    public async Task<bool> ItemExistsAsync(Guid id)
 2360    {
 2361        var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 2362        await using (dbContext.ConfigureAwait(false))
 2363        {
 2364            return await dbContext.BaseItems.AnyAsync(f => f.Id == id).ConfigureAwait(false);
 2365        }
 2366    }
 2367}

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)
DeserializeBaseItem(Jellyfin.Database.Implementations.Entities.BaseItemEntity,System.Boolean)
DeserializeBaseItem(Jellyfin.Database.Implementations.Entities.BaseItemEntity,Microsoft.Extensions.Logging.ILogger,MediaBrowser.Controller.IServerApplicationHost,System.Boolean)
GetItemValues(MediaBrowser.Controller.Entities.InternalItemsQuery,System.Collections.Generic.IReadOnlyList`1<Jellyfin.Database.Implementations.Entities.ItemValueType>,System.String)
PrepareFilterQuery(MediaBrowser.Controller.Entities.InternalItemsQuery)
GetCleanValue(System.String)
GetItemValuesToSave(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<System.String>)
Map(System.Guid,MediaBrowser.Controller.Entities.ItemImageInfo)
Map(Jellyfin.Database.Implementations.Entities.BaseItemImageInfo,MediaBrowser.Controller.IServerApplicationHost)
GetPathToSave(System.String)
GetItemByNameTypesInQuery(MediaBrowser.Controller.Entities.InternalItemsQuery)
IsTypeInQuery(Jellyfin.Data.Enums.BaseItemKind,MediaBrowser.Controller.Entities.InternalItemsQuery)
EnableGroupByPresentationUniqueKey(MediaBrowser.Controller.Entities.InternalItemsQuery)
ApplyOrder(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,MediaBrowser.Controller.Entities.InternalItemsQuery)
TranslateQuery(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,MediaBrowser.Controller.Entities.InternalItemsQuery)