diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bdf013b5d6..fbec4726fc 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -40,6 +40,7 @@ using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.Networking.Manager; using Jellyfin.Networking.Udp; using Jellyfin.Server.Implementations; +using Jellyfin.Server.Implementations.Item; using Jellyfin.Server.Implementations.MediaSegments; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -83,7 +84,6 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Lyric; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.Tmdb; @@ -494,7 +494,12 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -539,8 +544,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -578,8 +581,6 @@ namespace Emby.Server.Implementations } } - ((SqliteItemRepository)Resolve()).Initialize(); - var localizationManager = (LocalizationManager)Resolve(); await localizationManager.LoadAll().ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs new file mode 100644 index 0000000000..14dc68a327 --- /dev/null +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -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; + +/// +/// Provides static topic based lookups for the BaseItemKind. +/// +public class ItemTypeLookup : IItemTypeLookup +{ + /// + /// Gets all values of the ItemFields type. + /// + public IReadOnlyList AllItemFields { get; } = Enum.GetValues(); + + /// + /// Gets all BaseItemKinds that are considered Programs. + /// + public IReadOnlyList ProgramTypes { get; } = + [ + BaseItemKind.Program, + BaseItemKind.TvChannel, + BaseItemKind.LiveTvProgram, + BaseItemKind.LiveTvChannel + ]; + + /// + /// Gets all BaseItemKinds that should be excluded from parent lookup. + /// + public IReadOnlyList ProgramExcludeParentTypes { get; } = + [ + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicArtist, + BaseItemKind.PhotoAlbum + ]; + + /// + /// Gets all BaseItemKinds that are considered to be provided by services. + /// + public IReadOnlyList ServiceTypes { get; } = + [ + BaseItemKind.TvChannel, + BaseItemKind.LiveTvChannel + ]; + + /// + /// Gets all BaseItemKinds that have a StartDate. + /// + public IReadOnlyList StartDateTypes { get; } = + [ + BaseItemKind.Program, + BaseItemKind.LiveTvProgram + ]; + + /// + /// Gets all BaseItemKinds that are considered Series. + /// + public IReadOnlyList SeriesTypes { get; } = + [ + BaseItemKind.Book, + BaseItemKind.AudioBook, + BaseItemKind.Episode, + BaseItemKind.Season + ]; + + /// + /// Gets all BaseItemKinds that are not to be evaluated for Artists. + /// + public IReadOnlyList ArtistExcludeParentTypes { get; } = + [ + BaseItemKind.Series, + BaseItemKind.Season, + BaseItemKind.PhotoAlbum + ]; + + /// + /// Gets all BaseItemKinds that are considered Artists. + /// + public IReadOnlyList ArtistsTypes { get; } = + [ + BaseItemKind.Audio, + BaseItemKind.MusicAlbum, + BaseItemKind.MusicVideo, + BaseItemKind.AudioBook + ]; + + /// + /// Gets mapping for all BaseItemKinds and their expected serialisaition target. + /// + public IDictionary BaseItemKindNames { get; } = new Dictionary() + { + { 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(); +} diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index eb55e32c50..ea78968617 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.MediaEncoder private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly IMediaEncoder _encoder; - private readonly IChapterManager _chapterManager; + private readonly IChapterRepository _chapterManager; private readonly ILibraryManager _libraryManager; /// @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.MediaEncoder ILogger logger, IFileSystem fileSystem, IMediaEncoder encoder, - IChapterManager chapterManager, + IChapterRepository chapterManager, ILibraryManager libraryManager) { _logger = logger; diff --git a/Jellyfin.Data/Entities/AncestorId.cs b/Jellyfin.Data/Entities/AncestorId.cs index dc83b763ee..3839b1ae46 100644 --- a/Jellyfin.Data/Entities/AncestorId.cs +++ b/Jellyfin.Data/Entities/AncestorId.cs @@ -13,7 +13,7 @@ public class AncestorId public Guid ItemId { get; set; } - public required BaseItem Item { get; set; } + public required BaseItemEntity Item { get; set; } public string? AncestorIdText { get; set; } } diff --git a/Jellyfin.Data/Entities/AttachmentStreamInfo.cs b/Jellyfin.Data/Entities/AttachmentStreamInfo.cs index 858465424b..056d5b05ec 100644 --- a/Jellyfin.Data/Entities/AttachmentStreamInfo.cs +++ b/Jellyfin.Data/Entities/AttachmentStreamInfo.cs @@ -7,7 +7,7 @@ public class AttachmentStreamInfo { public required Guid ItemId { get; set; } - public required BaseItem Item { get; set; } + public required BaseItemEntity Item { get; set; } public required int Index { get; set; } diff --git a/Jellyfin.Data/Entities/BaseItem.cs b/Jellyfin.Data/Entities/BaseItemEntity.cs similarity index 97% rename from Jellyfin.Data/Entities/BaseItem.cs rename to Jellyfin.Data/Entities/BaseItemEntity.cs index 0e67a7ca45..92b5caf057 100644 --- a/Jellyfin.Data/Entities/BaseItem.cs +++ b/Jellyfin.Data/Entities/BaseItemEntity.cs @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -public class BaseItem +public class BaseItemEntity { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] @@ -160,6 +160,7 @@ public class BaseItem public long? Size { get; set; } +#pragma warning disable CA2227 // Collection properties should be read only public ICollection? Peoples { get; set; } public ICollection? UserData { get; set; } diff --git a/Jellyfin.Data/Entities/BaseItemProvider.cs b/Jellyfin.Data/Entities/BaseItemProvider.cs index 6f8e1c39bb..1fc721d6a2 100644 --- a/Jellyfin.Data/Entities/BaseItemProvider.cs +++ b/Jellyfin.Data/Entities/BaseItemProvider.cs @@ -5,11 +5,28 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities; +/// +/// Represents an Key-Value relaten of an BaseItem's provider. +/// public class BaseItemProvider { + /// + /// Gets or Sets the reference ItemId. + /// public Guid ItemId { get; set; } - public required BaseItem Item { get; set; } - public string ProviderId { get; set; } - public string ProviderValue { get; set; } + /// + /// Gets or Sets the reference BaseItem. + /// + public required BaseItemEntity Item { get; set; } + + /// + /// Gets or Sets the ProvidersId. + /// + public required string ProviderId { get; set; } + + /// + /// Gets or Sets the Providers Value. + /// + public required string ProviderValue { get; set; } } diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs index ad119d1c6b..be353b5da4 100644 --- a/Jellyfin.Data/Entities/Chapter.cs +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -10,7 +10,7 @@ public class Chapter { public Guid ItemId { get; set; } - public required BaseItem Item { get; set; } + public required BaseItemEntity Item { get; set; } public required int ChapterIndex { get; set; } diff --git a/Jellyfin.Data/Entities/ItemValue.cs b/Jellyfin.Data/Entities/ItemValue.cs index a3c0908bbe..1063aaa8b2 100644 --- a/Jellyfin.Data/Entities/ItemValue.cs +++ b/Jellyfin.Data/Entities/ItemValue.cs @@ -5,12 +5,33 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities; +/// +/// Represents an ItemValue for a BaseItem. +/// public class ItemValue { + /// + /// Gets or Sets the reference ItemId. + /// public Guid ItemId { get; set; } - public required BaseItem Item { get; set; } + /// + /// Gets or Sets the referenced BaseItem. + /// + public required BaseItemEntity Item { get; set; } + + /// + /// Gets or Sets the Type. + /// public required int Type { get; set; } + + /// + /// Gets or Sets the Value. + /// public required string Value { get; set; } + + /// + /// Gets or Sets the sanatised Value. + /// public required string CleanValue { get; set; } } diff --git a/Jellyfin.Data/Entities/MediaStreamInfo.cs b/Jellyfin.Data/Entities/MediaStreamInfo.cs index 3b89ca62f8..992f33ecf8 100644 --- a/Jellyfin.Data/Entities/MediaStreamInfo.cs +++ b/Jellyfin.Data/Entities/MediaStreamInfo.cs @@ -7,7 +7,7 @@ public class MediaStreamInfo { public Guid ItemId { get; set; } - public required BaseItem Item { get; set; } + public required BaseItemEntity Item { get; set; } public int StreamIndex { get; set; } diff --git a/Jellyfin.Data/Entities/People.cs b/Jellyfin.Data/Entities/People.cs index 014a0f1c97..8eb23f5e4d 100644 --- a/Jellyfin.Data/Entities/People.cs +++ b/Jellyfin.Data/Entities/People.cs @@ -4,14 +4,44 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities; + +/// +/// People entity. +/// public class People { - public Guid ItemId { get; set; } - public BaseItem Item { get; set; } + /// + /// Gets or Sets The ItemId. + /// + public required Guid ItemId { get; set; } + /// + /// Gets or Sets Reference Item. + /// + public required BaseItemEntity Item { get; set; } + + /// + /// Gets or Sets the Persons Name. + /// public required string Name { get; set; } + + /// + /// Gets or Sets the Role. + /// public string? Role { get; set; } + + /// + /// Gets or Sets the Type. + /// public string? PersonType { get; set; } + + /// + /// Gets or Sets the SortOrder. + /// public int? SortOrder { get; set; } + + /// + /// Gets or Sets the ListOrder. + /// public int? ListOrder { get; set; } } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemManager.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs similarity index 92% rename from Jellyfin.Server.Implementations/Item/BaseItemManager.cs rename to Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 66cc765f35..a3e617a211 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemManager.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -24,112 +24,23 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem; -using BaseItemEntity = Jellyfin.Data.Entities.BaseItem; +using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity; namespace Jellyfin.Server.Implementations.Item; /// /// Handles all storage logic for BaseItems. /// -public sealed class BaseItemManager : IItemRepository, IDisposable +/// +/// Initializes a new instance of the class. +/// +/// The db factory. +/// The Application host. +/// The static type lookup. +public sealed class BaseItemRepository(IDbContextFactory dbProvider, IServerApplicationHost appHost, IItemTypeLookup itemTypeLookup) + : IItemRepository, IDisposable { - private readonly IDbContextFactory _dbProvider; - private readonly IServerApplicationHost _appHost; - - private readonly ItemFields[] _allItemFields = Enum.GetValues(); - - 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 _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 } - }; - /// /// This holds all the types in the running assemblies /// 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 _typeMap = new ConcurrentDictionary(); private bool _disposed; - /// - /// Initializes a new instance of the class. - /// - /// The db factory. - /// The Application host. - public BaseItemManager(IDbContextFactory dbProvider, IServerApplicationHost appHost) - { - _dbProvider = dbProvider; - _appHost = appHost; - } - /// public void Dispose() { @@ -159,124 +59,12 @@ public sealed class BaseItemManager : IItemRepository, IDisposable _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; - } - /// public void DeleteItem(Guid id) { ArgumentNullException.ThrowIfNull(id.IsEmpty() ? null : id); - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); using var transaction = context.Database.BeginTransaction(); context.Peoples.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 /// public void UpdateInheritedValues() { - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); using var transaction = context.Database.BeginTransaction(); context.ItemValues.Where(e => e.Type == 6).ExecuteDelete(); @@ -324,7 +112,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable ArgumentNullException.ThrowIfNull(filter); PrepareFilterQuery(filter); - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter) .DistinctBy(e => e.Id); @@ -352,56 +140,56 @@ public sealed class BaseItemManager : IItemRepository, IDisposable /// 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!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter) { - return GetItemValues(filter, new[] { 0 }, typeof(MusicArtist).FullName!); + return GetItemValues(filter, [0], typeof(MusicArtist).FullName!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter) { - return GetItemValues(filter, new[] { 1 }, typeof(MusicArtist).FullName!); + return GetItemValues(filter, [1], typeof(MusicArtist).FullName!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter) { - return GetItemValues(filter, new[] { 3 }, typeof(Studio).FullName!); + return GetItemValues(filter, [3], typeof(Studio).FullName!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter) { - return GetItemValues(filter, new[] { 2 }, typeof(Genre).FullName!); + return GetItemValues(filter, [2], typeof(Genre).FullName!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter) { - return GetItemValues(filter, new[] { 2 }, typeof(MusicGenre).FullName!); + return GetItemValues(filter, [2], typeof(MusicGenre).FullName!); } /// public IReadOnlyList GetStudioNames() { - return GetItemValueNames(new[] { 3 }, Array.Empty(), Array.Empty()); + return GetItemValueNames([3], Array.Empty(), Array.Empty()); } /// public IReadOnlyList GetAllArtistNames() { - return GetItemValueNames(new[] { 0, 1 }, Array.Empty(), Array.Empty()); + return GetItemValueNames([0, 1], Array.Empty(), Array.Empty()); } /// public IReadOnlyList GetMusicGenreNames() { return GetItemValueNames( - new[] { 2 }, + [2], new string[] { typeof(Audio).FullName!, @@ -416,7 +204,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable public IReadOnlyList GetGenreNames() { return GetItemValueNames( - new[] { 2 }, + [2], Array.Empty(), new string[] { @@ -443,7 +231,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable PrepareFilterQuery(filter); var result = new QueryResult(); - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); var dbQuery = TranslateQuery(context.BaseItems, context, filter) .DistinctBy(e => e.Id); if (filter.EnableTotalRecordCount) @@ -477,7 +265,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable ArgumentNullException.ThrowIfNull(filter); PrepareFilterQuery(filter); - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); var dbQuery = TranslateQuery(context.BaseItems, context, filter) .DistinctBy(e => e.Id); 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 PrepareFilterQuery(filter); - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); var dbQuery = TranslateQuery(context.BaseItems, context, filter); return dbQuery.Count(); @@ -646,7 +434,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable var excludeTypes = filter.ExcludeItemTypes; 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); } @@ -656,7 +444,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable var excludeTypeName = new List(); foreach (var excludeType in excludeTypes) { - if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) + if (itemTypeLookup.BaseItemKindNames.TryGetValue(excludeType, out var baseItemKindName)) { excludeTypeName.Add(baseItemKindName!); } @@ -667,7 +455,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable } 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); } @@ -677,7 +465,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable var includeTypeName = new List(); foreach (var includeType in includeTypes) { - if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName)) + if (itemTypeLookup.BaseItemKindNames.TryGetValue(includeType, out var baseItemKindName)) { includeTypeName.Add(baseItemKindName!); } @@ -1421,7 +1209,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable ArgumentNullException.ThrowIfNull(item); var images = SerializeImages(item.ImageInfos); - using var db = _dbProvider.CreateDbContext(); + using var db = dbProvider.CreateDbContext(); db.BaseItems .Where(e => e.Id.Equals(item.Id)) @@ -1457,7 +1245,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags); } - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); foreach (var item in tuples) { 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)); } - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id)); if (item is null) { @@ -1832,7 +1620,7 @@ public sealed class BaseItemManager : IItemRepository, IDisposable private IReadOnlyList GetItemValueNames(int[] itemValueTypes, IReadOnlyList withItemTypes, IReadOnlyList excludeItemTypes) { - using var context = _dbProvider.CreateDbContext(); + using var context = dbProvider.CreateDbContext(); var query = context.ItemValues .Where(e => itemValueTypes.Contains(e.Type)); @@ -1857,6 +1645,118 @@ public sealed class BaseItemManager : IItemRepository, IDisposable 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) { if (query.Limit.HasValue && query.EnableGroupByMetadataKey) @@ -2046,12 +1946,12 @@ public sealed class BaseItemManager : IItemRepository, IDisposable return null; } - return _appHost.ReverseVirtualPath(path); + return appHost.ReverseVirtualPath(path); } private string RestorePath(string path) { - return _appHost.ExpandVirtualPath(path); + return appHost.ExpandVirtualPath(path); } internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan value) diff --git a/Jellyfin.Server.Implementations/Item/ChapterManager.cs b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs similarity index 61% rename from Jellyfin.Server.Implementations/Item/ChapterManager.cs rename to Jellyfin.Server.Implementations/Item/ChapterRepository.cs index 7b0f98fde5..d215a1d7ad 100644 --- a/Jellyfin.Server.Implementations/Item/ChapterManager.cs +++ b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs @@ -14,46 +14,69 @@ namespace Jellyfin.Server.Implementations.Item; /// /// The Chapter manager. /// -public class ChapterManager : IChapterManager +public class ChapterRepository : IChapterRepository { private readonly IDbContextFactory _dbProvider; private readonly IImageProcessor _imageProcessor; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The EFCore provider. /// The Image Processor. - public ChapterManager(IDbContextFactory dbProvider, IImageProcessor imageProcessor) + public ChapterRepository(IDbContextFactory dbProvider, IImageProcessor imageProcessor) { _dbProvider = dbProvider; _imageProcessor = imageProcessor; } - /// + /// public ChapterInfo? GetChapter(BaseItemDto baseItem, int index) + { + return GetChapter(baseItem.Id, index); + } + + /// + public IReadOnlyList GetChapters(BaseItemDto baseItem) + { + return GetChapters(baseItem.Id); + } + + /// + public ChapterInfo? GetChapter(Guid baseItemId, int index) { 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) { - return Map(chapter, baseItem); + return Map(chapter.chapter, chapter.baseItemPath!); } return null; } - /// - public IReadOnlyList GetChapters(BaseItemDto baseItem) + /// + public IReadOnlyList GetChapters(Guid baseItemId) { 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() - .Select(e => Map(e, baseItem)) + .Select(e => Map(e.chapter, e.baseItemPath!)) .ToImmutableArray(); } - /// + /// public void SaveChapters(Guid itemId, IReadOnlyList chapters) { using var context = _dbProvider.CreateDbContext(); @@ -80,20 +103,21 @@ public class ChapterManager : IChapterManager ImageDateModified = chapterInfo.ImageDateModified, ImagePath = chapterInfo.ImagePath, 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, ImageDateModified = chapterInfo.ImageDateModified.GetValueOrDefault(), ImagePath = chapterInfo.ImagePath, Name = chapterInfo.Name, }; - info.ImageTag = _imageProcessor.GetImageCacheTag(baseItem, info); - return info; + chapterEntity.ImageTag = _imageProcessor.GetImageCacheTag(baseItemPath, chapterEntity.ImageDateModified); + return chapterEntity; } } diff --git a/Jellyfin.Server.Implementations/Item/MediaAttachmentManager.cs b/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs similarity index 95% rename from Jellyfin.Server.Implementations/Item/MediaAttachmentManager.cs rename to Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs index 288b1943e7..70c5ff1e2e 100644 --- a/Jellyfin.Server.Implementations/Item/MediaAttachmentManager.cs +++ b/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Server.Implementations.Item; /// Manager for handling Media Attachments. /// /// Efcore Factory. -public class MediaAttachmentManager(IDbContextFactory dbProvider) : IMediaAttachmentManager +public class MediaAttachmentRepository(IDbContextFactory dbProvider) : IMediaAttachmentRepository { /// public void SaveMediaAttachments( diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamManager.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs similarity index 94% rename from Jellyfin.Server.Implementations/Item/MediaStreamManager.cs rename to Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs index b7124283a4..f7b714c296 100644 --- a/Jellyfin.Server.Implementations/Item/MediaStreamManager.cs +++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs @@ -13,12 +13,12 @@ using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations.Item; /// -/// Initializes a new instance of the class. +/// Initializes a new instance of the class. /// -/// -/// -/// -public class MediaStreamManager(IDbContextFactory dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) : IMediaStreamManager +/// The EFCore db factory. +/// The Application host. +/// The Localisation Provider. +public class MediaStreamRepository(IDbContextFactory dbProvider, IServerApplicationHost serverApplicationHost, ILocalizationManager localization) : IMediaStreamRepository { /// public void SaveMediaStreams(Guid id, IReadOnlyList streams, CancellationToken cancellationToken) diff --git a/Jellyfin.Server.Implementations/Item/PeopleManager.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs similarity index 95% rename from Jellyfin.Server.Implementations/Item/PeopleManager.cs rename to Jellyfin.Server.Implementations/Item/PeopleRepository.cs index d29d8b143e..3ced6e24e3 100644 --- a/Jellyfin.Server.Implementations/Item/PeopleManager.cs +++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs @@ -16,13 +16,13 @@ namespace Jellyfin.Server.Implementations.Item; /// /// Efcore Factory. /// -/// Initializes a new instance of the class. +/// Initializes a new instance of the class. /// -/// The EFCore Context factory. -public class PeopleManager(IDbContextFactory dbProvider) : IPeopleManager +public class PeopleRepository(IDbContextFactory dbProvider) : IPeopleRepository { private readonly IDbContextFactory _dbProvider = dbProvider; + /// public IReadOnlyList GetPeople(InternalPeopleQuery filter) { using var context = _dbProvider.CreateDbContext(); @@ -37,6 +37,7 @@ public class PeopleManager(IDbContextFactory dbProvider) : IP return dbQuery.ToList().Select(Map).ToImmutableArray(); } + /// public IReadOnlyList GetPeopleNames(InternalPeopleQuery filter) { using var context = _dbProvider.CreateDbContext(); diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs index fcc20a0d4f..c1d6d58cdf 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbContext.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs @@ -106,7 +106,7 @@ public class JellyfinDbContext : DbContext /// /// Gets the containing the user data. /// - public DbSet BaseItems => Set(); + public DbSet BaseItems => Set(); /// /// Gets the containing the user data. diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs index c0f09670d7..4aba9d07e1 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs @@ -8,10 +8,10 @@ namespace Jellyfin.Server.Implementations.ModelConfiguration; /// /// Configuration for BaseItem. /// -public class BaseItemConfiguration : IEntityTypeConfiguration +public class BaseItemConfiguration : IEntityTypeConfiguration { /// - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); builder.HasIndex(e => e.Path); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs index f34837c57c..d15049a1fa 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemProviderConfiguration.cs @@ -13,7 +13,7 @@ public class BaseItemProviderConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { - builder.HasNoKey(); + builder.HasKey(e => new { e.ItemId, e.ProviderId }); builder.HasOne(e => e.Item); builder.HasIndex(e => new { e.ProviderId, e.ProviderValue, e.ItemId }); } diff --git a/MediaBrowser.Controller/Chapters/ChapterManager.cs b/MediaBrowser.Controller/Chapters/ChapterManager.cs deleted file mode 100644 index a9e11f603a..0000000000 --- a/MediaBrowser.Controller/Chapters/ChapterManager.cs +++ /dev/null @@ -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 dbProvider) - { - _itemRepo = itemRepo; - } - - /// - public void SaveChapters(Guid itemId, IReadOnlyList chapters) - { - _itemRepo.SaveChapters(itemId, chapters); - } - } -} diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs deleted file mode 100644 index 55762c7fc4..0000000000 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Chapters -{ - /// - /// Interface IChapterManager. - /// - public interface IChapterManager - { - /// - /// Saves the chapters. - /// - /// The item. - /// The set of chapters. - void SaveChapters(Guid itemId, IReadOnlyList chapters); - - /// - /// Gets all chapters associated with the baseItem. - /// - /// The baseitem. - /// A readonly list of chapter instances. - IReadOnlyList GetChapters(BaseItemDto baseItem); - - /// - /// Gets a single chapter of a BaseItem on a specific index. - /// - /// The baseitem. - /// The index of that chapter. - /// A chapter instance. - ChapterInfo? GetChapter(BaseItemDto baseItem, int index); - } -} diff --git a/MediaBrowser.Controller/Chapters/IChapterRepository.cs b/MediaBrowser.Controller/Chapters/IChapterRepository.cs new file mode 100644 index 0000000000..e22cb0f584 --- /dev/null +++ b/MediaBrowser.Controller/Chapters/IChapterRepository.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Chapters; + +/// +/// Interface IChapterManager. +/// +public interface IChapterRepository +{ + /// + /// Saves the chapters. + /// + /// The item. + /// The set of chapters. + void SaveChapters(Guid itemId, IReadOnlyList chapters); + + /// + /// Gets all chapters associated with the baseItem. + /// + /// The baseitem. + /// A readonly list of chapter instances. + IReadOnlyList GetChapters(BaseItemDto baseItem); + + /// + /// Gets a single chapter of a BaseItem on a specific index. + /// + /// The baseitem. + /// The index of that chapter. + /// A chapter instance. + ChapterInfo? GetChapter(BaseItemDto baseItem, int index); + + /// + /// Gets all chapters associated with the baseItem. + /// + /// The BaseItems id. + /// A readonly list of chapter instances. + IReadOnlyList GetChapters(Guid baseItemId); + + /// + /// Gets a single chapter of a BaseItem on a specific index. + /// + /// The BaseItems id. + /// The index of that chapter. + /// A chapter instance. + ChapterInfo? GetChapter(Guid baseItemId, int index); +} diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 0d1e2a5a07..702ce39a2a 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Drawing @@ -57,6 +58,22 @@ namespace MediaBrowser.Controller.Drawing /// BlurHash. string GetImageBlurHash(string path, ImageDimensions imageDimensions); + /// + /// Gets the image cache tag. + /// + /// The items basePath. + /// The image last modification date. + /// Guid. + string? GetImageCacheTag(string baseItemPath, DateTime imageDateModified); + + /// + /// Gets the image cache tag. + /// + /// The item. + /// The image. + /// Guid. + string? GetImageCacheTag(BaseItemDto item, ChapterInfo image); + /// /// Gets the image cache tag. /// @@ -65,6 +82,14 @@ namespace MediaBrowser.Controller.Drawing /// Guid. string GetImageCacheTag(BaseItem item, ItemImageInfo image); + /// + /// Gets the image cache tag. + /// + /// The item. + /// The image. + /// Guid. + string GetImageCacheTag(BaseItemDto item, ItemImageInfo image); + string? GetImageCacheTag(BaseItem item, ChapterInfo chapter); string? GetImageCacheTag(User user); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index eb605f6c87..a4764dd33f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -16,6 +16,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Audio; @@ -479,6 +480,8 @@ namespace MediaBrowser.Controller.Entities public static IItemRepository ItemRepository { get; set; } + public static IChapterRepository ChapterRepository { get; set; } + public static IFileSystem FileSystem { get; set; } public static IUserDataManager UserDataManager { get; set; } @@ -2031,7 +2034,7 @@ namespace MediaBrowser.Controller.Entities { if (imageType == ImageType.Chapter) { - var chapter = ItemRepository.GetChapter(this, imageIndex); + var chapter = ChapterRepository.GetChapter(this.Id, imageIndex); if (chapter is null) { @@ -2081,7 +2084,7 @@ namespace MediaBrowser.Controller.Entities if (image.Type == ImageType.Chapter) { - var chapters = ItemRepository.GetChapters(this); + var chapters = ChapterRepository.GetChapters(this.Id); for (var i = 0; i < chapters.Count; i++) { if (chapters[i].ImagePath == image.Path) diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 313b1459ab..b27f156efe 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -52,7 +52,6 @@ public interface IItemRepository : IDisposable /// List<Guid>. IReadOnlyList GetItemIdsList(InternalItemsQuery filter); - /// /// Gets the item list. /// diff --git a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs new file mode 100644 index 0000000000..1b2ca2acb5 --- /dev/null +++ b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Controller.Persistence; + +/// +/// Provides static lookup data for and for the domain. +/// +public interface IItemTypeLookup +{ + /// + /// Gets all values of the ItemFields type. + /// + public IReadOnlyList AllItemFields { get; } + + /// + /// Gets all BaseItemKinds that are considered Programs. + /// + public IReadOnlyList ProgramTypes { get; } + + /// + /// Gets all BaseItemKinds that should be excluded from parent lookup. + /// + public IReadOnlyList ProgramExcludeParentTypes { get; } + + /// + /// Gets all BaseItemKinds that are considered to be provided by services. + /// + public IReadOnlyList ServiceTypes { get; } + + /// + /// Gets all BaseItemKinds that have a StartDate. + /// + public IReadOnlyList StartDateTypes { get; } + + /// + /// Gets all BaseItemKinds that are considered Series. + /// + public IReadOnlyList SeriesTypes { get; } + + /// + /// Gets all BaseItemKinds that are not to be evaluated for Artists. + /// + public IReadOnlyList ArtistExcludeParentTypes { get; } + + /// + /// Gets all BaseItemKinds that are considered Artists. + /// + public IReadOnlyList ArtistsTypes { get; } + + /// + /// Gets mapping for all BaseItemKinds and their expected serialisaition target. + /// + public IDictionary BaseItemKindNames { get; } +} diff --git a/MediaBrowser.Controller/Persistence/IMediaAttachmentManager.cs b/MediaBrowser.Controller/Persistence/IMediaAttachmentRepository.cs similarity index 95% rename from MediaBrowser.Controller/Persistence/IMediaAttachmentManager.cs rename to MediaBrowser.Controller/Persistence/IMediaAttachmentRepository.cs index 210d80afa2..4773f40581 100644 --- a/MediaBrowser.Controller/Persistence/IMediaAttachmentManager.cs +++ b/MediaBrowser.Controller/Persistence/IMediaAttachmentRepository.cs @@ -9,9 +9,8 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Persistence; -public interface IMediaAttachmentManager +public interface IMediaAttachmentRepository { - /// /// Gets the media attachments. /// diff --git a/MediaBrowser.Controller/Persistence/IMediaStreamManager.cs b/MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs similarity index 79% rename from MediaBrowser.Controller/Persistence/IMediaStreamManager.cs rename to MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs index ec7c72935b..665129eafd 100644 --- a/MediaBrowser.Controller/Persistence/IMediaStreamManager.cs +++ b/MediaBrowser.Controller/Persistence/IMediaStreamRepository.cs @@ -9,14 +9,17 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Persistence; -public interface IMediaStreamManager +/// +/// Provides methods for accessing MediaStreams. +/// +public interface IMediaStreamRepository { /// /// Gets the media streams. /// /// The query. /// IEnumerable{MediaStream}. - List GetMediaStreams(MediaStreamQuery filter); + IReadOnlyList GetMediaStreams(MediaStreamQuery filter); /// /// Saves the media streams. diff --git a/MediaBrowser.Controller/Persistence/IPeopleManager.cs b/MediaBrowser.Controller/Persistence/IPeopleRepository.cs similarity index 96% rename from MediaBrowser.Controller/Persistence/IPeopleManager.cs rename to MediaBrowser.Controller/Persistence/IPeopleRepository.cs index 84e503fefb..43a24703e4 100644 --- a/MediaBrowser.Controller/Persistence/IPeopleManager.cs +++ b/MediaBrowser.Controller/Persistence/IPeopleRepository.cs @@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Persistence; -public interface IPeopleManager +public interface IPeopleRepository { /// /// Gets the people. @@ -30,5 +30,4 @@ public interface IPeopleManager /// The query. /// List<System.String>. IReadOnlyList GetPeopleNames(InternalPeopleQuery filter); - } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 246ba2733f..62c5909441 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IEncodingManager _encodingManager; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; - private readonly IChapterManager _chapterManager; + private readonly IChapterRepository _chapterManager; private readonly ILibraryManager _libraryManager; private readonly AudioResolver _audioResolver; private readonly SubtitleResolver _subtitleResolver; @@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.MediaInfo IEncodingManager encodingManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, - IChapterManager chapterManager, + IChapterRepository chapterManager, ILibraryManager libraryManager, AudioResolver audioResolver, SubtitleResolver subtitleResolver) diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs index 04da8fb882..f5e9dddcfc 100644 --- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.MediaInfo /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. /// Instance of the . /// Instance of the interface. @@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo IEncodingManager encodingManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, - IChapterManager chapterManager, + IChapterRepository chapterManager, ILibraryManager libraryManager, IFileSystem fileSystem, ILoggerFactory loggerFactory, diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs index 5d4732234d..b57f2753f3 100644 --- a/src/Jellyfin.Drawing/ImageProcessor.cs +++ b/src/Jellyfin.Drawing/ImageProcessor.cs @@ -15,6 +15,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; @@ -403,10 +404,34 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable return _imageEncoder.GetImageBlurHash(xComp, yComp, path); } + /// + public string GetImageCacheTag(string baseItemPath, DateTime imageDateModified) + => (baseItemPath + imageDateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + /// public string GetImageCacheTag(BaseItem item, ItemImageInfo image) => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + /// + public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image) + => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + + /// + 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 + }); + } + /// public string? GetImageCacheTag(BaseItem item, ChapterInfo chapter) {