< 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: 548
Uncovered lines: 495
Coverable lines: 1043
Total lines: 2219
Line coverage: 52.5%
Branch coverage
48%
Covered branches: 314
Total branches: 648
Branch coverage: 48.4%
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%2295.83%
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%1317678.82%
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.66%2598734239.7%

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.Studios];
 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.MediaSegments.Where(e => e.ItemId == id).ExecuteDelete();
 2118        context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2119        context.PeopleBaseItemMap.Where(e => e.ItemId == id).ExecuteDelete();
 2120        context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete();
 2121        context.TrickplayInfos.Where(e => e.ItemId == id).ExecuteDelete();
 2122        context.SaveChanges();
 2123        transaction.Commit();
 4124    }
 125
 126    /// <inheritdoc />
 127    public void UpdateInheritedValues()
 128    {
 8129        using var context = _dbProvider.CreateDbContext();
 8130        using var transaction = context.Database.BeginTransaction();
 131
 8132        context.ItemValuesMap.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags).ExecuteDelete();
 133        // ItemValue Inheritance is now correctly mapped via AncestorId on demand
 8134        context.SaveChanges();
 135
 8136        transaction.Commit();
 16137    }
 138
 139    /// <inheritdoc />
 140    public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
 141    {
 10142        ArgumentNullException.ThrowIfNull(filter);
 10143        PrepareFilterQuery(filter);
 144
 10145        using var context = _dbProvider.CreateDbContext();
 10146        return ApplyQueryFilter(context.BaseItems.AsNoTracking(), context, filter).Select(e => e.Id).ToArray();
 10147    }
 148
 149    /// <inheritdoc />
 150    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter)
 151    {
 0152        return GetItemValues(filter, _getAllArtistsValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtis
 153    }
 154
 155    /// <inheritdoc />
 156    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter)
 157    {
 0158        return GetItemValues(filter, _getArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]);
 159    }
 160
 161    /// <inheritdoc />
 162    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
 163    {
 0164        return GetItemValues(filter, _getAlbumArtistValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArti
 165    }
 166
 167    /// <inheritdoc />
 168    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter)
 169    {
 0170        return GetItemValues(filter, _getStudiosValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]);
 171    }
 172
 173    /// <inheritdoc />
 174    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter)
 175    {
 0176        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]);
 177    }
 178
 179    /// <inheritdoc />
 180    public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
 181    {
 0182        return GetItemValues(filter, _getGenreValueTypes, _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]);
 183    }
 184
 185    /// <inheritdoc />
 186    public IReadOnlyList<string> GetStudioNames()
 187    {
 10188        return GetItemValueNames(_getStudiosValueTypes, [], []);
 189    }
 190
 191    /// <inheritdoc />
 192    public IReadOnlyList<string> GetAllArtistNames()
 193    {
 10194        return GetItemValueNames(_getAllArtistsValueTypes, [], []);
 195    }
 196
 197    /// <inheritdoc />
 198    public IReadOnlyList<string> GetMusicGenreNames()
 199    {
 10200        return GetItemValueNames(
 10201            _getGenreValueTypes,
 10202            _itemTypeLookup.MusicGenreTypes,
 10203            []);
 204    }
 205
 206    /// <inheritdoc />
 207    public IReadOnlyList<string> GetGenreNames()
 208    {
 10209        return GetItemValueNames(
 10210            _getGenreValueTypes,
 10211            [],
 10212            _itemTypeLookup.MusicGenreTypes);
 213    }
 214
 215    /// <inheritdoc />
 216    public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
 217    {
 1218        ArgumentNullException.ThrowIfNull(filter);
 1219        if (!filter.EnableTotalRecordCount || (!filter.Limit.HasValue && (filter.StartIndex ?? 0) == 0))
 220        {
 1221            var returnList = GetItemList(filter);
 1222            return new QueryResult<BaseItemDto>(
 1223                filter.StartIndex,
 1224                returnList.Count,
 1225                returnList);
 226        }
 227
 0228        PrepareFilterQuery(filter);
 0229        var result = new QueryResult<BaseItemDto>();
 230
 0231        using var context = _dbProvider.CreateDbContext();
 232
 0233        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 234
 0235        dbQuery = TranslateQuery(dbQuery, context, filter);
 0236        if (filter.EnableTotalRecordCount)
 237        {
 0238            result.TotalRecordCount = dbQuery.Count();
 239        }
 240
 0241        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 0242        dbQuery = ApplyQueryPaging(dbQuery, filter);
 243
 0244        result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDe
 0245        result.StartIndex = filter.StartIndex ?? 0;
 0246        return result;
 0247    }
 248
 249    /// <inheritdoc />
 250    public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
 251    {
 250252        ArgumentNullException.ThrowIfNull(filter);
 250253        PrepareFilterQuery(filter);
 254
 250255        using var context = _dbProvider.CreateDbContext();
 247256        IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
 257
 247258        dbQuery = TranslateQuery(dbQuery, context, filter);
 259
 247260        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 247261        dbQuery = ApplyQueryPaging(dbQuery, filter);
 262
 247263        return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserializ
 247264    }
 265
 266    /// <inheritdoc/>
 267    public IReadOnlyList<BaseItem> GetLatestItemList(InternalItemsQuery filter, CollectionType collectionType)
 268    {
 0269        ArgumentNullException.ThrowIfNull(filter);
 0270        PrepareFilterQuery(filter);
 271
 272        // Early exit if collection type is not tvshows or music
 0273        if (collectionType != CollectionType.tvshows && collectionType != CollectionType.music)
 274        {
 0275            return Array.Empty<BaseItem>();
 276        }
 277
 0278        using var context = _dbProvider.CreateDbContext();
 279
 280        // Subquery to group by SeriesNames/Album and get the max Date Created for each group.
 0281        var subquery = PrepareItemQuery(context, filter);
 0282        subquery = TranslateQuery(subquery, context, filter);
 0283        var subqueryGrouped = subquery.GroupBy(g => collectionType == CollectionType.tvshows ? g.SeriesName : g.Album)
 0284            .Select(g => new
 0285            {
 0286                Key = g.Key,
 0287                MaxDateCreated = g.Max(a => a.DateCreated)
 0288            })
 0289            .OrderByDescending(g => g.MaxDateCreated)
 0290            .Select(g => g);
 291
 0292        if (filter.Limit.HasValue)
 293        {
 0294            subqueryGrouped = subqueryGrouped.Take(filter.Limit.Value);
 295        }
 296
 0297        filter.Limit = null;
 298
 0299        var mainquery = PrepareItemQuery(context, filter);
 0300        mainquery = TranslateQuery(mainquery, context, filter);
 0301        mainquery = mainquery.Where(g => g.DateCreated >= subqueryGrouped.Min(s => s.MaxDateCreated));
 0302        mainquery = ApplyGroupingFilter(mainquery, filter);
 0303        mainquery = ApplyQueryPaging(mainquery, filter);
 304
 0305        return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserial
 0306    }
 307
 308    /// <inheritdoc />
 309    public IReadOnlyList<string> GetNextUpSeriesKeys(InternalItemsQuery filter, DateTime dateCutoff)
 310    {
 0311        ArgumentNullException.ThrowIfNull(filter);
 0312        ArgumentNullException.ThrowIfNull(filter.User);
 313
 0314        using var context = _dbProvider.CreateDbContext();
 315
 0316        var query = context.BaseItems
 0317            .AsNoTracking()
 0318            .Where(i => filter.TopParentIds.Contains(i.TopParentId!.Value))
 0319            .Where(i => i.Type == _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode])
 0320            .Join(
 0321                context.UserData.AsNoTracking(),
 0322                i => new { UserId = filter.User.Id, ItemId = i.Id },
 0323                u => new { UserId = u.UserId, ItemId = u.ItemId },
 0324                (entity, data) => new { Item = entity, UserData = data })
 0325            .GroupBy(g => g.Item.SeriesPresentationUniqueKey)
 0326            .Select(g => new { g.Key, LastPlayedDate = g.Max(u => u.UserData.LastPlayedDate) })
 0327            .Where(g => g.Key != null && g.LastPlayedDate != null && g.LastPlayedDate >= dateCutoff)
 0328            .OrderByDescending(g => g.LastPlayedDate)
 0329            .Select(g => g.Key!);
 330
 0331        if (filter.Limit.HasValue)
 332        {
 0333            query = query.Take(filter.Limit.Value);
 334        }
 335
 0336        return query.ToArray();
 0337    }
 338
 339    private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter
 340    {
 341        // This whole block is needed to filter duplicate entries on request
 342        // for the time being it cannot be used because it would destroy the ordering
 343        // this results in "duplicate" responses for queries that try to lookup individual series or multiple versions b
 344        // for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
 345
 346        // var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
 347        // if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
 348        // {
 349        //     dbQuery = ApplyOrder(dbQuery, filter);
 350        //     dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e =
 351        // }
 352        // else if (enableGroupByPresentationUniqueKey)
 353        // {
 354        //     dbQuery = ApplyOrder(dbQuery, filter);
 355        //     dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First());
 356        // }
 357        // else if (filter.GroupBySeriesPresentationUniqueKey)
 358        // {
 359        //     dbQuery = ApplyOrder(dbQuery, filter);
 360        //     dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First());
 361        // }
 362        // else
 363        // {
 364        //     dbQuery = dbQuery.Distinct();
 365        //     dbQuery = ApplyOrder(dbQuery, filter);
 366        // }
 257367        dbQuery = dbQuery.Distinct();
 257368        dbQuery = ApplyOrder(dbQuery, filter);
 369
 257370        return dbQuery;
 371    }
 372
 373    private IQueryable<BaseItemEntity> ApplyQueryPaging(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
 374    {
 257375        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 376        {
 82377            var offset = filter.StartIndex ?? 0;
 378
 82379            if (offset > 0)
 380            {
 0381                dbQuery = dbQuery.Skip(offset);
 382            }
 383
 82384            if (filter.Limit.HasValue)
 385            {
 82386                dbQuery = dbQuery.Take(filter.Limit.Value);
 387            }
 388        }
 389
 257390        return dbQuery;
 391    }
 392
 393    private IQueryable<BaseItemEntity> ApplyQueryFilter(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, I
 394    {
 10395        dbQuery = TranslateQuery(dbQuery, context, filter);
 10396        dbQuery = ApplyOrder(dbQuery, filter);
 10397        dbQuery = ApplyGroupingFilter(dbQuery, filter);
 10398        dbQuery = ApplyQueryPaging(dbQuery, filter);
 10399        return dbQuery;
 400    }
 401
 402    private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
 403    {
 461404        IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking().AsSplitQuery()
 461405            .Include(e => e.TrailerTypes)
 461406            .Include(e => e.Provider)
 461407            .Include(e => e.LockedFields);
 408
 461409        if (filter.DtoOptions.EnableImages)
 410        {
 461411            dbQuery = dbQuery.Include(e => e.Images);
 412        }
 413
 461414        return dbQuery;
 415    }
 416
 417    /// <inheritdoc/>
 418    public int GetCount(InternalItemsQuery filter)
 419    {
 0420        ArgumentNullException.ThrowIfNull(filter);
 421        // Hack for right now since we currently don't support filtering out these duplicates within a query
 0422        PrepareFilterQuery(filter);
 423
 0424        using var context = _dbProvider.CreateDbContext();
 0425        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 426
 0427        return dbQuery.Count();
 0428    }
 429
 430#pragma warning disable CA1307 // Specify StringComparison for clarity
 431    /// <summary>
 432    /// Gets the type.
 433    /// </summary>
 434    /// <param name="typeName">Name of the type.</param>
 435    /// <returns>Type.</returns>
 436    /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
 437    private static Type? GetType(string typeName)
 438    {
 182439        ArgumentException.ThrowIfNullOrEmpty(typeName);
 440
 441        // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static 
 442        // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are 
 182443        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 182444            .Select(a => a.GetType(k))
 182445            .FirstOrDefault(t => t is not null));
 446    }
 447
 448    /// <inheritdoc  />
 449    public void SaveImages(BaseItemDto item)
 450    {
 0451        ArgumentNullException.ThrowIfNull(item);
 452
 0453        var images = item.ImageInfos.Select(e => Map(item.Id, e));
 0454        using var context = _dbProvider.CreateDbContext();
 0455        using var transaction = context.Database.BeginTransaction();
 0456        context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
 0457        context.BaseItemImageInfos.AddRange(images);
 0458        context.SaveChanges();
 0459        transaction.Commit();
 0460    }
 461
 462    /// <inheritdoc  />
 463    public void SaveItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 464    {
 81465        UpdateOrInsertItems(items, cancellationToken);
 80466    }
 467
 468    /// <inheritdoc cref="IItemRepository"/>
 469    public void UpdateOrInsertItems(IReadOnlyList<BaseItemDto> items, CancellationToken cancellationToken)
 470    {
 81471        ArgumentNullException.ThrowIfNull(items);
 81472        cancellationToken.ThrowIfCancellationRequested();
 473
 80474        var tuples = new List<(BaseItemDto Item, List<Guid>? AncestorIds, BaseItemDto TopParent, IEnumerable<string> Use
 320475        foreach (var item in items.GroupBy(e => e.Id).Select(e => e.Last()))
 476        {
 80477            var ancestorIds = item.SupportsAncestors ?
 80478                item.GetAncestorIds().Distinct().ToList() :
 80479                null;
 480
 80481            var topParent = item.GetTopParent();
 482
 80483            var userdataKey = item.GetUserDataKeys();
 80484            var inheritedTags = item.GetInheritedTags();
 485
 80486            tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
 487        }
 488
 80489        var localItemValueCache = new Dictionary<(int MagicNumber, string Value), Guid>();
 490
 80491        using var context = _dbProvider.CreateDbContext();
 80492        using var transaction = context.Database.BeginTransaction();
 320493        foreach (var item in tuples)
 494        {
 80495            var entity = Map(item.Item);
 496            // TODO: refactor this "inconsistency"
 80497            entity.TopParentId = item.TopParent?.Id;
 498
 80499            if (!context.BaseItems.Any(e => e.Id == entity.Id))
 500            {
 49501                context.BaseItems.Add(entity);
 502            }
 503            else
 504            {
 31505                context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 31506                context.BaseItems.Attach(entity).State = EntityState.Modified;
 507            }
 508
 80509            context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 80510            if (item.Item.SupportsAncestors && item.AncestorIds != null)
 511            {
 172512                foreach (var ancestorId in item.AncestorIds)
 513                {
 6514                    if (!context.BaseItems.Any(f => f.Id == ancestorId))
 515                    {
 516                        continue;
 517                    }
 518
 6519                    context.AncestorIds.Add(new AncestorId()
 6520                    {
 6521                        ParentItemId = ancestorId,
 6522                        ItemId = entity.Id,
 6523                        Item = null!,
 6524                        ParentItem = null!
 6525                    });
 526                }
 527            }
 528
 529            // Never save duplicate itemValues as they are now mapped anyway.
 80530            var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags).DistinctBy(e => (GetCleanValue(e.V
 80531            context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
 160532            foreach (var itemValue in itemValuesToSave)
 533            {
 0534                if (!localItemValueCache.TryGetValue(itemValue, out var refValue))
 535                {
 0536                    refValue = context.ItemValues
 0537                                .Where(f => f.CleanValue == GetCleanValue(itemValue.Value) && (int)f.Type == itemValue.M
 0538                                .Select(e => e.ItemValueId)
 0539                                .FirstOrDefault();
 540                }
 541
 0542                if (refValue.IsEmpty())
 543                {
 0544                    context.ItemValues.Add(new ItemValue()
 0545                    {
 0546                        CleanValue = GetCleanValue(itemValue.Value),
 0547                        Type = (ItemValueType)itemValue.MagicNumber,
 0548                        ItemValueId = refValue = Guid.NewGuid(),
 0549                        Value = itemValue.Value
 0550                    });
 0551                    localItemValueCache[itemValue] = refValue;
 552                }
 553
 0554                context.ItemValuesMap.Add(new ItemValueMap()
 0555                {
 0556                    Item = null!,
 0557                    ItemId = entity.Id,
 0558                    ItemValue = null!,
 0559                    ItemValueId = refValue
 0560                });
 561            }
 562        }
 563
 80564        context.SaveChanges();
 80565        transaction.Commit();
 160566    }
 567
 568    /// <inheritdoc  />
 569    public BaseItemDto? RetrieveItem(Guid id)
 570    {
 214571        if (id.IsEmpty())
 572        {
 0573            throw new ArgumentException("Guid can't be empty", nameof(id));
 574        }
 575
 214576        using var context = _dbProvider.CreateDbContext();
 214577        var item = PrepareItemQuery(context, new()
 214578        {
 214579            DtoOptions = new()
 214580            {
 214581                EnableImages = true
 214582            }
 214583        }).FirstOrDefault(e => e.Id == id);
 214584        if (item is null)
 585        {
 214586            return null;
 587        }
 588
 0589        return DeserialiseBaseItem(item);
 214590    }
 591
 592    /// <summary>
 593    /// Maps a Entity to the DTO.
 594    /// </summary>
 595    /// <param name="entity">The entity.</param>
 596    /// <param name="dto">The dto base instance.</param>
 597    /// <param name="appHost">The Application server Host.</param>
 598    /// <returns>The dto to map.</returns>
 599    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 600    {
 91601        dto.Id = entity.Id;
 91602        dto.ParentId = entity.ParentId.GetValueOrDefault();
 91603        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 91604        dto.EndDate = entity.EndDate;
 91605        dto.CommunityRating = entity.CommunityRating;
 91606        dto.CustomRating = entity.CustomRating;
 91607        dto.IndexNumber = entity.IndexNumber;
 91608        dto.IsLocked = entity.IsLocked;
 91609        dto.Name = entity.Name;
 91610        dto.OfficialRating = entity.OfficialRating;
 91611        dto.Overview = entity.Overview;
 91612        dto.ParentIndexNumber = entity.ParentIndexNumber;
 91613        dto.PremiereDate = entity.PremiereDate;
 91614        dto.ProductionYear = entity.ProductionYear;
 91615        dto.SortName = entity.SortName;
 91616        dto.ForcedSortName = entity.ForcedSortName;
 91617        dto.RunTimeTicks = entity.RunTimeTicks;
 91618        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 91619        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 91620        dto.IsInMixedFolder = entity.IsInMixedFolder;
 91621        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 91622        dto.CriticRating = entity.CriticRating;
 91623        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 91624        dto.OriginalTitle = entity.OriginalTitle;
 91625        dto.Album = entity.Album;
 91626        dto.LUFS = entity.LUFS;
 91627        dto.NormalizationGain = entity.NormalizationGain;
 91628        dto.IsVirtualItem = entity.IsVirtualItem;
 91629        dto.ExternalSeriesId = entity.ExternalSeriesId;
 91630        dto.Tagline = entity.Tagline;
 91631        dto.TotalBitrate = entity.TotalBitrate;
 91632        dto.ExternalId = entity.ExternalId;
 91633        dto.Size = entity.Size;
 91634        dto.Genres = entity.Genres?.Split('|') ?? [];
 91635        dto.DateCreated = entity.DateCreated.GetValueOrDefault();
 91636        dto.DateModified = entity.DateModified.GetValueOrDefault();
 91637        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 91638        dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
 91639        dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
 91640        dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ow
 91641        dto.Width = entity.Width.GetValueOrDefault();
 91642        dto.Height = entity.Height.GetValueOrDefault();
 91643        if (entity.Provider is not null)
 644        {
 91645            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 646        }
 647
 91648        if (entity.ExtraType is not null)
 649        {
 0650            dto.ExtraType = (ExtraType)entity.ExtraType;
 651        }
 652
 91653        if (entity.LockedFields is not null)
 654        {
 91655            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 656        }
 657
 91658        if (entity.Audio is not null)
 659        {
 0660            dto.Audio = (ProgramAudio)entity.Audio;
 661        }
 662
 91663        dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? [] : entity.ExtraIds.Split('|').Select(e => Guid.Par
 91664        dto.ProductionLocations = entity.ProductionLocations?.Split('|') ?? [];
 91665        dto.Studios = entity.Studios?.Split('|') ?? [];
 91666        dto.Tags = entity.Tags?.Split('|') ?? [];
 667
 91668        if (dto is IHasProgramAttributes hasProgramAttributes)
 669        {
 0670            hasProgramAttributes.IsMovie = entity.IsMovie;
 0671            hasProgramAttributes.IsSeries = entity.IsSeries;
 0672            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0673            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 674        }
 675
 91676        if (dto is LiveTvChannel liveTvChannel)
 677        {
 0678            liveTvChannel.ServiceName = entity.ExternalServiceId;
 679        }
 680
 91681        if (dto is Trailer trailer)
 682        {
 0683            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 684        }
 685
 91686        if (dto is Video video)
 687        {
 0688            video.PrimaryVersionId = entity.PrimaryVersionId;
 689        }
 690
 91691        if (dto is IHasSeries hasSeriesName)
 692        {
 0693            hasSeriesName.SeriesName = entity.SeriesName;
 0694            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0695            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 696        }
 697
 91698        if (dto is Episode episode)
 699        {
 0700            episode.SeasonName = entity.SeasonName;
 0701            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 702        }
 703
 91704        if (dto is IHasArtist hasArtists)
 705        {
 0706            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 707        }
 708
 91709        if (dto is IHasAlbumArtist hasAlbumArtists)
 710        {
 0711            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 712        }
 713
 91714        if (dto is LiveTvProgram program)
 715        {
 0716            program.ShowId = entity.ShowId;
 717        }
 718
 91719        if (entity.Images is not null)
 720        {
 91721            dto.ImageInfos = entity.Images.Select(e => Map(e, appHost)).ToArray();
 722        }
 723
 724        // dto.Type = entity.Type;
 725        // dto.Data = entity.Data;
 726        // dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
 91727        if (dto is IHasStartDate hasStartDate)
 728        {
 0729            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 730        }
 731
 732        // Fields that are present in the DB but are never actually used
 733        // dto.UnratedType = entity.UnratedType;
 734        // dto.TopParentId = entity.TopParentId;
 735        // dto.CleanName = entity.CleanName;
 736        // dto.UserDataKey = entity.UserDataKey;
 737
 91738        if (dto is Folder folder)
 739        {
 91740            folder.DateLastMediaAdded = entity.DateLastMediaAdded;
 741        }
 742
 91743        return dto;
 744    }
 745
 746    /// <summary>
 747    /// Maps a Entity to the DTO.
 748    /// </summary>
 749    /// <param name="dto">The entity.</param>
 750    /// <returns>The dto to map.</returns>
 751    public BaseItemEntity Map(BaseItemDto dto)
 752    {
 80753        var dtoType = dto.GetType();
 80754        var entity = new BaseItemEntity()
 80755        {
 80756            Type = dtoType.ToString(),
 80757            Id = dto.Id
 80758        };
 759
 80760        if (TypeRequiresDeserialization(dtoType))
 761        {
 59762            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 763        }
 764
 80765        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 80766        entity.Path = GetPathToSave(dto.Path);
 80767        entity.EndDate = dto.EndDate;
 80768        entity.CommunityRating = dto.CommunityRating;
 80769        entity.CustomRating = dto.CustomRating;
 80770        entity.IndexNumber = dto.IndexNumber;
 80771        entity.IsLocked = dto.IsLocked;
 80772        entity.Name = dto.Name;
 80773        entity.CleanName = GetCleanValue(dto.Name);
 80774        entity.OfficialRating = dto.OfficialRating;
 80775        entity.Overview = dto.Overview;
 80776        entity.ParentIndexNumber = dto.ParentIndexNumber;
 80777        entity.PremiereDate = dto.PremiereDate;
 80778        entity.ProductionYear = dto.ProductionYear;
 80779        entity.SortName = dto.SortName;
 80780        entity.ForcedSortName = dto.ForcedSortName;
 80781        entity.RunTimeTicks = dto.RunTimeTicks;
 80782        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 80783        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 80784        entity.IsInMixedFolder = dto.IsInMixedFolder;
 80785        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 80786        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 80787        entity.CriticRating = dto.CriticRating;
 80788        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 80789        entity.OriginalTitle = dto.OriginalTitle;
 80790        entity.Album = dto.Album;
 80791        entity.LUFS = dto.LUFS;
 80792        entity.NormalizationGain = dto.NormalizationGain;
 80793        entity.IsVirtualItem = dto.IsVirtualItem;
 80794        entity.ExternalSeriesId = dto.ExternalSeriesId;
 80795        entity.Tagline = dto.Tagline;
 80796        entity.TotalBitrate = dto.TotalBitrate;
 80797        entity.ExternalId = dto.ExternalId;
 80798        entity.Size = dto.Size;
 80799        entity.Genres = string.Join('|', dto.Genres);
 80800        entity.DateCreated = dto.DateCreated;
 80801        entity.DateModified = dto.DateModified;
 80802        entity.ChannelId = dto.ChannelId;
 80803        entity.DateLastRefreshed = dto.DateLastRefreshed;
 80804        entity.DateLastSaved = dto.DateLastSaved;
 80805        entity.OwnerId = dto.OwnerId.ToString();
 80806        entity.Width = dto.Width;
 80807        entity.Height = dto.Height;
 80808        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 80809        {
 80810            Item = entity,
 80811            ProviderId = e.Key,
 80812            ProviderValue = e.Value
 80813        }).ToList();
 814
 80815        if (dto.Audio.HasValue)
 816        {
 0817            entity.Audio = (ProgramAudioEntity)dto.Audio;
 818        }
 819
 80820        if (dto.ExtraType.HasValue)
 821        {
 0822            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 823        }
 824
 80825        entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
 80826        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : n
 80827        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
 80828        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
 80829        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 80830            .Select(e => new BaseItemMetadataField()
 80831            {
 80832                Id = (int)e,
 80833                Item = entity,
 80834                ItemId = entity.Id
 80835            })
 80836            .ToArray() : null;
 837
 80838        if (dto is IHasProgramAttributes hasProgramAttributes)
 839        {
 0840            entity.IsMovie = hasProgramAttributes.IsMovie;
 0841            entity.IsSeries = hasProgramAttributes.IsSeries;
 0842            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0843            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 844        }
 845
 80846        if (dto is LiveTvChannel liveTvChannel)
 847        {
 0848            entity.ExternalServiceId = liveTvChannel.ServiceName;
 849        }
 850
 80851        if (dto is Video video)
 852        {
 0853            entity.PrimaryVersionId = video.PrimaryVersionId;
 854        }
 855
 80856        if (dto is IHasSeries hasSeriesName)
 857        {
 0858            entity.SeriesName = hasSeriesName.SeriesName;
 0859            entity.SeriesId = hasSeriesName.SeriesId;
 0860            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 861        }
 862
 80863        if (dto is Episode episode)
 864        {
 0865            entity.SeasonName = episode.SeasonName;
 0866            entity.SeasonId = episode.SeasonId;
 867        }
 868
 80869        if (dto is IHasArtist hasArtists)
 870        {
 0871            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
 872        }
 873
 80874        if (dto is IHasAlbumArtist hasAlbumArtists)
 875        {
 0876            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 877        }
 878
 80879        if (dto is LiveTvProgram program)
 880        {
 0881            entity.ShowId = program.ShowId;
 882        }
 883
 80884        if (dto.ImageInfos is not null)
 885        {
 80886            entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray();
 887        }
 888
 80889        if (dto is Trailer trailer)
 890        {
 0891            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0892            {
 0893                Id = (int)e,
 0894                Item = entity,
 0895                ItemId = entity.Id
 0896            }).ToArray() ?? [];
 897        }
 898
 899        // dto.Type = entity.Type;
 900        // dto.Data = entity.Data;
 80901        entity.MediaType = dto.MediaType.ToString();
 80902        if (dto is IHasStartDate hasStartDate)
 903        {
 0904            entity.StartDate = hasStartDate.StartDate;
 905        }
 906
 80907        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 908
 909        // Fields that are present in the DB but are never actually used
 910        // dto.UserDataKey = entity.UserDataKey;
 911
 80912        if (dto is Folder folder)
 913        {
 80914            entity.DateLastMediaAdded = folder.DateLastMediaAdded;
 80915            entity.IsFolder = folder.IsFolder;
 916        }
 917
 80918        return entity;
 919    }
 920
 921    private string[] GetItemValueNames(IReadOnlyList<ItemValueType> itemValueTypes, IReadOnlyList<string> withItemTypes,
 922    {
 40923        using var context = _dbProvider.CreateDbContext();
 924
 40925        var query = context.ItemValuesMap
 40926            .AsNoTracking()
 40927            .Where(e => itemValueTypes.Any(w => (ItemValueType)w == e.ItemValue.Type));
 40928        if (withItemTypes.Count > 0)
 929        {
 10930            query = query.Where(e => withItemTypes.Contains(e.Item.Type));
 931        }
 932
 40933        if (excludeItemTypes.Count > 0)
 934        {
 10935            query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
 936        }
 937
 938        // query = query.DistinctBy(e => e.CleanValue);
 40939        return query.Select(e => e.ItemValue)
 40940            .GroupBy(e => e.CleanValue)
 40941            .Select(e => e.First().Value)
 40942            .ToArray();
 40943    }
 944
 945    private static bool TypeRequiresDeserialization(Type type)
 946    {
 171947        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 948    }
 949
 950    private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
 951    {
 91952        ArgumentNullException.ThrowIfNull(baseItemEntity, nameof(baseItemEntity));
 91953        if (_serverConfigurationManager?.Configuration is null)
 954        {
 0955            throw new InvalidOperationException("Server Configuration manager or configuration is null");
 956        }
 957
 91958        var typeToSerialise = GetType(baseItemEntity.Type);
 91959        return BaseItemRepository.DeserialiseBaseItem(
 91960            baseItemEntity,
 91961            _logger,
 91962            _appHost,
 91963            skipDeserialization || (_serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeT
 964    }
 965
 966    /// <summary>
 967    /// Deserialises a BaseItemEntity and sets all properties.
 968    /// </summary>
 969    /// <param name="baseItemEntity">The DB entity.</param>
 970    /// <param name="logger">Logger.</param>
 971    /// <param name="appHost">The application server Host.</param>
 972    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 973    /// <returns>A mapped BaseItem.</returns>
 974    /// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
 975    public static BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost?
 976    {
 91977        var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unknown type.
 91978        BaseItemDto? dto = null;
 91979        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 980        {
 981            try
 982            {
 22983                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 22984            }
 0985            catch (JsonException ex)
 986            {
 0987                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 0988            }
 989        }
 990
 91991        if (dto is null)
 992        {
 69993            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 994        }
 995
 91996        return Map(baseItemEntity, dto, appHost);
 997    }
 998
 999    private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, IReadOnlyLis
 1000    {
 01001        ArgumentNullException.ThrowIfNull(filter);
 1002
 01003        if (!filter.Limit.HasValue)
 1004        {
 01005            filter.EnableTotalRecordCount = false;
 1006        }
 1007
 01008        using var context = _dbProvider.CreateDbContext();
 1009
 01010        var query = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
 1011
 01012        query = query.Where(e => e.Type == returnType);
 1013        // this does not seem to be nesseary but it does not make any sense why this isn't working.
 1014        // && e.ItemValues!.Any(f => e.CleanName == f.ItemValue.CleanValue && itemValueTypes.Any(w => (ItemValueType)w =
 1015
 01016        if (filter.OrderBy.Count != 0
 01017            || !string.IsNullOrEmpty(filter.SearchTerm))
 1018        {
 01019            query = ApplyOrder(query, filter);
 1020        }
 1021        else
 1022        {
 01023            query = query.OrderBy(e => e.SortName);
 1024        }
 1025
 01026        if (filter.Limit.HasValue || filter.StartIndex.HasValue)
 1027        {
 01028            var offset = filter.StartIndex ?? 0;
 1029
 01030            if (offset > 0)
 1031            {
 01032                query = query.Skip(offset);
 1033            }
 1034
 01035            if (filter.Limit.HasValue)
 1036            {
 01037                query = query.Take(filter.Limit.Value);
 1038            }
 1039        }
 1040
 01041        var result = new QueryResult<(BaseItemDto, ItemCounts)>();
 01042        if (filter.EnableTotalRecordCount)
 1043        {
 01044            result.TotalRecordCount = query.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First()).Count();
 1045        }
 1046
 01047        var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series];
 01048        var movieTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
 01049        var episodeTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode];
 01050        var musicAlbumTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum];
 01051        var musicArtistTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist];
 01052        var audioTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio];
 01053        var trailerTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer];
 1054
 01055        var resultQuery = query.Select(e => new
 01056        {
 01057            item = e,
 01058            // TODO: This is bad refactor!
 01059            itemCount = new ItemCounts()
 01060            {
 01061                SeriesCount = e.ItemValues!.Count(f => f.Item.Type == seriesTypeName),
 01062                EpisodeCount = e.ItemValues!.Count(f => f.Item.Type == episodeTypeName),
 01063                MovieCount = e.ItemValues!.Count(f => f.Item.Type == movieTypeName),
 01064                AlbumCount = e.ItemValues!.Count(f => f.Item.Type == musicAlbumTypeName),
 01065                ArtistCount = e.ItemValues!.Count(f => f.Item.Type == musicArtistTypeName),
 01066                SongCount = e.ItemValues!.Count(f => f.Item.Type == audioTypeName),
 01067                TrailerCount = e.ItemValues!.Count(f => f.Item.Type == trailerTypeName),
 01068            }
 01069        });
 1070
 01071        result.StartIndex = filter.StartIndex ?? 0;
 01072        result.Items = resultQuery.ToArray().Where(e => e is not null).Select(e =>
 01073        {
 01074            return (DeserialiseBaseItem(e.item, filter.SkipDeserialization), e.itemCount);
 01075        }).ToArray();
 1076
 01077        return result;
 01078    }
 1079
 1080    private static void PrepareFilterQuery(InternalItemsQuery query)
 1081    {
 2601082        if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
 1083        {
 01084            query.Limit = query.Limit.Value + 4;
 1085        }
 1086
 2601087        if (query.IsResumable ?? false)
 1088        {
 11089            query.IsVirtualItem = false;
 1090        }
 2601091    }
 1092
 1093    private string GetCleanValue(string value)
 1094    {
 831095        if (string.IsNullOrWhiteSpace(value))
 1096        {
 01097            return value;
 1098        }
 1099
 831100        return value.RemoveDiacritics().ToLowerInvariant();
 1101    }
 1102
 1103    private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inheritedTags)
 1104    {
 801105        var list = new List<(int, string)>();
 1106
 801107        if (item is IHasArtist hasArtist)
 1108        {
 01109            list.AddRange(hasArtist.Artists.Select(i => (0, i)));
 1110        }
 1111
 801112        if (item is IHasAlbumArtist hasAlbumArtist)
 1113        {
 01114            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i)));
 1115        }
 1116
 801117        list.AddRange(item.Genres.Select(i => (2, i)));
 801118        list.AddRange(item.Studios.Select(i => (3, i)));
 801119        list.AddRange(item.Tags.Select(i => (4, i)));
 1120
 1121        // keywords was 5
 1122
 801123        list.AddRange(inheritedTags.Select(i => (6, i)));
 1124
 1125        // Remove all invalid values.
 801126        list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
 1127
 801128        return list;
 1129    }
 1130
 1131    private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
 1132    {
 01133        return new BaseItemImageInfo()
 01134        {
 01135            ItemId = baseItemId,
 01136            Id = Guid.NewGuid(),
 01137            Path = e.Path,
 01138            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 01139            DateModified = e.DateModified,
 01140            Height = e.Height,
 01141            Width = e.Width,
 01142            ImageType = (ImageInfoImageType)e.Type,
 01143            Item = null!
 01144        };
 1145    }
 1146
 1147    private static ItemImageInfo Map(BaseItemImageInfo e, IServerApplicationHost? appHost)
 1148    {
 01149        return new ItemImageInfo()
 01150        {
 01151            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 01152            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 01153            DateModified = e.DateModified,
 01154            Height = e.Height,
 01155            Width = e.Width,
 01156            Type = (ImageType)e.ImageType
 01157        };
 1158    }
 1159
 1160    private string? GetPathToSave(string path)
 1161    {
 801162        if (path is null)
 1163        {
 01164            return null;
 1165        }
 1166
 801167        return _appHost.ReverseVirtualPath(path);
 1168    }
 1169
 1170    private List<string> GetItemByNameTypesInQuery(InternalItemsQuery query)
 1171    {
 171172        var list = new List<string>();
 1173
 171174        if (IsTypeInQuery(BaseItemKind.Person, query))
 1175        {
 11176            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!);
 1177        }
 1178
 171179        if (IsTypeInQuery(BaseItemKind.Genre, query))
 1180        {
 11181            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!);
 1182        }
 1183
 171184        if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
 1185        {
 11186            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!);
 1187        }
 1188
 171189        if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
 1190        {
 11191            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
 1192        }
 1193
 171194        if (IsTypeInQuery(BaseItemKind.Studio, query))
 1195        {
 11196            list.Add(_itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!);
 1197        }
 1198
 171199        return list;
 1200    }
 1201
 1202    private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
 1203    {
 851204        if (query.ExcludeItemTypes.Contains(type))
 1205        {
 01206            return false;
 1207        }
 1208
 851209        return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
 1210    }
 1211
 1212    private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
 1213    {
 01214        if (!query.GroupByPresentationUniqueKey)
 1215        {
 01216            return false;
 1217        }
 1218
 01219        if (query.GroupBySeriesPresentationUniqueKey)
 1220        {
 01221            return false;
 1222        }
 1223
 01224        if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
 1225        {
 01226            return false;
 1227        }
 1228
 01229        if (query.User is null)
 1230        {
 01231            return false;
 1232        }
 1233
 01234        if (query.IncludeItemTypes.Length == 0)
 1235        {
 01236            return true;
 1237        }
 1238
 01239        return query.IncludeItemTypes.Contains(BaseItemKind.Episode)
 01240            || query.IncludeItemTypes.Contains(BaseItemKind.Video)
 01241            || query.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01242            || query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
 01243            || query.IncludeItemTypes.Contains(BaseItemKind.Series)
 01244            || query.IncludeItemTypes.Contains(BaseItemKind.Season);
 1245    }
 1246
 1247    private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter)
 1248    {
 2671249        var orderBy = filter.OrderBy;
 2671250        var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
 1251
 2671252        if (hasSearch)
 1253        {
 01254            orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy];
 1255        }
 2671256        else if (orderBy.Count == 0)
 1257        {
 1841258            return query;
 1259        }
 1260
 831261        IOrderedQueryable<BaseItemEntity>? orderedQuery = null;
 1262
 831263        var firstOrdering = orderBy.FirstOrDefault();
 831264        if (firstOrdering != default)
 1265        {
 831266            var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
 831267            if (firstOrdering.SortOrder == SortOrder.Ascending)
 1268            {
 821269                orderedQuery = query.OrderBy(expression);
 1270            }
 1271            else
 1272            {
 11273                orderedQuery = query.OrderByDescending(expression);
 1274            }
 1275
 831276            if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
 1277            {
 01278                if (firstOrdering.SortOrder is SortOrder.Ascending)
 1279                {
 01280                    orderedQuery = orderedQuery.ThenBy(e => e.Name);
 1281                }
 1282                else
 1283                {
 01284                    orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
 1285                }
 1286            }
 1287        }
 1288
 2381289        foreach (var item in orderBy.Skip(1))
 1290        {
 361291            var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
 361292            if (item.SortOrder == SortOrder.Ascending)
 1293            {
 361294                orderedQuery = orderedQuery!.ThenBy(expression);
 1295            }
 1296            else
 1297            {
 01298                orderedQuery = orderedQuery!.ThenByDescending(expression);
 1299            }
 1300        }
 1301
 831302        return orderedQuery ?? query;
 1303    }
 1304
 1305    private IQueryable<BaseItemEntity> TranslateQuery(
 1306        IQueryable<BaseItemEntity> baseQuery,
 1307        JellyfinDbContext context,
 1308        InternalItemsQuery filter)
 1309    {
 2571310        var minWidth = filter.MinWidth;
 2571311        var maxWidth = filter.MaxWidth;
 2571312        var now = DateTime.UtcNow;
 1313
 2571314        if (filter.IsHD.HasValue)
 1315        {
 1316            const int Threshold = 1200;
 01317            if (filter.IsHD.Value)
 1318            {
 01319                minWidth = Threshold;
 1320            }
 1321            else
 1322            {
 01323                maxWidth = Threshold - 1;
 1324            }
 1325        }
 1326
 2571327        if (filter.Is4K.HasValue)
 1328        {
 1329            const int Threshold = 3800;
 01330            if (filter.Is4K.Value)
 1331            {
 01332                minWidth = Threshold;
 1333            }
 1334            else
 1335            {
 01336                maxWidth = Threshold - 1;
 1337            }
 1338        }
 1339
 2571340        if (minWidth.HasValue)
 1341        {
 01342            baseQuery = baseQuery.Where(e => e.Width >= minWidth);
 1343        }
 1344
 2571345        if (filter.MinHeight.HasValue)
 1346        {
 01347            baseQuery = baseQuery.Where(e => e.Height >= filter.MinHeight);
 1348        }
 1349
 2571350        if (maxWidth.HasValue)
 1351        {
 01352            baseQuery = baseQuery.Where(e => e.Width >= maxWidth);
 1353        }
 1354
 2571355        if (filter.MaxHeight.HasValue)
 1356        {
 01357            baseQuery = baseQuery.Where(e => e.Height <= filter.MaxHeight);
 1358        }
 1359
 2571360        if (filter.IsLocked.HasValue)
 1361        {
 201362            baseQuery = baseQuery.Where(e => e.IsLocked == filter.IsLocked);
 1363        }
 1364
 2571365        var tags = filter.Tags.ToList();
 2571366        var excludeTags = filter.ExcludeTags.ToList();
 1367
 2571368        if (filter.IsMovie == true)
 1369        {
 01370            if (filter.IncludeItemTypes.Length == 0
 01371                || filter.IncludeItemTypes.Contains(BaseItemKind.Movie)
 01372                || filter.IncludeItemTypes.Contains(BaseItemKind.Trailer))
 1373            {
 01374                baseQuery = baseQuery.Where(e => e.IsMovie);
 1375            }
 1376        }
 2571377        else if (filter.IsMovie.HasValue)
 1378        {
 01379            baseQuery = baseQuery.Where(e => e.IsMovie == filter.IsMovie);
 1380        }
 1381
 2571382        if (filter.IsSeries.HasValue)
 1383        {
 01384            baseQuery = baseQuery.Where(e => e.IsSeries == filter.IsSeries);
 1385        }
 1386
 2571387        if (filter.IsSports.HasValue)
 1388        {
 01389            if (filter.IsSports.Value)
 1390            {
 01391                tags.Add("Sports");
 1392            }
 1393            else
 1394            {
 01395                excludeTags.Add("Sports");
 1396            }
 1397        }
 1398
 2571399        if (filter.IsNews.HasValue)
 1400        {
 01401            if (filter.IsNews.Value)
 1402            {
 01403                tags.Add("News");
 1404            }
 1405            else
 1406            {
 01407                excludeTags.Add("News");
 1408            }
 1409        }
 1410
 2571411        if (filter.IsKids.HasValue)
 1412        {
 01413            if (filter.IsKids.Value)
 1414            {
 01415                tags.Add("Kids");
 1416            }
 1417            else
 1418            {
 01419                excludeTags.Add("Kids");
 1420            }
 1421        }
 1422
 2571423        if (!string.IsNullOrEmpty(filter.SearchTerm))
 1424        {
 01425            var searchTerm = filter.SearchTerm.ToLower();
 01426            baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && 
 1427        }
 1428
 2571429        if (filter.IsFolder.HasValue)
 1430        {
 01431            baseQuery = baseQuery.Where(e => e.IsFolder == filter.IsFolder);
 1432        }
 1433
 2571434        var includeTypes = filter.IncludeItemTypes;
 1435        // Only specify excluded types if no included types are specified
 2571436        if (filter.IncludeItemTypes.Length == 0)
 1437        {
 1871438            var excludeTypes = filter.ExcludeItemTypes;
 1871439            if (excludeTypes.Length == 1)
 1440            {
 01441                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
 1442                {
 01443                    baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
 1444                }
 1445            }
 1871446            else if (excludeTypes.Length > 1)
 1447            {
 01448                var excludeTypeName = new List<string>();
 01449                foreach (var excludeType in excludeTypes)
 1450                {
 01451                    if (_itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
 1452                    {
 01453                        excludeTypeName.Add(baseItemKindName!);
 1454                    }
 1455                }
 1456
 01457                baseQuery = baseQuery.Where(e => !excludeTypeName.Contains(e.Type));
 1458            }
 1459        }
 701460        else if (includeTypes.Length == 1)
 1461        {
 241462            if (_itemTypeLookup.BaseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
 1463            {
 241464                baseQuery = baseQuery.Where(e => e.Type == includeTypeName);
 1465            }
 1466        }
 461467        else if (includeTypes.Length > 1)
 1468        {
 461469            var includeTypeName = new List<string>();
 3721470            foreach (var includeType in includeTypes)
 1471            {
 1401472                if (_itemTypeLookup.BaseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
 1473                {
 1401474                    includeTypeName.Add(baseItemKindName!);
 1475                }
 1476            }
 1477
 461478            baseQuery = baseQuery.Where(e => includeTypeName.Contains(e.Type));
 1479        }
 1480
 2571481        if (filter.ChannelIds.Count > 0)
 1482        {
 01483            baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value));
 1484        }
 1485
 2571486        if (!filter.ParentId.IsEmpty())
 1487        {
 1401488            baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
 1489        }
 1490
 2571491        if (!string.IsNullOrWhiteSpace(filter.Path))
 1492        {
 01493            baseQuery = baseQuery.Where(e => e.Path == filter.Path);
 1494        }
 1495
 2571496        if (!string.IsNullOrWhiteSpace(filter.PresentationUniqueKey))
 1497        {
 01498            baseQuery = baseQuery.Where(e => e.PresentationUniqueKey == filter.PresentationUniqueKey);
 1499        }
 1500
 2571501        if (filter.MinCommunityRating.HasValue)
 1502        {
 01503            baseQuery = baseQuery.Where(e => e.CommunityRating >= filter.MinCommunityRating);
 1504        }
 1505
 2571506        if (filter.MinIndexNumber.HasValue)
 1507        {
 01508            baseQuery = baseQuery.Where(e => e.IndexNumber >= filter.MinIndexNumber);
 1509        }
 1510
 2571511        if (filter.MinParentAndIndexNumber.HasValue)
 1512        {
 01513            baseQuery = baseQuery
 01514                .Where(e => (e.ParentIndexNumber == filter.MinParentAndIndexNumber.Value.ParentIndexNumber && e.IndexNum
 1515        }
 1516
 2571517        if (filter.MinDateCreated.HasValue)
 1518        {
 01519            baseQuery = baseQuery.Where(e => e.DateCreated >= filter.MinDateCreated);
 1520        }
 1521
 2571522        if (filter.MinDateLastSaved.HasValue)
 1523        {
 01524            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSaved.Value
 1525        }
 1526
 2571527        if (filter.MinDateLastSavedForUser.HasValue)
 1528        {
 01529            baseQuery = baseQuery.Where(e => e.DateLastSaved != null && e.DateLastSaved >= filter.MinDateLastSavedForUse
 1530        }
 1531
 2571532        if (filter.IndexNumber.HasValue)
 1533        {
 01534            baseQuery = baseQuery.Where(e => e.IndexNumber == filter.IndexNumber.Value);
 1535        }
 1536
 2571537        if (filter.ParentIndexNumber.HasValue)
 1538        {
 01539            baseQuery = baseQuery.Where(e => e.ParentIndexNumber == filter.ParentIndexNumber.Value);
 1540        }
 1541
 2571542        if (filter.ParentIndexNumberNotEquals.HasValue)
 1543        {
 01544            baseQuery = baseQuery.Where(e => e.ParentIndexNumber != filter.ParentIndexNumberNotEquals.Value || e.ParentI
 1545        }
 1546
 2571547        var minEndDate = filter.MinEndDate;
 2571548        var maxEndDate = filter.MaxEndDate;
 1549
 2571550        if (filter.HasAired.HasValue)
 1551        {
 01552            if (filter.HasAired.Value)
 1553            {
 01554                maxEndDate = DateTime.UtcNow;
 1555            }
 1556            else
 1557            {
 01558                minEndDate = DateTime.UtcNow;
 1559            }
 1560        }
 1561
 2571562        if (minEndDate.HasValue)
 1563        {
 01564            baseQuery = baseQuery.Where(e => e.EndDate >= minEndDate);
 1565        }
 1566
 2571567        if (maxEndDate.HasValue)
 1568        {
 01569            baseQuery = baseQuery.Where(e => e.EndDate <= maxEndDate);
 1570        }
 1571
 2571572        if (filter.MinStartDate.HasValue)
 1573        {
 01574            baseQuery = baseQuery.Where(e => e.StartDate >= filter.MinStartDate.Value);
 1575        }
 1576
 2571577        if (filter.MaxStartDate.HasValue)
 1578        {
 01579            baseQuery = baseQuery.Where(e => e.StartDate <= filter.MaxStartDate.Value);
 1580        }
 1581
 2571582        if (filter.MinPremiereDate.HasValue)
 1583        {
 01584            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MinPremiereDate.Value);
 1585        }
 1586
 2571587        if (filter.MaxPremiereDate.HasValue)
 1588        {
 01589            baseQuery = baseQuery.Where(e => e.PremiereDate <= filter.MaxPremiereDate.Value);
 1590        }
 1591
 2571592        if (filter.TrailerTypes.Length > 0)
 1593        {
 01594            var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray();
 01595            baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f)));
 1596        }
 1597
 2571598        if (filter.IsAiring.HasValue)
 1599        {
 01600            if (filter.IsAiring.Value)
 1601            {
 01602                baseQuery = baseQuery.Where(e => e.StartDate <= now && e.EndDate >= now);
 1603            }
 1604            else
 1605            {
 01606                baseQuery = baseQuery.Where(e => e.StartDate > now && e.EndDate < now);
 1607            }
 1608        }
 1609
 2571610        if (filter.PersonIds.Length > 0)
 1611        {
 01612            baseQuery = baseQuery
 01613                .Where(e =>
 01614                    context.PeopleBaseItemMap.Where(w => context.BaseItems.Where(r => filter.PersonIds.Contains(r.Id)).A
 01615                        .Any(f => f.ItemId == e.Id));
 1616        }
 1617
 2571618        if (!string.IsNullOrWhiteSpace(filter.Person))
 1619        {
 01620            baseQuery = baseQuery.Where(e => e.Peoples!.Any(f => f.People.Name == filter.Person));
 1621        }
 1622
 2571623        if (!string.IsNullOrWhiteSpace(filter.MinSortName))
 1624        {
 1625            // this does not makes sense.
 1626            // baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName);
 1627            // whereClauses.Add("SortName>=@MinSortName");
 1628            // statement?.TryBind("@MinSortName", query.MinSortName);
 1629        }
 1630
 2571631        if (!string.IsNullOrWhiteSpace(filter.ExternalSeriesId))
 1632        {
 01633            baseQuery = baseQuery.Where(e => e.ExternalSeriesId == filter.ExternalSeriesId);
 1634        }
 1635
 2571636        if (!string.IsNullOrWhiteSpace(filter.ExternalId))
 1637        {
 01638            baseQuery = baseQuery.Where(e => e.ExternalId == filter.ExternalId);
 1639        }
 1640
 2571641        if (!string.IsNullOrWhiteSpace(filter.Name))
 1642        {
 31643            var cleanName = GetCleanValue(filter.Name);
 31644            baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
 1645        }
 1646
 1647        // These are the same, for now
 2571648        var nameContains = filter.NameContains;
 2571649        if (!string.IsNullOrWhiteSpace(nameContains))
 1650        {
 01651            baseQuery = baseQuery.Where(e =>
 01652                e.CleanName!.Contains(nameContains)
 01653                || e.OriginalTitle!.ToLower().Contains(nameContains!));
 1654        }
 1655
 2571656        if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
 1657        {
 01658            baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith) || e.Name!.StartsWith(filter.
 1659        }
 1660
 2571661        if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
 1662        {
 1663            // i hate this
 01664            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!
 1665        }
 1666
 2571667        if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
 1668        {
 1669            // i hate this
 01670            baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDef
 1671        }
 1672
 2571673        if (filter.ImageTypes.Length > 0)
 1674        {
 821675            var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray();
 821676            baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f)));
 1677        }
 1678
 2571679        if (filter.IsLiked.HasValue)
 1680        {
 01681            baseQuery = baseQuery
 01682                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLike
 1683        }
 1684
 2571685        if (filter.IsFavoriteOrLiked.HasValue)
 1686        {
 01687            baseQuery = baseQuery
 01688                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1689        }
 1690
 2571691        if (filter.IsFavorite.HasValue)
 1692        {
 01693            baseQuery = baseQuery
 01694                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorit
 1695        }
 1696
 2571697        if (filter.IsPlayed.HasValue)
 1698        {
 1699            // We should probably figure this out for all folders, but for right now, this is the only place where we ne
 01700            if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.Series)
 1701            {
 01702                baseQuery = baseQuery.Where(e => context.BaseItems
 01703                    .Where(e => e.IsFolder == false && e.IsVirtualItem == false)
 01704                    .Where(f => f.UserData!.FirstOrDefault(e => e.UserId == filter.User!.Id && e.Played)!.Played)
 01705                    .Any(f => f.SeriesPresentationUniqueKey == e.PresentationUniqueKey) == filter.IsPlayed);
 1706            }
 1707            else
 1708            {
 01709                baseQuery = baseQuery
 01710                    .Select(e => new
 01711                    {
 01712                        IsPlayed = e.UserData!.Where(f => f.UserId == filter.User!.Id).Select(f => (bool?)f.Played).Firs
 01713                        Item = e
 01714                    })
 01715                    .Where(e => e.IsPlayed == filter.IsPlayed)
 01716                    .Select(f => f.Item);
 1717            }
 1718        }
 1719
 2571720        if (filter.IsResumable.HasValue)
 1721        {
 11722            if (filter.IsResumable.Value)
 1723            {
 11724                baseQuery = baseQuery
 11725                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks >
 1726            }
 1727            else
 1728            {
 01729                baseQuery = baseQuery
 01730                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks =
 1731            }
 1732        }
 1733
 2571734        if (filter.ArtistIds.Length > 0)
 1735        {
 01736            baseQuery = baseQuery
 01737                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type <= ItemValueType.Artist && filter.ArtistIds.Conta
 1738        }
 1739
 2571740        if (filter.AlbumArtistIds.Length > 0)
 1741        {
 01742            baseQuery = baseQuery
 01743                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.AlbumArtistIds.
 1744        }
 1745
 2571746        if (filter.ContributingArtistIds.Length > 0)
 1747        {
 01748            baseQuery = baseQuery
 01749                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.ContributingArt
 1750        }
 1751
 2571752        if (filter.AlbumIds.Length > 0)
 1753        {
 01754            baseQuery = baseQuery.Where(e => context.BaseItems.Where(f => filter.AlbumIds.Contains(f.Id)).Any(f => f.Nam
 1755        }
 1756
 2571757        if (filter.ExcludeArtistIds.Length > 0)
 1758        {
 01759            baseQuery = baseQuery
 01760                   .Where(e => !e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Artist && filter.ExcludeArtistI
 1761        }
 1762
 2571763        if (filter.GenreIds.Count > 0)
 1764        {
 01765            baseQuery = baseQuery
 01766                   .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Genre && filter.GenreIds.Contain
 1767        }
 1768
 2571769        if (filter.Genres.Count > 0)
 1770        {
 01771            var cleanGenres = filter.Genres.Select(e => GetCleanValue(e)).ToArray();
 01772            baseQuery = baseQuery
 01773                    .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Genre && cleanGenres.Contains(f
 1774        }
 1775
 2571776        if (tags.Count > 0)
 1777        {
 01778            var cleanValues = tags.Select(e => GetCleanValue(e)).ToArray();
 01779            baseQuery = baseQuery
 01780                    .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && cleanValues.Contains(f.
 1781        }
 1782
 2571783        if (excludeTags.Count > 0)
 1784        {
 01785            var cleanValues = excludeTags.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
 2571790        if (filter.StudioIds.Length > 0)
 1791        {
 01792            baseQuery = baseQuery
 01793                    .Where(e => e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Studios && filter.StudioIds.Con
 1794        }
 1795
 2571796        if (filter.OfficialRatings.Length > 0)
 1797        {
 01798            baseQuery = baseQuery
 01799                   .Where(e => filter.OfficialRatings.Contains(e.OfficialRating));
 1800        }
 1801
 2571802        Expression<Func<BaseItemEntity, bool>>? minParentalRatingFilter = null;
 2571803        if (filter.MinParentalRating != null)
 1804        {
 01805            var min = filter.MinParentalRating;
 01806            minParentalRatingFilter = e => e.InheritedParentalRatingValue >= min.Score || e.InheritedParentalRatingValue
 01807            if (min.SubScore != null)
 1808            {
 01809                minParentalRatingFilter = minParentalRatingFilter.And(e => e.InheritedParentalRatingValue >= min.SubScor
 1810            }
 1811        }
 1812
 2571813        Expression<Func<BaseItemEntity, bool>>? maxParentalRatingFilter = null;
 2571814        if (filter.MaxParentalRating != null)
 1815        {
 301816            var max = filter.MaxParentalRating;
 301817            maxParentalRatingFilter = e => e.InheritedParentalRatingValue <= max.Score || e.InheritedParentalRatingValue
 301818            if (max.SubScore != null)
 1819            {
 01820                maxParentalRatingFilter = maxParentalRatingFilter.And(e => e.InheritedParentalRatingValue <= max.SubScor
 1821            }
 1822        }
 1823
 2571824        if (filter.HasParentalRating ?? false)
 1825        {
 01826            if (minParentalRatingFilter != null)
 1827            {
 01828                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1829            }
 1830
 01831            if (maxParentalRatingFilter != null)
 1832            {
 01833                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1834            }
 1835        }
 2571836        else if (filter.BlockUnratedItems.Length > 0)
 1837        {
 01838            var unratedItemTypes = filter.BlockUnratedItems.Select(f => f.ToString()).ToArray();
 01839            Expression<Func<BaseItemEntity, bool>> unratedItemFilter = e => e.InheritedParentalRatingValue != null || !u
 1840
 01841            if (minParentalRatingFilter != null && maxParentalRatingFilter != null)
 1842            {
 01843                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter.And(maxParentalRatingFilter)))
 1844            }
 01845            else if (minParentalRatingFilter != null)
 1846            {
 01847                baseQuery = baseQuery.Where(unratedItemFilter.And(minParentalRatingFilter));
 1848            }
 01849            else if (maxParentalRatingFilter != null)
 1850            {
 01851                baseQuery = baseQuery.Where(unratedItemFilter.And(maxParentalRatingFilter));
 1852            }
 1853            else
 1854            {
 01855                baseQuery = baseQuery.Where(unratedItemFilter);
 1856            }
 1857        }
 2571858        else if (minParentalRatingFilter != null || maxParentalRatingFilter != null)
 1859        {
 301860            if (minParentalRatingFilter != null)
 1861            {
 01862                baseQuery = baseQuery.Where(minParentalRatingFilter);
 1863            }
 1864
 301865            if (maxParentalRatingFilter != null)
 1866            {
 301867                baseQuery = baseQuery.Where(maxParentalRatingFilter);
 1868            }
 1869        }
 2271870        else if (!filter.HasParentalRating ?? false)
 1871        {
 01872            baseQuery = baseQuery
 01873                .Where(e => e.InheritedParentalRatingValue == null);
 1874        }
 1875
 2571876        if (filter.HasOfficialRating.HasValue)
 1877        {
 01878            if (filter.HasOfficialRating.Value)
 1879            {
 01880                baseQuery = baseQuery
 01881                    .Where(e => e.OfficialRating != null && e.OfficialRating != string.Empty);
 1882            }
 1883            else
 1884            {
 01885                baseQuery = baseQuery
 01886                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty);
 1887            }
 1888        }
 1889
 2571890        if (filter.HasOverview.HasValue)
 1891        {
 01892            if (filter.HasOverview.Value)
 1893            {
 01894                baseQuery = baseQuery
 01895                    .Where(e => e.Overview != null && e.Overview != string.Empty);
 1896            }
 1897            else
 1898            {
 01899                baseQuery = baseQuery
 01900                    .Where(e => e.Overview == null || e.Overview == string.Empty);
 1901            }
 1902        }
 1903
 2571904        if (filter.HasOwnerId.HasValue)
 1905        {
 01906            if (filter.HasOwnerId.Value)
 1907            {
 01908                baseQuery = baseQuery
 01909                    .Where(e => e.OwnerId != null);
 1910            }
 1911            else
 1912            {
 01913                baseQuery = baseQuery
 01914                    .Where(e => e.OwnerId == null);
 1915            }
 1916        }
 1917
 2571918        if (!string.IsNullOrWhiteSpace(filter.HasNoAudioTrackWithLanguage))
 1919        {
 01920            baseQuery = baseQuery
 01921                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Audio && f.Language == filte
 1922        }
 1923
 2571924        if (!string.IsNullOrWhiteSpace(filter.HasNoInternalSubtitleTrackWithLanguage))
 1925        {
 01926            baseQuery = baseQuery
 01927                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && !f.IsExternal &&
 1928        }
 1929
 2571930        if (!string.IsNullOrWhiteSpace(filter.HasNoExternalSubtitleTrackWithLanguage))
 1931        {
 01932            baseQuery = baseQuery
 01933                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.IsExternal && 
 1934        }
 1935
 2571936        if (!string.IsNullOrWhiteSpace(filter.HasNoSubtitleTrackWithLanguage))
 1937        {
 01938            baseQuery = baseQuery
 01939                .Where(e => !e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle && f.Language == fi
 1940        }
 1941
 2571942        if (filter.HasSubtitles.HasValue)
 1943        {
 01944            baseQuery = baseQuery
 01945                .Where(e => e.MediaStreams!.Any(f => f.StreamType == MediaStreamTypeEntity.Subtitle) == filter.HasSubtit
 1946        }
 1947
 2571948        if (filter.HasChapterImages.HasValue)
 1949        {
 01950            baseQuery = baseQuery
 01951                .Where(e => e.Chapters!.Any(f => f.ImagePath != null) == filter.HasChapterImages.Value);
 1952        }
 1953
 2571954        if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
 1955        {
 101956            baseQuery = baseQuery
 101957                .Where(e => e.ParentId.HasValue && !context.BaseItems.Any(f => f.Id == e.ParentId.Value));
 1958        }
 1959
 2571960        if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
 1961        {
 101962            baseQuery = baseQuery
 101963                    .Where(e => e.ItemValues!.Count(f => f.ItemValue.Type == ItemValueType.Artist || f.ItemValue.Type ==
 1964        }
 1965
 2571966        if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value)
 1967        {
 101968            baseQuery = baseQuery
 101969                    .Where(e => e.ItemValues!.Count(f => f.ItemValue.Type == ItemValueType.Studios) == 1);
 1970        }
 1971
 2571972        if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value)
 1973        {
 01974            baseQuery = baseQuery
 01975                .Where(e => !context.Peoples.Any(f => f.Name == e.Name));
 1976        }
 1977
 2571978        if (filter.Years.Length == 1)
 1979        {
 01980            baseQuery = baseQuery
 01981                .Where(e => e.ProductionYear == filter.Years[0]);
 1982        }
 2571983        else if (filter.Years.Length > 1)
 1984        {
 01985            baseQuery = baseQuery
 01986                .Where(e => filter.Years.Any(f => f == e.ProductionYear));
 1987        }
 1988
 2571989        var isVirtualItem = filter.IsVirtualItem ?? filter.IsMissing;
 2571990        if (isVirtualItem.HasValue)
 1991        {
 11992            baseQuery = baseQuery
 11993                .Where(e => e.IsVirtualItem == isVirtualItem.Value);
 1994        }
 1995
 2571996        if (filter.IsSpecialSeason.HasValue)
 1997        {
 01998            if (filter.IsSpecialSeason.Value)
 1999            {
 02000                baseQuery = baseQuery
 02001                    .Where(e => e.IndexNumber == 0);
 2002            }
 2003            else
 2004            {
 02005                baseQuery = baseQuery
 02006                    .Where(e => e.IndexNumber != 0);
 2007            }
 2008        }
 2009
 2572010        if (filter.IsUnaired.HasValue)
 2011        {
 02012            if (filter.IsUnaired.Value)
 2013            {
 02014                baseQuery = baseQuery
 02015                    .Where(e => e.PremiereDate >= now);
 2016            }
 2017            else
 2018            {
 02019                baseQuery = baseQuery
 02020                    .Where(e => e.PremiereDate < now);
 2021            }
 2022        }
 2023
 2572024        if (filter.MediaTypes.Length > 0)
 2025        {
 02026            var mediaTypes = filter.MediaTypes.Select(f => f.ToString()).ToArray();
 02027            baseQuery = baseQuery
 02028                .Where(e => mediaTypes.Contains(e.MediaType));
 2029        }
 2030
 2572031        if (filter.ItemIds.Length > 0)
 2032        {
 02033            baseQuery = baseQuery
 02034                .Where(e => filter.ItemIds.Contains(e.Id));
 2035        }
 2036
 2572037        if (filter.ExcludeItemIds.Length > 0)
 2038        {
 02039            baseQuery = baseQuery
 02040                .Where(e => !filter.ItemIds.Contains(e.Id));
 2041        }
 2042
 2572043        if (filter.ExcludeProviderIds is not null && filter.ExcludeProviderIds.Count > 0)
 2044        {
 02045            baseQuery = baseQuery.Where(e => !e.Provider!.All(f => !filter.ExcludeProviderIds.All(w => f.ProviderId == w
 2046        }
 2047
 2572048        if (filter.HasAnyProviderId is not null && filter.HasAnyProviderId.Count > 0)
 2049        {
 02050            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => !filter.HasAnyProviderId.Any(w => f.ProviderId == w.Ke
 2051        }
 2052
 2572053        if (filter.HasImdbId.HasValue)
 2054        {
 02055            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "imdb"));
 2056        }
 2057
 2572058        if (filter.HasTmdbId.HasValue)
 2059        {
 02060            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tmdb"));
 2061        }
 2062
 2572063        if (filter.HasTvdbId.HasValue)
 2064        {
 02065            baseQuery = baseQuery.Where(e => e.Provider!.Any(f => f.ProviderId == "tvdb"));
 2066        }
 2067
 2572068        var queryTopParentIds = filter.TopParentIds;
 2069
 2572070        if (queryTopParentIds.Length > 0)
 2071        {
 172072            var includedItemByNameTypes = GetItemByNameTypesInQuery(filter);
 172073            var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
 172074            if (enableItemsByName && includedItemByNameTypes.Count > 0)
 2075            {
 02076                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => 
 2077            }
 2078            else
 2079            {
 172080                baseQuery = baseQuery.Where(e => queryTopParentIds.Contains(e.TopParentId!.Value));
 2081            }
 2082        }
 2083
 2572084        if (filter.AncestorIds.Length > 0)
 2085        {
 372086            baseQuery = baseQuery.Where(e => e.Children!.Any(f => filter.AncestorIds.Contains(f.ParentItemId)));
 2087        }
 2088
 2572089        if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
 2090        {
 02091            baseQuery = baseQuery
 02092                .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqu
 2093        }
 2094
 2572095        if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
 2096        {
 02097            baseQuery = baseQuery
 02098                .Where(e => e.SeriesPresentationUniqueKey == filter.SeriesPresentationUniqueKey);
 2099        }
 2100
 2572101        if (filter.ExcludeInheritedTags.Length > 0)
 2102        {
 02103            baseQuery = baseQuery
 02104                .Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags)
 02105                    .Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
 2106        }
 2107
 2572108        if (filter.IncludeInheritedTags.Length > 0)
 2109        {
 2110            // Episodes do not store inherit tags from their parents in the database, and the tag may be still required 
 2111            // In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s 
 02112            if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
 2113            {
 02114                baseQuery = baseQuery
 02115                    .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags)
 02116                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
 02117                        ||
 02118                        (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value)!.Where(w 
 02119                        .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
 2120            }
 2121
 2122            // A playlist should be accessible to its owner regardless of allowed tags.
 02123            else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
 2124            {
 02125                baseQuery = baseQuery
 02126                    .Where(e =>
 02127                    e.ParentAncestors!
 02128                        .Any(f =>
 02129                            f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeIn
 02130                            || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
 2131                // d        ^^ this is stupid it hate this.
 2132            }
 2133            else
 2134            {
 02135                baseQuery = baseQuery
 02136                    .Where(e => e.ParentAncestors!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemVa
 2137            }
 2138        }
 2139
 2572140        if (filter.SeriesStatuses.Length > 0)
 2141        {
 02142            var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
 02143            baseQuery = baseQuery
 02144                .Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
 2145        }
 2146
 2572147        if (filter.BoxSetLibraryFolders.Length > 0)
 2148        {
 02149            var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).T
 02150            baseQuery = baseQuery
 02151                .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
 2152        }
 2153
 2572154        if (filter.VideoTypes.Length > 0)
 2155        {
 02156            var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"{e}\"");
 02157            baseQuery = baseQuery
 02158                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
 2159        }
 2160
 2572161        if (filter.Is3D.HasValue)
 2162        {
 02163            if (filter.Is3D.Value)
 2164            {
 02165                baseQuery = baseQuery
 02166                    .Where(e => e.Data!.Contains("Video3DFormat"));
 2167            }
 2168            else
 2169            {
 02170                baseQuery = baseQuery
 02171                    .Where(e => !e.Data!.Contains("Video3DFormat"));
 2172            }
 2173        }
 2174
 2572175        if (filter.IsPlaceHolder.HasValue)
 2176        {
 02177            if (filter.IsPlaceHolder.Value)
 2178            {
 02179                baseQuery = baseQuery
 02180                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
 2181            }
 2182            else
 2183            {
 02184                baseQuery = baseQuery
 02185                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
 2186            }
 2187        }
 2188
 2572189        if (filter.HasSpecialFeature.HasValue)
 2190        {
 02191            if (filter.HasSpecialFeature.Value)
 2192            {
 02193                baseQuery = baseQuery
 02194                    .Where(e => e.ExtraIds != null);
 2195            }
 2196            else
 2197            {
 02198                baseQuery = baseQuery
 02199                    .Where(e => e.ExtraIds == null);
 2200            }
 2201        }
 2202
 2572203        if (filter.HasTrailer.HasValue || filter.HasThemeSong.HasValue || filter.HasThemeVideo.HasValue)
 2204        {
 02205            if (filter.HasTrailer.GetValueOrDefault() || filter.HasThemeSong.GetValueOrDefault() || filter.HasThemeVideo
 2206            {
 02207                baseQuery = baseQuery
 02208                    .Where(e => e.ExtraIds != null);
 2209            }
 2210            else
 2211            {
 02212                baseQuery = baseQuery
 02213                    .Where(e => e.ExtraIds == null);
 2214            }
 2215        }
 2216
 2572217        return baseQuery;
 2218    }
 2219}

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)