< 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: 552
Uncovered lines: 502
Coverable lines: 1054
Total lines: 2232
Line coverage: 52.3%
Branch coverage
48%
Covered branches: 318
Total branches: 660
Branch coverage: 48.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
DeleteItem(...)50%2296%
UpdateInheritedValues()100%11100%
GetItemIdsList(...)100%11100%
GetAllArtists(...)100%210%
GetArtists(...)100%210%
GetAlbumArtists(...)100%210%
GetStudios(...)100%210%
GetGenres(...)100%210%
GetMusicGenres(...)100%210%
GetStudioNames()100%11100%
GetAllArtistNames()100%11100%
GetMusicGenreNames()100%11100%
GetGenreNames()100%11100%
GetItems(...)50%26835%
GetItemList(...)100%11100%
GetLatestItemList(...)0%4260%
GetNextUpSeriesKeys(...)0%620%
ApplyGroupingFilter(...)100%11100%
ApplyQueryPaging(...)87.5%8885.71%
ApplyQueryFilter(...)100%11100%
PrepareItemQuery(...)100%22100%
GetCount(...)100%210%
GetType(...)100%11100%
SaveImages(...)100%210%
SaveItems(...)100%11100%
UpdateOrInsertItems(...)75%502464.4%
RetrieveItem(...)50%4485.71%
Map(...)47.36%1297679.06%
Map(...)50%725079.46%
GetItemValueNames(...)100%44100%
TypeRequiresDeserialization(...)100%11100%
DeserialiseBaseItem(...)50%101088.88%
DeserialiseBaseItem(...)83.33%151272.72%
GetItemValues(...)0%272160%
PrepareFilterQuery(...)83.33%6680%
GetCleanValue(...)50%2266.66%
GetItemValuesToSave(...)50%4481.81%
Map(...)0%620%
Map(...)0%4260%
GetPathToSave(...)50%2266.66%
GetItemByNameTypesInQuery(...)100%1010100%
IsTypeInQuery(...)75%5466.66%
EnableGroupByPresentationUniqueKey(...)0%420200%
ApplyOrder(...)57.69%332678.26%
TranslateQuery(...)47.17%2833935439.33%

