Naming refactoring and WIP porting of new interface repositories

This commit is contained in:
JPVenson 2024-10-09 09:53:39 +00:00
parent 15bf43e3ad
commit be48cdd9e9
32 changed files with 601 additions and 367 deletions

View File

@ -40,6 +40,7 @@ using Jellyfin.MediaEncoding.Hls.Playlist;
using Jellyfin.Networking.Manager; using Jellyfin.Networking.Manager;
using Jellyfin.Networking.Udp; using Jellyfin.Networking.Udp;
using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Item;
using Jellyfin.Server.Implementations.MediaSegments; using Jellyfin.Server.Implementations.MediaSegments;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -83,7 +84,6 @@ using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Lyric; using MediaBrowser.Providers.Lyric;
using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.Tmdb; using MediaBrowser.Providers.Plugins.Tmdb;
@ -494,7 +494,12 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); serviceCollection.AddSingleton<IItemRepository, BaseItemRepository>();
serviceCollection.AddSingleton<IPeopleRepository, PeopleRepository>();
serviceCollection.AddSingleton<IChapterRepository, ChapterRepository>();
serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
serviceCollection.AddSingleton<IMediaStreamRepository, MediaStreamRepository>();
serviceCollection.AddSingleton<IItemTypeLookup, ItemTypeLookup>();
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
serviceCollection.AddSingleton<EncodingHelper>(); serviceCollection.AddSingleton<EncodingHelper>();
@ -539,8 +544,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>(); serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
serviceCollection.AddSingleton<IAuthService, AuthService>(); serviceCollection.AddSingleton<IAuthService, AuthService>();
@ -578,8 +581,6 @@ namespace Emby.Server.Implementations
} }
} }
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize();
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>(); var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
await localizationManager.LoadAll().ConfigureAwait(false); await localizationManager.LoadAll().ConfigureAwait(false);

