2024-12-15 14:46:40 +00:00
#pragma warning disable RS0030 // Do not use banned APIs
// Do not enforce that because EFCore cannot deal with cultures well.
#pragma warning disable CA1304 // Specify CultureInfo
#pragma warning disable CA1311 // Specify a culture or use an invariant version
#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
2024-09-08 16:56:14 +00:00
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Linq ;
2025-03-31 05:51:54 +02:00
using System.Linq.Expressions ;
2024-10-10 18:01:14 +00:00
using System.Reflection ;
2024-09-08 16:56:14 +00:00
using System.Text ;
2024-10-10 18:01:14 +00:00
using System.Text.Json ;
2024-09-08 16:56:14 +00:00
using System.Threading ;
2025-06-16 00:19:57 +03:00
using System.Threading.Tasks ;
2024-10-08 12:27:27 +00:00
using Jellyfin.Data.Enums ;
2025-03-25 15:30:22 +00:00
using Jellyfin.Database.Implementations ;
2025-03-25 16:45:00 +01:00
using Jellyfin.Database.Implementations.Entities ;
2025-03-25 15:30:22 +00:00
using Jellyfin.Database.Implementations.Enums ;
2024-09-08 16:56:14 +00:00
using Jellyfin.Extensions ;
2024-10-10 18:01:14 +00:00
using Jellyfin.Extensions.Json ;
2025-03-31 05:51:54 +02:00
using Jellyfin.Server.Implementations.Extensions ;
2024-10-10 18:01:14 +00:00
using MediaBrowser.Common ;
2024-09-08 16:56:14 +00:00
using MediaBrowser.Controller ;
2024-10-10 18:01:14 +00:00
using MediaBrowser.Controller.Channels ;
using MediaBrowser.Controller.Configuration ;
2024-09-08 16:56:14 +00:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities.Audio ;
using MediaBrowser.Controller.Entities.TV ;
using MediaBrowser.Controller.LiveTv ;
2024-10-08 12:27:27 +00:00
using MediaBrowser.Controller.Persistence ;
2024-10-08 19:11:31 +00:00
using MediaBrowser.Model.Dto ;
2024-09-08 16:56:14 +00:00
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.LiveTv ;
2024-10-08 12:27:27 +00:00
using MediaBrowser.Model.Querying ;
2024-09-08 16:56:14 +00:00
using Microsoft.EntityFrameworkCore ;
2024-10-10 18:01:14 +00:00
using Microsoft.Extensions.Logging ;
2024-09-08 16:56:14 +00:00
using BaseItemDto = MediaBrowser . Controller . Entities . BaseItem ;
2025-03-25 16:45:00 +01:00
using BaseItemEntity = Jellyfin . Database . Implementations . Entities . BaseItemEntity ;
2024-09-08 16:56:14 +00:00
namespace Jellyfin.Server.Implementations.Item ;
2024-12-15 14:46:40 +00:00
/ *
2025-01-11 18:13:16 +00:00
All queries in this class and all other nullable enabled EFCore repository classes will make liberal use of the null - forgiving operator "!" .
This is done as the code isn ' t actually executed client side , but only the expressions are interpret and the compiler cannot know that .
2024-12-15 14:46:40 +00:00
This is your only warning / message regarding this topic .
* /
2024-12-19 00:10:47 +00:00
2024-09-08 16:56:14 +00:00
/// <summary>
/// Handles all storage logic for BaseItems.
/// </summary>
2024-11-24 10:58:09 +00:00
public sealed class BaseItemRepository
2025-01-11 18:13:16 +00:00
: IItemRepository
2024-09-08 16:56:14 +00:00
{
2025-06-10 02:14:27 +03:00
/// <summary>
/// Gets the placeholder id for UserData detached items.
/// </summary>
public static readonly Guid PlaceholderId = Guid . Parse ( "00000000-0000-0000-0000-000000000001" ) ;
2024-10-08 12:27:27 +00:00
/// <summary>
2024-09-08 16:56:14 +00:00
/// This holds all the types in the running assemblies
/// so that we can de-serialize properly when we don't have strong types.
/// </summary>
private static readonly ConcurrentDictionary < string , Type ? > _typeMap = new ConcurrentDictionary < string , Type ? > ( ) ;
2024-11-24 10:58:09 +00:00
private readonly IDbContextFactory < JellyfinDbContext > _dbProvider ;
private readonly IServerApplicationHost _appHost ;
private readonly IItemTypeLookup _itemTypeLookup ;
private readonly IServerConfigurationManager _serverConfigurationManager ;
private readonly ILogger < BaseItemRepository > _logger ;
2024-09-08 16:56:14 +00:00
2024-11-24 10:58:09 +00:00
private static readonly IReadOnlyList < ItemValueType > _getAllArtistsValueTypes = [ ItemValueType . Artist , ItemValueType . AlbumArtist ] ;
private static readonly IReadOnlyList < ItemValueType > _getArtistValueTypes = [ ItemValueType . Artist ] ;
private static readonly IReadOnlyList < ItemValueType > _getAlbumArtistValueTypes = [ ItemValueType . AlbumArtist ] ;
private static readonly IReadOnlyList < ItemValueType > _getStudiosValueTypes = [ ItemValueType . Studios ] ;
2025-04-07 14:11:05 +02:00
private static readonly IReadOnlyList < ItemValueType > _getGenreValueTypes = [ ItemValueType . Genre ] ;
2025-10-03 02:33:01 +03:00
private static readonly IReadOnlyList < char > SearchWildcardTerms = [ '%' , '_' , '[' , ']' , '^' ] ;
2024-11-24 10:58:09 +00:00
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
/// </summary>
/// <param name="dbProvider">The db factory.</param>
/// <param name="appHost">The Application host.</param>
/// <param name="itemTypeLookup">The static type lookup.</param>
/// <param name="serverConfigurationManager">The server Configuration manager.</param>
/// <param name="logger">System logger.</param>
public BaseItemRepository (
IDbContextFactory < JellyfinDbContext > dbProvider ,
IServerApplicationHost appHost ,
IItemTypeLookup itemTypeLookup ,
IServerConfigurationManager serverConfigurationManager ,
ILogger < BaseItemRepository > logger )
{
_dbProvider = dbProvider ;
_appHost = appHost ;
_itemTypeLookup = itemTypeLookup ;
_serverConfigurationManager = serverConfigurationManager ;
_logger = logger ;
}
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-09-25 00:20:30 +03:00
public void DeleteItem ( params IReadOnlyList < Guid > ids )
2024-10-08 19:11:31 +00:00
{
2025-09-25 00:20:30 +03:00
if ( ids is null | | ids . Count = = 0 | | ids . Any ( f = > f . Equals ( PlaceholderId ) ) )
2025-01-19 13:30:31 +01:00
{
2025-09-25 00:20:30 +03:00
throw new ArgumentException ( "Guid can't be empty or the placeholder id." , nameof ( ids ) ) ;
2025-01-19 13:30:31 +01:00
}
2024-10-08 19:11:31 +00:00
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-10-08 19:11:31 +00:00
using var transaction = context . Database . BeginTransaction ( ) ;
2025-06-10 02:14:27 +03:00
2025-06-12 02:30:57 +03:00
var date = ( DateTime ? ) DateTime . UtcNow ;
2025-07-18 01:19:26 +02:00
2025-09-25 00:20:30 +03:00
var relatedItems = ids . SelectMany ( f = > TraverseHirachyDown ( f , context ) ) . ToArray ( ) ;
2025-09-16 21:08:04 +02:00
2025-07-18 01:19:26 +02:00
// Remove any UserData entries for the placeholder item that would conflict with the UserData
// being detached from the item being deleted. This is necessary because, during an update,
// UserData may be reattached to a new entry, but some entries can be left behind.
// Ensures there are no duplicate UserId/CustomDataKey combinations for the placeholder.
context . UserData
. Join (
2025-09-16 21:08:04 +02:00
context . UserData . WhereOneOrMany ( relatedItems , e = > e . ItemId ) ,
2025-07-18 01:19:26 +02:00
placeholder = > new { placeholder . UserId , placeholder . CustomDataKey } ,
userData = > new { userData . UserId , userData . CustomDataKey } ,
( placeholder , userData ) = > placeholder )
. Where ( e = > e . ItemId = = PlaceholderId )
. ExecuteDelete ( ) ;
2025-06-10 02:14:27 +03:00
// Detach all user watch data
2025-09-16 21:08:04 +02:00
context . UserData . WhereOneOrMany ( relatedItems , e = > e . ItemId )
2025-06-10 02:14:27 +03:00
. ExecuteUpdate ( e = > e
. SetProperty ( f = > f . RetentionDate , date )
. SetProperty ( f = > f . ItemId , PlaceholderId ) ) ;
2025-09-16 21:08:04 +02:00
context . AncestorIds . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . AncestorIds . WhereOneOrMany ( relatedItems , e = > e . ParentItemId ) . ExecuteDelete ( ) ;
context . AttachmentStreamInfos . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . BaseItemImageInfos . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . BaseItemMetadataFields . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . BaseItemProviders . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . BaseItemTrailerTypes . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . BaseItems . WhereOneOrMany ( relatedItems , e = > e . Id ) . ExecuteDelete ( ) ;
context . Chapters . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . CustomItemDisplayPreferences . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . ItemDisplayPreferences . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
2025-03-23 17:05:13 +01:00
context . ItemValues . Where ( e = > e . BaseItemsMap ! . Count = = 0 ) . ExecuteDelete ( ) ;
2025-09-16 21:08:04 +02:00
context . ItemValuesMap . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . KeyframeData . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . MediaSegments . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . MediaStreamInfos . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
var query = context . PeopleBaseItemMap . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . Select ( f = > f . PeopleId ) . Distinct ( ) . ToArray ( ) ;
context . PeopleBaseItemMap . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
context . Peoples . WhereOneOrMany ( query , e = > e . Id ) . Where ( e = > e . BaseItems ! . Count = = 0 ) . ExecuteDelete ( ) ;
context . TrickplayInfos . WhereOneOrMany ( relatedItems , e = > e . ItemId ) . ExecuteDelete ( ) ;
2024-10-08 19:11:31 +00:00
context . SaveChanges ( ) ;
transaction . Commit ( ) ;
}
/// <inheritdoc />
public void UpdateInheritedValues ( )
{
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-10-08 19:11:31 +00:00
using var transaction = context . Database . BeginTransaction ( ) ;
2024-10-10 14:32:49 +00:00
context . ItemValuesMap . Where ( e = > e . ItemValue . Type = = ItemValueType . InheritedTags ) . ExecuteDelete ( ) ;
2024-11-24 10:58:09 +00:00
// ItemValue Inheritance is now correctly mapped via AncestorId on demand
2024-10-08 19:11:31 +00:00
context . SaveChanges ( ) ;
transaction . Commit ( ) ;
2024-10-08 16:27:47 +00:00
}
2025-01-15 20:12:41 +00:00
/// <inheritdoc />
2024-10-08 16:27:47 +00:00
public IReadOnlyList < Guid > GetItemIdsList ( InternalItemsQuery filter )
{
ArgumentNullException . ThrowIfNull ( filter ) ;
PrepareFilterQuery ( filter ) ;
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2025-06-15 21:07:19 +03:00
return ApplyQueryFilter ( context . BaseItems . AsNoTracking ( ) . Where ( e = > e . Id ! = EF . Constant ( PlaceholderId ) ) , context , filter ) . Select ( e = > e . Id ) . ToArray ( ) ;
2024-10-08 16:27:47 +00:00
}
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-04-27 04:10:54 +03:00
public QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetAllArtists ( InternalItemsQuery filter )
2024-10-08 16:27:47 +00:00
{
2024-11-24 10:58:09 +00:00
return GetItemValues ( filter , _getAllArtistsValueTypes , _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicArtist ] ) ;
2024-10-08 19:11:31 +00:00
}
2024-10-08 16:27:47 +00:00
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-04-27 04:10:54 +03:00
public QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetArtists ( InternalItemsQuery filter )
2024-10-08 19:11:31 +00:00
{
2024-11-24 10:58:09 +00:00
return GetItemValues ( filter , _getArtistValueTypes , _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicArtist ] ) ;
2024-10-08 19:11:31 +00:00
}
2024-10-08 16:27:47 +00:00
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-04-27 04:10:54 +03:00
public QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetAlbumArtists ( InternalItemsQuery filter )
2024-10-08 19:11:31 +00:00
{
2024-11-24 10:58:09 +00:00
return GetItemValues ( filter , _getAlbumArtistValueTypes , _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicArtist ] ) ;
2024-10-08 19:11:31 +00:00
}
2024-10-08 16:27:47 +00:00
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-04-27 04:10:54 +03:00
public QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetStudios ( InternalItemsQuery filter )
2024-10-08 19:11:31 +00:00
{
2024-11-24 10:58:09 +00:00
return GetItemValues ( filter , _getStudiosValueTypes , _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Studio ] ) ;
2024-10-08 19:11:31 +00:00
}
2024-10-08 16:27:47 +00:00
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-04-27 04:10:54 +03:00
public QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetGenres ( InternalItemsQuery filter )
2024-10-08 19:11:31 +00:00
{
2024-11-24 10:58:09 +00:00
return GetItemValues ( filter , _getGenreValueTypes , _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Genre ] ) ;
2024-10-08 19:11:31 +00:00
}
2024-10-08 16:27:47 +00:00
2024-10-08 19:11:31 +00:00
/// <inheritdoc />
2025-04-27 04:10:54 +03:00
public QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetMusicGenres ( InternalItemsQuery filter )
2024-10-08 19:11:31 +00:00
{
2024-11-24 10:58:09 +00:00
return GetItemValues ( filter , _getGenreValueTypes , _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicGenre ] ) ;
2024-10-08 19:53:26 +00:00
}
/// <inheritdoc />
public IReadOnlyList < string > GetStudioNames ( )
{
2024-11-24 10:58:09 +00:00
return GetItemValueNames ( _getStudiosValueTypes , [ ] , [ ] ) ;
2024-10-08 19:53:26 +00:00
}
/// <inheritdoc />
public IReadOnlyList < string > GetAllArtistNames ( )
{
2024-11-24 10:58:09 +00:00
return GetItemValueNames ( _getAllArtistsValueTypes , [ ] , [ ] ) ;
2024-10-08 19:53:26 +00:00
}
/// <inheritdoc />
public IReadOnlyList < string > GetMusicGenreNames ( )
{
return GetItemValueNames (
2024-11-24 10:58:09 +00:00
_getGenreValueTypes ,
_itemTypeLookup . MusicGenreTypes ,
2024-11-11 23:11:17 +00:00
[] ) ;
2024-10-08 19:53:26 +00:00
}
/// <inheritdoc />
public IReadOnlyList < string > GetGenreNames ( )
{
return GetItemValueNames (
2024-11-24 10:58:09 +00:00
_getGenreValueTypes ,
2024-11-11 23:11:17 +00:00
[] ,
2024-11-24 10:58:09 +00:00
_itemTypeLookup . MusicGenreTypes ) ;
2024-10-08 16:27:47 +00:00
}
2025-01-15 20:12:41 +00:00
/// <inheritdoc />
2024-10-08 19:53:26 +00:00
public QueryResult < BaseItemDto > GetItems ( InternalItemsQuery filter )
2024-10-08 12:27:27 +00:00
{
2024-10-08 19:53:26 +00:00
ArgumentNullException . ThrowIfNull ( filter ) ;
if ( ! filter . EnableTotalRecordCount | | ( ! filter . Limit . HasValue & & ( filter . StartIndex ? ? 0 ) = = 0 ) )
2024-10-08 12:27:27 +00:00
{
2024-10-08 19:53:26 +00:00
var returnList = GetItemList ( filter ) ;
2024-10-08 19:11:31 +00:00
return new QueryResult < BaseItemDto > (
2024-10-08 19:53:26 +00:00
filter . StartIndex ,
2024-10-08 16:27:47 +00:00
returnList . Count ,
returnList ) ;
2024-10-08 12:27:27 +00:00
}
2024-10-08 19:53:26 +00:00
PrepareFilterQuery ( filter ) ;
2024-10-08 19:11:31 +00:00
var result = new QueryResult < BaseItemDto > ( ) ;
2024-10-08 16:27:47 +00:00
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-10-08 16:27:47 +00:00
2024-11-13 10:07:45 +00:00
IQueryable < BaseItemEntity > dbQuery = PrepareItemQuery ( context , filter ) ;
2024-11-12 13:29:29 +00:00
dbQuery = TranslateQuery ( dbQuery , context , filter ) ;
2025-10-03 01:33:50 +02:00
dbQuery = ApplyGroupingFilter ( context , dbQuery , filter ) ;
2024-11-12 13:29:29 +00:00
if ( filter . EnableTotalRecordCount )
{
result . TotalRecordCount = dbQuery . Count ( ) ;
}
2025-01-28 05:27:34 -05:00
dbQuery = ApplyQueryPaging ( dbQuery , filter ) ;
2024-11-12 13:29:29 +00:00
2025-06-18 21:33:32 +02:00
result . Items = dbQuery . AsEnumerable ( ) . Where ( e = > e is not null ) . Select ( w = > DeserializeBaseItem ( w , filter . SkipDeserialization ) ) . ToArray ( ) ;
2024-10-08 19:53:26 +00:00
result . StartIndex = filter . StartIndex ? ? 0 ;
2024-10-08 16:27:47 +00:00
return result ;
}
2025-01-15 20:12:41 +00:00
/// <inheritdoc />
2024-10-08 19:53:26 +00:00
public IReadOnlyList < BaseItemDto > GetItemList ( InternalItemsQuery filter )
2024-10-08 16:27:47 +00:00
{
2024-10-08 19:53:26 +00:00
ArgumentNullException . ThrowIfNull ( filter ) ;
PrepareFilterQuery ( filter ) ;
2024-10-08 16:27:47 +00:00
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-11-13 10:07:45 +00:00
IQueryable < BaseItemEntity > dbQuery = PrepareItemQuery ( context , filter ) ;
2024-11-11 17:39:50 +00:00
2024-11-12 15:37:01 +00:00
dbQuery = TranslateQuery ( dbQuery , context , filter ) ;
2024-11-24 10:58:09 +00:00
2025-09-23 00:31:21 +03:00
dbQuery = ApplyGroupingFilter ( context , dbQuery , filter ) ;
2025-01-28 05:27:34 -05:00
dbQuery = ApplyQueryPaging ( dbQuery , filter ) ;
2024-11-12 15:37:01 +00:00
2025-06-18 21:33:32 +02:00
return dbQuery . AsEnumerable ( ) . Where ( e = > e is not null ) . Select ( w = > DeserializeBaseItem ( w , filter . SkipDeserialization ) ) . ToArray ( ) ;
2024-11-11 22:29:44 +00:00
}
2024-11-11 17:39:50 +00:00
2025-03-27 10:26:47 +08:00
/// <inheritdoc/>
public IReadOnlyList < BaseItem > GetLatestItemList ( InternalItemsQuery filter , CollectionType collectionType )
{
ArgumentNullException . ThrowIfNull ( filter ) ;
PrepareFilterQuery ( filter ) ;
// Early exit if collection type is not tvshows or music
if ( collectionType ! = CollectionType . tvshows & & collectionType ! = CollectionType . music )
{
return Array . Empty < BaseItem > ( ) ;
}
using var context = _dbProvider . CreateDbContext ( ) ;
// Subquery to group by SeriesNames/Album and get the max Date Created for each group.
var subquery = PrepareItemQuery ( context , filter ) ;
subquery = TranslateQuery ( subquery , context , filter ) ;
var subqueryGrouped = subquery . GroupBy ( g = > collectionType = = CollectionType . tvshows ? g . SeriesName : g . Album )
. Select ( g = > new
{
Key = g . Key ,
MaxDateCreated = g . Max ( a = > a . DateCreated )
} )
. OrderByDescending ( g = > g . MaxDateCreated )
. Select ( g = > g ) ;
if ( filter . Limit . HasValue )
{
subqueryGrouped = subqueryGrouped . Take ( filter . Limit . Value ) ;
}
filter . Limit = null ;
var mainquery = PrepareItemQuery ( context , filter ) ;
mainquery = TranslateQuery ( mainquery , context , filter ) ;
mainquery = mainquery . Where ( g = > g . DateCreated > = subqueryGrouped . Min ( s = > s . MaxDateCreated ) ) ;
2025-09-23 00:31:21 +03:00
mainquery = ApplyGroupingFilter ( context , mainquery , filter ) ;
2025-03-27 10:26:47 +08:00
mainquery = ApplyQueryPaging ( mainquery , filter ) ;
2025-06-18 21:33:32 +02:00
return mainquery . AsEnumerable ( ) . Where ( e = > e is not null ) . Select ( w = > DeserializeBaseItem ( w , filter . SkipDeserialization ) ) . ToArray ( ) ;
2025-03-27 10:26:47 +08:00
}
2025-03-18 17:37:04 -06:00
/// <inheritdoc />
public IReadOnlyList < string > GetNextUpSeriesKeys ( InternalItemsQuery filter , DateTime dateCutoff )
{
ArgumentNullException . ThrowIfNull ( filter ) ;
ArgumentNullException . ThrowIfNull ( filter . User ) ;
using var context = _dbProvider . CreateDbContext ( ) ;
var query = context . BaseItems
. AsNoTracking ( )
. Where ( i = > filter . TopParentIds . Contains ( i . TopParentId ! . Value ) )
. Where ( i = > i . Type = = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Episode ] )
. Join (
2025-06-15 21:07:19 +03:00
context . UserData . AsNoTracking ( ) . Where ( e = > e . ItemId ! = EF . Constant ( PlaceholderId ) ) ,
2025-03-18 17:37:04 -06:00
i = > new { UserId = filter . User . Id , ItemId = i . Id } ,
u = > new { UserId = u . UserId , ItemId = u . ItemId } ,
( entity , data ) = > new { Item = entity , UserData = data } )
. GroupBy ( g = > g . Item . SeriesPresentationUniqueKey )
. Select ( g = > new { g . Key , LastPlayedDate = g . Max ( u = > u . UserData . LastPlayedDate ) } )
. Where ( g = > g . Key ! = null & & g . LastPlayedDate ! = null & & g . LastPlayedDate > = dateCutoff )
. OrderByDescending ( g = > g . LastPlayedDate )
. Select ( g = > g . Key ! ) ;
if ( filter . Limit . HasValue )
{
query = query . Take ( filter . Limit . Value ) ;
}
return query . ToArray ( ) ;
}
2025-09-23 00:31:21 +03:00
private IQueryable < BaseItemEntity > ApplyGroupingFilter ( JellyfinDbContext context , IQueryable < BaseItemEntity > dbQuery , InternalItemsQuery filter )
2024-11-11 22:29:44 +00:00
{
2024-11-24 10:58:09 +00:00
// This whole block is needed to filter duplicate entries on request
2025-01-25 21:04:37 -05:00
// for the time being it cannot be used because it would destroy the ordering
2024-11-24 10:58:09 +00:00
// this results in "duplicate" responses for queries that try to lookup individual series or multiple versions but
// for that case the invoker has to run a DistinctBy(e => e.PresentationUniqueKey) on their own
2025-09-23 00:31:21 +03:00
var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey ( filter ) ;
if ( enableGroupByPresentationUniqueKey & & filter . GroupBySeriesPresentationUniqueKey )
{
var tempQuery = dbQuery . GroupBy ( e = > new { e . PresentationUniqueKey , e . SeriesPresentationUniqueKey } ) . Select ( e = > e . FirstOrDefault ( ) ) . Select ( e = > e ! . Id ) ;
dbQuery = context . BaseItems . Where ( e = > tempQuery . Contains ( e . Id ) ) ;
}
else if ( enableGroupByPresentationUniqueKey )
{
var tempQuery = dbQuery . GroupBy ( e = > e . PresentationUniqueKey ) . Select ( e = > e . FirstOrDefault ( ) ) . Select ( e = > e ! . Id ) ;
dbQuery = context . BaseItems . Where ( e = > tempQuery . Contains ( e . Id ) ) ;
}
else if ( filter . GroupBySeriesPresentationUniqueKey )
{
var tempQuery = dbQuery . GroupBy ( e = > e . SeriesPresentationUniqueKey ) . Select ( e = > e . FirstOrDefault ( ) ) . Select ( e = > e ! . Id ) ;
dbQuery = context . BaseItems . Where ( e = > tempQuery . Contains ( e . Id ) ) ;
}
else
{
dbQuery = dbQuery . Distinct ( ) ;
}
2025-09-23 16:02:30 +03:00
dbQuery = ApplyOrder ( dbQuery , filter ) ;
dbQuery = ApplyNavigations ( dbQuery , filter ) ;
return dbQuery ;
}
private static IQueryable < BaseItemEntity > ApplyNavigations ( IQueryable < BaseItemEntity > dbQuery , InternalItemsQuery filter )
{
2025-09-23 00:31:21 +03:00
dbQuery = dbQuery . Include ( e = > e . TrailerTypes )
. Include ( e = > e . Provider )
. Include ( e = > e . LockedFields )
. Include ( e = > e . UserData ) ;
if ( filter . DtoOptions . EnableImages )
{
dbQuery = dbQuery . Include ( e = > e . Images ) ;
}
2024-11-12 13:29:29 +00:00
return dbQuery ;
}
2024-11-11 17:39:50 +00:00
2025-01-28 05:27:34 -05:00
private IQueryable < BaseItemEntity > ApplyQueryPaging ( IQueryable < BaseItemEntity > dbQuery , InternalItemsQuery filter )
2024-11-12 13:29:29 +00:00
{
2024-10-08 19:53:26 +00:00
if ( filter . Limit . HasValue | | filter . StartIndex . HasValue )
2024-10-08 16:27:47 +00:00
{
2024-10-08 19:53:26 +00:00
var offset = filter . StartIndex ? ? 0 ;
2024-10-08 16:27:47 +00:00
if ( offset > 0 )
{
dbQuery = dbQuery . Skip ( offset ) ;
}
2024-10-08 19:53:26 +00:00
if ( filter . Limit . HasValue )
2024-10-08 16:27:47 +00:00
{
2024-10-08 19:53:26 +00:00
dbQuery = dbQuery . Take ( filter . Limit . Value ) ;
2024-10-08 16:27:47 +00:00
}
2024-10-08 12:27:27 +00:00
}
2024-11-11 22:29:44 +00:00
return dbQuery ;
}
2024-11-12 13:29:29 +00:00
private IQueryable < BaseItemEntity > ApplyQueryFilter ( IQueryable < BaseItemEntity > dbQuery , JellyfinDbContext context , InternalItemsQuery filter )
{
dbQuery = TranslateQuery ( dbQuery , context , filter ) ;
2025-09-23 00:31:21 +03:00
dbQuery = ApplyGroupingFilter ( context , dbQuery , filter ) ;
2025-01-28 05:27:34 -05:00
dbQuery = ApplyQueryPaging ( dbQuery , filter ) ;
2024-11-12 13:29:29 +00:00
return dbQuery ;
}
2024-11-11 22:29:44 +00:00
private IQueryable < BaseItemEntity > PrepareItemQuery ( JellyfinDbContext context , InternalItemsQuery filter )
{
2025-04-27 04:10:54 +03:00
IQueryable < BaseItemEntity > dbQuery = context . BaseItems . AsNoTracking ( ) ;
2025-09-23 00:31:21 +03:00
dbQuery = dbQuery . AsSingleQuery ( ) ;
2024-11-11 22:29:44 +00:00
2024-11-13 10:07:45 +00:00
return dbQuery ;
2024-10-08 16:27:47 +00:00
}
/// <inheritdoc/>
2024-10-08 19:53:26 +00:00
public int GetCount ( InternalItemsQuery filter )
2024-10-08 16:27:47 +00:00
{
2024-10-08 19:53:26 +00:00
ArgumentNullException . ThrowIfNull ( filter ) ;
2024-10-08 16:27:47 +00:00
// Hack for right now since we currently don't support filtering out these duplicates within a query
2024-10-08 19:53:26 +00:00
PrepareFilterQuery ( filter ) ;
2024-10-08 12:27:27 +00:00
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-10-09 23:58:55 +00:00
var dbQuery = TranslateQuery ( context . BaseItems . AsNoTracking ( ) , context , filter ) ;
2024-10-08 12:27:27 +00:00
2024-10-08 16:27:47 +00:00
return dbQuery . Count ( ) ;
2024-10-08 12:27:27 +00:00
}
2025-08-11 21:03:55 -06:00
/// <inheritdoc />
public ItemCounts GetItemCounts ( InternalItemsQuery filter )
{
ArgumentNullException . ThrowIfNull ( filter ) ;
// Hack for right now since we currently don't support filtering out these duplicates within a query
PrepareFilterQuery ( filter ) ;
using var context = _dbProvider . CreateDbContext ( ) ;
var dbQuery = TranslateQuery ( context . BaseItems . AsNoTracking ( ) , context , filter ) ;
var counts = dbQuery
. GroupBy ( x = > x . Type )
. Select ( x = > new { x . Key , Count = x . Count ( ) } )
2025-09-25 00:22:05 +03:00
. ToArray ( ) ;
2025-08-11 21:03:55 -06:00
var lookup = _itemTypeLookup . BaseItemKindNames ;
var result = new ItemCounts ( ) ;
foreach ( var count in counts )
{
if ( string . Equals ( count . Key , lookup [ BaseItemKind . MusicAlbum ] , StringComparison . Ordinal ) )
{
result . AlbumCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . MusicArtist ] , StringComparison . Ordinal ) )
{
result . ArtistCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . Episode ] , StringComparison . Ordinal ) )
{
result . EpisodeCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . Movie ] , StringComparison . Ordinal ) )
{
result . MovieCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . MusicVideo ] , StringComparison . Ordinal ) )
{
result . MusicVideoCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . LiveTvProgram ] , StringComparison . Ordinal ) )
{
result . ProgramCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . Series ] , StringComparison . Ordinal ) )
{
result . SeriesCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . Audio ] , StringComparison . Ordinal ) )
{
result . SongCount = count . Count ;
}
else if ( string . Equals ( count . Key , lookup [ BaseItemKind . Trailer ] , StringComparison . Ordinal ) )
{
result . TrailerCount = count . Count ;
}
}
return result ;
}
2024-11-13 10:43:11 +00:00
#pragma warning disable CA1307 // Specify StringComparison for clarity
2024-11-14 19:53:59 +00:00
/// <summary>
/// Gets the type.
/// </summary>
/// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns>
/// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
private static Type ? GetType ( string typeName )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
ArgumentException . ThrowIfNullOrEmpty ( typeName ) ;
2024-10-08 12:27:27 +00:00
2025-01-28 05:27:34 -05:00
// TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static variable and be loaded eagerly.
2025-01-15 20:12:41 +00:00
// currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are first called, before or after plugins are loaded
2024-11-14 19:53:59 +00:00
return _typeMap . GetOrAdd ( typeName , k = > AppDomain . CurrentDomain . GetAssemblies ( )
. Select ( a = > a . GetType ( k ) )
. FirstOrDefault ( t = > t is not null ) ) ;
}
2024-10-08 12:27:27 +00:00
2025-01-15 20:12:41 +00:00
/// <inheritdoc />
2024-11-14 19:53:59 +00:00
public void SaveImages ( BaseItemDto item )
{
ArgumentNullException . ThrowIfNull ( item ) ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
var images = item . ImageInfos . Select ( e = > Map ( item . Id , e ) ) ;
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2025-08-02 21:59:11 +02:00
if ( ! context . BaseItems . Any ( bi = > bi . Id = = item . Id ) )
{
_logger . LogWarning ( "Unable to save ImageInfo for non existing BaseItem" ) ;
return ;
}
2024-11-14 19:53:59 +00:00
context . BaseItemImageInfos . Where ( e = > e . ItemId = = item . Id ) . ExecuteDelete ( ) ;
context . BaseItemImageInfos . AddRange ( images ) ;
context . SaveChanges ( ) ;
}
2024-10-08 12:27:27 +00:00
2025-01-15 20:12:41 +00:00
/// <inheritdoc />
2024-11-14 19:53:59 +00:00
public void SaveItems ( IReadOnlyList < BaseItemDto > items , CancellationToken cancellationToken )
{
UpdateOrInsertItems ( items , cancellationToken ) ;
}
2024-10-08 12:27:27 +00:00
2025-01-15 20:34:24 +00:00
/// <inheritdoc cref="IItemRepository"/>
2024-11-14 19:53:59 +00:00
public void UpdateOrInsertItems ( IReadOnlyList < BaseItemDto > items , CancellationToken cancellationToken )
{
ArgumentNullException . ThrowIfNull ( items ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
2024-10-08 12:27:27 +00:00
2024-11-15 18:30:26 +00:00
var tuples = new List < ( BaseItemDto Item , List < Guid > ? AncestorIds , BaseItemDto TopParent , IEnumerable < string > UserDataKey , List < string > InheritedTags ) > ( ) ;
2025-06-15 21:07:19 +03:00
foreach ( var item in items . GroupBy ( e = > e . Id ) . Select ( e = > e . Last ( ) ) . Where ( e = > e . Id ! = PlaceholderId ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
var ancestorIds = item . SupportsAncestors ?
item . GetAncestorIds ( ) . Distinct ( ) . ToList ( ) :
null ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
var topParent = item . GetTopParent ( ) ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
var userdataKey = item . GetUserDataKeys ( ) ;
var inheritedTags = item . GetInheritedTags ( ) ;
2024-10-08 12:27:27 +00:00
2024-11-15 18:30:26 +00:00
tuples . Add ( ( item , ancestorIds , topParent , userdataKey , inheritedTags ) ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-11-14 19:53:59 +00:00
using var transaction = context . Database . BeginTransaction ( ) ;
2025-04-26 18:32:12 +03:00
var ids = tuples . Select ( f = > f . Item . Id ) . ToArray ( ) ;
var existingItems = context . BaseItems . Where ( e = > ids . Contains ( e . Id ) ) . Select ( f = > f . Id ) . ToArray ( ) ;
2025-06-10 02:14:27 +03:00
var newItems = tuples . Where ( e = > ! existingItems . Contains ( e . Item . Id ) ) . ToArray ( ) ;
2025-04-26 18:32:12 +03:00
2024-11-14 19:53:59 +00:00
foreach ( var item in tuples )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
var entity = Map ( item . Item ) ;
// TODO: refactor this "inconsistency"
entity . TopParentId = item . TopParent ? . Id ;
2024-10-08 12:27:27 +00:00
2025-04-26 18:32:12 +03:00
if ( ! existingItems . Any ( e = > e = = entity . Id ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
context . BaseItems . Add ( entity ) ;
2024-10-08 12:27:27 +00:00
}
else
{
2024-11-14 19:53:59 +00:00
context . BaseItemProviders . Where ( e = > e . ItemId = = entity . Id ) . ExecuteDelete ( ) ;
2025-10-27 15:43:09 -04:00
context . BaseItemImageInfos . Where ( e = > e . ItemId = = entity . Id ) . ExecuteDelete ( ) ;
if ( entity . Images is { Count : > 0 } )
{
context . BaseItemImageInfos . AddRange ( entity . Images ) ;
}
2024-11-14 19:53:59 +00:00
context . BaseItems . Attach ( entity ) . State = EntityState . Modified ;
2024-10-08 12:27:27 +00:00
}
2025-04-26 18:32:12 +03:00
}
2024-10-08 12:27:27 +00:00
2025-04-26 18:32:12 +03:00
context . SaveChanges ( ) ;
2025-06-10 02:14:27 +03:00
foreach ( var item in newItems )
{
// reattach old userData entries
var userKeys = item . UserDataKey . ToArray ( ) ;
2025-06-12 02:30:57 +03:00
var retentionDate = ( DateTime ? ) null ;
2025-06-10 02:14:27 +03:00
context . UserData
. Where ( e = > e . ItemId = = PlaceholderId )
. Where ( e = > userKeys . Contains ( e . CustomDataKey ) )
. ExecuteUpdate ( e = > e
. SetProperty ( f = > f . ItemId , item . Item . Id )
. SetProperty ( f = > f . RetentionDate , retentionDate ) ) ;
}
2025-04-26 18:32:12 +03:00
var itemValueMaps = tuples
2025-06-27 01:50:37 +02:00
. Select ( e = > ( e . Item , Values : GetItemValuesToSave ( e . Item , e . InheritedTags ) ) )
2025-04-26 18:32:12 +03:00
. ToArray ( ) ;
var allListedItemValues = itemValueMaps
. SelectMany ( f = > f . Values )
. Distinct ( )
. ToArray ( ) ;
var existingValues = context . ItemValues
. Select ( e = > new
2024-10-08 12:27:27 +00:00
{
2025-04-26 18:32:12 +03:00
item = e ,
Key = e . Type + "+" + e . Value
} )
. Where ( f = > allListedItemValues . Select ( e = > $"{(int)e.MagicNumber}+{e.Value}" ) . Contains ( f . Key ) )
. Select ( e = > e . item )
. ToArray ( ) ;
var missingItemValues = allListedItemValues . Except ( existingValues . Select ( f = > ( MagicNumber : f . Type , f . Value ) ) ) . Select ( f = > new ItemValue ( )
{
CleanValue = GetCleanValue ( f . Value ) ,
ItemValueId = Guid . NewGuid ( ) ,
Type = f . MagicNumber ,
Value = f . Value
} ) . ToArray ( ) ;
context . ItemValues . AddRange ( missingItemValues ) ;
context . SaveChanges ( ) ;
var itemValuesStore = existingValues . Concat ( missingItemValues ) . ToArray ( ) ;
var valueMap = itemValueMaps
2025-07-07 18:14:01 +03:00
. Select ( f = > ( f . Item , Values : f . Values . Select ( e = > itemValuesStore . First ( g = > g . Value = = e . Value & & g . Type = = e . MagicNumber ) ) . DistinctBy ( e = > e . ItemValueId ) . ToArray ( ) ) )
2025-04-26 18:32:12 +03:00
. ToArray ( ) ;
2024-11-14 19:53:59 +00:00
2025-04-26 18:32:12 +03:00
var mappedValues = context . ItemValuesMap . Where ( e = > ids . Contains ( e . ItemId ) ) . ToList ( ) ;
foreach ( var item in valueMap )
{
var itemMappedValues = mappedValues . Where ( e = > e . ItemId = = item . Item . Id ) . ToList ( ) ;
foreach ( var itemValue in item . Values )
{
var existingItem = itemMappedValues . FirstOrDefault ( f = > f . ItemValueId = = itemValue . ItemValueId ) ;
if ( existingItem is null )
{
context . ItemValuesMap . Add ( new ItemValueMap ( )
2024-11-14 19:53:59 +00:00
{
Item = null ! ,
2025-04-26 18:32:12 +03:00
ItemId = item . Item . Id ,
ItemValue = null ! ,
ItemValueId = itemValue . ItemValueId
2024-11-14 19:53:59 +00:00
} ) ;
2024-10-08 12:27:27 +00:00
}
2025-04-26 18:32:12 +03:00
else
2024-11-14 20:36:27 +00:00
{
2025-04-26 18:32:12 +03:00
// map exists, remove from list so its been handled.
itemMappedValues . Remove ( existingItem ) ;
2024-11-14 20:36:27 +00:00
}
2025-04-26 18:32:12 +03:00
}
2024-11-14 20:36:27 +00:00
2025-04-26 18:32:12 +03:00
// all still listed values are not in the new list so remove them.
context . ItemValuesMap . RemoveRange ( itemMappedValues ) ;
}
context . SaveChanges ( ) ;
foreach ( var item in tuples )
{
if ( item . Item . SupportsAncestors & & item . AncestorIds ! = null )
{
var existingAncestorIds = context . AncestorIds . Where ( e = > e . ItemId = = item . Item . Id ) . ToList ( ) ;
var validAncestorIds = context . BaseItems . Where ( e = > item . AncestorIds . Contains ( e . Id ) ) . Select ( f = > f . Id ) . ToArray ( ) ;
foreach ( var ancestorId in validAncestorIds )
2024-10-08 12:27:27 +00:00
{
2025-04-26 18:32:12 +03:00
var existingAncestorId = existingAncestorIds . FirstOrDefault ( e = > e . ParentItemId = = ancestorId ) ;
if ( existingAncestorId is null )
2024-10-08 12:27:27 +00:00
{
2025-04-26 18:32:12 +03:00
context . AncestorIds . Add ( new AncestorId ( )
{
ParentItemId = ancestorId ,
ItemId = item . Item . Id ,
Item = null ! ,
ParentItem = null !
} ) ;
}
else
{
existingAncestorIds . Remove ( existingAncestorId ) ;
}
2024-10-08 12:27:27 +00:00
}
2025-04-26 18:32:12 +03:00
context . AncestorIds . RemoveRange ( existingAncestorIds ) ;
2024-10-08 12:27:27 +00:00
}
}
2024-11-14 19:53:59 +00:00
context . SaveChanges ( ) ;
transaction . Commit ( ) ;
}
2025-01-15 20:12:41 +00:00
/// <inheritdoc />
2024-11-14 19:53:59 +00:00
public BaseItemDto ? RetrieveItem ( Guid id )
{
if ( id . IsEmpty ( ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
throw new ArgumentException ( "Guid can't be empty" , nameof ( id ) ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2025-09-23 00:31:21 +03:00
var dbQuery = PrepareItemQuery ( context , new ( )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
DtoOptions = new ( )
{
EnableImages = true
}
2025-09-23 00:31:21 +03:00
} ) ;
dbQuery = dbQuery . Include ( e = > e . TrailerTypes )
. Include ( e = > e . Provider )
. Include ( e = > e . LockedFields )
. Include ( e = > e . UserData )
. Include ( e = > e . Images ) ;
var item = dbQuery . FirstOrDefault ( e = > e . Id = = id ) ;
2024-11-14 19:53:59 +00:00
if ( item is null )
{
return null ;
2024-10-08 12:27:27 +00:00
}
2025-06-18 21:33:32 +02:00
return DeserializeBaseItem ( item ) ;
2024-11-14 19:53:59 +00:00
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
/// <summary>
/// Maps a Entity to the DTO.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="dto">The dto base instance.</param>
/// <param name="appHost">The Application server Host.</param>
2025-09-16 21:08:04 +02:00
/// <param name="logger">The applogger.</param>
2024-11-14 19:53:59 +00:00
/// <returns>The dto to map.</returns>
2025-09-16 21:08:04 +02:00
public static BaseItemDto Map ( BaseItemEntity entity , BaseItemDto dto , IServerApplicationHost ? appHost , ILogger logger )
2024-11-14 19:53:59 +00:00
{
dto . Id = entity . Id ;
dto . ParentId = entity . ParentId . GetValueOrDefault ( ) ;
dto . Path = appHost ? . ExpandVirtualPath ( entity . Path ) ? ? entity . Path ;
dto . EndDate = entity . EndDate ;
dto . CommunityRating = entity . CommunityRating ;
dto . CustomRating = entity . CustomRating ;
dto . IndexNumber = entity . IndexNumber ;
dto . IsLocked = entity . IsLocked ;
dto . Name = entity . Name ;
dto . OfficialRating = entity . OfficialRating ;
dto . Overview = entity . Overview ;
dto . ParentIndexNumber = entity . ParentIndexNumber ;
dto . PremiereDate = entity . PremiereDate ;
dto . ProductionYear = entity . ProductionYear ;
dto . SortName = entity . SortName ;
dto . ForcedSortName = entity . ForcedSortName ;
dto . RunTimeTicks = entity . RunTimeTicks ;
dto . PreferredMetadataLanguage = entity . PreferredMetadataLanguage ;
dto . PreferredMetadataCountryCode = entity . PreferredMetadataCountryCode ;
dto . IsInMixedFolder = entity . IsInMixedFolder ;
dto . InheritedParentalRatingValue = entity . InheritedParentalRatingValue ;
2025-04-09 03:19:01 +02:00
dto . InheritedParentalRatingSubValue = entity . InheritedParentalRatingSubValue ;
2024-11-14 19:53:59 +00:00
dto . CriticRating = entity . CriticRating ;
dto . PresentationUniqueKey = entity . PresentationUniqueKey ;
dto . OriginalTitle = entity . OriginalTitle ;
dto . Album = entity . Album ;
dto . LUFS = entity . LUFS ;
dto . NormalizationGain = entity . NormalizationGain ;
dto . IsVirtualItem = entity . IsVirtualItem ;
dto . ExternalSeriesId = entity . ExternalSeriesId ;
dto . Tagline = entity . Tagline ;
dto . TotalBitrate = entity . TotalBitrate ;
dto . ExternalId = entity . ExternalId ;
dto . Size = entity . Size ;
2025-06-23 10:30:59 -04:00
dto . Genres = string . IsNullOrWhiteSpace ( entity . Genres ) ? [ ] : entity . Genres . Split ( '|' ) ;
2025-06-27 01:50:37 +02:00
dto . DateCreated = entity . DateCreated ? ? DateTime . SpecifyKind ( DateTime . MinValue , DateTimeKind . Utc ) ;
dto . DateModified = entity . DateModified ? ? DateTime . SpecifyKind ( DateTime . MinValue , DateTimeKind . Utc ) ;
2025-02-13 20:17:25 -07:00
dto . ChannelId = entity . ChannelId ? ? Guid . Empty ;
2025-06-27 01:50:37 +02:00
dto . DateLastRefreshed = entity . DateLastRefreshed ? ? DateTime . SpecifyKind ( DateTime . MinValue , DateTimeKind . Utc ) ;
dto . DateLastSaved = entity . DateLastSaved ? ? DateTime . SpecifyKind ( DateTime . MinValue , DateTimeKind . Utc ) ;
2024-11-14 19:53:59 +00:00
dto . OwnerId = string . IsNullOrWhiteSpace ( entity . OwnerId ) ? Guid . Empty : ( Guid . TryParse ( entity . OwnerId , out var ownerId ) ? ownerId : Guid . Empty ) ;
dto . Width = entity . Width . GetValueOrDefault ( ) ;
dto . Height = entity . Height . GetValueOrDefault ( ) ;
2025-09-16 21:08:04 +02:00
dto . UserData = entity . UserData ;
2024-11-14 19:53:59 +00:00
if ( entity . Provider is not null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
dto . ProviderIds = entity . Provider . ToDictionary ( e = > e . ProviderId , e = > e . ProviderValue ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( entity . ExtraType is not null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
dto . ExtraType = ( ExtraType ) entity . ExtraType ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( entity . LockedFields is not null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
dto . LockedFields = entity . LockedFields ? . Select ( e = > ( MetadataField ) e . Id ) . ToArray ( ) ? ? [ ] ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( entity . Audio is not null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
dto . Audio = ( ProgramAudio ) entity . Audio ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
dto . ExtraIds = string . IsNullOrWhiteSpace ( entity . ExtraIds ) ? [ ] : entity . ExtraIds . Split ( '|' ) . Select ( e = > Guid . Parse ( e ) ) . ToArray ( ) ;
dto . ProductionLocations = entity . ProductionLocations ? . Split ( '|' ) ? ? [ ] ;
dto . Studios = entity . Studios ? . Split ( '|' ) ? ? [ ] ;
2025-06-23 10:30:59 -04:00
dto . Tags = string . IsNullOrWhiteSpace ( entity . Tags ) ? [ ] : entity . Tags . Split ( '|' ) ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
if ( dto is IHasProgramAttributes hasProgramAttributes )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
hasProgramAttributes . IsMovie = entity . IsMovie ;
hasProgramAttributes . IsSeries = entity . IsSeries ;
hasProgramAttributes . EpisodeTitle = entity . EpisodeTitle ;
hasProgramAttributes . IsRepeat = entity . IsRepeat ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is LiveTvChannel liveTvChannel )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
liveTvChannel . ServiceName = entity . ExternalServiceId ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is Trailer trailer )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
trailer . TrailerTypes = entity . TrailerTypes ? . Select ( e = > ( TrailerType ) e . Id ) . ToArray ( ) ? ? [ ] ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is Video video )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
video . PrimaryVersionId = entity . PrimaryVersionId ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is IHasSeries hasSeriesName )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
hasSeriesName . SeriesName = entity . SeriesName ;
hasSeriesName . SeriesId = entity . SeriesId . GetValueOrDefault ( ) ;
hasSeriesName . SeriesPresentationUniqueKey = entity . SeriesPresentationUniqueKey ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is Episode episode )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
episode . SeasonName = entity . SeasonName ;
episode . SeasonId = entity . SeasonId . GetValueOrDefault ( ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is IHasArtist hasArtists )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
hasArtists . Artists = entity . Artists ? . Split ( '|' , StringSplitOptions . RemoveEmptyEntries ) ? ? [ ] ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is IHasAlbumArtist hasAlbumArtists )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
hasAlbumArtists . AlbumArtists = entity . AlbumArtists ? . Split ( '|' , StringSplitOptions . RemoveEmptyEntries ) ? ? [ ] ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is LiveTvProgram program )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
program . ShowId = entity . ShowId ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( entity . Images is not null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
dto . ImageInfos = entity . Images . Select ( e = > Map ( e , appHost ) ) . ToArray ( ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
// dto.Type = entity.Type;
// dto.Data = entity.Data;
// dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
if ( dto is IHasStartDate hasStartDate )
2024-10-08 12:27:27 +00:00
{
2025-02-05 08:02:07 +08:00
hasStartDate . StartDate = entity . StartDate . GetValueOrDefault ( ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
// Fields that are present in the DB but are never actually used
// dto.UnratedType = entity.UnratedType;
// dto.TopParentId = entity.TopParentId;
// dto.CleanName = entity.CleanName;
// dto.UserDataKey = entity.UserDataKey;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
if ( dto is Folder folder )
2024-10-08 12:27:27 +00:00
{
2025-06-27 01:50:37 +02:00
folder . DateLastMediaAdded = entity . DateLastMediaAdded ? ? DateTime . SpecifyKind ( DateTime . MinValue , DateTimeKind . Utc ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return dto ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
/// <summary>
/// Maps a Entity to the DTO.
/// </summary>
/// <param name="dto">The entity.</param>
/// <returns>The dto to map.</returns>
public BaseItemEntity Map ( BaseItemDto dto )
{
var dtoType = dto . GetType ( ) ;
var entity = new BaseItemEntity ( )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
Type = dtoType . ToString ( ) ,
Id = dto . Id
} ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
if ( TypeRequiresDeserialization ( dtoType ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . Data = JsonSerializer . Serialize ( dto , dtoType , JsonDefaults . Options ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
entity . ParentId = ! dto . ParentId . IsEmpty ( ) ? dto . ParentId : null ;
entity . Path = GetPathToSave ( dto . Path ) ;
2025-02-05 08:02:07 +08:00
entity . EndDate = dto . EndDate ;
2024-11-14 19:53:59 +00:00
entity . CommunityRating = dto . CommunityRating ;
entity . CustomRating = dto . CustomRating ;
entity . IndexNumber = dto . IndexNumber ;
entity . IsLocked = dto . IsLocked ;
entity . Name = dto . Name ;
2025-02-14 11:19:24 +08:00
entity . CleanName = GetCleanValue ( dto . Name ) ;
2024-11-14 19:53:59 +00:00
entity . OfficialRating = dto . OfficialRating ;
entity . Overview = dto . Overview ;
entity . ParentIndexNumber = dto . ParentIndexNumber ;
entity . PremiereDate = dto . PremiereDate ;
entity . ProductionYear = dto . ProductionYear ;
entity . SortName = dto . SortName ;
entity . ForcedSortName = dto . ForcedSortName ;
entity . RunTimeTicks = dto . RunTimeTicks ;
entity . PreferredMetadataLanguage = dto . PreferredMetadataLanguage ;
entity . PreferredMetadataCountryCode = dto . PreferredMetadataCountryCode ;
entity . IsInMixedFolder = dto . IsInMixedFolder ;
entity . InheritedParentalRatingValue = dto . InheritedParentalRatingValue ;
2025-03-31 05:51:54 +02:00
entity . InheritedParentalRatingSubValue = dto . InheritedParentalRatingSubValue ;
2024-11-14 19:53:59 +00:00
entity . CriticRating = dto . CriticRating ;
entity . PresentationUniqueKey = dto . PresentationUniqueKey ;
entity . OriginalTitle = dto . OriginalTitle ;
entity . Album = dto . Album ;
entity . LUFS = dto . LUFS ;
entity . NormalizationGain = dto . NormalizationGain ;
entity . IsVirtualItem = dto . IsVirtualItem ;
entity . ExternalSeriesId = dto . ExternalSeriesId ;
entity . Tagline = dto . Tagline ;
entity . TotalBitrate = dto . TotalBitrate ;
entity . ExternalId = dto . ExternalId ;
entity . Size = dto . Size ;
entity . Genres = string . Join ( '|' , dto . Genres ) ;
2025-06-27 01:50:37 +02:00
entity . DateCreated = dto . DateCreated = = DateTime . MinValue ? null : dto . DateCreated ;
entity . DateModified = dto . DateModified = = DateTime . MinValue ? null : dto . DateModified ;
2025-02-13 20:17:25 -07:00
entity . ChannelId = dto . ChannelId ;
2025-06-27 01:50:37 +02:00
entity . DateLastRefreshed = dto . DateLastRefreshed = = DateTime . MinValue ? null : dto . DateLastRefreshed ;
entity . DateLastSaved = dto . DateLastSaved = = DateTime . MinValue ? null : dto . DateLastSaved ;
2024-11-14 19:53:59 +00:00
entity . OwnerId = dto . OwnerId . ToString ( ) ;
entity . Width = dto . Width ;
entity . Height = dto . Height ;
entity . Provider = dto . ProviderIds . Select ( e = > new BaseItemProvider ( )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
Item = entity ,
ProviderId = e . Key ,
ProviderValue = e . Value
} ) . ToList ( ) ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
if ( dto . Audio . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . Audio = ( ProgramAudioEntity ) dto . Audio ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto . ExtraType . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . ExtraType = ( BaseItemExtraType ) dto . ExtraType ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
entity . ExtraIds = dto . ExtraIds is not null ? string . Join ( '|' , dto . ExtraIds ) : null ;
entity . ProductionLocations = dto . ProductionLocations is not null ? string . Join ( '|' , dto . ProductionLocations ) : null ;
entity . Studios = dto . Studios is not null ? string . Join ( '|' , dto . Studios ) : null ;
entity . Tags = dto . Tags is not null ? string . Join ( '|' , dto . Tags ) : null ;
entity . LockedFields = dto . LockedFields is not null ? dto . LockedFields
. Select ( e = > new BaseItemMetadataField ( )
{
Id = ( int ) e ,
Item = entity ,
ItemId = entity . Id
} )
. ToArray ( ) : null ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
if ( dto is IHasProgramAttributes hasProgramAttributes )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . IsMovie = hasProgramAttributes . IsMovie ;
entity . IsSeries = hasProgramAttributes . IsSeries ;
entity . EpisodeTitle = hasProgramAttributes . EpisodeTitle ;
entity . IsRepeat = hasProgramAttributes . IsRepeat ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is LiveTvChannel liveTvChannel )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . ExternalServiceId = liveTvChannel . ServiceName ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is Video video )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . PrimaryVersionId = video . PrimaryVersionId ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is IHasSeries hasSeriesName )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . SeriesName = hasSeriesName . SeriesName ;
entity . SeriesId = hasSeriesName . SeriesId ;
entity . SeriesPresentationUniqueKey = hasSeriesName . SeriesPresentationUniqueKey ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is Episode episode )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . SeasonName = episode . SeasonName ;
entity . SeasonId = episode . SeasonId ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is IHasArtist hasArtists )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . Artists = hasArtists . Artists is not null ? string . Join ( '|' , hasArtists . Artists ) : null ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is IHasAlbumArtist hasAlbumArtists )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . AlbumArtists = hasAlbumArtists . AlbumArtists is not null ? string . Join ( '|' , hasAlbumArtists . AlbumArtists ) : null ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is LiveTvProgram program )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . ShowId = program . ShowId ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto . ImageInfos is not null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . Images = dto . ImageInfos . Select ( f = > Map ( dto . Id , f ) ) . ToArray ( ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is Trailer trailer )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . TrailerTypes = trailer . TrailerTypes ? . Select ( e = > new BaseItemTrailerType ( )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
Id = ( int ) e ,
Item = entity ,
ItemId = entity . Id
} ) . ToArray ( ) ? ? [ ] ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
// dto.Type = entity.Type;
// dto.Data = entity.Data;
entity . MediaType = dto . MediaType . ToString ( ) ;
if ( dto is IHasStartDate hasStartDate )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
entity . StartDate = hasStartDate . StartDate ;
2024-10-08 12:27:27 +00:00
}
2025-02-14 11:19:24 +08:00
entity . UnratedType = dto . GetBlockUnratedType ( ) . ToString ( ) ;
2024-11-14 19:53:59 +00:00
// Fields that are present in the DB but are never actually used
// dto.UserDataKey = entity.UserDataKey;
if ( dto is Folder folder )
2024-10-08 12:27:27 +00:00
{
2025-06-27 01:50:37 +02:00
entity . DateLastMediaAdded = folder . DateLastMediaAdded = = DateTime . MinValue ? null : folder . DateLastMediaAdded ;
2024-11-14 19:53:59 +00:00
entity . IsFolder = folder . IsFolder ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return entity ;
}
2024-11-24 10:58:09 +00:00
private string [ ] GetItemValueNames ( IReadOnlyList < ItemValueType > itemValueTypes , IReadOnlyList < string > withItemTypes , IReadOnlyList < string > excludeItemTypes )
2024-11-14 19:53:59 +00:00
{
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-11-14 19:53:59 +00:00
var query = context . ItemValuesMap
. AsNoTracking ( )
. Where ( e = > itemValueTypes . Any ( w = > ( ItemValueType ) w = = e . ItemValue . Type ) ) ;
if ( withItemTypes . Count > 0 )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
query = query . Where ( e = > withItemTypes . Contains ( e . Item . Type ) ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( excludeItemTypes . Count > 0 )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
query = query . Where ( e = > ! excludeItemTypes . Contains ( e . Item . Type ) ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
// query = query.DistinctBy(e => e.CleanValue);
2025-02-11 11:45:53 +08:00
return query . Select ( e = > e . ItemValue )
. GroupBy ( e = > e . CleanValue )
. Select ( e = > e . First ( ) . Value )
. ToArray ( ) ;
2024-11-14 19:53:59 +00:00
}
private static bool TypeRequiresDeserialization ( Type type )
{
return type . GetCustomAttribute < RequiresSourceSerialisationAttribute > ( ) = = null ;
}
2025-06-18 21:33:32 +02:00
private BaseItemDto DeserializeBaseItem ( BaseItemEntity baseItemEntity , bool skipDeserialization = false )
2024-11-14 19:53:59 +00:00
{
ArgumentNullException . ThrowIfNull ( baseItemEntity , nameof ( baseItemEntity ) ) ;
2024-11-24 10:58:09 +00:00
if ( _serverConfigurationManager ? . Configuration is null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
throw new InvalidOperationException ( "Server Configuration manager or configuration is null" ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
var typeToSerialise = GetType ( baseItemEntity . Type ) ;
2025-06-18 21:33:32 +02:00
return BaseItemRepository . DeserializeBaseItem (
2024-11-14 19:53:59 +00:00
baseItemEntity ,
2024-11-24 10:58:09 +00:00
_logger ,
_appHost ,
skipDeserialization | | ( _serverConfigurationManager . Configuration . SkipDeserializationForBasicTypes & & ( typeToSerialise = = typeof ( Channel ) | | typeToSerialise = = typeof ( UserRootFolder ) ) ) ) ;
2024-11-14 19:53:59 +00:00
}
/// <summary>
2025-06-18 21:33:32 +02:00
/// Deserializes a BaseItemEntity and sets all properties.
2024-11-14 19:53:59 +00:00
/// </summary>
/// <param name="baseItemEntity">The DB entity.</param>
/// <param name="logger">Logger.</param>
/// <param name="appHost">The application server Host.</param>
/// <param name="skipDeserialization">If only mapping should be processed.</param>
/// <returns>A mapped BaseItem.</returns>
/// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
2025-06-18 21:33:32 +02:00
public static BaseItemDto DeserializeBaseItem ( BaseItemEntity baseItemEntity , ILogger logger , IServerApplicationHost ? appHost , bool skipDeserialization = false )
2024-11-14 19:53:59 +00:00
{
2025-06-19 11:40:40 +02:00
var type = GetType ( baseItemEntity . Type ) ? ? throw new InvalidOperationException ( "Cannot deserialize unknown type." ) ;
2024-11-14 19:53:59 +00:00
BaseItemDto ? dto = null ;
if ( TypeRequiresDeserialization ( type ) & & baseItemEntity . Data is not null & & ! skipDeserialization )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
try
{
2025-01-15 20:12:41 +00:00
dto = JsonSerializer . Deserialize ( baseItemEntity . Data , type , JsonDefaults . Options ) as BaseItemDto ;
2024-11-14 19:53:59 +00:00
}
catch ( JsonException ex )
{
logger . LogError ( ex , "Error deserializing item with JSON: {Data}" , baseItemEntity . Data ) ;
}
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( dto is null )
2024-10-08 12:27:27 +00:00
{
2025-06-18 23:08:05 +02:00
dto = Activator . CreateInstance ( type ) as BaseItemDto ? ? throw new InvalidOperationException ( "Cannot deserialize unknown type." ) ;
2024-10-08 12:27:27 +00:00
}
2025-09-16 21:08:04 +02:00
return Map ( baseItemEntity , dto , appHost , logger ) ;
2024-11-14 19:53:59 +00:00
}
2025-04-27 04:10:54 +03:00
private QueryResult < ( BaseItemDto Item , ItemCounts ? ItemCounts ) > GetItemValues ( InternalItemsQuery filter , IReadOnlyList < ItemValueType > itemValueTypes , string returnType )
2024-11-14 19:53:59 +00:00
{
ArgumentNullException . ThrowIfNull ( filter ) ;
if ( ! filter . Limit . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
filter . EnableTotalRecordCount = false ;
2024-10-08 12:27:27 +00:00
}
2024-11-24 10:58:09 +00:00
using var context = _dbProvider . CreateDbContext ( ) ;
2024-11-14 19:53:59 +00:00
2025-06-15 21:07:19 +03:00
var innerQueryFilter = TranslateQuery ( context . BaseItems . Where ( e = > e . Id ! = EF . Constant ( PlaceholderId ) ) , context , new InternalItemsQuery ( filter . User )
2025-04-27 04:10:54 +03:00
{
ExcludeItemTypes = filter . ExcludeItemTypes ,
IncludeItemTypes = filter . IncludeItemTypes ,
MediaTypes = filter . MediaTypes ,
AncestorIds = filter . AncestorIds ,
ItemIds = filter . ItemIds ,
TopParentIds = filter . TopParentIds ,
ParentId = filter . ParentId ,
IsAiring = filter . IsAiring ,
IsMovie = filter . IsMovie ,
IsSports = filter . IsSports ,
IsKids = filter . IsKids ,
IsNews = filter . IsNews ,
IsSeries = filter . IsSeries
} ) ;
2024-10-08 12:27:27 +00:00
2025-08-12 03:05:13 +08:00
var itemValuesQuery = context . ItemValues
. Where ( f = > itemValueTypes . Contains ( f . Type ) )
. SelectMany ( f = > f . BaseItemsMap ! , ( f , w ) = > new { f , w } )
. Join (
innerQueryFilter ,
fw = > fw . w . ItemId ,
g = > g . Id ,
( fw , g ) = > fw . f . CleanValue ) ;
2025-04-27 04:10:54 +03:00
var innerQuery = PrepareItemQuery ( context , filter )
. Where ( e = > e . Type = = returnType )
2025-08-12 03:05:13 +08:00
. Where ( e = > itemValuesQuery . Contains ( e . CleanName ) ) ;
2025-04-27 04:10:54 +03:00
var outerQueryFilter = new InternalItemsQuery ( filter . User )
{
IsPlayed = filter . IsPlayed ,
IsFavorite = filter . IsFavorite ,
IsFavoriteOrLiked = filter . IsFavoriteOrLiked ,
IsLiked = filter . IsLiked ,
IsLocked = filter . IsLocked ,
NameLessThan = filter . NameLessThan ,
NameStartsWith = filter . NameStartsWith ,
NameStartsWithOrGreater = filter . NameStartsWithOrGreater ,
Tags = filter . Tags ,
OfficialRatings = filter . OfficialRatings ,
StudioIds = filter . StudioIds ,
GenreIds = filter . GenreIds ,
Genres = filter . Genres ,
Years = filter . Years ,
NameContains = filter . NameContains ,
SearchTerm = filter . SearchTerm ,
ExcludeItemIds = filter . ExcludeItemIds
} ;
2024-11-14 19:53:59 +00:00
2025-10-10 09:10:21 +00:00
var masterQuery = TranslateQuery ( innerQuery , context , outerQueryFilter )
. GroupBy ( e = > e . PresentationUniqueKey )
. Select ( e = > e . FirstOrDefault ( ) )
. Select ( e = > e ! . Id ) ;
var query = context . BaseItems
. Include ( e = > e . TrailerTypes )
. Include ( e = > e . Provider )
. Include ( e = > e . LockedFields )
. Include ( e = > e . Images )
. AsSingleQuery ( )
. Where ( e = > masterQuery . Contains ( e . Id ) ) ;
2025-10-10 20:08:59 +00:00
query = ApplyOrder ( query , filter ) ;
2025-04-27 04:10:54 +03:00
var result = new QueryResult < ( BaseItemDto , ItemCounts ? ) > ( ) ;
if ( filter . EnableTotalRecordCount )
2024-10-08 12:27:27 +00:00
{
2025-04-27 04:10:54 +03:00
result . TotalRecordCount = query . Count ( ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . Limit . HasValue | | filter . StartIndex . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
var offset = filter . StartIndex ? ? 0 ;
if ( offset > 0 )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
query = query . Skip ( offset ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . Limit . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
query = query . Take ( filter . Limit . Value ) ;
2024-10-08 12:27:27 +00:00
}
}
2024-11-14 19:53:59 +00:00
2025-04-27 04:10:54 +03:00
IQueryable < BaseItemEntity > ? itemCountQuery = null ;
2024-10-08 12:27:27 +00:00
2025-04-27 04:10:54 +03:00
if ( filter . IncludeItemTypes . Length > 0 )
2024-10-08 12:27:27 +00:00
{
2025-04-27 04:10:54 +03:00
// if we are to include more then one type, sub query those items beforehand.
var typeSubQuery = new InternalItemsQuery ( filter . User )
2024-10-08 12:27:27 +00:00
{
2025-04-27 04:10:54 +03:00
ExcludeItemTypes = filter . ExcludeItemTypes ,
IncludeItemTypes = filter . IncludeItemTypes ,
MediaTypes = filter . MediaTypes ,
AncestorIds = filter . AncestorIds ,
ExcludeItemIds = filter . ExcludeItemIds ,
ItemIds = filter . ItemIds ,
TopParentIds = filter . TopParentIds ,
ParentId = filter . ParentId ,
IsPlayed = filter . IsPlayed
} ;
2024-10-08 12:27:27 +00:00
2025-06-15 21:07:19 +03:00
itemCountQuery = TranslateQuery ( context . BaseItems . AsNoTracking ( ) . Where ( e = > e . Id ! = EF . Constant ( PlaceholderId ) ) , context , typeSubQuery )
2025-04-27 04:10:54 +03:00
. Where ( e = > e . ItemValues ! . Any ( f = > itemValueTypes ! . Contains ( f . ItemValue . Type ) ) ) ;
var seriesTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Series ] ;
var movieTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Movie ] ;
var episodeTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Episode ] ;
var musicAlbumTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicAlbum ] ;
var musicArtistTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicArtist ] ;
var audioTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Audio ] ;
var trailerTypeName = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Trailer ] ;
var resultQuery = query . Select ( e = > new
{
2025-10-10 09:10:21 +00:00
item = e ,
2025-04-27 04:10:54 +03:00
// TODO: This is bad refactor!
itemCount = new ItemCounts ( )
{
SeriesCount = itemCountQuery ! . Count ( f = > f . Type = = seriesTypeName ) ,
EpisodeCount = itemCountQuery ! . Count ( f = > f . Type = = episodeTypeName ) ,
MovieCount = itemCountQuery ! . Count ( f = > f . Type = = movieTypeName ) ,
AlbumCount = itemCountQuery ! . Count ( f = > f . Type = = musicAlbumTypeName ) ,
ArtistCount = itemCountQuery ! . Count ( f = > f . Type = = musicArtistTypeName ) ,
SongCount = itemCountQuery ! . Count ( f = > f . Type = = audioTypeName ) ,
TrailerCount = itemCountQuery ! . Count ( f = > f . Type = = trailerTypeName ) ,
}
} ) ;
result . StartIndex = filter . StartIndex ? ? 0 ;
result . Items =
[
. . resultQuery
. AsEnumerable ( )
. Where ( e = > e is not null )
. Select ( e = >
{
2025-06-18 21:33:32 +02:00
return ( DeserializeBaseItem ( e . item , filter . SkipDeserialization ) , e . itemCount ) ;
2025-04-27 04:10:54 +03:00
} )
] ;
}
else
2024-10-08 12:27:27 +00:00
{
2025-04-27 04:10:54 +03:00
result . StartIndex = filter . StartIndex ? ? 0 ;
result . Items =
[
. . query
. AsEnumerable ( )
. Where ( e = > e is not null )
. Select < BaseItemEntity , ( BaseItemDto , ItemCounts ? ) > ( e = >
{
2025-06-18 21:33:32 +02:00
return ( DeserializeBaseItem ( e , filter . SkipDeserialization ) , null ) ;
2025-04-27 04:10:54 +03:00
} )
] ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
return result ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
private static void PrepareFilterQuery ( InternalItemsQuery query )
{
if ( query . Limit . HasValue & & query . EnableGroupByMetadataKey )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
query . Limit = query . Limit . Value + 4 ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( query . IsResumable ? ? false )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
query . IsVirtualItem = false ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
private string GetCleanValue ( string value )
{
if ( string . IsNullOrWhiteSpace ( value ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return value ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return value . RemoveDiacritics ( ) . ToLowerInvariant ( ) ;
}
2025-04-26 18:32:12 +03:00
private List < ( ItemValueType MagicNumber , string Value ) > GetItemValuesToSave ( BaseItemDto item , List < string > inheritedTags )
2024-11-14 19:53:59 +00:00
{
2025-04-26 18:32:12 +03:00
var list = new List < ( ItemValueType , string ) > ( ) ;
2024-11-14 19:53:59 +00:00
if ( item is IHasArtist hasArtist )
2024-10-08 12:27:27 +00:00
{
2025-04-26 18:32:12 +03:00
list . AddRange ( hasArtist . Artists . Select ( i = > ( ( ItemValueType ) 0 , i ) ) ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( item is IHasAlbumArtist hasAlbumArtist )
2024-10-08 12:27:27 +00:00
{
2025-04-26 18:32:12 +03:00
list . AddRange ( hasAlbumArtist . AlbumArtists . Select ( i = > ( ItemValueType . AlbumArtist , i ) ) ) ;
2024-10-08 12:27:27 +00:00
}
2025-04-26 18:32:12 +03:00
list . AddRange ( item . Genres . Select ( i = > ( ItemValueType . Genre , i ) ) ) ;
list . AddRange ( item . Studios . Select ( i = > ( ItemValueType . Studios , i ) ) ) ;
list . AddRange ( item . Tags . Select ( i = > ( ItemValueType . Tags , i ) ) ) ;
2024-11-14 19:53:59 +00:00
// keywords was 5
2025-04-26 18:32:12 +03:00
list . AddRange ( inheritedTags . Select ( i = > ( ItemValueType . InheritedTags , i ) ) ) ;
2024-11-14 19:53:59 +00:00
// Remove all invalid values.
list . RemoveAll ( i = > string . IsNullOrWhiteSpace ( i . Item2 ) ) ;
return list ;
}
private static BaseItemImageInfo Map ( Guid baseItemId , ItemImageInfo e )
{
return new BaseItemImageInfo ( )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
ItemId = baseItemId ,
Id = Guid . NewGuid ( ) ,
Path = e . Path ,
2024-12-15 14:46:40 +00:00
Blurhash = e . BlurHash is null ? null : Encoding . UTF8 . GetBytes ( e . BlurHash ) ,
2024-11-14 19:53:59 +00:00
DateModified = e . DateModified ,
Height = e . Height ,
Width = e . Width ,
ImageType = ( ImageInfoImageType ) e . Type ,
Item = null !
} ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
private static ItemImageInfo Map ( BaseItemImageInfo e , IServerApplicationHost ? appHost )
{
return new ItemImageInfo ( )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
Path = appHost ? . ExpandVirtualPath ( e . Path ) ? ? e . Path ,
2024-12-15 14:46:40 +00:00
BlurHash = e . Blurhash is null ? null : Encoding . UTF8 . GetString ( e . Blurhash ) ,
2025-06-27 01:50:37 +02:00
DateModified = e . DateModified ? ? DateTime . SpecifyKind ( DateTime . MinValue , DateTimeKind . Utc ) ,
2024-11-14 19:53:59 +00:00
Height = e . Height ,
Width = e . Width ,
Type = ( ImageType ) e . ImageType
} ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
private string? GetPathToSave ( string path )
{
if ( path is null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return null ;
2024-10-08 12:27:27 +00:00
}
2024-11-24 10:58:09 +00:00
return _appHost . ReverseVirtualPath ( path ) ;
2024-11-14 19:53:59 +00:00
}
private List < string > GetItemByNameTypesInQuery ( InternalItemsQuery query )
{
var list = new List < string > ( ) ;
if ( IsTypeInQuery ( BaseItemKind . Person , query ) )
2024-10-08 12:27:27 +00:00
{
2024-11-24 10:58:09 +00:00
list . Add ( _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Person ] ! ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( IsTypeInQuery ( BaseItemKind . Genre , query ) )
2024-10-08 12:27:27 +00:00
{
2024-11-24 10:58:09 +00:00
list . Add ( _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Genre ] ! ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( IsTypeInQuery ( BaseItemKind . MusicGenre , query ) )
2024-10-08 12:27:27 +00:00
{
2024-11-24 10:58:09 +00:00
list . Add ( _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicGenre ] ! ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( IsTypeInQuery ( BaseItemKind . MusicArtist , query ) )
2024-10-08 12:27:27 +00:00
{
2024-11-24 10:58:09 +00:00
list . Add ( _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicArtist ] ! ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( IsTypeInQuery ( BaseItemKind . Studio , query ) )
2024-10-08 12:27:27 +00:00
{
2024-11-24 10:58:09 +00:00
list . Add ( _itemTypeLookup . BaseItemKindNames [ BaseItemKind . Studio ] ! ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return list ;
}
private bool IsTypeInQuery ( BaseItemKind type , InternalItemsQuery query )
{
if ( query . ExcludeItemTypes . Contains ( type ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return false ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return query . IncludeItemTypes . Length = = 0 | | query . IncludeItemTypes . Contains ( type ) ;
}
private bool EnableGroupByPresentationUniqueKey ( InternalItemsQuery query )
{
if ( ! query . GroupByPresentationUniqueKey )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return false ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( query . GroupBySeriesPresentationUniqueKey )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return false ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( query . PresentationUniqueKey ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return false ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( query . User is null )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return false ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( query . IncludeItemTypes . Length = = 0 )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
return true ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return query . IncludeItemTypes . Contains ( BaseItemKind . Episode )
| | query . IncludeItemTypes . Contains ( BaseItemKind . Video )
| | query . IncludeItemTypes . Contains ( BaseItemKind . Movie )
| | query . IncludeItemTypes . Contains ( BaseItemKind . MusicVideo )
| | query . IncludeItemTypes . Contains ( BaseItemKind . Series )
| | query . IncludeItemTypes . Contains ( BaseItemKind . Season ) ;
}
private IQueryable < BaseItemEntity > ApplyOrder ( IQueryable < BaseItemEntity > query , InternalItemsQuery filter )
{
var orderBy = filter . OrderBy ;
2024-11-24 10:58:09 +00:00
var hasSearch = ! string . IsNullOrEmpty ( filter . SearchTerm ) ;
2024-11-14 19:53:59 +00:00
if ( hasSearch )
2024-10-08 12:27:27 +00:00
{
2025-01-19 12:29:14 +00:00
orderBy = filter . OrderBy = [ ( ItemSortBy . SortName , SortOrder . Ascending ) , . . orderBy ] ;
2024-11-14 19:53:59 +00:00
}
else if ( orderBy . Count = = 0 )
2024-10-08 12:27:27 +00:00
{
2025-04-27 04:10:54 +03:00
return query . OrderBy ( e = > e . SortName ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
IOrderedQueryable < BaseItemEntity > ? orderedQuery = null ;
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
var firstOrdering = orderBy . FirstOrDefault ( ) ;
if ( firstOrdering ! = default )
2024-10-08 12:27:27 +00:00
{
2025-03-27 20:05:03 -04:00
var expression = OrderMapper . MapOrderByField ( firstOrdering . OrderBy , filter ) ;
2024-11-14 19:53:59 +00:00
if ( firstOrdering . SortOrder = = SortOrder . Ascending )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
orderedQuery = query . OrderBy ( expression ) ;
2024-10-08 12:27:27 +00:00
}
2024-10-08 15:16:03 +00:00
else
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
orderedQuery = query . OrderByDescending ( expression ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( firstOrdering . OrderBy is ItemSortBy . Default or ItemSortBy . SortName )
{
if ( firstOrdering . SortOrder is SortOrder . Ascending )
{
orderedQuery = orderedQuery . ThenBy ( e = > e . Name ) ;
}
else
{
orderedQuery = orderedQuery . ThenByDescending ( e = > e . Name ) ;
}
}
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
foreach ( var item in orderBy . Skip ( 1 ) )
2024-10-08 12:27:27 +00:00
{
2025-03-27 20:05:03 -04:00
var expression = OrderMapper . MapOrderByField ( item . OrderBy , filter ) ;
2024-11-14 19:53:59 +00:00
if ( item . SortOrder = = SortOrder . Ascending )
{
orderedQuery = orderedQuery ! . ThenBy ( expression ) ;
}
else
{
orderedQuery = orderedQuery ! . ThenByDescending ( expression ) ;
}
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
return orderedQuery ? ? query ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
private IQueryable < BaseItemEntity > TranslateQuery (
IQueryable < BaseItemEntity > baseQuery ,
JellyfinDbContext context ,
InternalItemsQuery filter )
{
2025-04-19 12:45:19 -04:00
const int HDWidth = 1200 ;
const int UHDWidth = 3800 ;
const int UHDHeight = 2100 ;
2024-11-14 19:53:59 +00:00
var minWidth = filter . MinWidth ;
var maxWidth = filter . MaxWidth ;
var now = DateTime . UtcNow ;
2024-10-08 12:27:27 +00:00
2025-04-19 12:45:19 -04:00
if ( filter . IsHD . HasValue | | filter . Is4K . HasValue )
2024-10-08 12:27:27 +00:00
{
2025-04-19 12:45:19 -04:00
bool includeSD = false ;
bool includeHD = false ;
bool include4K = false ;
if ( filter . IsHD . HasValue & & ! filter . IsHD . Value )
2024-11-14 19:53:59 +00:00
{
2025-04-19 12:45:19 -04:00
includeSD = true ;
2024-10-08 15:16:03 +00:00
}
2024-10-08 12:27:27 +00:00
2025-04-19 12:45:19 -04:00
if ( filter . IsHD . HasValue & & filter . IsHD . Value )
2024-10-08 15:16:03 +00:00
{
2025-04-19 12:45:19 -04:00
includeHD = true ;
2024-10-08 12:27:27 +00:00
}
2025-04-19 12:45:19 -04:00
if ( filter . Is4K . HasValue & & filter . Is4K . Value )
2024-10-08 12:27:27 +00:00
{
2025-04-19 12:45:19 -04:00
include4K = true ;
2024-10-08 12:27:27 +00:00
}
2025-04-19 12:45:19 -04:00
baseQuery = baseQuery . Where ( e = >
( includeSD & & e . Width < HDWidth ) | |
( includeHD & & e . Width > = HDWidth & & ! ( e . Width > = UHDWidth | | e . Height > = UHDHeight ) ) | |
( include4K & & ( e . Width > = UHDWidth | | e . Height > = UHDHeight ) ) ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( minWidth . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Width > = minWidth ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinHeight . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Height > = filter . MinHeight ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( maxWidth . HasValue )
2024-10-08 12:27:27 +00:00
{
2025-05-19 00:35:12 +00:00
baseQuery = baseQuery . Where ( e = > e . Width < = maxWidth ) ;
2024-10-08 12:27:27 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MaxHeight . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Height < = filter . MaxHeight ) ;
}
if ( filter . IsLocked . HasValue )
{
baseQuery = baseQuery . Where ( e = > e . IsLocked = = filter . IsLocked ) ;
}
var tags = filter . Tags . ToList ( ) ;
var excludeTags = filter . ExcludeTags . ToList ( ) ;
if ( filter . IsMovie = = true )
{
if ( filter . IncludeItemTypes . Length = = 0
| | filter . IncludeItemTypes . Contains ( BaseItemKind . Movie )
| | filter . IncludeItemTypes . Contains ( BaseItemKind . Trailer ) )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . IsMovie ) ;
2024-10-08 12:27:27 +00:00
}
}
2024-11-14 19:53:59 +00:00
else if ( filter . IsMovie . HasValue )
{
baseQuery = baseQuery . Where ( e = > e . IsMovie = = filter . IsMovie ) ;
}
2024-10-08 12:27:27 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . IsSeries . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . IsSeries = = filter . IsSeries ) ;
}
if ( filter . IsSports . HasValue )
{
if ( filter . IsSports . Value )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
tags . Add ( "Sports" ) ;
2024-10-08 12:27:27 +00:00
}
else
{
2024-11-14 19:53:59 +00:00
excludeTags . Add ( "Sports" ) ;
2024-10-08 12:27:27 +00:00
}
}
2024-11-14 19:53:59 +00:00
if ( filter . IsNews . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . IsNews . Value )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
tags . Add ( "News" ) ;
2024-10-08 12:27:27 +00:00
}
else
{
2024-11-14 19:53:59 +00:00
excludeTags . Add ( "News" ) ;
2024-10-08 12:27:27 +00:00
}
}
2024-11-14 19:53:59 +00:00
if ( filter . IsKids . HasValue )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . IsKids . Value )
2024-10-08 12:27:27 +00:00
{
2024-11-14 19:53:59 +00:00
tags . Add ( "Kids" ) ;
2024-10-08 12:27:27 +00:00
}
else
{
2024-11-14 19:53:59 +00:00
excludeTags . Add ( "Kids" ) ;
2024-10-08 12:27:27 +00:00
}
}
2024-10-08 16:27:47 +00:00
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrEmpty ( filter . SearchTerm ) )
2024-09-08 16:56:14 +00:00
{
2024-11-16 22:10:07 +00:00
var searchTerm = filter . SearchTerm . ToLower ( ) ;
2025-10-03 02:33:01 +03:00
if ( SearchWildcardTerms . Any ( f = > searchTerm . Contains ( f ) ) )
{
searchTerm = $"%{searchTerm.Trim('%')}%" ;
baseQuery = baseQuery . Where ( e = > EF . Functions . Like ( e . CleanName ! . ToLower ( ) , searchTerm ) | | ( e . OriginalTitle ! = null & & EF . Functions . Like ( e . OriginalTitle . ToLower ( ) , searchTerm ) ) ) ;
}
else
{
baseQuery = baseQuery . Where ( e = > e . CleanName ! . ToLower ( ) . Contains ( searchTerm ) | | ( e . OriginalTitle ! = null & & e . OriginalTitle . ToLower ( ) . Contains ( searchTerm ) ) ) ;
}
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsFolder . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . IsFolder = = filter . IsFolder ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
var includeTypes = filter . IncludeItemTypes ;
2025-04-27 04:10:54 +03:00
2024-11-14 19:53:59 +00:00
// Only specify excluded types if no included types are specified
if ( filter . IncludeItemTypes . Length = = 0 )
{
var excludeTypes = filter . ExcludeItemTypes ;
if ( excludeTypes . Length = = 1 )
2024-10-10 19:27:26 +00:00
{
2024-11-24 10:58:09 +00:00
if ( _itemTypeLookup . BaseItemKindNames . TryGetValue ( excludeTypes [ 0 ] , out var excludeTypeName ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Type ! = excludeTypeName ) ;
2024-09-08 16:56:14 +00:00
}
2024-10-10 19:27:26 +00:00
}
2024-11-14 19:53:59 +00:00
else if ( excludeTypes . Length > 1 )
2024-10-10 19:27:26 +00:00
{
2024-11-14 19:53:59 +00:00
var excludeTypeName = new List < string > ( ) ;
foreach ( var excludeType in excludeTypes )
2024-09-08 16:56:14 +00:00
{
2024-11-24 10:58:09 +00:00
if ( _itemTypeLookup . BaseItemKindNames . TryGetValue ( excludeType , out var baseItemKindName ) )
2024-10-10 00:49:06 +00:00
{
2024-11-14 19:53:59 +00:00
excludeTypeName . Add ( baseItemKindName ! ) ;
}
2024-10-10 19:27:26 +00:00
}
2024-11-13 20:28:52 +00:00
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > ! excludeTypeName . Contains ( e . Type ) ) ;
2024-09-08 16:56:14 +00:00
}
2024-10-10 00:49:06 +00:00
}
2025-04-27 04:10:54 +03:00
else
2024-11-13 22:04:03 +00:00
{
2025-04-27 04:10:54 +03:00
string [ ] types = includeTypes . Select ( f = > _itemTypeLookup . BaseItemKindNames . GetValueOrDefault ( f ) ) . Where ( e = > e ! = null ) . ToArray ( ) ! ;
baseQuery = baseQuery . WhereOneOrMany ( types , f = > f . Type ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ChannelIds . Count > 0 )
{
2025-02-13 20:17:25 -07:00
baseQuery = baseQuery . Where ( e = > e . ChannelId ! = null & & filter . ChannelIds . Contains ( e . ChannelId . Value ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( ! filter . ParentId . IsEmpty ( ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . ParentId ! . Value = = filter . ParentId ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . Path ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Path = = filter . Path ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . PresentationUniqueKey ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . PresentationUniqueKey = = filter . PresentationUniqueKey ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinCommunityRating . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . CommunityRating > = filter . MinCommunityRating ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinIndexNumber . HasValue )
{
baseQuery = baseQuery . Where ( e = > e . IndexNumber > = filter . MinIndexNumber ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . MinParentAndIndexNumber . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > ( e . ParentIndexNumber = = filter . MinParentAndIndexNumber . Value . ParentIndexNumber & & e . IndexNumber > = filter . MinParentAndIndexNumber . Value . IndexNumber ) | | e . ParentIndexNumber > filter . MinParentAndIndexNumber . Value . ParentIndexNumber ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinDateCreated . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . DateCreated > = filter . MinDateCreated ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinDateLastSaved . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . DateLastSaved ! = null & & e . DateLastSaved > = filter . MinDateLastSaved . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinDateLastSavedForUser . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . DateLastSaved ! = null & & e . DateLastSaved > = filter . MinDateLastSavedForUser . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IndexNumber . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . IndexNumber = = filter . IndexNumber . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ParentIndexNumber . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . ParentIndexNumber = = filter . ParentIndexNumber . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ParentIndexNumberNotEquals . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . ParentIndexNumber ! = filter . ParentIndexNumberNotEquals . Value | | e . ParentIndexNumber = = null ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
var minEndDate = filter . MinEndDate ;
var maxEndDate = filter . MaxEndDate ;
if ( filter . HasAired . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . HasAired . Value )
{
maxEndDate = DateTime . UtcNow ;
}
else
{
minEndDate = DateTime . UtcNow ;
}
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( minEndDate . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . EndDate > = minEndDate ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( maxEndDate . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . EndDate < = maxEndDate ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MinStartDate . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . StartDate > = filter . MinStartDate . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MaxStartDate . HasValue )
{
baseQuery = baseQuery . Where ( e = > e . StartDate < = filter . MaxStartDate . Value ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . MinPremiereDate . HasValue )
2024-09-08 16:56:14 +00:00
{
2025-05-19 00:35:12 +00:00
baseQuery = baseQuery . Where ( e = > e . PremiereDate > = filter . MinPremiereDate . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MaxPremiereDate . HasValue )
{
baseQuery = baseQuery . Where ( e = > e . PremiereDate < = filter . MaxPremiereDate . Value ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . TrailerTypes . Length > 0 )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
var trailerTypes = filter . TrailerTypes . Select ( e = > ( int ) e ) . ToArray ( ) ;
baseQuery = baseQuery . Where ( e = > trailerTypes . Any ( f = > e . TrailerTypes ! . Any ( w = > w . Id = = f ) ) ) ;
}
2024-10-10 00:49:06 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . IsAiring . HasValue )
2024-10-10 20:03:15 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . IsAiring . Value )
{
baseQuery = baseQuery . Where ( e = > e . StartDate < = now & & e . EndDate > = now ) ;
}
else
{
baseQuery = baseQuery . Where ( e = > e . StartDate > now & & e . EndDate < now ) ;
}
2024-10-10 20:03:15 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . PersonIds . Length > 0 )
{
2025-10-08 11:23:20 -04:00
var peopleEntityIds = context . BaseItems
. WhereOneOrMany ( filter . PersonIds , b = > b . Id )
. Join (
context . Peoples ,
b = > b . Name ,
p = > p . Name ,
( b , p ) = > p . Id ) ;
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-10-08 11:23:20 -04:00
. Where ( e = > context . PeopleBaseItemMap
. Any ( m = > m . ItemId = = e . Id & & peopleEntityIds . Contains ( m . PeopleId ) ) ) ;
2024-11-14 19:53:59 +00:00
}
if ( ! string . IsNullOrWhiteSpace ( filter . Person ) )
2024-10-08 16:27:47 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Peoples ! . Any ( f = > f . People . Name = = filter . Person ) ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . MinSortName ) )
2024-10-09 23:01:54 +00:00
{
2024-11-14 19:53:59 +00:00
// this does not makes sense.
// baseQuery = baseQuery.Where(e => e.SortName >= query.MinSortName);
// whereClauses.Add("SortName>=@MinSortName");
// statement?.TryBind("@MinSortName", query.MinSortName);
2024-10-09 23:01:54 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . ExternalSeriesId ) )
2024-10-09 23:01:54 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . ExternalSeriesId = = filter . ExternalSeriesId ) ;
2024-10-09 23:01:54 +00:00
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . ExternalId ) )
{
baseQuery = baseQuery . Where ( e = > e . ExternalId = = filter . ExternalId ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . Name ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
var cleanName = GetCleanValue ( filter . Name ) ;
baseQuery = baseQuery . Where ( e = > e . CleanName = = cleanName ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
// These are the same, for now
var nameContains = filter . NameContains ;
if ( ! string . IsNullOrWhiteSpace ( nameContains ) )
2024-09-08 16:56:14 +00:00
{
2025-10-03 02:33:01 +03:00
if ( SearchWildcardTerms . Any ( f = > nameContains . Contains ( f ) ) )
{
nameContains = $"%{nameContains.Trim('%')}%" ;
baseQuery = baseQuery . Where ( e = > EF . Functions . Like ( e . CleanName , nameContains ) | | EF . Functions . Like ( e . OriginalTitle , nameContains ) ) ;
}
else
{
baseQuery = baseQuery . Where ( e = >
e . CleanName ! . Contains ( nameContains )
| | e . OriginalTitle ! . ToLower ( ) . Contains ( nameContains ! ) ) ;
}
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . NameStartsWith ) )
2024-09-08 16:56:14 +00:00
{
2025-09-12 15:58:42 -04:00
baseQuery = baseQuery . Where ( e = > e . SortName ! . StartsWith ( filter . NameStartsWith ) ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . NameStartsWithOrGreater ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
// i hate this
baseQuery = baseQuery . Where ( e = > e . SortName ! . FirstOrDefault ( ) > filter . NameStartsWithOrGreater [ 0 ] | | e . Name ! . FirstOrDefault ( ) > filter . NameStartsWithOrGreater [ 0 ] ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . NameLessThan ) )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
// i hate this
baseQuery = baseQuery . Where ( e = > e . SortName ! . FirstOrDefault ( ) < filter . NameLessThan [ 0 ] | | e . Name ! . FirstOrDefault ( ) < filter . NameLessThan [ 0 ] ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ImageTypes . Length > 0 )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
var imgTypes = filter . ImageTypes . Select ( e = > ( ImageInfoImageType ) e ) . ToArray ( ) ;
baseQuery = baseQuery . Where ( e = > imgTypes . Any ( f = > e . Images ! . Any ( w = > w . ImageType = = f ) ) ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsLiked . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . UserData ! . FirstOrDefault ( f = > f . UserId = = filter . User ! . Id ) ! . Rating > = UserItemData . MinLikeValue ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsFavoriteOrLiked . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . UserData ! . FirstOrDefault ( f = > f . UserId = = filter . User ! . Id ) ! . IsFavorite = = filter . IsFavoriteOrLiked ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsFavorite . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . UserData ! . FirstOrDefault ( f = > f . UserId = = filter . User ! . Id ) ! . IsFavorite = = filter . IsFavorite ) ;
2024-10-09 23:01:54 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsPlayed . HasValue )
2024-10-09 23:01:54 +00:00
{
2024-11-14 19:53:59 +00:00
// We should probably figure this out for all folders, but for right now, this is the only place where we need it
if ( filter . IncludeItemTypes . Length = = 1 & & filter . IncludeItemTypes [ 0 ] = = BaseItemKind . Series )
2024-10-09 23:01:54 +00:00
{
2025-06-15 21:07:19 +03:00
baseQuery = baseQuery . Where ( e = > context . BaseItems . Where ( e = > e . Id ! = EF . Constant ( PlaceholderId ) )
2024-11-14 19:53:59 +00:00
. Where ( e = > e . IsFolder = = false & & e . IsVirtualItem = = false )
. Where ( f = > f . UserData ! . FirstOrDefault ( e = > e . UserId = = filter . User ! . Id & & e . Played ) ! . Played )
. Any ( f = > f . SeriesPresentationUniqueKey = = e . PresentationUniqueKey ) = = filter . IsPlayed ) ;
}
else
{
baseQuery = baseQuery
. Select ( e = > new
{
IsPlayed = e . UserData ! . Where ( f = > f . UserId = = filter . User ! . Id ) . Select ( f = > ( bool? ) f . Played ) . FirstOrDefault ( ) ? ? false ,
Item = e
} )
. Where ( e = > e . IsPlayed = = filter . IsPlayed )
. Select ( f = > f . Item ) ;
}
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsResumable . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . IsResumable . Value )
{
baseQuery = baseQuery
. Where ( e = > e . UserData ! . FirstOrDefault ( f = > f . UserId = = filter . User ! . Id ) ! . PlaybackPositionTicks > 0 ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > e . UserData ! . FirstOrDefault ( f = > f . UserId = = filter . User ! . Id ) ! . PlaybackPositionTicks = = 0 ) ;
}
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ArtistIds . Length > 0 )
{
2025-10-13 10:09:09 -05:00
baseQuery = baseQuery . WhereReferencedItemMultipleTypes ( context , [ ItemValueType . Artist , ItemValueType . AlbumArtist ] , filter . ArtistIds ) ;
2024-11-14 19:53:59 +00:00
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . AlbumArtistIds . Length > 0 )
2024-09-08 16:56:14 +00:00
{
2025-08-13 19:52:54 +08:00
baseQuery = baseQuery . WhereReferencedItem ( context , ItemValueType . AlbumArtist , filter . AlbumArtistIds ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ContributingArtistIds . Length > 0 )
{
2025-10-13 10:09:09 -05:00
var contributingNames = context . BaseItems
. Where ( b = > filter . ContributingArtistIds . Contains ( b . Id ) )
. Select ( b = > b . CleanName ) ;
baseQuery = baseQuery . Where ( e = >
e . ItemValues ! . Any ( ivm = >
ivm . ItemValue . Type = = ItemValueType . Artist & &
contributingNames . Contains ( ivm . ItemValue . CleanValue ) )
& &
! e . ItemValues ! . Any ( ivm = >
ivm . ItemValue . Type = = ItemValueType . AlbumArtist & &
contributingNames . Contains ( ivm . ItemValue . CleanValue ) ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . AlbumIds . Length > 0 )
{
2025-04-27 04:10:54 +03:00
var subQuery = context . BaseItems . WhereOneOrMany ( filter . AlbumIds , f = > f . Id ) ;
baseQuery = baseQuery . Where ( e = > subQuery . Any ( f = > f . Name = = e . Album ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-10-08 19:53:26 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . ExcludeArtistIds . Length > 0 )
2024-10-08 19:53:26 +00:00
{
2025-10-27 15:43:10 -04:00
baseQuery = baseQuery . WhereReferencedItemMultipleTypes ( context , [ ItemValueType . Artist , ItemValueType . AlbumArtist ] , filter . ExcludeArtistIds , true ) ;
2024-10-08 19:53:26 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . GenreIds . Count > 0 )
2024-10-08 19:53:26 +00:00
{
2025-04-27 04:10:54 +03:00
baseQuery = baseQuery . WhereReferencedItem ( context , ItemValueType . Genre , filter . GenreIds . ToArray ( ) ) ;
2024-10-08 19:53:26 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . Genres . Count > 0 )
{
2025-04-27 04:10:54 +03:00
var cleanGenres = filter . Genres . Select ( e = > GetCleanValue ( e ) ) . ToArray ( ) . OneOrManyExpressionBuilder < ItemValueMap , string > ( f = > f . ItemValue . CleanValue ) ;
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-04-27 04:10:54 +03:00
. Where ( e = > e . ItemValues ! . AsQueryable ( ) . Where ( f = > f . ItemValue . Type = = ItemValueType . Genre ) . Any ( cleanGenres ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-10-08 19:53:26 +00:00
2024-11-14 19:53:59 +00:00
if ( tags . Count > 0 )
{
2025-04-27 04:10:54 +03:00
var cleanValues = tags . Select ( e = > GetCleanValue ( e ) ) . ToArray ( ) . OneOrManyExpressionBuilder < ItemValueMap , string > ( f = > f . ItemValue . CleanValue ) ;
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-04-27 04:10:54 +03:00
. Where ( e = > e . ItemValues ! . AsQueryable ( ) . Where ( f = > f . ItemValue . Type = = ItemValueType . Tags ) . Any ( cleanValues ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-10-10 18:01:14 +00:00
2024-11-14 19:53:59 +00:00
if ( excludeTags . Count > 0 )
2024-11-13 00:23:06 +00:00
{
2025-04-27 04:10:54 +03:00
var cleanValues = excludeTags . Select ( e = > GetCleanValue ( e ) ) . ToArray ( ) . OneOrManyExpressionBuilder < ItemValueMap , string > ( f = > f . ItemValue . CleanValue ) ;
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-04-27 04:10:54 +03:00
. Where ( e = > ! e . ItemValues ! . AsQueryable ( ) . Where ( f = > f . ItemValue . Type = = ItemValueType . Tags ) . Any ( cleanValues ) ) ;
2024-11-13 00:23:06 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . StudioIds . Length > 0 )
{
2025-04-27 04:10:54 +03:00
baseQuery = baseQuery . WhereReferencedItem ( context , ItemValueType . Studios , filter . StudioIds . ToArray ( ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-11-12 15:37:01 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . OfficialRatings . Length > 0 )
{
baseQuery = baseQuery
. Where ( e = > filter . OfficialRatings . Contains ( e . OfficialRating ) ) ;
}
2025-03-31 05:51:54 +02:00
Expression < Func < BaseItemEntity , bool > > ? minParentalRatingFilter = null ;
if ( filter . MinParentalRating ! = null )
2024-11-14 19:53:59 +00:00
{
2025-03-31 05:51:54 +02:00
var min = filter . MinParentalRating ;
2025-10-01 19:26:30 -04:00
var minScore = min . Score ;
var minSubScore = min . SubScore ? ? 0 ;
minParentalRatingFilter = e = >
e . InheritedParentalRatingValue = = null | |
e . InheritedParentalRatingValue > minScore | |
( e . InheritedParentalRatingValue = = minScore & & ( e . InheritedParentalRatingSubValue ? ? 0 ) > = minSubScore ) ;
2025-03-31 05:51:54 +02:00
}
2024-11-14 19:53:59 +00:00
2025-03-31 05:51:54 +02:00
Expression < Func < BaseItemEntity , bool > > ? maxParentalRatingFilter = null ;
if ( filter . MaxParentalRating ! = null )
{
var max = filter . MaxParentalRating ;
2025-10-01 19:26:30 -04:00
var maxScore = max . Score ;
var maxSubScore = max . SubScore ? ? 0 ;
maxParentalRatingFilter = e = >
e . InheritedParentalRatingValue = = null | |
e . InheritedParentalRatingValue < maxScore | |
( e . InheritedParentalRatingValue = = maxScore & & ( e . InheritedParentalRatingSubValue ? ? 0 ) < = maxSubScore ) ;
2024-11-14 19:53:59 +00:00
}
2025-03-31 05:51:54 +02:00
if ( filter . HasParentalRating ? ? false )
2024-11-14 19:53:59 +00:00
{
2025-03-31 05:51:54 +02:00
if ( minParentalRatingFilter ! = null )
2024-11-14 19:53:59 +00:00
{
2025-03-31 05:51:54 +02:00
baseQuery = baseQuery . Where ( minParentalRatingFilter ) ;
2024-11-14 19:53:59 +00:00
}
2025-03-31 05:51:54 +02:00
if ( maxParentalRatingFilter ! = null )
2024-11-14 19:53:59 +00:00
{
2025-03-31 05:51:54 +02:00
baseQuery = baseQuery . Where ( maxParentalRatingFilter ) ;
2024-11-14 19:53:59 +00:00
}
}
2025-03-31 05:51:54 +02:00
else if ( filter . BlockUnratedItems . Length > 0 )
2024-10-10 18:01:14 +00:00
{
2025-03-31 05:51:54 +02:00
var unratedItemTypes = filter . BlockUnratedItems . Select ( f = > f . ToString ( ) ) . ToArray ( ) ;
Expression < Func < BaseItemEntity , bool > > unratedItemFilter = e = > e . InheritedParentalRatingValue ! = null | | ! unratedItemTypes . Contains ( e . UnratedType ) ;
if ( minParentalRatingFilter ! = null & & maxParentalRatingFilter ! = null )
2024-10-10 18:01:14 +00:00
{
2025-03-31 05:51:54 +02:00
baseQuery = baseQuery . Where ( unratedItemFilter . And ( minParentalRatingFilter . And ( maxParentalRatingFilter ) ) ) ;
}
else if ( minParentalRatingFilter ! = null )
{
baseQuery = baseQuery . Where ( unratedItemFilter . And ( minParentalRatingFilter ) ) ;
}
else if ( maxParentalRatingFilter ! = null )
{
baseQuery = baseQuery . Where ( unratedItemFilter . And ( maxParentalRatingFilter ) ) ;
2024-10-10 18:01:14 +00:00
}
2024-11-14 19:53:59 +00:00
else
2024-10-10 18:01:14 +00:00
{
2025-03-31 05:51:54 +02:00
baseQuery = baseQuery . Where ( unratedItemFilter ) ;
2024-10-10 18:01:14 +00:00
}
}
2025-03-31 05:51:54 +02:00
else if ( minParentalRatingFilter ! = null | | maxParentalRatingFilter ! = null )
2024-10-10 18:01:14 +00:00
{
2025-03-31 05:51:54 +02:00
if ( minParentalRatingFilter ! = null )
{
baseQuery = baseQuery . Where ( minParentalRatingFilter ) ;
}
if ( maxParentalRatingFilter ! = null )
{
baseQuery = baseQuery . Where ( maxParentalRatingFilter ) ;
}
2024-10-10 18:01:14 +00:00
}
2024-11-14 19:53:59 +00:00
else if ( ! filter . HasParentalRating ? ? false )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . InheritedParentalRatingValue = = null ) ;
2024-10-09 09:53:39 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasOfficialRating . HasValue )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . HasOfficialRating . Value )
{
baseQuery = baseQuery
. Where ( e = > e . OfficialRating ! = null & & e . OfficialRating ! = string . Empty ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > e . OfficialRating = = null | | e . OfficialRating = = string . Empty ) ;
}
2024-10-09 09:53:39 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasOverview . HasValue )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . HasOverview . Value )
{
baseQuery = baseQuery
. Where ( e = > e . Overview ! = null & & e . Overview ! = string . Empty ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > e . Overview = = null | | e . Overview = = string . Empty ) ;
}
2024-10-09 09:53:39 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasOwnerId . HasValue )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . HasOwnerId . Value )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . OwnerId ! = null ) ;
2024-10-09 09:53:39 +00:00
}
2024-11-14 19:53:59 +00:00
else
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . OwnerId = = null ) ;
2024-10-09 09:53:39 +00:00
}
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . HasNoAudioTrackWithLanguage ) )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > ! e . MediaStreams ! . Any ( f = > f . StreamType = = MediaStreamTypeEntity . Audio & & f . Language = = filter . HasNoAudioTrackWithLanguage ) ) ;
2024-10-09 09:53:39 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . HasNoInternalSubtitleTrackWithLanguage ) )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > ! e . MediaStreams ! . Any ( f = > f . StreamType = = MediaStreamTypeEntity . Subtitle & & ! f . IsExternal & & f . Language = = filter . HasNoInternalSubtitleTrackWithLanguage ) ) ;
}
2024-10-09 09:53:39 +00:00
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . HasNoExternalSubtitleTrackWithLanguage ) )
2024-10-09 09:53:39 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > ! e . MediaStreams ! . Any ( f = > f . StreamType = = MediaStreamTypeEntity . Subtitle & & f . IsExternal & & f . Language = = filter . HasNoExternalSubtitleTrackWithLanguage ) ) ;
}
2024-10-09 09:53:39 +00:00
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . HasNoSubtitleTrackWithLanguage ) )
2024-10-08 16:27:47 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > ! e . MediaStreams ! . Any ( f = > f . StreamType = = MediaStreamTypeEntity . Subtitle & & f . Language = = filter . HasNoSubtitleTrackWithLanguage ) ) ;
2024-10-08 16:27:47 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasSubtitles . HasValue )
2024-10-08 16:27:47 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . MediaStreams ! . Any ( f = > f . StreamType = = MediaStreamTypeEntity . Subtitle ) = = filter . HasSubtitles . Value ) ;
2024-10-08 16:27:47 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasChapterImages . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . Chapters ! . Any ( f = > f . ImagePath ! = null ) = = filter . HasChapterImages . Value ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasDeadParentId . HasValue & & filter . HasDeadParentId . Value )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-06-15 21:07:19 +03:00
. Where ( e = > e . ParentId . HasValue & & ! context . BaseItems . Where ( e = > e . Id ! = EF . Constant ( PlaceholderId ) ) . Any ( f = > f . Id = = e . ParentId . Value ) ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsDeadArtist . HasValue & & filter . IsDeadArtist . Value )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-04-10 02:40:16 +02:00
. Where ( e = > ! context . ItemValues . Where ( f = > _getAllArtistsValueTypes . Contains ( f . Type ) ) . Any ( f = > f . Value = = e . Name ) ) ;
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IsDeadStudio . HasValue & & filter . IsDeadStudio . Value )
{
baseQuery = baseQuery
2025-04-10 02:40:16 +02:00
. Where ( e = > ! context . ItemValues . Where ( f = > _getStudiosValueTypes . Contains ( f . Type ) ) . Any ( f = > f . Value = = e . Name ) ) ;
}
if ( filter . IsDeadGenre . HasValue & & filter . IsDeadGenre . Value )
{
baseQuery = baseQuery
. Where ( e = > ! context . ItemValues . Where ( f = > _getGenreValueTypes . Contains ( f . Type ) ) . Any ( f = > f . Value = = e . Name ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . IsDeadPerson . HasValue & & filter . IsDeadPerson . Value )
{
baseQuery = baseQuery
. Where ( e = > ! context . Peoples . Any ( f = > f . Name = = e . Name ) ) ;
}
2024-09-08 16:56:14 +00:00
2025-04-27 04:10:54 +03:00
if ( filter . Years . Length > 0 )
2024-11-14 19:53:59 +00:00
{
2025-04-27 04:10:54 +03:00
baseQuery = baseQuery . WhereOneOrMany ( filter . Years , e = > e . ProductionYear ! . Value ) ;
2024-11-14 19:53:59 +00:00
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
var isVirtualItem = filter . IsVirtualItem ? ? filter . IsMissing ;
if ( isVirtualItem . HasValue )
2024-10-09 23:01:54 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . IsVirtualItem = = isVirtualItem . Value ) ;
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . IsSpecialSeason . HasValue )
2024-10-09 23:01:54 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . IsSpecialSeason . Value )
{
baseQuery = baseQuery
. Where ( e = > e . IndexNumber = = 0 ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > e . IndexNumber ! = 0 ) ;
}
}
2024-09-08 16:56:14 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . IsUnaired . HasValue )
2024-09-08 16:56:14 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . IsUnaired . Value )
{
baseQuery = baseQuery
. Where ( e = > e . PremiereDate > = now ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > e . PremiereDate < now ) ;
}
2024-09-08 16:56:14 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . MediaTypes . Length > 0 )
2024-10-08 15:16:03 +00:00
{
2024-11-14 19:53:59 +00:00
var mediaTypes = filter . MediaTypes . Select ( f = > f . ToString ( ) ) . ToArray ( ) ;
2025-04-27 04:10:54 +03:00
baseQuery = baseQuery . WhereOneOrMany ( mediaTypes , e = > e . MediaType ) ;
2024-10-08 15:16:03 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ItemIds . Length > 0 )
2024-10-08 15:16:03 +00:00
{
2025-04-27 04:10:54 +03:00
baseQuery = baseQuery . WhereOneOrMany ( filter . ItemIds , e = > e . Id ) ;
2024-10-08 15:16:03 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ExcludeItemIds . Length > 0 )
2024-10-08 15:16:03 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-06-09 04:53:18 +03:00
. Where ( e = > ! filter . ExcludeItemIds . Contains ( e . Id ) ) ;
2024-10-08 15:16:03 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ExcludeProviderIds is not null & & filter . ExcludeProviderIds . Count > 0 )
2024-10-08 15:16:03 +00:00
{
2025-06-09 04:53:18 +03:00
var exclude = filter . ExcludeProviderIds . Select ( e = > $"{e.Key}:{e.Value}" ) . ToArray ( ) ;
baseQuery = baseQuery . Where ( e = > e . Provider ! . Select ( f = > f . ProviderId + ":" + f . ProviderValue ) ! . All ( f = > ! exclude . Contains ( f ) ) ) ;
2024-10-08 15:16:03 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasAnyProviderId is not null & & filter . HasAnyProviderId . Count > 0 )
2024-10-08 15:16:03 +00:00
{
2025-09-22 17:56:41 +02:00
// Allow setting a null or empty value to get all items that have the specified provider set.
var includeAny = filter . HasAnyProviderId . Where ( e = > string . IsNullOrEmpty ( e . Value ) ) . Select ( e = > e . Key ) . ToArray ( ) ;
if ( includeAny . Length > 0 )
{
baseQuery = baseQuery . Where ( e = > e . Provider ! . Any ( f = > includeAny . Contains ( f . ProviderId ) ) ) ;
}
var includeSelected = filter . HasAnyProviderId . Where ( e = > ! string . IsNullOrEmpty ( e . Value ) ) . Select ( e = > $"{e.Key}:{e.Value}" ) . ToArray ( ) ;
if ( includeSelected . Length > 0 )
{
baseQuery = baseQuery . Where ( e = > e . Provider ! . Select ( f = > f . ProviderId + ":" + f . ProviderValue ) ! . Any ( f = > includeSelected . Contains ( f ) ) ) ;
}
2024-10-08 15:16:03 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasImdbId . HasValue )
2024-10-08 15:16:03 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Provider ! . Any ( f = > f . ProviderId = = "imdb" ) ) ;
2024-10-08 15:16:03 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasTmdbId . HasValue )
{
baseQuery = baseQuery . Where ( e = > e . Provider ! . Any ( f = > f . ProviderId = = "tmdb" ) ) ;
}
2024-10-08 19:11:31 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . HasTvdbId . HasValue )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery . Where ( e = > e . Provider ! . Any ( f = > f . ProviderId = = "tvdb" ) ) ;
}
2024-10-08 19:11:31 +00:00
2024-11-14 19:53:59 +00:00
var queryTopParentIds = filter . TopParentIds ;
2024-10-08 19:11:31 +00:00
2024-11-14 19:53:59 +00:00
if ( queryTopParentIds . Length > 0 )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
var includedItemByNameTypes = GetItemByNameTypesInQuery ( filter ) ;
var enableItemsByName = ( filter . IncludeItemsByName ? ? false ) & & includedItemByNameTypes . Count > 0 ;
if ( enableItemsByName & & includedItemByNameTypes . Count > 0 )
{
baseQuery = baseQuery . Where ( e = > includedItemByNameTypes . Contains ( e . Type ) | | queryTopParentIds . Any ( w = > w = = e . TopParentId ! . Value ) ) ;
}
else
{
2025-04-27 04:10:54 +03:00
baseQuery = baseQuery . WhereOneOrMany ( queryTopParentIds , e = > e . TopParentId ! . Value ) ;
2024-11-14 19:53:59 +00:00
}
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . AncestorIds . Length > 0 )
2024-10-08 19:11:31 +00:00
{
2025-05-15 11:48:20 +02:00
baseQuery = baseQuery . Where ( e = > e . Parents ! . Any ( f = > filter . AncestorIds . Contains ( f . ParentItemId ) ) ) ;
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . AncestorWithPresentationUniqueKey ) )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-06-15 21:07:19 +03:00
. Where ( e = > context . BaseItems . Where ( e = > e . Id ! = EF . Constant ( PlaceholderId ) ) . Where ( f = > f . PresentationUniqueKey = = filter . AncestorWithPresentationUniqueKey ) . Any ( f = > f . Children ! . Any ( w = > w . ItemId = = e . Id ) ) ) ;
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( ! string . IsNullOrWhiteSpace ( filter . SeriesPresentationUniqueKey ) )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . SeriesPresentationUniqueKey = = filter . SeriesPresentationUniqueKey ) ;
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . ExcludeInheritedTags . Length > 0 )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-07-27 21:28:04 -04:00
. Where ( e = > ! e . ItemValues ! . Where ( w = > w . ItemValue . Type = = ItemValueType . InheritedTags | | w . ItemValue . Type = = ItemValueType . Tags )
. Any ( f = > filter . ExcludeInheritedTags . Contains ( f . ItemValue . CleanValue ) ) ) ;
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . IncludeInheritedTags . Length > 0 )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
// Episodes do not store inherit tags from their parents in the database, and the tag may be still required by the client.
2025-01-25 21:04:37 -05:00
// In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s tags as well.
2024-11-14 19:53:59 +00:00
if ( includeTypes . Length = = 1 & & includeTypes . FirstOrDefault ( ) is BaseItemKind . Episode )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
2025-07-27 21:28:04 -04:00
. Where ( e = > e . ItemValues ! . Where ( f = > f . ItemValue . Type = = ItemValueType . InheritedTags | | f . ItemValue . Type = = ItemValueType . Tags )
2024-11-14 19:53:59 +00:00
. Any ( f = > filter . IncludeInheritedTags . Contains ( f . ItemValue . CleanValue ) )
| |
2025-07-27 21:28:04 -04:00
( e . ParentId . HasValue & & context . ItemValuesMap . Where ( w = > w . ItemId = = e . ParentId . Value & & ( w . ItemValue . Type = = ItemValueType . InheritedTags | | w . ItemValue . Type = = ItemValueType . Tags ) )
2024-11-14 19:53:59 +00:00
. Any ( f = > filter . IncludeInheritedTags . Contains ( f . ItemValue . CleanValue ) ) ) ) ;
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
// A playlist should be accessible to its owner regardless of allowed tags.
else if ( includeTypes . Length = = 1 & & includeTypes . FirstOrDefault ( ) is BaseItemKind . Playlist )
{
baseQuery = baseQuery
2025-07-27 21:28:04 -04:00
. Where ( e = > e . ItemValues ! . Where ( f = > f . ItemValue . Type = = ItemValueType . InheritedTags | | f . ItemValue . Type = = ItemValueType . Tags )
. Any ( f = > filter . IncludeInheritedTags . Contains ( f . ItemValue . CleanValue ) )
| | e . Data ! . Contains ( $"OwnerUserId\" : \ "{filter.User!.Id:N}\"" ) ) ;
2024-11-14 19:53:59 +00:00
// d ^^ this is stupid it hate this.
}
else
{
baseQuery = baseQuery
2025-07-27 21:28:04 -04:00
. Where ( e = > e . ItemValues ! . Where ( f = > f . ItemValue . Type = = ItemValueType . InheritedTags | | f . ItemValue . Type = = ItemValueType . Tags )
. Any ( f = > filter . IncludeInheritedTags . Contains ( f . ItemValue . CleanValue ) ) ) ;
2024-11-14 19:53:59 +00:00
}
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . SeriesStatuses . Length > 0 )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
var seriesStatus = filter . SeriesStatuses . Select ( e = > e . ToString ( ) ) . ToArray ( ) ;
baseQuery = baseQuery
. Where ( e = > seriesStatus . Any ( f = > e . Data ! . Contains ( f ) ) ) ;
2024-10-08 19:11:31 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . BoxSetLibraryFolders . Length > 0 )
{
var boxsetFolders = filter . BoxSetLibraryFolders . Select ( e = > e . ToString ( "N" , CultureInfo . InvariantCulture ) ) . ToArray ( ) ;
baseQuery = baseQuery
. Where ( e = > boxsetFolders . Any ( f = > e . Data ! . Contains ( f ) ) ) ;
}
2024-11-11 06:21:43 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . VideoTypes . Length > 0 )
2024-11-11 06:21:43 +00:00
{
2024-11-14 19:53:59 +00:00
var videoTypeBs = filter . VideoTypes . Select ( e = > $"\" VideoType \ ":\"{e}\"" ) ;
baseQuery = baseQuery
. Where ( e = > videoTypeBs . Any ( f = > e . Data ! . Contains ( f ) ) ) ;
}
if ( filter . Is3D . HasValue )
{
if ( filter . Is3D . Value )
2024-11-11 06:21:43 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . Data ! . Contains ( "Video3DFormat" ) ) ;
2024-11-11 06:21:43 +00:00
}
else
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > ! e . Data ! . Contains ( "Video3DFormat" ) ) ;
2024-11-11 06:21:43 +00:00
}
2024-11-14 19:53:59 +00:00
}
2024-11-12 15:37:01 +00:00
2024-11-14 19:53:59 +00:00
if ( filter . IsPlaceHolder . HasValue )
{
if ( filter . IsPlaceHolder . Value )
2024-11-12 15:37:01 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . Data ! . Contains ( "IsPlaceHolder\":true" ) ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > ! e . Data ! . Contains ( "IsPlaceHolder\":true" ) ) ;
2024-11-12 15:37:01 +00:00
}
2024-11-11 06:21:43 +00:00
}
2024-11-14 19:53:59 +00:00
if ( filter . HasSpecialFeature . HasValue )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
if ( filter . HasSpecialFeature . Value )
2024-10-08 19:11:31 +00:00
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . ExtraIds ! = null ) ;
2024-10-08 19:11:31 +00:00
}
else
{
2024-11-14 19:53:59 +00:00
baseQuery = baseQuery
. Where ( e = > e . ExtraIds = = null ) ;
2024-10-08 19:11:31 +00:00
}
}
2024-11-14 19:53:59 +00:00
if ( filter . HasTrailer . HasValue | | filter . HasThemeSong . HasValue | | filter . HasThemeVideo . HasValue )
{
if ( filter . HasTrailer . GetValueOrDefault ( ) | | filter . HasThemeSong . GetValueOrDefault ( ) | | filter . HasThemeVideo . GetValueOrDefault ( ) )
{
baseQuery = baseQuery
. Where ( e = > e . ExtraIds ! = null ) ;
}
else
{
baseQuery = baseQuery
. Where ( e = > e . ExtraIds = = null ) ;
}
}
return baseQuery ;
2024-10-08 19:11:31 +00:00
}
2025-06-16 00:19:57 +03:00
/// <inheritdoc/>
public async Task < bool > ItemExistsAsync ( Guid id )
{
var dbContext = await _dbProvider . CreateDbContextAsync ( ) . ConfigureAwait ( false ) ;
await using ( dbContext . ConfigureAwait ( false ) )
{
return await dbContext . BaseItems . AnyAsync ( f = > f . Id = = id ) . ConfigureAwait ( false ) ;
}
}
2025-09-16 21:08:04 +02:00
/// <inheritdoc/>
public bool GetIsPlayed ( User user , Guid id , bool recursive )
{
using var dbContext = _dbProvider . CreateDbContext ( ) ;
if ( recursive )
{
var folderList = TraverseHirachyDown ( id , dbContext , item = > ( item . IsFolder | | item . IsVirtualItem ) ) ;
return dbContext . BaseItems
. Where ( e = > folderList . Contains ( e . ParentId ! . Value ) & & ! e . IsFolder & & ! e . IsVirtualItem )
. All ( f = > f . UserData ! . Any ( e = > e . UserId = = user . Id & & e . Played ) ) ;
}
return dbContext . BaseItems . Where ( e = > e . ParentId = = id ) . All ( f = > f . UserData ! . Any ( e = > e . UserId = = user . Id & & e . Played ) ) ;
}
private static HashSet < Guid > TraverseHirachyDown ( Guid parentId , JellyfinDbContext dbContext , Expression < Func < BaseItemEntity , bool > > ? filter = null )
{
var folderStack = new HashSet < Guid > ( )
{
parentId
} ;
var folderList = new HashSet < Guid > ( )
{
parentId
} ;
while ( folderStack . Count ! = 0 )
{
var items = folderStack . ToArray ( ) ;
folderStack . Clear ( ) ;
var query = dbContext . BaseItems
. WhereOneOrMany ( items , e = > e . ParentId ! . Value ) ;
if ( filter ! = null )
{
query = query . Where ( filter ) ;
}
foreach ( var item in query . Select ( e = > e . Id ) . ToArray ( ) )
{
if ( folderList . Add ( item ) )
{
folderStack . Add ( item ) ;
}
}
}
return folderList ;
}
2025-09-25 00:20:30 +03:00
/// <inheritdoc/>
public IReadOnlyDictionary < string , MusicArtist [ ] > FindArtists ( IReadOnlyList < string > artistNames )
{
using var dbContext = _dbProvider . CreateDbContext ( ) ;
var artists = dbContext . BaseItems . Where ( e = > e . Type = = _itemTypeLookup . BaseItemKindNames [ BaseItemKind . MusicArtist ] ! )
. Where ( e = > artistNames . Contains ( e . Name ) )
. ToArray ( ) ;
return artists . GroupBy ( e = > e . Name ) . ToDictionary ( e = > e . Key ! , e = > e . Select ( f = > DeserializeBaseItem ( f ) ) . Cast < MusicArtist > ( ) . ToArray ( ) ) ;
}
2024-09-08 16:56:14 +00:00
}