File(s)

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

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2// Do not enforce that because EFCore cannot deal with cultures well.
 3#pragma warning disable CA1304 // Specify CultureInfo
 4#pragma warning disable CA1311 // Specify a culture or use an invariant version
 5#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string compari
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Collections.Generic;
 10using System.Globalization;
 11using System.Linq;
 12using System.Linq.Expressions;
 13using System.Reflection;
 14using System.Text;
 15using System.Text.Json;
 16using System.Threading;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations;
 19using Jellyfin.Database.Implementations.Entities;
 20using Jellyfin.Database.Implementations.Enums;
 21using Jellyfin.Extensions;
 22using Jellyfin.Extensions.Json;
 23using Jellyfin.Server.Implementations.Extensions;
 24using MediaBrowser.Common;
 25using MediaBrowser.Controller;
 26using MediaBrowser.Controller.Channels;
 27using MediaBrowser.Controller.Configuration;
 28using MediaBrowser.Controller.Entities;
 29using MediaBrowser.Controller.Entities.Audio;
 30using MediaBrowser.Controller.Entities.TV;
 31using MediaBrowser.Controller.LiveTv;
 32using MediaBrowser.Controller.Persistence;
 33using MediaBrowser.Model.Dto;
 34using MediaBrowser.Model.Entities;
 35using MediaBrowser.Model.LiveTv;
 36using MediaBrowser.Model.Querying;
 37using Microsoft.EntityFrameworkCore;
 38using Microsoft.Extensions.Logging;
 39using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 40using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 41
 42namespace Jellyfin.Server.Implementations.Item;
 43
 44/*
 45    All queries in this class and all other nullable enabled EFCore repository classes will make liberal use of the null
 46    This is done as the code isn't actually executed client side, but only the expressions are interpret and the compile
 47    This is your only warning/message regarding this topic.
 48*/
 49
 50/// <summary>
 51/// Handles all storage logic for BaseItems.
 52/// </summary>
 53public sealed class BaseItemRepository
 54    : IItemRepository
 55{
 56    /// <summary>
 57    /// This holds all the types in the running assemblies
 58    /// so that we can de-serialize properly when we don't have strong types.
 59    /// </summary>
 160    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 61    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 62    private readonly IServerApplicationHost _appHost;
 63    private readonly IItemTypeLookup _itemTypeLookup;
 64    private readonly IServerConfigurationManager _serverConfigurationManager;
 65    private readonly ILogger<BaseItemRepository> _logger;
 66
 167    private static readonly IReadOnlyList<ItemValueType> _getAllArtistsValueTypes = [ItemValueType.Artist, ItemValueType
 168    private static readonly IReadOnlyList<ItemValueType> _getArtistValueTypes = [ItemValueType.Artist];
 169    private static readonly IReadOnlyList<ItemValueType> _getAlbumArtistValueTypes = [ItemValueType.AlbumArtist];
 170    private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios];
 171    private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre];
 72
 73    /// <summary>
 74    /// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
 75    /// </summary>
 76    /// <param name="dbProvider">The db factory.</param>
 77    /// <param name="appHost">The Application host.</param>
 78    /// <param name="itemTypeLookup">The static type lookup.</param>
 79    /// <param name="serverConfigurationManager">The server Configuration manager.</param>
 80    /// <param name="logger">System logger.</param>
 81    public BaseItemRepository(
 82        IDbContextFactory<JellyfinDbContext> dbProvider,
 83        IServerApplicationHost appHost,
 84        IItemTypeLookup itemTypeLookup,
 85        IServerConfigurationManager serverConfigurationManager,
 86        ILogger<BaseItemRepository> logger)
 87    {
 2188        _dbProvider = dbProvider;
 2189        _appHost = appHost;
 2190        _itemTypeLookup = itemTypeLookup;
 2191        _serverConfigurationManager = serverConfigurationManager;
 2192        _logger = logger;
 2193    }
 94
 95    /// <inheritdoc />
 96    public void DeleteItem(Guid id)
 97    {
 298        if (id.IsEmpty())
 99        {
 0100            throw new ArgumentException("Guid can't be empty", nameof(id));
 101        }
 102
 2103        using var context = _dbProvider.CreateDbContext();
 2104        using var transaction = context.Database.BeginTransaction();
 2105        context.AncestorIds.Where(e => e.ItemId == id || e.ParentItemId == id).ExecuteDelete();
 2106        context.AttachmentStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2107        context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2108        context.BaseItemMetadataFields.Where(e => e.ItemId == id).ExecuteDelete();
 2109        context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete();
 2110        context.BaseItemTrailerTypes.Where(e => e.ItemId == id).ExecuteDelete();
 2111        context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
 2112        context.Chapters.Where(e => e.ItemId == id).ExecuteDelete();
 2113        context.CustomItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete();
 2114        context.ItemDisplayPreferences.Where(e => e.ItemId == id).ExecuteDelete();
 2115        context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete();
 2116        context.ItemValuesMap.Where(e => e.ItemId == id).ExecuteDelete();
 2117        context.KeyframeData.Where(e => e.ItemId == id).ExecuteDelete();
 2118        context.MediaSegments.Where(e => e.ItemId == id).ExecuteDelete();
 2119        context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2120        context.PeopleBaseItemMap.Where(e => e.ItemId == id).ExecuteDelete();
 2121        context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete();
 2122        context.TrickplayInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2123        context.SaveChanges();
 2124        transaction.Commit();
 4125    }
 126
 127    /// <inheritdoc />
 128    public void UpdateInheritedValues()
 129    {
 11130        using var context = _dbProvider.CreateDbContext();
 11131        using var transaction = context.Database.BeginTransaction();
 132
 11133        context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
 134        // ItemValue Inheritance is now correctly mapped via AncestorId on demand
 11135        context.SaveChanges();
 136
 11137        transaction.Commit();
 22138    }
 139
 140    /// <inheritdoc />
 141    public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
 142    {
 11143        ArgumentNullException.ThrowIfNull(filter);
 11144        PrepareFilterQuery(filter);
 145
 11146        using var context = _dbProvider.CreateDbContext();
 11147        return ApplyQueryFilter(context.BaseItems.AsNoTracking(), context, filter).Select(e => e.Id).ToArray();
 11148    }
 149
 150    /// <inheritdoc />
 151    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter)
 152    {
 0153        return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtis
 154    }
 155
 156    /// <inheritdoc />
 157    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter)
 158    {
 0159        return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]);
 160    }
 161
 162    /// <inheritdoc />
 163    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
 164    {
 0165        return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArti
 166    }
 167
 168    /// <inheritdoc />
 169    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter)
 170    {
 0171        return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]);
 172    }
 173
 174    /// <inheritdoc />
 175    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter)
 176    {
 0177        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]);
 178    }
 179
 180    /// <inheritdoc />
 181    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
 182    {
 0183        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]);
 184    }
 185
 186    /// <inheritdoc />
 187    public IReadOnlyList<string> GetStudioNames()
 188    {
 11189        return GetItemValueNames(_getStudiosValueTypes, [], []);
 190    }
 191
 192    /// <inheritdoc />
 193    public IReadOnlyList<string> GetAllArtistNames()
 194    {
 11195        return GetItemValueNames(_getAllArtistsValueTypes, [], []);
 196    }
 197
 198    /// <inheritdoc />
 199    public IReadOnlyList<string> GetMusicGenreNames()
 200    {
 11201        return GetItemValueNames(
 11202            _getGenreValueTypes,
 11203            _itemTypeLookup.MusicGenreTypes,
 11204            []);
 205    }
 206
 207    /// <inheritdoc />
 208    public IReadOnlyList<string> GetGenreNames()
 209    {
 11210        return GetItemValueNames(
 11211            _getGenreValueTypes,
 11212            [],
 11213            _itemTypeLookup.MusicGenreTypes);
 214    }
 215
 216    /// <inheritdoc />
 217    public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
 218    {
 1219        ArgumentNullException.ThrowIfNull(filter);
 1220        if (!filter.EnableTotalRecordCount || (!filter.Limit.HasValue && (filter.StartIndex ?? 0) == 0))
 221        {
 1222            var returnList = GetItemList(filter);
 1223            return new QueryResult<BaseItemDto>(
 1224                filter.StartIndex,
 1225                returnList.Count,
 1226                returnList);
 227        }
 228
 0229        PrepareFilterQuery(filter);
 0230        var result = new QueryResult<BaseItemDto>();
 231
 0232        using var context = _dbProvider.CreateDbContext();
 233
 0234        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 235
 0236        dbQuery = TranslateQuery(dbQuery, context, filter);
 0237        if (filter.EnableTotalRecordCount)
 238        {
 0239            result.TotalRecordCount = dbQuery.Count();
 240        }
 241
 0242        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 0243        dbQuery = ApplyQueryPaging(dbQuery, filter);
 244
 0245        result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDe
 0246        result.StartIndex = filter.StartIndex ?? 0;
 0247        return result;
 0248    }
 249
 250    /// <inheritdoc />
 251    public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
 252    {
 268253        ArgumentNullException.ThrowIfNull(filter);
 268254        PrepareFilterQuery(filter);
 255
 268256        using var context = _dbProvider.CreateDbContext();
 267257        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 258
 267259        dbQuery = TranslateQuery(dbQuery, context, filter);
 260
 267261        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 267262        dbQuery = ApplyQueryPaging(dbQuery, filter);
 263
 267264        return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserializ
 267265    }
 266
 267    /// <inheritdoc/>
 268    public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType)
 269    {
 0270        ArgumentNullException.ThrowIfNull(filter);
 0271        PrepareFilterQuery(filter);
 272
 273        // Early exit if collection type is not tvshows or music
 0274        if (collectionType != CollectionType.tvshows && collectionType != CollectionType.music)
 275        {
 0276            return Array.Empty<BaseItem>();
 277        }
 278
 0279        using var context = _dbProvider.CreateDbContext();
 280
 281        // Subquery to group by SeriesNames/Album and get the max Date Created for each group.
 0282        var subquery = PrepareItemQuery(context, filter);
 0283        subquery = TranslateQuery(subquery, context, filter);
 0284        var subqueryGrouped = subquery.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
 0285            .Select(g => new
 0286            {
 0287                Key = g.Key,
 0288                MaxDateCreated = g.Max(a => a.DateCreated)
 0289            })
 0290            .OrderByDescending(g => g.MaxDateCreated)
 0291            .Select(g => g);
 292
 0293        if (filter.Limit.HasValue)
 294        {
 0295            subqueryGrouped = subqueryGrouped.Take(filter.Limit.Value);
 296        }
 297
 0298        filter.Limit = null;
 299
 0300        var mainquery = PrepareItemQuery(context, filter);
 0301        mainquery = TranslateQuery(mainquery, context, filter);
 0302        mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated));
 0303        mainquery = ApplyGroupingFilter(mainquery, filter);
 0304        mainquery = ApplyQueryPaging(mainquery, filter);
 305
 0306        return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserial
 0307    }
 308
 309    /// <inheritdoc />
 310    public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff)
 311    {
 0312        ArgumentNullException.ThrowIfNull(filter);
 0313        ArgumentNullException.ThrowIfNull(filter.User);
 314
 0315        using var context = _dbProvider.CreateDbContext();
 316
 0317        var query = context.BaseItems
 0318            .AsNoTracking()
 0319            .Where(i => filter.TopParentIds.Contains(i.TopParentId!.Value))
 0320            .Where(i => i.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode])
 0321            .Join(
 0322                context.UserData.AsNoTracking(),
 0323                i => new { UserId = filter.User.Id, ItemId = i.Id },
 0324                u => new { UserId = u.UserId, ItemId = u.ItemId },
 0325                (entity, data) => new { Item = entity, UserData = data })
 0326            .GroupBy(g => g.Item.SeriesPresentationUniqueKey)
 0327            .Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
 0328            .Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
 0329            .OrderByDescending(g => g.LastPlayedDate)
 0330            .Select(g => g.Key!);
 331
 0332        if (filter.Limit.HasValue)
 333        {
 0334            query = query.Take(filter.Limit.Value);
 335        }
 336
 0337        return query.ToArray();
 0338    }
 339
 340    private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter
 341    {
 342        // This whole block is needed to filter duplicate entries on request
 343        // for the time being it cannot be used because it would destroy the ordering
 344        // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions b
 345        // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
 346
 347        // var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
 348        // if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
 349        // {
 350        //     dbQuery = ApplyOrder(dbQuery, filter);
 351        //     dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e =
 352        // }
 353        // else if (enableGroupByPresentationUniqueKey)
 354        // {
 355        //     dbQuery = ApplyOrder(dbQuery, filter);
 356        //     dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First());
 357        // }
 358        // else if (filter.GroupBySeriesPresentationUniqueKey)
 359        // {
 360        //     dbQuery = ApplyOrder(dbQuery, filter);
 361        //     dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First());
 362        // }
 363        // else
 364        // {
 365        //     dbQuery = dbQuery.Distinct();
 366        //     dbQuery = ApplyOrder(dbQuery, filter);
 367        // }
 278368        dbQuery = dbQuery.Distinct();
 278369        dbQuery = ApplyOrder(dbQuery, filter);
 370
 278371        return dbQuery;
 372    }
 373
 374    private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
 375    {
 278376        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 377        {
 84378            var offset = filter.StartIndex ?? 0;
 379
 84380            if (offset > 0)
 381            {
 0382                dbQuery = dbQuery.Skip(offset);
 383            }
 384
 84385            if (filter.Limit.HasValue)
 386            {
 84387                dbQuery = dbQuery.Take(filter.Limit.Value);
 388            }
 389        }
 390
 278391        return dbQuery;
 392    }
 393
 394    private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, I
 395    {
 11396        dbQuery = TranslateQuery(dbQuery, context, filter);
 11397        dbQuery = ApplyOrder(dbQuery, filter);
 11398        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 11399        dbQuery = ApplyQueryPaging(dbQuery, filter);
 11400        return dbQuery;
 401    }
 402
 403    private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
 404    {
 483405        IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking().AsSplitQuery()
 483406            .Include(e => e.TrailerTypes)
 483407            .Include(e => e.Provider)
 483408            .Include(e => e.LockedFields);
 409
 483410        if (filter.DtoOptions.EnableImages)
 411        {
 483412            dbQuery = dbQuery.Include(e => e.Images);
 413        }
 414
 483415        return dbQuery;
 416    }
 417
 418    /// <inheritdoc/>
 419    public int GetCount(InternalItemsQuery filter)
 420    {
 0421        ArgumentNullException.ThrowIfNull(filter);
 422        // Hack for right now since we currently don't support filtering out these duplicates within a query
 0423        PrepareFilterQuery(filter);
 424
 0425        using var context = _dbProvider.CreateDbContext();
 0426        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 427
 0428        return dbQuery.Count();
 0429    }
 430
 431#pragma warning disable CA1307 // Specify StringComparison for clarity
 432    /// <summary>
 433    /// Gets the type.
 434    /// </summary>
 435    /// <param name="typeName">Name of the type.</param>
 436    /// <returns>Type.</returns>
 437    /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
 438    private static Type? GetType(string typeName)
 439    {
 174440        ArgumentException.ThrowIfNullOrEmpty(typeName);
 441
 442        // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static 
 443        // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are 
 174444        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 174445            .Select(a => a.GetType(k))
 174446            .FirstOrDefault(t => t is not null));
 447    }
 448
 449    /// <inheritdoc  />
 450    public void SaveImages(BaseItemDto item)
 451    {
 0452        ArgumentNullException.ThrowIfNull(item);
 453
 0454        var images = item.ImageInfos.Select(e => Map(item.Id, e));
 0455        using var context = _dbProvider.CreateDbContext();
 0456        using var transaction = context.Database.BeginTransaction();
 0457        context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
 0458        context.BaseItemImageInfos.AddRange(images);
 0459        context.SaveChanges();
 0460        transaction.Commit();
 0461    }
 462
 463    /// <inheritdoc  />
 464    public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 465    {
 83466        UpdateOrInsertItems(items, cancellationToken);
 80467    }
 468
 469    /// <inheritdoc cref="IItemRepository"/>
 470    public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 471    {
 83472        ArgumentNullException.ThrowIfNull(items);
 83473        cancellationToken.ThrowIfCancellationRequested();
 474
 80475        var tuples = new List<(BaseItemDto Item, List<Guid>? AncestorIds, BaseItemDto TopParent, IEnumerable<string> Use
 320476        foreach (var item in items.GroupBy(e => e.Id).Select(e => e.Last()))
 477        {
 80478            var ancestorIds = item.SupportsAncestors ?
 80479                item.GetAncestorIds().Distinct().ToList() :
 80480                null;
 481
 80482            var topParent = item.GetTopParent();
 483
 80484            var userdataKey = item.GetUserDataKeys();
 80485            var inheritedTags = item.GetInheritedTags();
 486
 80487            tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
 488        }
 489
 80490        var localItemValueCache = new Dictionary<(int MagicNumber, string Value), Guid>();
 491
 80492        using var context = _dbProvider.CreateDbContext();
 80493        using var transaction = context.Database.BeginTransaction();
 320494        foreach (var item in tuples)
 495        {
 80496            var entity = Map(item.Item);
 497            // TODO: refactor this "inconsistency"
 80498            entity.TopParentId = item.TopParent?.Id;
 499
 80500            if (!context.BaseItems.Any(e => e.Id == entity.Id))
 501            {
 50502                context.BaseItems.Add(entity);
 503            }
 504            else
 505            {
 30506                context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 30507                context.BaseItems.Attach(entity).State = EntityState.Modified;
 508            }
 509
 80510            context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 80511            if (item.Item.SupportsAncestors && item.AncestorIds != null)
 512            {
 172513                foreach (var ancestorId in item.AncestorIds)
 514                {
 6515                    if (!context.BaseItems.Any(f => f.Id == ancestorId))
 516                    {
 517                        continue;
 518                    }
 519
 6520                    context.AncestorIds.Add(new AncestorId()
 6521                    {
 6522                        ParentItemId = ancestorId,
 6523                        ItemId = entity.Id,
 6524                        Item = null!,
 6525                        ParentItem = null!
 6526                    });
 527                }
 528            }
 529
 530            // Never save duplicate itemValues as they are now mapped anyway.
 80531            var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags).DistinctBy(e => (GetCleanValue(e.V
 80532            context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 160533            foreach (var itemValue in itemValuesToSave)
 534            {
 0535                if (!localItemValueCache.TryGetValue(itemValue, out var refValue))
 536                {
 0537                    refValue = context.ItemValues
 0538                                .Where(f => f.Value == itemValue.Value && (int)f.Type == itemValue.MagicNumber)
 0539                                .Select(e => e.ItemValueId)
 0540                                .FirstOrDefault();
 541                }
 542
 0543                if (refValue.IsEmpty())
 544                {
 0545                    context.ItemValues.Add(new ItemValue()
 0546                    {
 0547                        CleanValue = GetCleanValue(itemValue.Value),
 0548                        Type = (ItemValueType)itemValue.MagicNumber,
 0549                        ItemValueId = refValue = Guid.NewGuid(),
 0550                        Value = itemValue.Value
 0551                    });
 0552                    localItemValueCache[itemValue] = refValue;
 553                }
 554
 0555                context.ItemValuesMap.Add(new ItemValueMap()
 0556                {
 0557                    Item = null!,
 0558                    ItemId = entity.Id,
 0559                    ItemValue = null!,
 0560                    ItemValueId = refValue
 0561                });
 562            }
 563        }
 564
 80565        context.SaveChanges();
 80566        transaction.Commit();
 160567    }
 568
 569    /// <inheritdoc  />
 570    public BaseItemDto? RetrieveItem(Guid id)
 571    {
 216572        if (id.IsEmpty())
 573        {
 0574            throw new ArgumentException("Guid can't be empty", nameof(id));
 575        }
 576
 216577        using var context = _dbProvider.CreateDbContext();
 216578        var item = PrepareItemQuery(context, new()
 216579        {
 216580            DtoOptions = new()
 216581            {
 216582                EnableImages = true
 216583            }
 216584        }).FirstOrDefault(e => e.Id == id);
 216585        if (item is null)
 586        {
 216587            return null;
 588        }
 589
 0590        return DeserialiseBaseItem(item);
 216591    }
 592
 593    /// <summary>
 594    /// Maps a Entity to the DTO.
 595    /// </summary>
 596    /// <param name="entity">The entity.</param>
 597    /// <param name="dto">The dto base instance.</param>
 598    /// <param name="appHost">The Application server Host.</param>
 599    /// <returns>The dto to map.</returns>
 600    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 601    {
 87602        dto.Id = entity.Id;
 87603        dto.ParentId = entity.ParentId.GetValueOrDefault();
 87604        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 87605        dto.EndDate = entity.EndDate;
 87606        dto.CommunityRating = entity.CommunityRating;
 87607        dto.CustomRating = entity.CustomRating;
 87608        dto.IndexNumber = entity.IndexNumber;
 87609        dto.IsLocked = entity.IsLocked;
 87610        dto.Name = entity.Name;
 87611        dto.OfficialRating = entity.OfficialRating;
 87612        dto.Overview = entity.Overview;
 87613        dto.ParentIndexNumber = entity.ParentIndexNumber;
 87614        dto.PremiereDate = entity.PremiereDate;
 87615        dto.ProductionYear = entity.ProductionYear;
 87616        dto.SortName = entity.SortName;
 87617        dto.ForcedSortName = entity.ForcedSortName;
 87618        dto.RunTimeTicks = entity.RunTimeTicks;
 87619        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 87620        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 87621        dto.IsInMixedFolder = entity.IsInMixedFolder;
 87622        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 87623        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 87624        dto.CriticRating = entity.CriticRating;
 87625        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 87626        dto.OriginalTitle = entity.OriginalTitle;
 87627        dto.Album = entity.Album;
 87628        dto.LUFS = entity.LUFS;
 87629        dto.NormalizationGain = entity.NormalizationGain;
 87630        dto.IsVirtualItem = entity.IsVirtualItem;
 87631        dto.ExternalSeriesId = entity.ExternalSeriesId;
 87632        dto.Tagline = entity.Tagline;
 87633        dto.TotalBitrate = entity.TotalBitrate;
 87634        dto.ExternalId = entity.ExternalId;
 87635        dto.Size = entity.Size;
 87636        dto.Genres = entity.Genres?.Split('|') ?? [];
 87637        dto.DateCreated = entity.DateCreated.GetValueOrDefault();
 87638        dto.DateModified = entity.DateModified.GetValueOrDefault();
 87639        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 87640        dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
 87641        dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
 87642        dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ow
 87643        dto.Width = entity.Width.GetValueOrDefault();
 87644        dto.Height = entity.Height.GetValueOrDefault();
 87645        if (entity.Provider is not null)
 646        {
 87647            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 648        }
 649
 87650        if (entity.ExtraType is not null)
 651        {
 0652            dto.ExtraType = (ExtraType)entity.ExtraType;
 653        }
 654
 87655        if (entity.LockedFields is not null)
 656        {
 87657            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 658        }
 659
 87660        if (entity.Audio is not null)
 661        {
 0662            dto.Audio = (ProgramAudio)entity.Audio;
 663        }
 664
 87665        dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Par
 87666        dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
 87667        dto.Studios = entity.Studios?.Split('|') ?? [];
 87668        dto.Tags = entity.Tags?.Split('|') ?? [];
 669
 87670        if (dto is IHasProgramAttributes hasProgramAttributes)
 671        {
 0672            hasProgramAttributes.IsMovie = entity.IsMovie;
 0673            hasProgramAttributes.IsSeries = entity.IsSeries;
 0674            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0675            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 676        }
 677
 87678        if (dto is LiveTvChannel liveTvChannel)
 679        {
 0680            liveTvChannel.ServiceName = entity.ExternalServiceId;
 681        }
 682
 87683        if (dto is Trailer trailer)
 684        {
 0685            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 686        }
 687
 87688        if (dto is Video video)
 689        {
 0690            video.PrimaryVersionId = entity.PrimaryVersionId;
 691        }
 692
 87693        if (dto is IHasSeries hasSeriesName)
 694        {
 0695            hasSeriesName.SeriesName = entity.SeriesName;
 0696            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0697            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 698        }
 699
 87700        if (dto is Episode episode)
 701        {
 0702            episode.SeasonName = entity.SeasonName;
 0703            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 704        }
 705
 87706        if (dto is IHasArtist hasArtists)
 707        {
 0708            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 709        }
 710
 87711        if (dto is IHasAlbumArtist hasAlbumArtists)
 712        {
 0713            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 714        }
 715
 87716        if (dto is LiveTvProgram program)
 717        {
 0718            program.ShowId = entity.ShowId;
 719        }
 720
 87721        if (entity.Images is not null)
 722        {
 87723            dto.ImageInfos = entity.Images.Select(e => Map(e, appHost)).ToArray();
 724        }
 725
 726        // dto.Type = entity.Type;
 727        // dto.Data = entity.Data;
 728        // dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
 87729        if (dto is IHasStartDate hasStartDate)
 730        {
 0731            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 732        }
 733
 734        // Fields that are present in the DB but are never actually used
 735        // dto.UnratedType = entity.UnratedType;
 736        // dto.TopParentId = entity.TopParentId;
 737        // dto.CleanName = entity.CleanName;
 738        // dto.UserDataKey = entity.UserDataKey;
 739
 87740        if (dto is Folder folder)
 741        {
 87742            folder.DateLastMediaAdded = entity.DateLastMediaAdded;
 743        }
 744
 87745        return dto;
 746    }
 747
 748    /// <summary>
 749    /// Maps a Entity to the DTO.
 750    /// </summary>
 751    /// <param name="dto">The entity.</param>
 752    /// <returns>The dto to map.</returns>
 753    public BaseItemEntity Map(BaseItemDto dto)
 754    {
 80755        var dtoType = dto.GetType();
 80756        var entity = new BaseItemEntity()
 80757        {
 80758            Type = dtoType.ToString(),
 80759            Id = dto.Id
 80760        };
 761
 80762        if (TypeRequiresDeserialization(dtoType))
 763        {
 59764            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 765        }
 766
 80767        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 80768        entity.Path = GetPathToSave(dto.Path);
 80769        entity.EndDate = dto.EndDate;
 80770        entity.CommunityRating = dto.CommunityRating;
 80771        entity.CustomRating = dto.CustomRating;
 80772        entity.IndexNumber = dto.IndexNumber;
 80773        entity.IsLocked = dto.IsLocked;
 80774        entity.Name = dto.Name;
 80775        entity.CleanName = GetCleanValue(dto.Name);
 80776        entity.OfficialRating = dto.OfficialRating;
 80777        entity.Overview = dto.Overview;
 80778        entity.ParentIndexNumber = dto.ParentIndexNumber;
 80779        entity.PremiereDate = dto.PremiereDate;
 80780        entity.ProductionYear = dto.ProductionYear;
 80781        entity.SortName = dto.SortName;
 80782        entity.ForcedSortName = dto.ForcedSortName;
 80783        entity.RunTimeTicks = dto.RunTimeTicks;
 80784        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 80785        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 80786        entity.IsInMixedFolder = dto.IsInMixedFolder;
 80787        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 80788        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 80789        entity.CriticRating = dto.CriticRating;
 80790        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 80791        entity.OriginalTitle = dto.OriginalTitle;
 80792        entity.Album = dto.Album;
 80793        entity.LUFS = dto.LUFS;
 80794        entity.NormalizationGain = dto.NormalizationGain;
 80795        entity.IsVirtualItem = dto.IsVirtualItem;
 80796        entity.ExternalSeriesId = dto.ExternalSeriesId;
 80797        entity.Tagline = dto.Tagline;
 80798        entity.TotalBitrate = dto.TotalBitrate;
 80799        entity.ExternalId = dto.ExternalId;
 80800        entity.Size = dto.Size;
 80801        entity.Genres = string.Join('|', dto.Genres);
 80802        entity.DateCreated = dto.DateCreated;
 80803        entity.DateModified = dto.DateModified;
 80804        entity.ChannelId = dto.ChannelId;
 80805        entity.DateLastRefreshed = dto.DateLastRefreshed;
 80806        entity.DateLastSaved = dto.DateLastSaved;
 80807        entity.OwnerId = dto.OwnerId.ToString();
 80808        entity.Width = dto.Width;
 80809        entity.Height = dto.Height;
 80810        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 80811        {
 80812            Item = entity,
 80813            ProviderId = e.Key,
 80814            ProviderValue = e.Value
 80815        }).ToList();
 816
 80817        if (dto.Audio.HasValue)
 818        {
 0819            entity.Audio = (ProgramAudioEntity)dto.Audio;
 820        }
 821
 80822        if (dto.ExtraType.HasValue)
 823        {
 0824            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 825        }
 826
 80827        entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
 80828        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : n
 80829        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
 80830        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
 80831        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 80832            .Select(e => new BaseItemMetadataField()
 80833            {
 80834                Id = (int)e,
 80835                Item = entity,
 80836                ItemId = entity.Id
 80837            })
 80838            .ToArray() : null;
 839
 80840        if (dto is IHasProgramAttributes hasProgramAttributes)
 841        {
 0842            entity.IsMovie = hasProgramAttributes.IsMovie;
 0843            entity.IsSeries = hasProgramAttributes.IsSeries;
 0844            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0845            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 846        }
 847
 80848        if (dto is LiveTvChannel liveTvChannel)
 849        {
 0850            entity.ExternalServiceId = liveTvChannel.ServiceName;
 851        }
 852
 80853        if (dto is Video video)
 854        {
 0855            entity.PrimaryVersionId = video.PrimaryVersionId;
 856        }
 857
 80858        if (dto is IHasSeries hasSeriesName)
 859        {
 0860            entity.SeriesName = hasSeriesName.SeriesName;
 0861            entity.SeriesId = hasSeriesName.SeriesId;
 0862            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 863        }
 864
 80865        if (dto is Episode episode)
 866        {
 0867            entity.SeasonName = episode.SeasonName;
 0868            entity.SeasonId = episode.SeasonId;
 869        }
 870
 80871        if (dto is IHasArtist hasArtists)
 872        {
 0873            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
 874        }
 875
 80876        if (dto is IHasAlbumArtist hasAlbumArtists)
 877        {
 0878            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 879        }
 880
 80881        if (dto is LiveTvProgram program)
 882        {
 0883            entity.ShowId = program.ShowId;
 884        }
 885
 80886        if (dto.ImageInfos is not null)
 887        {
 80888            entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
 889        }
 890
 80891        if (dto is Trailer trailer)
 892        {
 0893            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0894            {
 0895                Id = (int)e,
 0896                Item = entity,
 0897                ItemId = entity.Id
 0898            }).ToArray() ?? [];
 899        }
 900
 901        // dto.Type = entity.Type;
 902        // dto.Data = entity.Data;
 80903        entity.MediaType = dto.MediaType.ToString();
 80904        if (dto is IHasStartDate hasStartDate)
 905        {
 0906            entity.StartDate = hasStartDate.StartDate;
 907        }
 908
 80909        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 910
 911        // Fields that are present in the DB but are never actually used
 912        // dto.UserDataKey = entity.UserDataKey;
 913
 80914        if (dto is Folder folder)
 915        {
 80916            entity.DateLastMediaAdded = folder.DateLastMediaAdded;
 80917            entity.IsFolder = folder.IsFolder;
 918        }
 919
 80920        return entity;
 921    }
 922
 923    private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes,
 924    {
 44925        using var context = _dbProvider.CreateDbContext();
 926
 44927        var query = context.ItemValuesMap
 44928            .AsNoTracking()
 44929            .Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.Type));
 44930        if (withItemTypes.Count > 0)
 931        {
 11932            query = query.Where(e => withItemTypes.Contains(e.Item.Type));
 933        }
 934
 44935        if (excludeItemTypes.Count > 0)
 936        {
 11937            query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
 938        }
 939
 940        // query = query.DistinctBy(e => e.CleanValue);
 44941        return query.Select(e => e.ItemValue)
 44942            .GroupBy(e => e.CleanValue)
 44943            .Select(e => e.First().Value)
 44944            .ToArray();
 44945    }
 946
 947    private static bool TypeRequiresDeserialization(Type type)
 948    {
 167949        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 950    }
 951
 952    private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
 953    {
 87954        ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity));
 87955        if (_serverConfigurationManager?.Configuration is null)
 956        {
 0957            throw new InvalidOperationException("Server Configuration manager or configuration is null");
 958        }
 959
 87960        var typeToSerialise = GetType(baseItemEntity.Type);
 87961        return BaseItemRepository.DeserialiseBaseItem(
 87962            baseItemEntity,
 87963            _logger,
 87964            _appHost,
 87965            skipDeserialization || (_serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeT
 966    }
 967
 968    /// <summary>
 969    /// Deserialises a BaseItemEntity and sets all properties.
 970    /// </summary>
 971    /// <param name="baseItemEntity">The DB entity.</param>
 972    /// <param name="logger">Logger.</param>
 973    /// <param name="appHost">The application server Host.</param>
 974    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 975    /// <returns>A mapped BaseItem.</returns>
 976    /// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
 977    public static BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost?
 978    {
 87979        var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unknown type.
 87980        BaseItemDto? dto = null;
 87981        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 982        {
 983            try
 984            {
 18985                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 18986            }
 0987            catch (JsonException ex)
 988            {
 0989                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 0990            }
 991        }
 992
 87993        if (dto is null)
 994        {
 69995            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 996        }
 997
 87998        return Map(baseItemEntity, dto, appHost);
 999    }
 1000
 1001    private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyLis
 1002    {
 01003        ArgumentNullException.ThrowIfNull(filter);
 1004
 01005        if (!filter.Limit.HasValue)
 1006        {
 01007            filter.EnableTotalRecordCount = false;
 1008        }
 1009
 01010        using var context = _dbProvider.CreateDbContext();
 1011
 01012        var query = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 1013
 01014        query = query.Where(e => e.Type == returnType);
 1015        // this does not seem to be nesseary but it does not make any sense why this isn't working.
 1016        // && e.ItemValues!.Any(f => e.CleanName == f.ItemValue.CleanValue && itemValueTypes.Any(w => (ItemValueType)w =
 1017
 01018        if (filter.OrderBy.Count != 0
 01019            || !string.IsNullOrEmpty(filter.SearchTerm))
 1020        {
 01021            query = ApplyOrder(query, filter);
 1022        }
 1023        else
 1024        {
 01025            query = query.OrderBy(e => e.SortName);
 1026        }
 1027
 01028        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 1029        {
 01030            var offset = filter.StartIndex ?? 0;
 1031
 01032            if (offset > 0)
 1033            {
 01034                query = query.Skip(offset);
 1035            }
 1036
 01037            if (filter.Limit.HasValue)
 1038            {
 01039                query = query.Take(filter.Limit.Value);
 1040            }
 1041        }
 1042
 01043        var result = new QueryResult<(BaseItemDto, ItemCounts)>();
 01044        if (filter.EnableTotalRecordCount)
 1045        {
 01046            result.TotalRecordCount = query.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First()).Count();
 1047        }
 1048
 01049        var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series];
 01050        var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
 01051        var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode];
 01052        var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum];
 01053        var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist];
 01054        var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio];
 01055        var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer];
 1056
 01057        var resultQuery = query.Select(e => new
 01058        {
 01059            item = e,
 01060            // TODO: This is bad refactor!
 01061            itemCount = new ItemCounts()
 01062            {
 01063                SeriesCount = e.ItemValues!.Count(f => f.Item.Type == seriesTypeName),
 01064                EpisodeCount = e.ItemValues!.Count(f => f.Item.Type == episodeTypeName),
 01065                MovieCount = e.ItemValues!.Count(f => f.Item.Type == movieTypeName),
 01066                AlbumCount = e.ItemValues!.Count(f => f.Item.Type == musicAlbumTypeName),
 01067                ArtistCount = e.ItemValues!.Count(f => f.Item.Type == musicArtistTypeName),
 01068                SongCount = e.ItemValues!.Count(f => f.Item.Type == audioTypeName),
 01069                TrailerCount = e.ItemValues!.Count(f => f.Item.Type == trailerTypeName),
 01070            }
 01071        });
 1072
 01073        result.StartIndex = filter.StartIndex ?? 0;
 01074        result.Items = resultQuery.ToArray().Where(e => e is not null).Select(e =>
 01075        {
 01076            return (DeserialiseBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
 01077        }).ToArray();
 1078
 01079        return result;
 01080    }
 1081
 1082    private static void PrepareFilterQuery(InternalItemsQuery query)
 1083    {
 2791084        if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
 1085        {
 01086            query.Limit = query.Limit.Value + 4;
 1087        }
 1088
 2791089        if (query.IsResumable ?? false)
 1090        {
 11091            query.IsVirtualItem = false;
 1092        }
 2791093    }
 1094
 1095    private string GetCleanValue(string value)
 1096    {
 831097        if (string.IsNullOrWhiteSpace(value))
 1098        {
 01099            return value;
 1100        }
 1101
 831102        return value.RemoveDiacritics().ToLowerInvariant();
 1103    }
 1104
 1105    private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inheritedTags)
 1106    {
 801107        var list = new List<(int, string)>();
 1108
 801109        if (item is IHasArtist hasArtist)
 1110        {
 01111            list.AddRange(hasArtist.Artists.Select(i => (0, i)));
 1112        }
 1113
 801114        if (item is IHasAlbumArtist hasAlbumArtist)
 1115        {
 01116            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i)));
 1117        }
 1118
 801119        list.AddRange(item.Genres.Select(i => (2, i)));
 801120        list.AddRange(item.Studios.Select(i => (3, i)));
 801121        list.AddRange(item.Tags.Select(i => (4, i)));
 1122
 1123        // keywords was 5
 1124
 801125        list.AddRange(inheritedTags.Select(i => (6, i)));
 1126
 1127        // Remove all invalid values.
 801128        list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
 1129
 801130        return list;
 1131    }
 1132
 1133    private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
 1134    {
 01135        return new BaseItemImageInfo()
 01136        {
 01137            ItemId = baseItemId,
 01138            Id = Guid.NewGuid(),
 01139            Path = e.Path,
 01140            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 01141            DateModified = e.DateModified,
 01142            Height = e.Height,
 01143            Width = e.Width,
 01144            ImageType = (ImageInfoImageType)e.Type,
 01145            Item = null!
 01146        };
 1147    }
 1148
 1149    private static ItemImageInfo Map(BaseItemImageInfo e, IServerApplicationHost? appHost)
 1150    {
 01151        return new ItemImageInfo()
 01152        {
 01153            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 01154            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 01155            DateModified = e.DateModified,
 01156            Height = e.Height,
 01157            Width = e.Width,
 01158            Type = (ImageType)e.ImageType
 01159        };
 1160    }
 1161
 1162    private string? GetPathToSave(string path)
 1163    {
 801164        if (path is null)
 1165        {
 01166            return null;
 1167        }
 1168
 801169        return _appHost.ReverseVirtualPath(path);
 1170    }
 1171
 1172    private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
 1173    {
 141174        var list = new List<string>();
 1175
 141176        if (IsTypeInQuery(BaseItemKind.Person, query))
 1177        {
 11178            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!);
 1179        }
 1180
 141181        if (IsTypeInQuery(BaseItemKind.Genre, query))
 1182        {
 11183            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!);
 1184        }
 1185
 141186        if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
 1187        {
 11188            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!);
 1189        }
 1190
 141191        if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
 1192        {
 11193            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
 1194        }
 1195
 141196        if (IsTypeInQuery(BaseItemKind.Studio, query))
 1197        {
 11198            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!);
 1199        }
 1200
 141201        return list;
 1202    }
 1203
 1204    private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
 1205    {
 701206        if (query.ExcludeItemTypes.Contains(type))
 1207        {
 01208            return false;
 1209        }
 1210
 701211        return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
 1212    }
 1213
 1214    private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
 1215    {
 01216        if (!query.GroupByPresentationUniqueKey)
 1217        {
 01218            return false;
 1219        }
 1220
 01221        if (query.GroupBySeriesPresentationUniqueKey)
 1222        {
 01223            return false;
 1224        }
 1225
 01226        if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
 1227        {
 01228            return false;
 1229        }
 1230
 01231        if (query.User is null)
 1232        {
 01233            return false;
 1234        }
 1235
 01236        if (query.IncludeItemTypes.Length == 0)
 1237        {
 01238            return true;
 1239        }
 1240
 01241        return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
 01242            || query.IncludeItemTypes.Contains(BaseItemKind.Video)
 01243            || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01244            || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
 01245            || query.IncludeItemTypes.Contains(BaseItemKind.Series)
 01246            || query.IncludeItemTypes.Contains(BaseItemKind.Season);
 1247    }
 1248
 1249    private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter)
 1250    {
 2891251        var orderBy = filter.OrderBy;
 2891252        var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
 1253
 2891254        if (hasSearch)
 1255        {
 01256            orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
 1257        }
 2891258        else if (orderBy.Count == 0)
 1259        {
 2041260            return query;
 1261        }
 1262
 851263        IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
 1264
 851265        var firstOrdering = orderBy.FirstOrDefault();
 851266        if (firstOrdering != default)
 1267        {
 851268            var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
 851269            if (firstOrdering.SortOrder == SortOrder.Ascending)
 1270            {
 841271                orderedQuery = query.OrderBy(expression);
 1272            }
 1273            else
 1274            {
 11275                orderedQuery = query.OrderByDescending(expression);
 1276            }
 1277
 851278            if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
 1279            {
 01280                if (firstOrdering.SortOrder is SortOrder.Ascending)
 1281                {
 01282                    orderedQuery = orderedQuery.ThenBy(e => e.Name);
 1283                }
 1284                else
 1285                {
 01286                    orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
 1287                }
 1288            }
 1289        }
 1290
 2461291        foreach (var item in orderBy.Skip(1))
 1292        {
 381293            var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
 381294            if (item.SortOrder == SortOrder.Ascending)
 1295            {
 381296                orderedQuery = orderedQuery!.ThenBy(expression);
 1297            }
 1298            else
 1299            {
 01300                orderedQuery = orderedQuery!.ThenByDescending(expression);
 1301            }
 1302        }
 1303
 851304        return orderedQuery ?? query;
 1305    }
 1306
 1307    private IQueryable<BaseItemEntity> TranslateQuery(
 1308        IQueryable<BaseItemEntity> baseQuery,
 1309        JellyfinDbContext context,
 1310        InternalItemsQuery filter)
 1311    {
 1312        const int HDWidth = 1200;
 1313        const int UHDWidth = 3800;
 1314        const int UHDHeight = 2100;
 1315
 2781316        var minWidth = filter.MinWidth;
 2781317        var maxWidth = filter.MaxWidth;
 2781318        var now = DateTime.UtcNow;
 1319
 2781320        if (filter.IsHD.HasValue || filter.Is4K.HasValue)
 1321        {
 01322            bool includeSD = false;
 01323            bool includeHD = false;
 01324            bool include4K = false;
 1325
 01326            if (filter.IsHD.HasValue && !filter.IsHD.Value)
 1327            {
 01328                includeSD = true;
 1329            }
 1330
 01331            if (filter.IsHD.HasValue && filter.IsHD.Value)
 1332            {
 01333                includeHD = true;
 1334            }
 1335
 01336            if (filter.Is4K.HasValue && filter.Is4K.Value)
 1337            {
 01338                include4K = true;
 1339            }
 1340
 01341            baseQuery = baseQuery.Where(e =>
 01342                (includeSD && e.Width < HDWidth) ||
 01343                (includeHD && e.Width >= HDWidth && !(e.Width >= UHDWidth || e.Height >= UHDHeight)) ||
 01344                (include4K && (e.Width >= UHDWidth || e.Height >= UHDHeight)));
 1345        }
 1346
 2781347        if (minWidth.HasValue)
 1348        {
 01349            baseQuery = baseQuery.Where(e => e.Width >= minWidth);
 1350        }
 1351
 2781352        if (filter.MinHeight.HasValue)
 1353        {
 01354            baseQuery = baseQuery.Where(e => e.Height >= filter.MinHeight);
 1355        }
 1356
 2781357        if (maxWidth.HasValue)
 1358        {
 01359            baseQuery = baseQuery.Where(e => e.Width >= maxWidth);
 1360        }
 1361
 2781362        if (filter.MaxHeight.HasValue)
 1363        {
 01364            baseQuery = baseQuery.Where(e => e.Height <= filter.MaxHeight);
 1365        }
 1366
 2781367        if (filter.IsLocked.HasValue)
 1368        {
 331369            baseQuery = baseQuery.Where(e => e.IsLocked == filter.IsLocked);
 1370        }
 1371
 2781372        var tags = filter.Tags.ToList();
 2781373        var excludeTags = filter.ExcludeTags.ToList();
 1374
 2781375        if (filter.IsMovie == true)
 1376        {
 01377            if (filter.IncludeItemTypes.Length == 0
 01378                || filter.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01379                || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
 1380            {
 01381                baseQuery = baseQuery.Where(e => e.IsMovie);
 1382            }
 1383        }
 2781384        else if (filter.IsMovie.HasValue)
 1385        {
 01386            baseQuery = baseQuery.Where(e => e.IsMovie == filter.IsMovie);
 1387        }
 1388
 2781389        if (filter.IsSeries.HasValue)
 1390        {
 01391            baseQuery = baseQuery.Where(e => e.IsSeries == filter.IsSeries);
 1392        }
 1393
 2781394        if (filter.IsSports.HasValue)
 1395        {
 01396            if (filter.IsSports.Value)
 1397            {
 01398                tags.Add("Sports");
 1399            }
 1400            else
 1401            {
 01402                excludeTags.Add("Sports");
 1403            }
 1404        }
 1405
 2781406        if (filter.IsNews.HasValue)
 1407        {
 01408            if (filter.IsNews.Value)
 1409            {
 01410                tags.Add("News");
 1411            }
 1412            else
 1413            {
 01414                excludeTags.Add("News");
 1415            }
 1416        }
 1417
 2781418        if (filter.IsKids.HasValue)
 1419        {
 01420            if (filter.IsKids.Value)
 1421            {
 01422                tags.Add("Kids");
 1423            }
 1424            else
 1425            {
 01426                excludeTags.Add("Kids");
 1427            }
 1428        }
 1429
 2781430        if (!string.IsNullOrEmpty(filter.SearchTerm))
 1431        {
 01432            var searchTerm = filter.SearchTerm.ToLower();
 01433            baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && 
 1434        }
 1435
 2781436        if (filter.IsFolder.HasValue)
 1437        {
 01438            baseQuery = baseQuery.Where(e => e.IsFolder == filter.IsFolder);
 1439        }
 1440
 2781441        var includeTypes = filter.IncludeItemTypes;
 1442        // Only specify excluded types if no included types are specified
 2781443        if (filter.IncludeItemTypes.Length == 0)
 1444        {
 1951445            var excludeTypes = filter.ExcludeItemTypes;
 1951446            if (excludeTypes.Length == 1)
 1447            {
 01448                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
 1449                {
 01450                    baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
 1451                }
 1452            }
 1951453            else if (excludeTypes.Length > 1)
 1454            {
 01455                var excludeTypeName = new List<string>();
 01456                foreach (var excludeType in excludeTypes)
 1457                {
 01458                    if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
 1459                    {
 01460                        excludeTypeName.Add(baseItemKindName!);
 1461                    }
 1462                }
 1463
 01464                baseQuery = baseQuery.Where(e => !excludeTypeName.Contains(e.Type));
 1465            }
 1466        }
 831467        else if (includeTypes.Length == 1)
 1468        {
 261469            if (_itemTypeLookup.BaseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
 1470            {
 261471                baseQuery = baseQuery.Where(e => e.Type == includeTypeName);
 1472            }
 1473        }
 571474        else if (includeTypes.Length > 1)
 1475        {
 571476            var includeTypeName = new List<string>();
 4201477            foreach (var includeType in includeTypes)
 1478            {
 1531479                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
 1480                {
 1531481                    includeTypeName.Add(baseItemKindName!);
 1482                }
 1483            }
 1484
 571485            baseQuery = baseQuery.Where(e => includeTypeName.Contains(e.Type));
 1486        }
 1487
 2781488        if (filter.ChannelIds.Count > 0)
 1489        {
 01490            baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value));
 1491        }
 1492
 2781493        if (!filter.ParentId.IsEmpty())
 1494        {
 1451495            baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
 1496        }
 1497
 2781498        if (!string.IsNullOrWhiteSpace(filter.Path))
 1499        {
 01500            baseQuery = baseQuery.Where(e => e.Path == filter.Path);
 1501        }
 1502
 2781503        if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))
 1504        {
 01505            baseQuery = baseQuery.Where(e => e.PresentationUniqueKey == filter.PresentationUniqueKey);
 1506        }
 1507
 2781508        if (filter.MinCommunityRating.HasValue)
 1509        {
 01510            baseQuery = baseQuery.Where(e => e.CommunityRating >= filter.MinCommunityRating);
 1511        }
 1512
 2781513        if (filter.MinIndexNumber.HasValue)
 1514        {
 01515            baseQuery = baseQuery.Where(e => e.IndexNumber >= filter.MinIndexNumber);
 1516        }
 1517
 2781518        if (filter.MinParentAndIndexNumber.HasValue)
 1519        {
 01520            baseQuery = baseQuery
 01521                .Where(e => (e.ParentIndexNumber == filter.MinParentAndIndexNumber.Value.ParentIndexNumber && e.IndexNum
 1522        }
 1523
 2781524        if (filter.MinDateCreated.HasValue)
 1525        {
 01526            baseQuery = baseQuery.Where(e => e.DateCreated >= filter.MinDateCreated);
 1527        }
 1528
 2781529        if (filter.MinDateLastSaved.HasValue)
 1530        {
 01531            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSaved.Value
 1532        }
 1533
 2781534        if (filter.MinDateLastSavedForUser.HasValue)
 1535        {
 01536            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSavedForUse
 1537        }
 1538
 2781539        if (filter.IndexNumber.HasValue)
 1540        {
 01541            baseQuery = baseQuery.Where(e => e.IndexNumber == filter.IndexNumber.Value);
 1542        }
 1543
 2781544        if (filter.ParentIndexNumber.HasValue)
 1545        {
 01546            baseQuery = baseQuery.Where(e => e.ParentIndexNumber == filter.ParentIndexNumber.Value);
 1547        }
 1548
 2781549        if (filter.ParentIndexNumberNotEquals.HasValue)
 1550        {
 01551            baseQuery = baseQuery.Where(e => e.ParentIndexNumber != filter.ParentIndexNumberNotEquals.Value || e.ParentI
 1552        }
 1553
 2781554        var minEndDate = filter.MinEndDate;
 2781555        var maxEndDate = filter.MaxEndDate;
 1556
 2781557        if (filter.HasAired.HasValue)
 1558        {
 01559            if (filter.HasAired.Value)
 1560            {
 01561                maxEndDate = DateTime.UtcNow;
 1562            }
 1563            else
 1564            {
 01565                minEndDate = DateTime.UtcNow;
 1566            }
 1567        }
 1568
 2781569        if (minEndDate.HasValue)
 1570        {
 01571            baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
 1572        }
 1573
 2781574        if (maxEndDate.HasValue)
 1575        {
 01576            baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
 1577        }
 1578
 2781579        if (filter.MinStartDate.HasValue)
 1580        {
 01581            baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
 1582        }
 1583
 2781584        if (filter.MaxStartDate.HasValue)
 1585        {
 01586            baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
 1587        }
 1588
 2781589        if (filter.MinPremiereDate.HasValue)
 1590        {
 01591            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MinPremiereDate.Value);
 1592        }
 1593
 2781594        if (filter.MaxPremiereDate.HasValue)
 1595        {
 01596            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MaxPremiereDate.Value);
 1597        }
 1598
 2781599        if (filter.TrailerTypes.Length > 0)
 1600        {
 01601            var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray();
 01602            baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f)));
 1603        }
 1604
 2781605        if (filter.IsAiring.HasValue)
 1606        {
 01607            if (filter.IsAiring.Value)
 1608            {
 01609                baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
 1610            }
 1611            else
 1612            {
 01613                baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
 1614            }
 1615        }
 1616
 2781617        if (filter.PersonIds.Length > 0)
 1618        {
 01619            baseQuery = baseQuery
 01620                .Where(e =>
 01621                    context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).A
 01622                        .Any(f => f.ItemId == e.Id));
 1623        }
 1624
 2781625        if (!string.IsNullOrWhiteSpace(filter.Person))
 1626        {
 01627            baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person));
 1628        }
 1629
 2781630        if (!string.IsNullOrWhiteSpace(filter.MinSortName))
 1631        {
 1632            // this does not makes sense.
 1633            // baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName);
 1634            // whereClauses.Add("SortName>=@MinSortName");
 1635            // statement?.TryBind("@MinSortName", query.MinSortName);
 1636        }
 1637
 2781638        if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
 1639        {
 01640            baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
 1641        }
 1642
 2781643        if (!string.IsNullOrWhiteSpace(filter.ExternalId))
 1644        {
 01645            baseQuery = baseQuery.Where(e => e.ExternalId == filter.ExternalId);
 1646        }
 1647
 2781648        if (!string.IsNullOrWhiteSpace(filter.Name))
 1649        {
 31650            var cleanName = GetCleanValue(filter.Name);
 31651            baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
 1652        }
 1653
 1654        // These are the same, for now
 2781655        var nameContains = filter.NameContains;
 2781656        if (!string.IsNullOrWhiteSpace(nameContains))
 1657        {
 01658            baseQuery = baseQuery.Where(e =>
 01659                e.CleanName!.Contains(nameContains)
 01660                || e.OriginalTitle!.ToLower().Contains(nameContains!));
 1661        }
 1662
 2781663        if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
 1664        {
 01665            baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith) || e.Name!.StartsWith(filter.
 1666        }
 1667
 2781668        if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
 1669        {
 1670            // i hate this
 01671            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!
 1672        }
 1673
 2781674        if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
 1675        {
 1676            // i hate this
 01677            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDef
 1678        }
 1679
 2781680        if (filter.ImageTypes.Length > 0)
 1681        {
 841682            var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray();
 841683            baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f)));
 1684        }
 1685
 2781686        if (filter.IsLiked.HasValue)
 1687        {
 01688            baseQuery = baseQuery
 01689                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLike
 1690        }
 1691
 2781692        if (filter.IsFavoriteOrLiked.HasValue)
 1693        {
 01694            baseQuery = baseQuery
 01695                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1696        }
 1697
 2781698        if (filter.IsFavorite.HasValue)
 1699        {
 01700            baseQuery = baseQuery
 01701                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1702        }
 1703
 2781704        if (filter.IsPlayed.HasValue)
 1705        {
 1706            // We should probably figure this out for all folders, but for right now, this is the only place where we ne
 01707            if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series)
 1708            {
 01709                baseQuery = baseQuery.Where(e => context.BaseItems
 01710                    .Where(e => e.IsFolder == false && e.IsVirtualItem == false)
 01711                    .Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
 01712                    .Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
 1713            }
 1714            else
 1715            {
 01716                baseQuery = baseQuery
 01717                    .Select(e => new
 01718                    {
 01719                        IsPlayed = e.UserData!.Where(f => f.UserId == filter.User!.Id).Select(f => (bool?)f.Played).Firs
 01720                        Item = e
 01721                    })
 01722                    .Where(e => e.IsPlayed == filter.IsPlayed)
 01723                    .Select(f => f.Item);
 1724            }
 1725        }
 1726
 2781727        if (filter.IsResumable.HasValue)
 1728        {
 11729            if (filter.IsResumable.Value)
 1730            {
 11731                baseQuery = baseQuery
 11732                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks >
 1733            }
 1734            else
 1735            {
 01736                baseQuery = baseQuery
 01737                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks =
 1738            }
 1739        }
 1740
 2781741        if (filter.ArtistIds.Length > 0)
 1742        {
 01743            baseQuery = baseQuery
 01744                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type <= ItemValueType.Artist && filter.ArtistIds.Conta
 1745        }
 1746
 2781747        if (filter.AlbumArtistIds.Length > 0)
 1748        {
 01749            baseQuery = baseQuery
 01750                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.AlbumArtistIds.
 1751        }
 1752
 2781753        if (filter.ContributingArtistIds.Length > 0)
 1754        {
 01755            baseQuery = baseQuery
 01756                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.ContributingArt
 1757        }
 1758
 2781759        if (filter.AlbumIds.Length > 0)
 1760        {
 01761            baseQuery = baseQuery.Where(e => context.BaseItems.Where(f => filter.AlbumIds.Contains(f.Id)).Any(f => f.Nam
 1762        }
 1763
 2781764        if (filter.ExcludeArtistIds.Length > 0)
 1765        {
 01766            baseQuery = baseQuery
 01767                   .Where(e => !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.ExcludeArtistI
 1768        }
 1769
 2781770        if (filter.GenreIds.Count > 0)
 1771        {
 01772            baseQuery = baseQuery
 01773                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Genre && filter.GenreIds.Contain
 1774        }
 1775
 2781776        if (filter.Genres.Count > 0)
 1777        {
 01778            var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray();
 01779            baseQuery = baseQuery
 01780                    .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Genre && cleanGenres.Contains(f
 1781        }
 1782
 2781783        if (tags.Count > 0)
 1784        {
 01785            var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray();
 01786            baseQuery = baseQuery
 01787                    .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && cleanValues.Contains(f.
 1788        }
 1789
 2781790        if (excludeTags.Count > 0)
 1791        {
 01792            var cleanValues = excludeTags.Select(e => GetCleanValue(e)).ToArray();
 01793            baseQuery = baseQuery
 01794                    .Where(e => !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && cleanValues.Contains(f
 1795        }
 1796
 2781797        if (filter.StudioIds.Length > 0)
 1798        {
 01799            baseQuery = baseQuery
 01800                    .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Studios && filter.StudioIds.Con
 1801        }
 1802
 2781803        if (filter.OfficialRatings.Length > 0)
 1804        {
 01805            baseQuery = baseQuery
 01806                   .Where(e => filter.OfficialRatings.Contains(e.OfficialRating));
 1807        }
 1808
 2781809        Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null;
 2781810        if (filter.MinParentalRating != null)
 1811        {
 01812            var min = filter.MinParentalRating;
 01813            minParentalRatingFilter = e => e.InheritedParentalRatingValue >= min.Score || e.InheritedParentalRatingValue
 01814            if (min.SubScore != null)
 1815            {
 01816                minParentalRatingFilter = minParentalRatingFilter.And(e => e.InheritedParentalRatingValue >= min.SubScor
 1817            }
 1818        }
 1819
 2781820        Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null;
 2781821        if (filter.MaxParentalRating != null)
 1822        {
 331823            var max = filter.MaxParentalRating;
 331824            maxParentalRatingFilter = e => e.InheritedParentalRatingValue <= max.Score || e.InheritedParentalRatingValue
 331825            if (max.SubScore != null)
 1826            {
 01827                maxParentalRatingFilter = maxParentalRatingFilter.And(e => e.InheritedParentalRatingValue <= max.SubScor
 1828            }
 1829        }
 1830
 2781831        if (filter.HasParentalRating ?? false)
 1832        {
 01833            if (minParentalRatingFilter != null)
 1834            {
 01835                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1836            }
 1837
 01838            if (maxParentalRatingFilter != null)
 1839            {
 01840                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1841            }
 1842        }
 2781843        else if (filter.BlockUnratedItems.Length > 0)
 1844        {
 01845            var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
 01846            Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !u
 1847
 01848            if (minParentalRatingFilter != null && maxParentalRatingFilter != null)
 1849            {
 01850                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter)))
 1851            }
 01852            else if (minParentalRatingFilter != null)
 1853            {
 01854                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter));
 1855            }
 01856            else if (maxParentalRatingFilter != null)
 1857            {
 01858                baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter));
 1859            }
 1860            else
 1861            {
 01862                baseQuery = baseQuery.Where(unratedItemFilter);
 1863            }
 1864        }
 2781865        else if (minParentalRatingFilter != null || maxParentalRatingFilter != null)
 1866        {
 331867            if (minParentalRatingFilter != null)
 1868            {
 01869                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1870            }
 1871
 331872            if (maxParentalRatingFilter != null)
 1873            {
 331874                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1875            }
 1876        }
 2451877        else if (!filter.HasParentalRating ?? false)
 1878        {
 01879            baseQuery = baseQuery
 01880                .Where(e => e.InheritedParentalRatingValue == null);
 1881        }
 1882
 2781883        if (filter.HasOfficialRating.HasValue)
 1884        {
 01885            if (filter.HasOfficialRating.Value)
 1886            {
 01887                baseQuery = baseQuery
 01888                    .Where(e => e.OfficialRating != null && e.OfficialRating != string.Empty);
 1889            }
 1890            else
 1891            {
 01892                baseQuery = baseQuery
 01893                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty);
 1894            }
 1895        }
 1896
 2781897        if (filter.HasOverview.HasValue)
 1898        {
 01899            if (filter.HasOverview.Value)
 1900            {
 01901                baseQuery = baseQuery
 01902                    .Where(e => e.Overview != null && e.Overview != string.Empty);
 1903            }
 1904            else
 1905            {
 01906                baseQuery = baseQuery
 01907                    .Where(e => e.Overview == null || e.Overview == string.Empty);
 1908            }
 1909        }
 1910
 2781911        if (filter.HasOwnerId.HasValue)
 1912        {
 01913            if (filter.HasOwnerId.Value)
 1914            {
 01915                baseQuery = baseQuery
 01916                    .Where(e => e.OwnerId != null);
 1917            }
 1918            else
 1919            {
 01920                baseQuery = baseQuery
 01921                    .Where(e => e.OwnerId == null);
 1922            }
 1923        }
 1924
 2781925        if (!string.IsNullOrWhiteSpace(filter.HasNoAudioTrackWithLanguage))
 1926        {
 01927            baseQuery = baseQuery
 01928                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Audio && f.Language == filte
 1929        }
 1930
 2781931        if (!string.IsNullOrWhiteSpace(filter.HasNoInternalSubtitleTrackWithLanguage))
 1932        {
 01933            baseQuery = baseQuery
 01934                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && !f.IsExternal &&
 1935        }
 1936
 2781937        if (!string.IsNullOrWhiteSpace(filter.HasNoExternalSubtitleTrackWithLanguage))
 1938        {
 01939            baseQuery = baseQuery
 01940                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.IsExternal && 
 1941        }
 1942
 2781943        if (!string.IsNullOrWhiteSpace(filter.HasNoSubtitleTrackWithLanguage))
 1944        {
 01945            baseQuery = baseQuery
 01946                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.Language == fi
 1947        }
 1948
 2781949        if (filter.HasSubtitles.HasValue)
 1950        {
 01951            baseQuery = baseQuery
 01952                .Where(e => e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle) == filter.HasSubtit
 1953        }
 1954
 2781955        if (filter.HasChapterImages.HasValue)
 1956        {
 01957            baseQuery = baseQuery
 01958                .Where(e => e.Chapters!.Any(f => f.ImagePath != null) == filter.HasChapterImages.Value);
 1959        }
 1960
 2781961        if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
 1962        {
 111963            baseQuery = baseQuery
 111964                .Where(e => e.ParentId.HasValue && !context.BaseItems.Any(f => f.Id == e.ParentId.Value));
 1965        }
 1966
 2781967        if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
 1968        {
 111969            baseQuery = baseQuery
 111970                    .Where(e => !context.ItemValues.Where(f => _getAllArtistsValueTypes.Contains(f.Type)).Any(f => f.Val
 1971        }
 1972
 2781973        if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value)
 1974        {
 111975            baseQuery = baseQuery
 111976                    .Where(e => !context.ItemValues.Where(f => _getStudiosValueTypes.Contains(f.Type)).Any(f => f.Value 
 1977        }
 1978
 2781979        if (filter.IsDeadGenre.HasValue && filter.IsDeadGenre.Value)
 1980        {
 111981            baseQuery = baseQuery
 111982                    .Where(e => !context.ItemValues.Where(f => _getGenreValueTypes.Contains(f.Type)).Any(f => f.Value ==
 1983        }
 1984
 2781985        if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
 1986        {
 01987            baseQuery = baseQuery
 01988                .Where(e => !context.Peoples.Any(f => f.Name == e.Name));
 1989        }
 1990
 2781991        if (filter.Years.Length == 1)
 1992        {
 01993            baseQuery = baseQuery
 01994                .Where(e => e.ProductionYear == filter.Years[0]);
 1995        }
 2781996        else if (filter.Years.Length > 1)
 1997        {
 01998            baseQuery = baseQuery
 01999                .Where(e => filter.Years.Any(f => f == e.ProductionYear));
 2000        }
 2001
 2782002        var isVirtualItem = filter.IsVirtualItem ?? filter.IsMissing;
 2782003        if (isVirtualItem.HasValue)
 2004        {
 12005            baseQuery = baseQuery
 12006                .Where(e => e.IsVirtualItem == isVirtualItem.Value);
 2007        }
 2008
 2782009        if (filter.IsSpecialSeason.HasValue)
 2010        {
 02011            if (filter.IsSpecialSeason.Value)
 2012            {
 02013                baseQuery = baseQuery
 02014                    .Where(e => e.IndexNumber == 0);
 2015            }
 2016            else
 2017            {
 02018                baseQuery = baseQuery
 02019                    .Where(e => e.IndexNumber != 0);
 2020            }
 2021        }
 2022
 2782023        if (filter.IsUnaired.HasValue)
 2024        {
 02025            if (filter.IsUnaired.Value)
 2026            {
 02027                baseQuery = baseQuery
 02028                    .Where(e => e.PremiereDate >= now);
 2029            }
 2030            else
 2031            {
 02032                baseQuery = baseQuery
 02033                    .Where(e => e.PremiereDate < now);
 2034            }
 2035        }
 2036
 2782037        if (filter.MediaTypes.Length > 0)
 2038        {
 02039            var mediaTypes = filter.MediaTypes.Select(f => f.ToString()).ToArray();
 02040            baseQuery = baseQuery
 02041                .Where(e => mediaTypes.Contains(e.MediaType));
 2042        }
 2043
 2782044        if (filter.ItemIds.Length > 0)
 2045        {
 02046            baseQuery = baseQuery
 02047                .Where(e => filter.ItemIds.Contains(e.Id));
 2048        }
 2049
 2782050        if (filter.ExcludeItemIds.Length > 0)
 2051        {
 02052            baseQuery = baseQuery
 02053                .Where(e => !filter.ItemIds.Contains(e.Id));
 2054        }
 2055
 2782056        if (filter.ExcludeProviderIds is not null && filter.ExcludeProviderIds.Count > 0)
 2057        {
 02058            baseQuery = baseQuery.Where(e => !e.Provider!.All(f => !filter.ExcludeProviderIds.All(w => f.ProviderId == w
 2059        }
 2060
 2782061        if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0)
 2062        {
 02063            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => !filter.HasAnyProviderId.Any(w => f.ProviderId == w.Ke
 2064        }
 2065
 2782066        if (filter.HasImdbId.HasValue)
 2067        {
 02068            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb"));
 2069        }
 2070
 2782071        if (filter.HasTmdbId.HasValue)
 2072        {
 02073            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb"));
 2074        }
 2075
 2782076        if (filter.HasTvdbId.HasValue)
 2077        {
 02078            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb"));
 2079        }
 2080
 2782081        var queryTopParentIds = filter.TopParentIds;
 2082
 2782083        if (queryTopParentIds.Length > 0)
 2084        {
 142085            var includedItemByNameTypes = GetItemByNameTypesInQuery(filter);
 142086            var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
 142087            if (enableItemsByName && includedItemByNameTypes.Count > 0)
 2088            {
 02089                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => 
 2090            }
 2091            else
 2092            {
 142093                baseQuery = baseQuery.Where(e => queryTopParentIds.Contains(e.TopParentId!.Value));
 2094            }
 2095        }
 2096
 2782097        if (filter.AncestorIds.Length > 0)
 2098        {
 392099            baseQuery = baseQuery.Where(e => e.Children!.Any(f => filter.AncestorIds.Contains(f.ParentItemId)));
 2100        }
 2101
 2782102        if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
 2103        {
 02104            baseQuery = baseQuery
 02105                .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqu
 2106        }
 2107
 2782108        if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
 2109        {
 02110            baseQuery = baseQuery
 02111                .Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
 2112        }
 2113
 2782114        if (filter.ExcludeInheritedTags.Length > 0)
 2115        {
 02116            baseQuery = baseQuery
 02117                .Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags)
 02118                    .Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
 2119        }
 2120
 2782121        if (filter.IncludeInheritedTags.Length > 0)
 2122        {
 2123            // Episodes do not store inherit tags from their parents in the database, and the tag may be still required 
 2124            // In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s 
 02125            if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
 2126            {
 02127                baseQuery = baseQuery
 02128                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags)
 02129                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
 02130                        ||
 02131                        (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value)!.Where(w 
 02132                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
 2133            }
 2134
 2135            // A playlist should be accessible to its owner regardless of allowed tags.
 02136            else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
 2137            {
 02138                baseQuery = baseQuery
 02139                    .Where(e =>
 02140                    e.Parents!
 02141                        .Any(f =>
 02142                            f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeIn
 02143                            || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
 2144                // d        ^^ this is stupid it hate this.
 2145            }
 2146            else
 2147            {
 02148                baseQuery = baseQuery
 02149                    .Where(e => e.Parents!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.
 2150            }
 2151        }
 2152
 2782153        if (filter.SeriesStatuses.Length > 0)
 2154        {
 02155            var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
 02156            baseQuery = baseQuery
 02157                .Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
 2158        }
 2159
 2782160        if (filter.BoxSetLibraryFolders.Length > 0)
 2161        {
 02162            var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).T
 02163            baseQuery = baseQuery
 02164                .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
 2165        }
 2166
 2782167        if (filter.VideoTypes.Length > 0)
 2168        {
 02169            var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"{e}\"");
 02170            baseQuery = baseQuery
 02171                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
 2172        }
 2173
 2782174        if (filter.Is3D.HasValue)
 2175        {
 02176            if (filter.Is3D.Value)
 2177            {
 02178                baseQuery = baseQuery
 02179                    .Where(e => e.Data!.Contains("Video3DFormat"));
 2180            }
 2181            else
 2182            {
 02183                baseQuery = baseQuery
 02184                    .Where(e => !e.Data!.Contains("Video3DFormat"));
 2185            }
 2186        }
 2187
 2782188        if (filter.IsPlaceHolder.HasValue)
 2189        {
 02190            if (filter.IsPlaceHolder.Value)
 2191            {
 02192                baseQuery = baseQuery
 02193                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
 2194            }
 2195            else
 2196            {
 02197                baseQuery = baseQuery
 02198                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
 2199            }
 2200        }
 2201
 2782202        if (filter.HasSpecialFeature.HasValue)
 2203        {
 02204            if (filter.HasSpecialFeature.Value)
 2205            {
 02206                baseQuery = baseQuery
 02207                    .Where(e => e.ExtraIds != null);
 2208            }
 2209            else
 2210            {
 02211                baseQuery = baseQuery
 02212                    .Where(e => e.ExtraIds == null);
 2213            }
 2214        }
 2215
 2782216        if (filter.HasTrailer.HasValue || filter.HasThemeSong.HasValue || filter.HasThemeVideo.HasValue)
 2217        {
 02218            if (filter.HasTrailer.GetValueOrDefault() || filter.HasThemeSong.GetValueOrDefault() || filter.HasThemeVideo
 2219            {
 02220                baseQuery = baseQuery
 02221                    .Where(e => e.ExtraIds != null);
 2222            }
 2223            else
 2224            {
 02225                baseQuery = baseQuery
 02226                    .Where(e => e.ExtraIds == null);
 2227            }
 2228        }
 2229
 2782230        return baseQuery;
 2231    }
 2232}

Methods/Properties

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