View File

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Threading.Channels;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Querying;
namespace Jellyfin.Server.Implementations.Item;
/// <summary>
/// Provides static topic based lookups for the BaseItemKind.
/// </summary>
public class ItemTypeLookup : IItemTypeLookup
{
/// <summary>
/// Gets all values of the ItemFields type.
/// </summary>
public IReadOnlyList<ItemFields> AllItemFields { get; } = Enum.GetValues<ItemFields>();
/// <summary>
/// Gets all BaseItemKinds that are considered Programs.
/// </summary>
public IReadOnlyList<BaseItemKind> ProgramTypes { get; } =
[
BaseItemKind.Program,
BaseItemKind.TvChannel,
BaseItemKind.LiveTvProgram,
BaseItemKind.LiveTvChannel
];
/// <summary>
/// Gets all BaseItemKinds that should be excluded from parent lookup.
/// </summary>
public IReadOnlyList<BaseItemKind> ProgramExcludeParentTypes { get; } =
[
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.PhotoAlbum
];
/// <summary>
/// Gets all BaseItemKinds that are considered to be provided by services.
/// </summary>
public IReadOnlyList<BaseItemKind> ServiceTypes { get; } =
[
BaseItemKind.TvChannel,
BaseItemKind.LiveTvChannel
];
/// <summary>
/// Gets all BaseItemKinds that have a StartDate.
/// </summary>
public IReadOnlyList<BaseItemKind> StartDateTypes { get; } =
[
BaseItemKind.Program,
BaseItemKind.LiveTvProgram
];
/// <summary>
/// Gets all BaseItemKinds that are considered Series.
/// </summary>
public IReadOnlyList<BaseItemKind> SeriesTypes { get; } =
[
BaseItemKind.Book,
BaseItemKind.AudioBook,
BaseItemKind.Episode,
BaseItemKind.Season
];
/// <summary>
/// Gets all BaseItemKinds that are not to be evaluated for Artists.
/// </summary>
public IReadOnlyList<BaseItemKind> ArtistExcludeParentTypes { get; } =
[
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.PhotoAlbum
];
/// <summary>
/// Gets all BaseItemKinds that are considered Artists.
/// </summary>
public IReadOnlyList<BaseItemKind> ArtistsTypes { get; } =
[
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo,
BaseItemKind.AudioBook
];
/// <summary>
/// Gets mapping for all BaseItemKinds and their expected serialisaition target.
/// </summary>
public IDictionary<BaseItemKind, string?> BaseItemKindNames { get; } = new Dictionary<BaseItemKind, string?>()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
{ BaseItemKind.Audio, typeof(Audio).FullName },
{ BaseItemKind.AudioBook, typeof(AudioBook).FullName },
{ BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
{ BaseItemKind.Book, typeof(Book).FullName },
{ BaseItemKind.BoxSet, typeof(BoxSet).FullName },
{ BaseItemKind.Channel, typeof(Channel).FullName },
{ BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
{ BaseItemKind.Episode, typeof(Episode).FullName },
{ BaseItemKind.Folder, typeof(Folder).FullName },
{ BaseItemKind.Genre, typeof(Genre).FullName },
{ BaseItemKind.Movie, typeof(Movie).FullName },
{ BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
{ BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
{ BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
{ BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
{ BaseItemKind.Person, typeof(Person).FullName },
{ BaseItemKind.Photo, typeof(Photo).FullName },
{ BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
{ BaseItemKind.Playlist, typeof(Playlist).FullName },
{ BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
{ BaseItemKind.Season, typeof(Season).FullName },
{ BaseItemKind.Series, typeof(Series).FullName },
{ BaseItemKind.Studio, typeof(Studio).FullName },
{ BaseItemKind.Trailer, typeof(Trailer).FullName },
{ BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
{ BaseItemKind.UserView, typeof(UserView).FullName },
{ BaseItemKind.Video, typeof(Video).FullName },
{ BaseItemKind.Year, typeof(Year).FullName }
}.AsReadOnly();
}

View File

@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.MediaEncoder
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger<EncodingManager> _logger; private readonly ILogger<EncodingManager> _logger;
private readonly IMediaEncoder _encoder; private readonly IMediaEncoder _encoder;
private readonly IChapterManager _chapterManager; private readonly IChapterRepository _chapterManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
/// <summary> /// <summary>
@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.MediaEncoder
ILogger<EncodingManager> logger, ILogger<EncodingManager> logger,
IFileSystem fileSystem, IFileSystem fileSystem,
IMediaEncoder encoder, IMediaEncoder encoder,
IChapterManager chapterManager, IChapterRepository chapterManager,
ILibraryManager libraryManager) ILibraryManager libraryManager)
{ {
_logger = logger; _logger = logger;

View File

@ -13,7 +13,7 @@ public class AncestorId
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public required BaseItem Item { get; set; } public required BaseItemEntity Item { get; set; }
public string? AncestorIdText { get; set; } public string? AncestorIdText { get; set; }
} }

View File

@ -7,7 +7,7 @@ public class AttachmentStreamInfo
{ {
public required Guid ItemId { get; set; } public required Guid ItemId { get; set; }
public required BaseItem Item { get; set; } public required BaseItemEntity Item { get; set; }
public required int Index { get; set; } public required int Index { get; set; }

View File

@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities; namespace Jellyfin.Data.Entities;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public class BaseItem public class BaseItemEntity
{ {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
@ -160,6 +160,7 @@ public class BaseItem
public long? Size { get; set; } public long? Size { get; set; }
#pragma warning disable CA2227 // Collection properties should be read only
public ICollection<People>? Peoples { get; set; } public ICollection<People>? Peoples { get; set; }
public ICollection<UserData>? UserData { get; set; } public ICollection<UserData>? UserData { get; set; }

View File

@ -5,11 +5,28 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities; namespace Jellyfin.Data.Entities;
/// <summary>
/// Represents an Key-Value relaten of an BaseItem's provider.
/// </summary>
public class BaseItemProvider public class BaseItemProvider
{ {
/// <summary>
/// Gets or Sets the reference ItemId.
/// </summary>
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public required BaseItem Item { get; set; }
public string ProviderId { get; set; } /// <summary>
public string ProviderValue { get; set; } /// Gets or Sets the reference BaseItem.
/// </summary>
public required BaseItemEntity Item { get; set; }
/// <summary>
/// Gets or Sets the ProvidersId.
/// </summary>
public required string ProviderId { get; set; }
/// <summary>
/// Gets or Sets the Providers Value.
/// </summary>
public required string ProviderValue { get; set; }
} }

View File

@ -10,7 +10,7 @@ public class Chapter
{ {
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public required BaseItem Item { get; set; } public required BaseItemEntity Item { get; set; }
public required int ChapterIndex { get; set; } public required int ChapterIndex { get; set; }

View File

@ -5,12 +5,33 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities; namespace Jellyfin.Data.Entities;
/// <summary>
/// Represents an ItemValue for a BaseItem.
/// </summary>
public class ItemValue public class ItemValue
{ {
/// <summary>
/// Gets or Sets the reference ItemId.
/// </summary>
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public required BaseItem Item { get; set; }
/// <summary>
/// Gets or Sets the referenced BaseItem.
/// </summary>
public required BaseItemEntity Item { get; set; }
/// <summary>
/// Gets or Sets the Type.
/// </summary>
public required int Type { get; set; } public required int Type { get; set; }
/// <summary>
/// Gets or Sets the Value.
/// </summary>
public required string Value { get; set; } public required string Value { get; set; }
/// <summary>
/// Gets or Sets the sanatised Value.
/// </summary>
public required string CleanValue { get; set; } public required string CleanValue { get; set; }
} }

View File

@ -7,7 +7,7 @@ public class MediaStreamInfo
{ {
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public required BaseItem Item { get; set; } public required BaseItemEntity Item { get; set; }
public int StreamIndex { get; set; } public int StreamIndex { get; set; }

View File

@ -4,14 +4,44 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace Jellyfin.Data.Entities; namespace Jellyfin.Data.Entities;
/// <summary>
/// People entity.
/// </summary>
public class People public class People
{ {
public Guid ItemId { get; set; } /// <summary>
public BaseItem Item { get; set; } /// Gets or Sets The ItemId.
/// </summary>
public required Guid ItemId { get; set; }
/// <summary>
/// Gets or Sets Reference Item.
/// </summary>
public required BaseItemEntity Item { get; set; }
/// <summary>
/// Gets or Sets the Persons Name.
/// </summary>
public required string Name { get; set; } public required string Name { get; set; }
/// <summary>
/// Gets or Sets the Role.
/// </summary>
public string? Role { get; set; } public string? Role { get; set; }
/// <summary>
/// Gets or Sets the Type.
/// </summary>
public string? PersonType { get; set; } public string? PersonType { get; set; }
/// <summary>
/// Gets or Sets the SortOrder.
/// </summary>
public int? SortOrder { get; set; } public int? SortOrder { get; set; }
/// <summary>
/// Gets or Sets the ListOrder.
/// </summary>
public int? ListOrder { get; set; } public int? ListOrder { get; set; }
} }

View File

@ -24,112 +24,23 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem; using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
using BaseItemEntity = Jellyfin.Data.Entities.BaseItem; using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity;
namespace Jellyfin.Server.Implementations.Item; namespace Jellyfin.Server.Implementations.Item;
/// <summary> /// <summary>
/// Handles all storage logic for BaseItems. /// Handles all storage logic for BaseItems.
/// </summary> /// </summary>
public sealed class BaseItemManager : IItemRepository, IDisposable /// <remarks>
/// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
/// </remarks>
/// <param name="dbProvider">The db factory.</param>
/// <param name="appHost">The Application host.</param>
/// <param name="itemTypeLookup">The static type lookup.</param>
public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost appHost, IItemTypeLookup itemTypeLookup)
: IItemRepository, IDisposable
{ {
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IServerApplicationHost _appHost;
private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>();
private static readonly BaseItemKind[] _programTypes = new[]
{
BaseItemKind.Program,
BaseItemKind.TvChannel,
BaseItemKind.LiveTvProgram,
BaseItemKind.LiveTvChannel
};
private static readonly BaseItemKind[] _programExcludeParentTypes = new[]
{
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicArtist,
BaseItemKind.PhotoAlbum
};
private static readonly BaseItemKind[] _serviceTypes = new[]
{
BaseItemKind.TvChannel,
BaseItemKind.LiveTvChannel
};
private static readonly BaseItemKind[] _startDateTypes = new[]
{
BaseItemKind.Program,
BaseItemKind.LiveTvProgram
};
private static readonly BaseItemKind[] _seriesTypes = new[]
{
BaseItemKind.Book,
BaseItemKind.AudioBook,
BaseItemKind.Episode,
BaseItemKind.Season
};
private static readonly BaseItemKind[] _artistExcludeParentTypes = new[]
{
BaseItemKind.Series,
BaseItemKind.Season,
BaseItemKind.PhotoAlbum
};
private static readonly BaseItemKind[] _artistsTypes = new[]
{
BaseItemKind.Audio,
BaseItemKind.MusicAlbum,
BaseItemKind.MusicVideo,
BaseItemKind.AudioBook
};
private static readonly Dictionary<BaseItemKind, string?> _baseItemKindNames = new()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
{ BaseItemKind.Audio, typeof(Audio).FullName },
{ BaseItemKind.AudioBook, typeof(AudioBook).FullName },
{ BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
{ BaseItemKind.Book, typeof(Book).FullName },
{ BaseItemKind.BoxSet, typeof(BoxSet).FullName },
{ BaseItemKind.Channel, typeof(Channel).FullName },
{ BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
{ BaseItemKind.Episode, typeof(Episode).FullName },
{ BaseItemKind.Folder, typeof(Folder).FullName },
{ BaseItemKind.Genre, typeof(Genre).FullName },
{ BaseItemKind.Movie, typeof(Movie).FullName },
{ BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
{ BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
{ BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
{ BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
{ BaseItemKind.Person, typeof(Person).FullName },
{ BaseItemKind.Photo, typeof(Photo).FullName },
{ BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
{ BaseItemKind.Playlist, typeof(Playlist).FullName },
{ BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
{ BaseItemKind.Season, typeof(Season).FullName },
{ BaseItemKind.Series, typeof(Series).FullName },
{ BaseItemKind.Studio, typeof(Studio).FullName },
{ BaseItemKind.Trailer, typeof(Trailer).FullName },
{ BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
{ BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
{ BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
{ BaseItemKind.UserView, typeof(UserView).FullName },
{ BaseItemKind.Video, typeof(Video).FullName },
{ BaseItemKind.Year, typeof(Year).FullName }
};
/// <summary> /// <summary>
/// This holds all the types in the running assemblies /// This holds all the types in the running assemblies
/// so that we can de-serialize properly when we don't have strong types. /// so that we can de-serialize properly when we don't have strong types.
@ -137,17 +48,6 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>(); private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
private bool _disposed; private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
/// </summary>
/// <param name="dbProvider">The db factory.</param>
/// <param name="appHost">The Application host.</param>
public BaseItemManager(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost appHost)
{
_dbProvider = dbProvider;
_appHost = appHost;
}
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
@ -159,124 +59,12 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
_disposed = true; _disposed = true;
} }
private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, int[] itemValueTypes, string returnType)
{
ArgumentNullException.ThrowIfNull(filter);
if (!filter.Limit.HasValue)
{
filter.EnableTotalRecordCount = false;
}
using var context = _dbProvider.CreateDbContext();
var innerQuery = new InternalItemsQuery(filter.User)
{
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
};
var query = TranslateQuery(context.BaseItems, context, innerQuery);
query = query.Where(e => e.Type == returnType && e.ItemValues!.Any(f => e.CleanName == f.CleanValue && itemValueTypes.Contains(f.Type)));
var outerQuery = 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,
SimilarTo = filter.SimilarTo,
ExcludeItemIds = filter.ExcludeItemIds
};
query = TranslateQuery(query, context, outerQuery)
.OrderBy(e => e.PresentationUniqueKey);
if (filter.OrderBy.Count != 0
|| filter.SimilarTo is not null
|| !string.IsNullOrEmpty(filter.SearchTerm))
{
query = ApplyOrder(query, filter);
}
else
{
query = query.OrderBy(e => e.SortName);
}
if (filter.Limit.HasValue || filter.StartIndex.HasValue)
{
var offset = filter.StartIndex ?? 0;
if (offset > 0)
{
query = query.Skip(offset);
}
if (filter.Limit.HasValue)
{
query.Take(filter.Limit.Value);
}
}
var result = new QueryResult<(BaseItem, ItemCounts)>();
string countText = string.Empty;
if (filter.EnableTotalRecordCount)
{
result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count();
}
var resultQuery = query.Select(e => new
{
item = e,
itemCount = new ItemCounts()
{
SeriesCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Series),
EpisodeCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Episode),
MovieCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Movie),
AlbumCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
ArtistCount = e.ItemValues!.Count(e => e.Type == 0 || e.Type == 1),
SongCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
TrailerCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Trailer),
}
});
result.StartIndex = filter.StartIndex ?? 0;
result.Items = resultQuery.ToImmutableArray().Select(e =>
{
return (DeserialiseBaseItem(e.item), e.itemCount);
}).ToImmutableArray();
return result;
}
/// <inheritdoc /> /// <inheritdoc />
public void DeleteItem(Guid id) public void DeleteItem(Guid id)
{ {
ArgumentNullException.ThrowIfNull(id.IsEmpty() ? null : id); ArgumentNullException.ThrowIfNull(id.IsEmpty() ? null : id);
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction(); using var transaction = context.Database.BeginTransaction();
context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
@ -291,7 +79,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
/// <inheritdoc /> /// <inheritdoc />
public void UpdateInheritedValues() public void UpdateInheritedValues()
{ {
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction(); using var transaction = context.Database.BeginTransaction();
context.ItemValues.Where(e => e.Type == 6).ExecuteDelete(); context.ItemValues.Where(e => e.Type == 6).ExecuteDelete();
@ -324,7 +112,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
ArgumentNullException.ThrowIfNull(filter); ArgumentNullException.ThrowIfNull(filter);
PrepareFilterQuery(filter); PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter) var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter)
.DistinctBy(e => e.Id); .DistinctBy(e => e.Id);
@ -352,56 +140,56 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter)
{ {
return GetItemValues(filter, new[] { 0, 1 }, typeof(MusicArtist).FullName!); return GetItemValues(filter, [0, 1], typeof(MusicArtist).FullName!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter)
{ {
return GetItemValues(filter, new[] { 0 }, typeof(MusicArtist).FullName!); return GetItemValues(filter, [0], typeof(MusicArtist).FullName!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
{ {
return GetItemValues(filter, new[] { 1 }, typeof(MusicArtist).FullName!); return GetItemValues(filter, [1], typeof(MusicArtist).FullName!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter)
{ {
return GetItemValues(filter, new[] { 3 }, typeof(Studio).FullName!); return GetItemValues(filter, [3], typeof(Studio).FullName!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter)
{ {
return GetItemValues(filter, new[] { 2 }, typeof(Genre).FullName!); return GetItemValues(filter, [2], typeof(Genre).FullName!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter) public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
{ {
return GetItemValues(filter, new[] { 2 }, typeof(MusicGenre).FullName!); return GetItemValues(filter, [2], typeof(MusicGenre).FullName!);
} }
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<string> GetStudioNames() public IReadOnlyList<string> GetStudioNames()
{ {
return GetItemValueNames(new[] { 3 }, Array.Empty<string>(), Array.Empty<string>()); return GetItemValueNames([3], Array.Empty<string>(), Array.Empty<string>());
} }
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<string> GetAllArtistNames() public IReadOnlyList<string> GetAllArtistNames()
{ {
return GetItemValueNames(new[] { 0, 1 }, Array.Empty<string>(), Array.Empty<string>()); return GetItemValueNames([0, 1], Array.Empty<string>(), Array.Empty<string>());
} }
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<string> GetMusicGenreNames() public IReadOnlyList<string> GetMusicGenreNames()
{ {
return GetItemValueNames( return GetItemValueNames(
new[] { 2 }, [2],
new string[] new string[]
{ {
typeof(Audio).FullName!, typeof(Audio).FullName!,
@ -416,7 +204,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
public IReadOnlyList<string> GetGenreNames() public IReadOnlyList<string> GetGenreNames()
{ {
return GetItemValueNames( return GetItemValueNames(
new[] { 2 }, [2],
Array.Empty<string>(), Array.Empty<string>(),
new string[] new string[]
{ {
@ -443,7 +231,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
PrepareFilterQuery(filter); PrepareFilterQuery(filter);
var result = new QueryResult<BaseItemDto>(); var result = new QueryResult<BaseItemDto>();
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems, context, filter) var dbQuery = TranslateQuery(context.BaseItems, context, filter)
.DistinctBy(e => e.Id); .DistinctBy(e => e.Id);
if (filter.EnableTotalRecordCount) if (filter.EnableTotalRecordCount)
@ -477,7 +265,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
ArgumentNullException.ThrowIfNull(filter); ArgumentNullException.ThrowIfNull(filter);
PrepareFilterQuery(filter); PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems, context, filter) var dbQuery = TranslateQuery(context.BaseItems, context, filter)
.DistinctBy(e => e.Id); .DistinctBy(e => e.Id);
if (filter.Limit.HasValue || filter.StartIndex.HasValue) if (filter.Limit.HasValue || filter.StartIndex.HasValue)
@ -505,7 +293,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
// Hack for right now since we currently don't support filtering out these duplicates within a query // Hack for right now since we currently don't support filtering out these duplicates within a query
PrepareFilterQuery(filter); PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems, context, filter); var dbQuery = TranslateQuery(context.BaseItems, context, filter);
return dbQuery.Count(); return dbQuery.Count();
@ -646,7 +434,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
var excludeTypes = filter.ExcludeItemTypes; var excludeTypes = filter.ExcludeItemTypes;
if (excludeTypes.Length == 1) if (excludeTypes.Length == 1)
{ {
if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName)) if (itemTypeLookup.BaseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
{ {
baseQuery = baseQuery.Where(e => e.Type != excludeTypeName); baseQuery = baseQuery.Where(e => e.Type != excludeTypeName);
} }
@ -656,7 +444,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
var excludeTypeName = new List<string>(); var excludeTypeName = new List<string>();
foreach (var excludeType in excludeTypes) foreach (var excludeType in excludeTypes)
{ {
if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) if (itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
{ {
excludeTypeName.Add(baseItemKindName!); excludeTypeName.Add(baseItemKindName!);
} }
@ -667,7 +455,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
} }
else if (includeTypes.Length == 1) else if (includeTypes.Length == 1)
{ {
if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName)) if (itemTypeLookup.BaseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
{ {
baseQuery = baseQuery.Where(e => e.Type == includeTypeName); baseQuery = baseQuery.Where(e => e.Type == includeTypeName);
} }
@ -677,7 +465,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
var includeTypeName = new List<string>(); var includeTypeName = new List<string>();
foreach (var includeType in includeTypes) foreach (var includeType in includeTypes)
{ {
if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName)) if (itemTypeLookup.BaseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
{ {
includeTypeName.Add(baseItemKindName!); includeTypeName.Add(baseItemKindName!);
} }
@ -1421,7 +1209,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
ArgumentNullException.ThrowIfNull(item); ArgumentNullException.ThrowIfNull(item);
var images = SerializeImages(item.ImageInfos); var images = SerializeImages(item.ImageInfos);
using var db = _dbProvider.CreateDbContext(); using var db = dbProvider.CreateDbContext();
db.BaseItems db.BaseItems
.Where(e => e.Id.Equals(item.Id)) .Where(e => e.Id.Equals(item.Id))
@ -1457,7 +1245,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags); tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags);
} }
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
foreach (var item in tuples) foreach (var item in tuples)
{ {
var entity = Map(item.Item); var entity = Map(item.Item);
@ -1501,7 +1289,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
throw new ArgumentException("Guid can't be empty", nameof(id)); throw new ArgumentException("Guid can't be empty", nameof(id));
} }
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id)); var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id));
if (item is null) if (item is null)
{ {
@ -1832,7 +1620,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
private IReadOnlyList<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes) private IReadOnlyList<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes)
{ {
using var context = _dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
var query = context.ItemValues var query = context.ItemValues
.Where(e => itemValueTypes.Contains(e.Type)); .Where(e => itemValueTypes.Contains(e.Type));
@ -1857,6 +1645,118 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
return Map(baseItemEntity, dto); return Map(baseItemEntity, dto);
} }
private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, int[] itemValueTypes, string returnType)
{
ArgumentNullException.ThrowIfNull(filter);
if (!filter.Limit.HasValue)
{
filter.EnableTotalRecordCount = false;
}
using var context = dbProvider.CreateDbContext();
var innerQuery = new InternalItemsQuery(filter.User)
{
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
};
var query = TranslateQuery(context.BaseItems, context, innerQuery);
query = query.Where(e => e.Type == returnType && e.ItemValues!.Any(f => e.CleanName == f.CleanValue && itemValueTypes.Contains(f.Type)));
var outerQuery = 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,
SimilarTo = filter.SimilarTo,
ExcludeItemIds = filter.ExcludeItemIds
};
query = TranslateQuery(query, context, outerQuery)
.OrderBy(e => e.PresentationUniqueKey);
if (filter.OrderBy.Count != 0
|| filter.SimilarTo is not null
|| !string.IsNullOrEmpty(filter.SearchTerm))
{
query = ApplyOrder(query, filter);
}
else
{
query = query.OrderBy(e => e.SortName);
}
if (filter.Limit.HasValue || filter.StartIndex.HasValue)
{
var offset = filter.StartIndex ?? 0;
if (offset > 0)
{
query = query.Skip(offset);
}
if (filter.Limit.HasValue)
{
query.Take(filter.Limit.Value);
}
}
var result = new QueryResult<(BaseItem, ItemCounts)>();
string countText = string.Empty;
if (filter.EnableTotalRecordCount)
{
result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count();
}
var resultQuery = query.Select(e => new
{
item = e,
itemCount = new ItemCounts()
{
SeriesCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Series),
EpisodeCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Episode),
MovieCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Movie),
AlbumCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
ArtistCount = e.ItemValues!.Count(e => e.Type == 0 || e.Type == 1),
SongCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.MusicAlbum),
TrailerCount = e.ItemValues!.Count(e => e.Type == (int)BaseItemKind.Trailer),
}
});
result.StartIndex = filter.StartIndex ?? 0;
result.Items = resultQuery.ToImmutableArray().Select(e =>
{
return (DeserialiseBaseItem(e.item), e.itemCount);
}).ToImmutableArray();
return result;
}
private static void PrepareFilterQuery(InternalItemsQuery query) private static void PrepareFilterQuery(InternalItemsQuery query)
{ {
if (query.Limit.HasValue && query.EnableGroupByMetadataKey) if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
@ -2046,12 +1946,12 @@ public sealed class BaseItemManager : IItemRepository, IDisposable
return null; return null;
} }
return _appHost.ReverseVirtualPath(path); return appHost.ReverseVirtualPath(path);
} }
private string RestorePath(string path) private string RestorePath(string path)
{ {
return _appHost.ExpandVirtualPath(path); return appHost.ExpandVirtualPath(path);
} }
internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan<char> value) internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan<char> value)

View File

@ -14,46 +14,69 @@ namespace Jellyfin.Server.Implementations.Item;
/// <summary> /// <summary>
/// The Chapter manager. /// The Chapter manager.
/// </summary> /// </summary>
public class ChapterManager : IChapterManager public class ChapterRepository : IChapterRepository
{ {
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChapterManager"/> class. /// Initializes a new instance of the <see cref="ChapterRepository"/> class.
/// </summary> /// </summary>
/// <param name="dbProvider">The EFCore provider.</param> /// <param name="dbProvider">The EFCore provider.</param>
/// <param name="imageProcessor">The Image Processor.</param> /// <param name="imageProcessor">The Image Processor.</param>
public ChapterManager(IDbContextFactory<JellyfinDbContext> dbProvider, IImageProcessor imageProcessor) public ChapterRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IImageProcessor imageProcessor)
{ {
_dbProvider = dbProvider; _dbProvider = dbProvider;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
} }
/// <inheritdoc cref="IChapterManager"/> /// <inheritdoc cref="IChapterRepository"/>
public ChapterInfo? GetChapter(BaseItemDto baseItem, int index) public ChapterInfo? GetChapter(BaseItemDto baseItem, int index)
{
return GetChapter(baseItem.Id, index);
}
/// <inheritdoc cref="IChapterRepository"/>
public IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem)
{
return GetChapters(baseItem.Id);
}
/// <inheritdoc cref="IChapterRepository"/>
public ChapterInfo? GetChapter(Guid baseItemId, int index)
{ {
using var context = _dbProvider.CreateDbContext(); using var context = _dbProvider.CreateDbContext();
var chapter = context.Chapters.FirstOrDefault(e => e.ItemId.Equals(baseItem.Id) && e.ChapterIndex == index); var chapter = context.Chapters
.Select(e => new
{
chapter = e,
baseItemPath = e.Item.Path
})
.FirstOrDefault(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index);
if (chapter is not null) if (chapter is not null)
{ {
return Map(chapter, baseItem); return Map(chapter.chapter, chapter.baseItemPath!);
} }
return null; return null;
} }
/// <inheritdoc cref="IChapterManager"/> /// <inheritdoc cref="IChapterRepository"/>
public IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem) public IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId)
{ {
using var context = _dbProvider.CreateDbContext(); using var context = _dbProvider.CreateDbContext();
return context.Chapters.Where(e => e.ItemId.Equals(baseItem.Id)) return context.Chapters.Where(e => e.ItemId.Equals(baseItemId))
.Select(e => new
{
chapter = e,
baseItemPath = e.Item.Path
})
.ToList() .ToList()
.Select(e => Map(e, baseItem)) .Select(e => Map(e.chapter, e.baseItemPath!))
.ToImmutableArray(); .ToImmutableArray();
} }
/// <inheritdoc cref="IChapterManager"/> /// <inheritdoc cref="IChapterRepository"/>
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters) public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
{ {
using var context = _dbProvider.CreateDbContext(); using var context = _dbProvider.CreateDbContext();
@ -80,20 +103,21 @@ public class ChapterManager : IChapterManager
ImageDateModified = chapterInfo.ImageDateModified, ImageDateModified = chapterInfo.ImageDateModified,
ImagePath = chapterInfo.ImagePath, ImagePath = chapterInfo.ImagePath,
ItemId = itemId, ItemId = itemId,
Name = chapterInfo.Name Name = chapterInfo.Name,
Item = null!
}; };
} }
private ChapterInfo Map(Chapter chapterInfo, BaseItemDto baseItem) private ChapterInfo Map(Chapter chapterInfo, string baseItemPath)
{ {
var info = new ChapterInfo() var chapterEntity = new ChapterInfo()
{ {
StartPositionTicks = chapterInfo.StartPositionTicks, StartPositionTicks = chapterInfo.StartPositionTicks,
ImageDateModified = chapterInfo.ImageDateModified.GetValueOrDefault(), ImageDateModified = chapterInfo.ImageDateModified.GetValueOrDefault(),
ImagePath = chapterInfo.ImagePath, ImagePath = chapterInfo.ImagePath,
Name = chapterInfo.Name, Name = chapterInfo.Name,
}; };
info.ImageTag = _imageProcessor.GetImageCacheTag(baseItem, info); chapterEntity.ImageTag = _imageProcessor.GetImageCacheTag(baseItemPath, chapterEntity.ImageDateModified);
return info; return chapterEntity;
} }
} }

View File

@ -14,7 +14,7 @@ namespace Jellyfin.Server.Implementations.Item;
/// Manager for handling Media Attachments. /// Manager for handling Media Attachments.
/// </summary> /// </summary>
/// <param name="dbProvider">Efcore Factory.</param> /// <param name="dbProvider">Efcore Factory.</param>
public class MediaAttachmentManager(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentManager public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentRepository
{ {
/// <inheritdoc /> /// <inheritdoc />
public void SaveMediaAttachments( public void SaveMediaAttachments(

View File

@ -13,12 +13,12 @@ using Microsoft.EntityFrameworkCore;
namespace Jellyfin.Server.Implementations.Item; namespace Jellyfin.Server.Implementations.Item;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MediaStreamManager"/> class. /// Initializes a new instance of the <see cref="MediaStreamRepository"/> class.
/// </summary> /// </summary>
/// <param name="dbProvider"></param> /// <param name="dbProvider">The EFCore db factory.</param>
/// <param name="serverApplicationHost"></param> /// <param name="serverApplicationHost">The Application host.</param>
/// <param name="localization"></param> /// <param name="localization">The Localisation Provider.</param>
public class MediaStreamManager(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) : IMediaStreamManager public class MediaStreamRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) : IMediaStreamRepository
{ {
/// <inheritdoc /> /// <inheritdoc />
public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken) public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)

View File

@ -16,13 +16,13 @@ namespace Jellyfin.Server.Implementations.Item;
/// </summary> /// </summary>
/// <param name="dbProvider">Efcore Factory.</param> /// <param name="dbProvider">Efcore Factory.</param>
/// <remarks> /// <remarks>
/// Initializes a new instance of the <see cref="PeopleManager"/> class. /// Initializes a new instance of the <see cref="PeopleRepository"/> class.
/// </remarks> /// </remarks>
/// <param name="dbProvider">The EFCore Context factory.</param> public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IPeopleRepository
public class PeopleManager(IDbContextFactory<JellyfinDbContext> dbProvider) : IPeopleManager
{ {
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider = dbProvider; private readonly IDbContextFactory<JellyfinDbContext> _dbProvider = dbProvider;
/// <inheritdoc/>
public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter) public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter)
{ {
using var context = _dbProvider.CreateDbContext(); using var context = _dbProvider.CreateDbContext();
@ -37,6 +37,7 @@ public class PeopleManager(IDbContextFactory<JellyfinDbContext> dbProvider) : IP
return dbQuery.ToList().Select(Map).ToImmutableArray(); return dbQuery.ToList().Select(Map).ToImmutableArray();
} }
/// <inheritdoc/>
public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter) public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter)
{ {
using var context = _dbProvider.CreateDbContext(); using var context = _dbProvider.CreateDbContext();

View File

@ -106,7 +106,7 @@ public class JellyfinDbContext : DbContext
/// <summary> /// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the user data. /// Gets the <see cref="DbSet{TEntity}"/> containing the user data.
/// </summary> /// </summary>
public DbSet<BaseItem> BaseItems => Set<BaseItem>(); public DbSet<BaseItemEntity> BaseItems => Set<BaseItemEntity>();
/// <summary> /// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the user data. /// Gets the <see cref="DbSet{TEntity}"/> containing the user data.

View File

@ -8,10 +8,10 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration;
/// <summary> /// <summary>
/// Configuration for BaseItem. /// Configuration for BaseItem.
/// </summary> /// </summary>
public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItem> public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public void Configure(EntityTypeBuilder<BaseItem> builder) public void Configure(EntityTypeBuilder<BaseItemEntity> builder)
{ {
builder.HasNoKey(); builder.HasNoKey();
builder.HasIndex(e => e.Path); builder.HasIndex(e => e.Path);

View File

@ -13,7 +13,7 @@ public class BaseItemProviderConfiguration : IEntityTypeConfiguration<BaseItemPr
/// <inheritdoc/> /// <inheritdoc/>
public void Configure(EntityTypeBuilder<BaseItemProvider> builder) public void Configure(EntityTypeBuilder<BaseItemProvider> builder)
{ {
builder.HasNoKey(); builder.HasKey(e => new { e.ItemId, e.ProviderId });
builder.HasOne(e => e.Item); builder.HasOne(e => e.Item);
builder.HasIndex(e => new { e.ProviderId, e.ProviderValue, e.ItemId }); builder.HasIndex(e => new { e.ProviderId, e.ProviderValue, e.ItemId });
} }

View File

@ -1,24 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Providers.Chapters
{
public class ChapterManager : IChapterManager
{
public ChapterManager(IDbContextFactory<JellyfinDbContext> dbProvider)
{
_itemRepo = itemRepo;
}
/// <inheritdoc />
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
{
_itemRepo.SaveChapters(itemId, chapters);
}
}
}

View File

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Chapters
{
/// <summary>
/// Interface IChapterManager.
/// </summary>
public interface IChapterManager
{
/// <summary>
/// Saves the chapters.
/// </summary>
/// <param name="itemId">The item.</param>
/// <param name="chapters">The set of chapters.</param>
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
/// <summary>
/// Gets all chapters associated with the baseItem.
/// </summary>
/// <param name="baseItem">The baseitem.</param>
/// <returns>A readonly list of chapter instances.</returns>
IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem);
/// <summary>
/// Gets a single chapter of a BaseItem on a specific index.
/// </summary>
/// <param name="baseItem">The baseitem.</param>
/// <param name="index">The index of that chapter.</param>
/// <returns>A chapter instance.</returns>
ChapterInfo? GetChapter(BaseItemDto baseItem, int index);
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Chapters;
/// <summary>
/// Interface IChapterManager.
/// </summary>
public interface IChapterRepository
{
/// <summary>
/// Saves the chapters.
/// </summary>
/// <param name="itemId">The item.</param>
/// <param name="chapters">The set of chapters.</param>
void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
/// <summary>
/// Gets all chapters associated with the baseItem.
/// </summary>
/// <param name="baseItem">The baseitem.</param>
/// <returns>A readonly list of chapter instances.</returns>
IReadOnlyList<ChapterInfo> GetChapters(BaseItemDto baseItem);
/// <summary>
/// Gets a single chapter of a BaseItem on a specific index.
/// </summary>
/// <param name="baseItem">The baseitem.</param>
/// <param name="index">The index of that chapter.</param>
/// <returns>A chapter instance.</returns>
ChapterInfo? GetChapter(BaseItemDto baseItem, int index);
/// <summary>
/// Gets all chapters associated with the baseItem.
/// </summary>
/// <param name="baseItemId">The BaseItems id.</param>
/// <returns>A readonly list of chapter instances.</returns>
IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId);
/// <summary>
/// Gets a single chapter of a BaseItem on a specific index.
/// </summary>
/// <param name="baseItemId">The BaseItems id.</param>
/// <param name="index">The index of that chapter.</param>
/// <returns>A chapter instance.</returns>
ChapterInfo? GetChapter(Guid baseItemId, int index);
}

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Drawing namespace MediaBrowser.Controller.Drawing
@ -57,6 +58,22 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>BlurHash.</returns> /// <returns>BlurHash.</returns>
string GetImageBlurHash(string path, ImageDimensions imageDimensions); string GetImageBlurHash(string path, ImageDimensions imageDimensions);
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="baseItemPath">The items basePath.</param>
/// <param name="imageDateModified">The image last modification date.</param>
/// <returns>Guid.</returns>
string? GetImageCacheTag(string baseItemPath, DateTime imageDateModified);
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="image">The image.</param>
/// <returns>Guid.</returns>
string? GetImageCacheTag(BaseItemDto item, ChapterInfo image);
/// <summary> /// <summary>
/// Gets the image cache tag. /// Gets the image cache tag.
/// </summary> /// </summary>
@ -65,6 +82,14 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>Guid.</returns> /// <returns>Guid.</returns>
string GetImageCacheTag(BaseItem item, ItemImageInfo image); string GetImageCacheTag(BaseItem item, ItemImageInfo image);
/// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="image">The image.</param>
/// <returns>Guid.</returns>
string GetImageCacheTag(BaseItemDto item, ItemImageInfo image);
string? GetImageCacheTag(BaseItem item, ChapterInfo chapter); string? GetImageCacheTag(BaseItem item, ChapterInfo chapter);
string? GetImageCacheTag(User user); string? GetImageCacheTag(User user);

View File

@ -16,6 +16,7 @@ using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -479,6 +480,8 @@ namespace MediaBrowser.Controller.Entities
public static IItemRepository ItemRepository { get; set; } public static IItemRepository ItemRepository { get; set; }
public static IChapterRepository ChapterRepository { get; set; }
public static IFileSystem FileSystem { get; set; } public static IFileSystem FileSystem { get; set; }
public static IUserDataManager UserDataManager { get; set; } public static IUserDataManager UserDataManager { get; set; }
@ -2031,7 +2034,7 @@ namespace MediaBrowser.Controller.Entities
{ {
if (imageType == ImageType.Chapter) if (imageType == ImageType.Chapter)
{ {
var chapter = ItemRepository.GetChapter(this, imageIndex); var chapter = ChapterRepository.GetChapter(this.Id, imageIndex);
if (chapter is null) if (chapter is null)
{ {
@ -2081,7 +2084,7 @@ namespace MediaBrowser.Controller.Entities
if (image.Type == ImageType.Chapter) if (image.Type == ImageType.Chapter)
{ {
var chapters = ItemRepository.GetChapters(this); var chapters = ChapterRepository.GetChapters(this.Id);
for (var i = 0; i < chapters.Count; i++) for (var i = 0; i < chapters.Count; i++)
{ {
if (chapters[i].ImagePath == image.Path) if (chapters[i].ImagePath == image.Path)

View File

@ -52,7 +52,6 @@ public interface IItemRepository : IDisposable
/// <returns>List&lt;Guid&gt;.</returns> /// <returns>List&lt;Guid&gt;.</returns>
IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter); IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter);
/// <summary> /// <summary>
/// Gets the item list. /// Gets the item list.
/// </summary> /// </summary>

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Persistence;
/// <summary>
/// Provides static lookup data for <see cref="ItemFields"/> and <see cref="BaseItemKind"/> for the domain.
/// </summary>
public interface IItemTypeLookup
{
/// <summary>
/// Gets all values of the ItemFields type.
/// </summary>
public IReadOnlyList<ItemFields> AllItemFields { get; }
/// <summary>
/// Gets all BaseItemKinds that are considered Programs.
/// </summary>
public IReadOnlyList<BaseItemKind> ProgramTypes { get; }
/// <summary>
/// Gets all BaseItemKinds that should be excluded from parent lookup.
/// </summary>
public IReadOnlyList<BaseItemKind> ProgramExcludeParentTypes { get; }
/// <summary>
/// Gets all BaseItemKinds that are considered to be provided by services.
/// </summary>
public IReadOnlyList<BaseItemKind> ServiceTypes { get; }
/// <summary>
/// Gets all BaseItemKinds that have a StartDate.
/// </summary>
public IReadOnlyList<BaseItemKind> StartDateTypes { get; }
/// <summary>
/// Gets all BaseItemKinds that are considered Series.
/// </summary>
public IReadOnlyList<BaseItemKind> SeriesTypes { get; }
/// <summary>
/// Gets all BaseItemKinds that are not to be evaluated for Artists.
/// </summary>
public IReadOnlyList<BaseItemKind> ArtistExcludeParentTypes { get; }
/// <summary>
/// Gets all BaseItemKinds that are considered Artists.
/// </summary>
public IReadOnlyList<BaseItemKind> ArtistsTypes { get; }
/// <summary>
/// Gets mapping for all BaseItemKinds and their expected serialisaition target.
/// </summary>
public IDictionary<BaseItemKind, string?> BaseItemKindNames { get; }
}

View File

@ -9,9 +9,8 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence; namespace MediaBrowser.Controller.Persistence;
public interface IMediaAttachmentManager public interface IMediaAttachmentRepository
{ {
/// <summary> /// <summary>
/// Gets the media attachments. /// Gets the media attachments.
/// </summary> /// </summary>

View File

@ -9,14 +9,17 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence; namespace MediaBrowser.Controller.Persistence;
public interface IMediaStreamManager /// <summary>
/// Provides methods for accessing MediaStreams.
/// </summary>
public interface IMediaStreamRepository
{ {
/// <summary> /// <summary>
/// Gets the media streams. /// Gets the media streams.
/// </summary> /// </summary>
/// <param name="filter">The query.</param> /// <param name="filter">The query.</param>
/// <returns>IEnumerable{MediaStream}.</returns> /// <returns>IEnumerable{MediaStream}.</returns>
List<MediaStream> GetMediaStreams(MediaStreamQuery filter); IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter);
/// <summary> /// <summary>
/// Saves the media streams. /// Saves the media streams.

View File

@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Persistence; namespace MediaBrowser.Controller.Persistence;
public interface IPeopleManager public interface IPeopleRepository
{ {
/// <summary> /// <summary>
/// Gets the people. /// Gets the people.
@ -30,5 +30,4 @@ public interface IPeopleManager
/// <param name="filter">The query.</param> /// <param name="filter">The query.</param>
/// <returns>List&lt;System.String&gt;.</returns> /// <returns>List&lt;System.String&gt;.</returns>
IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter); IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter);
} }

View File

@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IEncodingManager _encodingManager; private readonly IEncodingManager _encodingManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager; private readonly ISubtitleManager _subtitleManager;
private readonly IChapterManager _chapterManager; private readonly IChapterRepository _chapterManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly AudioResolver _audioResolver; private readonly AudioResolver _audioResolver;
private readonly SubtitleResolver _subtitleResolver; private readonly SubtitleResolver _subtitleResolver;
@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.MediaInfo
IEncodingManager encodingManager, IEncodingManager encodingManager,
IServerConfigurationManager config, IServerConfigurationManager config,
ISubtitleManager subtitleManager, ISubtitleManager subtitleManager,
IChapterManager chapterManager, IChapterRepository chapterManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
AudioResolver audioResolver, AudioResolver audioResolver,
SubtitleResolver subtitleResolver) SubtitleResolver subtitleResolver)

View File

@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param> /// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param>
/// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param> /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
/// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param> /// <param name="chapterManager">Instance of the <see cref="IChapterRepository"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo
IEncodingManager encodingManager, IEncodingManager encodingManager,
IServerConfigurationManager config, IServerConfigurationManager config,
ISubtitleManager subtitleManager, ISubtitleManager subtitleManager,
IChapterManager chapterManager, IChapterRepository chapterManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IFileSystem fileSystem, IFileSystem fileSystem,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,

View File

@ -15,6 +15,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
@ -403,10 +404,34 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
return _imageEncoder.GetImageBlurHash(xComp, yComp, path); return _imageEncoder.GetImageBlurHash(xComp, yComp, path);
} }
/// <inheritdoc />
public string GetImageCacheTag(string baseItemPath, DateTime imageDateModified)
=> (baseItemPath + imageDateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
/// <inheritdoc /> /// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image) public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
/// <inheritdoc />
public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image)
=> (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
/// <inheritdoc />
public string? GetImageCacheTag(BaseItemDto item, ChapterInfo chapter)
{
if (chapter.ImagePath is null)
{
return null;
}
return GetImageCacheTag(item, new ItemImageInfo
{
Path = chapter.ImagePath,
Type = ImageType.Chapter,
DateModified = chapter.ImageDateModified
});
}
/// <inheritdoc /> /// <inheritdoc />
public string? GetImageCacheTag(BaseItem item, ChapterInfo chapter) public string? GetImageCacheTag(BaseItem item, ChapterInfo chapter)
{ {