From be48cdd9e90ed147c5526ef3fed0624bcbad7741 Mon Sep 17 00:00:00 2001 From: JPVenson <6794763+JPVenson@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:53:39 +0000 Subject: Naming refactoring and WIP porting of new interface repositories --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 139 +++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Emby.Server.Implementations/Data/ItemTypeLookup.cs (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs new file mode 100644 index 000000000..14dc68a32 --- /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(); +} -- cgit v1.2.3 From 473628ba3a9f68479e0051e76594dc47f7fa08f3 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 9 Oct 2024 21:03:57 +0200 Subject: Apply suggestions from code review Co-authored-by: Cody Robibero --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 2 +- Jellyfin.Api/Controllers/MoviesController.cs | 2 +- Jellyfin.Data/Entities/BaseItemProvider.cs | 2 +- Jellyfin.Data/Entities/MediaStreamInfo.cs | 4 ---- Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs | 2 +- MediaBrowser.Controller/Persistence/IItemTypeLookup.cs | 2 +- MediaBrowser.Controller/Persistence/IPeopleRepository.cs | 4 ++-- 7 files changed, 7 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index 14dc68a32..1f73755f5 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -12,7 +12,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Querying; -namespace Jellyfin.Server.Implementations.Item; +namespace Emby.Server.Implementations.Data; /// /// Provides static topic based lookups for the BaseItemKind. diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs index 11559419c..f537ffa11 100644 --- a/Jellyfin.Api/Controllers/MoviesController.cs +++ b/Jellyfin.Api/Controllers/MoviesController.cs @@ -97,7 +97,7 @@ public class MoviesController : BaseJellyfinApiController DtoOptions = dtoOptions }; - var recentlyPlayedMovies = _libraryManager.GetItemList(query)!; + var recentlyPlayedMovies = _libraryManager.GetItemList(query); var itemTypes = new List { BaseItemKind.Movie }; if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) diff --git a/Jellyfin.Data/Entities/BaseItemProvider.cs b/Jellyfin.Data/Entities/BaseItemProvider.cs index 1fc721d6a..9a1565728 100644 --- a/Jellyfin.Data/Entities/BaseItemProvider.cs +++ b/Jellyfin.Data/Entities/BaseItemProvider.cs @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities; /// -/// Represents an Key-Value relaten of an BaseItem's provider. +/// Represents a Key-Value relation of an BaseItem's provider. /// public class BaseItemProvider { diff --git a/Jellyfin.Data/Entities/MediaStreamInfo.cs b/Jellyfin.Data/Entities/MediaStreamInfo.cs index a46d3f195..1198026e7 100644 --- a/Jellyfin.Data/Entities/MediaStreamInfo.cs +++ b/Jellyfin.Data/Entities/MediaStreamInfo.cs @@ -6,10 +6,6 @@ namespace Jellyfin.Data.Entities; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public class MediaStreamInfo { - public MediaStreamInfo() - { - } - public required Guid ItemId { get; set; } public required BaseItemEntity Item { get; set; } diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs index f44ead6e0..df434fdb3 100644 --- a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs +++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs @@ -37,7 +37,7 @@ public class MediaStreamRepository(IDbContextFactory dbProvid public IReadOnlyList GetMediaStreams(MediaStreamQuery filter) { using var context = dbProvider.CreateDbContext(); - return TranslateQuery(context.MediaStreamInfos, filter).ToList().Select(Map).ToImmutableArray(); + return TranslateQuery(context.MediaStreamInfos, filter).AsEnumerable().Select(Map).ToImmutableArray(); } private string? GetPathToSave(string? path) diff --git a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs index 1b2ca2acb..6ad8380d7 100644 --- a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs +++ b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs @@ -51,7 +51,7 @@ public interface IItemTypeLookup public IReadOnlyList ArtistsTypes { get; } /// - /// Gets mapping for all BaseItemKinds and their expected serialisaition target. + /// Gets mapping for all BaseItemKinds and their expected serialization target. /// public IDictionary BaseItemKindNames { get; } } diff --git a/MediaBrowser.Controller/Persistence/IPeopleRepository.cs b/MediaBrowser.Controller/Persistence/IPeopleRepository.cs index 43a24703e..418289cb4 100644 --- a/MediaBrowser.Controller/Persistence/IPeopleRepository.cs +++ b/MediaBrowser.Controller/Persistence/IPeopleRepository.cs @@ -14,7 +14,7 @@ public interface IPeopleRepository /// Gets the people. /// /// The query. - /// List<PersonInfo>. + /// The list of people matching the filter. IReadOnlyList GetPeople(InternalPeopleQuery filter); /// @@ -28,6 +28,6 @@ public interface IPeopleRepository /// Gets the people names. /// /// The query. - /// List<System.String>. + /// The list of people names matching the filter. IReadOnlyList GetPeopleNames(InternalPeopleQuery filter); } -- cgit v1.2.3 From eb601e944cd392a8007b540ab5627977a37368c6 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Wed, 9 Oct 2024 23:01:54 +0000 Subject: Expanded BaseItem aggregate types --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 41 +- Jellyfin.Data/Entities/BaseItemEntity.cs | 22 +- Jellyfin.Data/Entities/BaseItemExtraType.cs | 18 + Jellyfin.Data/Entities/BaseItemImageInfo.cs | 57 + Jellyfin.Data/Entities/BaseItemMetadataField.cs | 26 + Jellyfin.Data/Entities/BaseItemTrailerType.cs | 25 + Jellyfin.Data/Entities/EnumLikeTable.cs | 14 + Jellyfin.Data/Entities/ImageInfoImageType.cs | 76 + Jellyfin.Data/Entities/ProgramAudioEntity.cs | 37 + .../Item/BaseItemRepository.cs | 316 ++-- .../JellyfinDbContext.cs | 15 + ...241009225800_ExpandedBaseItemFields.Designer.cs | 1540 ++++++++++++++++++++ .../20241009225800_ExpandedBaseItemFields.cs | 169 +++ .../Migrations/JellyfinDbModelSnapshot.cs | 123 +- .../ModelConfiguration/BaseItemConfiguration.cs | 3 + .../BaseItemMetadataFieldConfiguration.cs | 22 + .../BaseItemTrailerTypeConfiguration.cs | 22 + .../Migrations/Routines/MigrateLibraryDb.cs | 323 +++- .../Data/SqliteItemRepositoryTests.cs | 66 - 19 files changed, 2487 insertions(+), 428 deletions(-) create mode 100644 Jellyfin.Data/Entities/BaseItemExtraType.cs create mode 100644 Jellyfin.Data/Entities/BaseItemImageInfo.cs create mode 100644 Jellyfin.Data/Entities/BaseItemMetadataField.cs create mode 100644 Jellyfin.Data/Entities/BaseItemTrailerType.cs create mode 100644 Jellyfin.Data/Entities/EnumLikeTable.cs create mode 100644 Jellyfin.Data/Entities/ImageInfoImageType.cs create mode 100644 Jellyfin.Data/Entities/ProgramAudioEntity.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/BaseItemMetadataFieldConfiguration.cs create mode 100644 Jellyfin.Server.Implementations/ModelConfiguration/BaseItemTrailerTypeConfiguration.cs (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index 1f73755f5..b66e7f5d9 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Channels; using Emby.Server.Implementations.Playlists; using Jellyfin.Data.Enums; +using Jellyfin.Server.Implementations; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; @@ -14,19 +15,13 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Data; -/// -/// 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, @@ -35,9 +30,7 @@ public class ItemTypeLookup : IItemTypeLookup BaseItemKind.LiveTvChannel ]; - /// - /// Gets all BaseItemKinds that should be excluded from parent lookup. - /// + /// public IReadOnlyList ProgramExcludeParentTypes { get; } = [ BaseItemKind.Series, @@ -47,27 +40,21 @@ public class ItemTypeLookup : IItemTypeLookup 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, @@ -76,9 +63,7 @@ public class ItemTypeLookup : IItemTypeLookup BaseItemKind.Season ]; - /// - /// Gets all BaseItemKinds that are not to be evaluated for Artists. - /// + /// public IReadOnlyList ArtistExcludeParentTypes { get; } = [ BaseItemKind.Series, @@ -86,9 +71,7 @@ public class ItemTypeLookup : IItemTypeLookup BaseItemKind.PhotoAlbum ]; - /// - /// Gets all BaseItemKinds that are considered Artists. - /// + /// public IReadOnlyList ArtistsTypes { get; } = [ BaseItemKind.Audio, @@ -97,9 +80,7 @@ public class ItemTypeLookup : IItemTypeLookup BaseItemKind.AudioBook ]; - /// - /// Gets mapping for all BaseItemKinds and their expected serialisaition target. - /// + /// public IDictionary BaseItemKindNames { get; } = new Dictionary() { { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName }, diff --git a/Jellyfin.Data/Entities/BaseItemEntity.cs b/Jellyfin.Data/Entities/BaseItemEntity.cs index dbe5a5372..cd1991891 100644 --- a/Jellyfin.Data/Entities/BaseItemEntity.cs +++ b/Jellyfin.Data/Entities/BaseItemEntity.cs @@ -10,9 +10,7 @@ namespace Jellyfin.Data.Entities; public class BaseItemEntity { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - - public Guid Id { get; set; } + public required Guid Id { get; set; } public required string Type { get; set; } @@ -78,12 +76,8 @@ public class BaseItemEntity public bool IsInMixedFolder { get; set; } - public string? LockedFields { get; set; } - public string? Studios { get; set; } - public string? Audio { get; set; } - public string? ExternalServiceId { get; set; } public string? Tags { get; set; } @@ -94,8 +88,6 @@ public class BaseItemEntity public string? UnratedType { get; set; } - public string? TrailerTypes { get; set; } - public float? CriticRating { get; set; } public string? CleanName { get; set; } @@ -126,15 +118,13 @@ public class BaseItemEntity public string? Tagline { get; set; } - public string? Images { get; set; } - public string? ProductionLocations { get; set; } public string? ExtraIds { get; set; } public int? TotalBitrate { get; set; } - public string? ExtraType { get; set; } + public BaseItemExtraType? ExtraType { get; set; } public string? Artists { get; set; } @@ -154,6 +144,8 @@ public class BaseItemEntity public long? Size { get; set; } + public ProgramAudioEntity? Audio { get; set; } + public Guid? ParentId { get; set; } public Guid? TopParentId { get; set; } @@ -176,6 +168,12 @@ public class BaseItemEntity public ICollection? AncestorIds { get; set; } + public ICollection? LockedFields { get; set; } + + public ICollection? TrailerTypes { get; set; } + + public ICollection? Images { get; set; } + // those are references to __LOCAL__ ids not DB ids ... TODO: Bring the whole folder structure into the DB // public ICollection? SeriesEpisodes { get; set; } // public BaseItemEntity? Series { get; set; } diff --git a/Jellyfin.Data/Entities/BaseItemExtraType.cs b/Jellyfin.Data/Entities/BaseItemExtraType.cs new file mode 100644 index 000000000..341697436 --- /dev/null +++ b/Jellyfin.Data/Entities/BaseItemExtraType.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Data.Entities; + +#pragma warning disable CS1591 +public enum BaseItemExtraType +{ + Unknown = 0, + Clip = 1, + Trailer = 2, + BehindTheScenes = 3, + DeletedScene = 4, + Interview = 5, + Scene = 6, + Sample = 7, + ThemeSong = 8, + ThemeVideo = 9, + Featurette = 10, + Short = 11 +} diff --git a/Jellyfin.Data/Entities/BaseItemImageInfo.cs b/Jellyfin.Data/Entities/BaseItemImageInfo.cs new file mode 100644 index 000000000..6390cac58 --- /dev/null +++ b/Jellyfin.Data/Entities/BaseItemImageInfo.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Data.Entities; +#pragma warning disable CA2227 + +/// +/// Enum TrailerTypes. +/// +public class BaseItemImageInfo +{ + /// + /// Gets or Sets. + /// + public required Guid Id { get; set; } + + /// + /// Gets or Sets the path to the original image. + /// + public required string Path { get; set; } + + /// + /// Gets or Sets the time the image was last modified. + /// + public DateTime DateModified { get; set; } + + /// + /// Gets or Sets the imagetype. + /// + public ImageInfoImageType ImageType { get; set; } + + /// + /// Gets or Sets the width of the original image. + /// + public int Width { get; set; } + + /// + /// Gets or Sets the height of the original image. + /// + public int Height { get; set; } + +#pragma warning disable CA1819 + /// + /// Gets or Sets the blurhash. + /// + public byte[]? Blurhash { get; set; } + + /// + /// Gets or Sets the reference id to the BaseItem. + /// + public required Guid ItemId { get; set; } + + /// + /// Gets or Sets the referenced Item. + /// + public required BaseItemEntity Item { get; set; } +} diff --git a/Jellyfin.Data/Entities/BaseItemMetadataField.cs b/Jellyfin.Data/Entities/BaseItemMetadataField.cs new file mode 100644 index 000000000..2f8e910f2 --- /dev/null +++ b/Jellyfin.Data/Entities/BaseItemMetadataField.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Data.Entities; +#pragma warning disable CA2227 + +/// +/// Enum MetadataFields. +/// +public class BaseItemMetadataField +{ + /// + /// Gets or Sets Numerical ID of this enumeratable. + /// + public required int Id { get; set; } + + /// + /// Gets or Sets all referenced . + /// + public required Guid ItemId { get; set; } + + /// + /// Gets or Sets all referenced . + /// + public required BaseItemEntity Item { get; set; } +} diff --git a/Jellyfin.Data/Entities/BaseItemTrailerType.cs b/Jellyfin.Data/Entities/BaseItemTrailerType.cs new file mode 100644 index 000000000..7dee20c87 --- /dev/null +++ b/Jellyfin.Data/Entities/BaseItemTrailerType.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Data.Entities; +#pragma warning disable CA2227 +/// +/// Enum TrailerTypes. +/// +public class BaseItemTrailerType +{ + /// + /// Gets or Sets Numerical ID of this enumeratable. + /// + public required int Id { get; set; } + + /// + /// Gets or Sets all referenced . + /// + public required Guid ItemId { get; set; } + + /// + /// Gets or Sets all referenced . + /// + public required BaseItemEntity Item { get; set; } +} diff --git a/Jellyfin.Data/Entities/EnumLikeTable.cs b/Jellyfin.Data/Entities/EnumLikeTable.cs new file mode 100644 index 000000000..11e1d0aa9 --- /dev/null +++ b/Jellyfin.Data/Entities/EnumLikeTable.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Jellyfin.Data.Entities; + +/// +/// Defines an Entity that is modeled after an Enum. +/// +public abstract class EnumLikeTable +{ + /// + /// Gets or Sets Numerical ID of this enumeratable. + /// + public required int Id { get; set; } +} diff --git a/Jellyfin.Data/Entities/ImageInfoImageType.cs b/Jellyfin.Data/Entities/ImageInfoImageType.cs new file mode 100644 index 000000000..f78178dd2 --- /dev/null +++ b/Jellyfin.Data/Entities/ImageInfoImageType.cs @@ -0,0 +1,76 @@ +namespace Jellyfin.Data.Entities; + +/// +/// Enum ImageType. +/// +public enum ImageInfoImageType +{ + /// + /// The primary. + /// + Primary = 0, + + /// + /// The art. + /// + Art = 1, + + /// + /// The backdrop. + /// + Backdrop = 2, + + /// + /// The banner. + /// + Banner = 3, + + /// + /// The logo. + /// + Logo = 4, + + /// + /// The thumb. + /// + Thumb = 5, + + /// + /// The disc. + /// + Disc = 6, + + /// + /// The box. + /// + Box = 7, + + /// + /// The screenshot. + /// + /// + /// This enum value is obsolete. + /// XmlSerializer does not serialize/deserialize objects that are marked as [Obsolete]. + /// + Screenshot = 8, + + /// + /// The menu. + /// + Menu = 9, + + /// + /// The chapter image. + /// + Chapter = 10, + + /// + /// The box rear. + /// + BoxRear = 11, + + /// + /// The user profile image. + /// + Profile = 12 +} diff --git a/Jellyfin.Data/Entities/ProgramAudioEntity.cs b/Jellyfin.Data/Entities/ProgramAudioEntity.cs new file mode 100644 index 000000000..fafccb13c --- /dev/null +++ b/Jellyfin.Data/Entities/ProgramAudioEntity.cs @@ -0,0 +1,37 @@ +namespace Jellyfin.Data.Entities; + +/// +/// Lists types of Audio. +/// +public enum ProgramAudioEntity +{ + /// + /// Mono. + /// + Mono, + + /// + /// Sterio. + /// + Stereo, + + /// + /// Dolby. + /// + Dolby, + + /// + /// DolbyDigital. + /// + DolbyDigital, + + /// + /// Thx. + /// + Thx, + + /// + /// Atmos. + /// + Atmos +} diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 480d83eb1..6ddab9e3d 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller; @@ -69,6 +70,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr context.AncestorIds.Where(e => e.ItemId == id).ExecuteDelete(); context.ItemValues.Where(e => e.ItemId == id).ExecuteDelete(); context.BaseItems.Where(e => e.Id == id).ExecuteDelete(); + context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete(); + context.BaseItemProviders.Where(e => e.ItemId == id).ExecuteDelete(); context.SaveChanges(); transaction.Commit(); } @@ -229,7 +232,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr var result = new QueryResult(); using var context = dbProvider.CreateDbContext(); - var dbQuery = TranslateQuery(context.BaseItems, context, filter) + IQueryable dbQuery = context.BaseItems + .Include(e => e.ExtraType) + .Include(e => e.TrailerTypes) + .Include(e => e.Images) + .Include(e => e.LockedFields); + dbQuery = TranslateQuery(dbQuery, context, filter) .DistinctBy(e => e.Id); if (filter.EnableTotalRecordCount) { @@ -585,8 +593,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.TrailerTypes.Length > 0) { - var trailerTypes = filter.TrailerTypes.Select(e => e.ToString()).ToArray(); - baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Contains(f))); + var trailerTypes = filter.TrailerTypes.Select(e => (int)e).ToArray(); + baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Any(w => w.Id == f))); } if (filter.IsAiring.HasValue) @@ -666,8 +674,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.ImageTypes.Length > 0) { - var imgTypes = filter.ImageTypes.Select(e => e.ToString()).ToArray(); - baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Contains(f))); + var imgTypes = filter.ImageTypes.Select(e => (ImageInfoImageType)e).ToArray(); + baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Any(w => w.ImageType == f))); } if (filter.IsLiked.HasValue) @@ -1206,12 +1214,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr { ArgumentNullException.ThrowIfNull(item); - var images = SerializeImages(item.ImageInfos); + var images = item.ImageInfos.Select(e => Map(item.Id, e)); using var db = dbProvider.CreateDbContext(); - - db.BaseItems - .Where(e => e.Id == item.Id) - .ExecuteUpdate(e => e.SetProperty(f => f.Images, images)); + using var transaction = db.Database.BeginTransaction(); + db.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete(); + db.BaseItemImageInfos.AddRange(images); + transaction.Commit(); } /// @@ -1260,29 +1268,32 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete(); if (item.Item.SupportsAncestors && item.AncestorIds != null) { + entity.AncestorIds = new List(); foreach (var ancestorId in item.AncestorIds) { - context.AncestorIds.Add(new Data.Entities.AncestorId() + entity.AncestorIds.Add(new AncestorId() { Item = entity, AncestorIdText = ancestorId.ToString(), Id = ancestorId, - ItemId = Guid.Empty + ItemId = entity.Id }); } } var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags); context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + entity.ItemValues = new List(); + foreach (var itemValue in itemValues) { - context.ItemValues.Add(new() + entity.ItemValues.Add(new() { Item = entity, Type = itemValue.MagicNumber, Value = itemValue.Value, CleanValue = GetCleanValue(itemValue.Value), - ItemId = Guid.Empty + ItemId = entity.Id }); } } @@ -1366,26 +1377,17 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (entity.ExtraType is not null) { - dto.ExtraType = Enum.Parse(entity.ExtraType); + dto.ExtraType = (ExtraType)entity.ExtraType; } if (entity.LockedFields is not null) { - List? fields = null; - foreach (var i in entity.LockedFields.AsSpan().Split('|')) - { - if (Enum.TryParse(i, true, out MetadataField parsedValue)) - { - (fields ??= new List()).Add(parsedValue); - } - } - - dto.LockedFields = fields?.ToArray() ?? Array.Empty(); + dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? []; } if (entity.Audio is not null) { - dto.Audio = Enum.Parse(entity.Audio); + dto.Audio = (ProgramAudio)entity.Audio; } dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? null : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray(); @@ -1408,16 +1410,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (dto is Trailer trailer) { - List? types = null; - foreach (var i in entity.TrailerTypes.AsSpan().Split('|')) - { - if (Enum.TryParse(i, true, out TrailerType parsedValue)) - { - (types ??= new List()).Add(parsedValue); - } - } - - trailer.TrailerTypes = types?.ToArray() ?? Array.Empty(); + trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? []; } if (dto is Video video) @@ -1455,7 +1448,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (entity.Images is not null) { - dto.ImageInfos = DeserializeImages(entity.Images); + dto.ImageInfos = entity.Images.Select(Map).ToArray(); } // dto.Type = entity.Type; @@ -1490,8 +1483,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr var entity = new BaseItemEntity() { Type = dto.GetType().ToString(), + Id = dto.Id }; - entity.Id = dto.Id; entity.ParentId = dto.ParentId; entity.Path = GetPathToSave(dto.Path); entity.EndDate = dto.EndDate.GetValueOrDefault(); @@ -1533,21 +1526,35 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr entity.OwnerId = dto.OwnerId.ToString(); entity.Width = dto.Width; entity.Height = dto.Height; - entity.Provider = dto.ProviderIds.Select(e => new Data.Entities.BaseItemProvider() + entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider() { Item = entity, ProviderId = e.Key, ProviderValue = e.Value }).ToList(); - entity.Audio = dto.Audio?.ToString(); - entity.ExtraType = dto.ExtraType?.ToString(); + if (dto.Audio.HasValue) + { + entity.Audio = (ProgramAudioEntity)dto.Audio; + } + + if (dto.ExtraType.HasValue) + { + entity.ExtraType = (BaseItemExtraType)dto.ExtraType; + } entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null; entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : null; entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null; entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null; - entity.LockedFields = dto.LockedFields is not null ? string.Join('|', dto.LockedFields) : null; + entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields + .Select(e => new BaseItemMetadataField() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }) + .ToArray() : null; if (dto is IHasProgramAttributes hasProgramAttributes) { @@ -1562,11 +1569,6 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr entity.ExternalServiceId = liveTvChannel.ServiceName; } - if (dto is Trailer trailer) - { - entity.LockedFields = trailer.LockedFields is not null ? string.Join('|', trailer.LockedFields) : null; - } - if (dto is Video video) { entity.PrimaryVersionId = video.PrimaryVersionId; @@ -1602,7 +1604,17 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (dto.ImageInfos is not null) { - entity.Images = SerializeImages(dto.ImageInfos); + entity.Images = dto.ImageInfos.Select(f => Map(dto.Id, f)).ToArray(); + } + + if (dto is Trailer trailer) + { + entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }).ToArray() ?? []; } // dto.Type = entity.Type; @@ -1863,90 +1875,33 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr } } - internal string? SerializeImages(ItemImageInfo[] images) - { - if (images.Length == 0) - { - return null; - } - - StringBuilder str = new StringBuilder(); - foreach (var i in images) - { - if (string.IsNullOrWhiteSpace(i.Path)) - { - continue; - } - - AppendItemImageInfo(str, i); - str.Append('|'); - } - - str.Length -= 1; // Remove last | - return str.ToString(); - } - - internal ItemImageInfo[] DeserializeImages(string value) + private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e) { - if (string.IsNullOrWhiteSpace(value)) - { - return Array.Empty(); - } - - // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed - var valueSpan = value.AsSpan(); - var count = valueSpan.Count('|') + 1; - - var position = 0; - var result = new ItemImageInfo[count]; - foreach (var part in valueSpan.Split('|')) - { - var image = ItemImageInfoFromValueString(part); - - if (image is not null) - { - result[position++] = image; - } - } - - if (position == count) - { - return result; - } - - if (position == 0) - { - return Array.Empty(); - } - - // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array. - return result[..position]; + return new BaseItemImageInfo() + { + ItemId = baseItemId, + Id = Guid.NewGuid(), + Path = e.Path, + Blurhash = e.BlurHash != null ? Encoding.UTF8.GetBytes(e.BlurHash) : null, + DateModified = e.DateModified, + Height = e.Height, + Width = e.Width, + ImageType = (ImageInfoImageType)e.Type, + Item = null! + }; } - private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) + private static ItemImageInfo Map(BaseItemImageInfo e) { - const char Delimiter = '*'; - - var path = image.Path ?? string.Empty; - - bldr.Append(GetPathToSave(path)) - .Append(Delimiter) - .Append(image.DateModified.Ticks) - .Append(Delimiter) - .Append(image.Type) - .Append(Delimiter) - .Append(image.Width) - .Append(Delimiter) - .Append(image.Height); - - var hash = image.BlurHash; - if (!string.IsNullOrEmpty(hash)) - { - bldr.Append(Delimiter) - // Replace delimiters with other characters. - // This can be removed when we migrate to a proper DB. - .Append(hash.Replace(Delimiter, '/').Replace('|', '\\')); - } + return new ItemImageInfo() + { + Path = e.Path, + BlurHash = e.Blurhash != null ? Encoding.UTF8.GetString(e.Blurhash) : null, + DateModified = e.DateModified, + Height = e.Height, + Width = e.Width, + Type = (ImageType)e.ImageType + }; } private string? GetPathToSave(string path) @@ -1964,111 +1919,6 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr return appHost.ExpandVirtualPath(path); } - internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan value) - { - const char Delimiter = '*'; - - var nextSegment = value.IndexOf(Delimiter); - if (nextSegment == -1) - { - return null; - } - - ReadOnlySpan path = value[..nextSegment]; - value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf(Delimiter); - if (nextSegment == -1) - { - return null; - } - - ReadOnlySpan dateModified = value[..nextSegment]; - value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf(Delimiter); - if (nextSegment == -1) - { - nextSegment = value.Length; - } - - ReadOnlySpan imageType = value[..nextSegment]; - - var image = new ItemImageInfo - { - Path = RestorePath(path.ToString()) - }; - - if (long.TryParse(dateModified, CultureInfo.InvariantCulture, out var ticks) - && ticks >= DateTime.MinValue.Ticks - && ticks <= DateTime.MaxValue.Ticks) - { - image.DateModified = new DateTime(ticks, DateTimeKind.Utc); - } - else - { - return null; - } - - if (Enum.TryParse(imageType, true, out ImageType type)) - { - image.Type = type; - } - else - { - return null; - } - - // Optional parameters: width*height*blurhash - if (nextSegment + 1 < value.Length - 1) - { - value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf(Delimiter); - if (nextSegment == -1 || nextSegment == value.Length) - { - return image; - } - - ReadOnlySpan widthSpan = value[..nextSegment]; - - value = value[(nextSegment + 1)..]; - nextSegment = value.IndexOf(Delimiter); - if (nextSegment == -1) - { - nextSegment = value.Length; - } - - ReadOnlySpan heightSpan = value[..nextSegment]; - - if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) - && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) - { - image.Width = width; - image.Height = height; - } - - if (nextSegment < value.Length - 1) - { - value = value[(nextSegment + 1)..]; - var length = value.Length; - - Span blurHashSpan = stackalloc char[length]; - for (int i = 0; i < length; i++) - { - var c = value[i]; - blurHashSpan[i] = c switch - { - '/' => Delimiter, - '\\' => '|', - _ => c - }; - } - - image.BlurHash = new string(blurHashSpan); - } - } - - return image; - } - private List GetItemByNameTypesInQuery(InternalItemsQuery query) { var list = new List(); diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs index a9eda1b64..406230a70 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbContext.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs @@ -131,6 +131,21 @@ public class JellyfinDbContext(DbContextOptions options, ILog /// public DbSet BaseItemProviders => Set(); + /// + /// Gets the . + /// + public DbSet BaseItemImageInfos => Set(); + + /// + /// Gets the . + /// + public DbSet BaseItemMetadataFields => Set(); + + /// + /// Gets the . + /// + public DbSet BaseItemTrailerTypes => Set(); + /*public DbSet Artwork => Set(); public DbSet Books => Set(); diff --git a/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs new file mode 100644 index 000000000..7f69e8448 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.Designer.cs @@ -0,0 +1,1540 @@ +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDbContext))] + [Migration("20241009225800_ExpandedBaseItemFields")] + partial class ExpandedBaseItemFields + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DateCreated"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AncestorId", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AncestorIdText") + .HasColumnType("TEXT"); + + b.HasKey("ItemId", "Id"); + + b.HasIndex("Id"); + + b.HasIndex("ItemId", "AncestorIdText"); + + b.ToTable("AncestorIds"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AttachmentStreamInfo", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("Codec") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodecTag") + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Filename") + .HasColumnType("TEXT"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.HasKey("ItemId", "Index"); + + b.ToTable("AttachmentStreamInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Album") + .HasColumnType("TEXT"); + + b.Property("AlbumArtists") + .HasColumnType("TEXT"); + + b.Property("Artists") + .HasColumnType("TEXT"); + + b.Property("Audio") + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("TEXT"); + + b.Property("CleanName") + .HasColumnType("TEXT"); + + b.Property("CommunityRating") + .HasColumnType("REAL"); + + b.Property("CriticRating") + .HasColumnType("REAL"); + + b.Property("CustomRating") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("DateLastMediaAdded") + .HasColumnType("TEXT"); + + b.Property("DateLastRefreshed") + .HasColumnType("TEXT"); + + b.Property("DateLastSaved") + .HasColumnType("TEXT"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("EpisodeTitle") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasColumnType("TEXT"); + + b.Property("ExternalSeriesId") + .HasColumnType("TEXT"); + + b.Property("ExternalServiceId") + .HasColumnType("TEXT"); + + b.Property("ExtraIds") + .HasColumnType("TEXT"); + + b.Property("ExtraType") + .HasColumnType("INTEGER"); + + b.Property("ForcedSortName") + .HasColumnType("TEXT"); + + b.Property("Genres") + .HasColumnType("TEXT"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("IndexNumber") + .HasColumnType("INTEGER"); + + b.Property("InheritedParentalRatingValue") + .HasColumnType("INTEGER"); + + b.Property("IsFolder") + .HasColumnType("INTEGER"); + + b.Property("IsInMixedFolder") + .HasColumnType("INTEGER"); + + b.Property("IsLocked") + .HasColumnType("INTEGER"); + + b.Property("IsMovie") + .HasColumnType("INTEGER"); + + b.Property("IsRepeat") + .HasColumnType("INTEGER"); + + b.Property("IsSeries") + .HasColumnType("INTEGER"); + + b.Property("IsVirtualItem") + .HasColumnType("INTEGER"); + + b.Property("LUFS") + .HasColumnType("REAL"); + + b.Property("MediaType") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizationGain") + .HasColumnType("REAL"); + + b.Property("OfficialRating") + .HasColumnType("TEXT"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("ParentIndexNumber") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PreferredMetadataCountryCode") + .HasColumnType("TEXT"); + + b.Property("PreferredMetadataLanguage") + .HasColumnType("TEXT"); + + b.Property("PremiereDate") + .HasColumnType("TEXT"); + + b.Property("PresentationUniqueKey") + .HasColumnType("TEXT"); + + b.Property("PrimaryVersionId") + .HasColumnType("TEXT"); + + b.Property("ProductionLocations") + .HasColumnType("TEXT"); + + b.Property("ProductionYear") + .HasColumnType("INTEGER"); + + b.Property("RunTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("TEXT"); + + b.Property("SeasonName") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("SeriesName") + .HasColumnType("TEXT"); + + b.Property("SeriesPresentationUniqueKey") + .HasColumnType("TEXT"); + + b.Property("ShowId") + .HasColumnType("TEXT"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("Studios") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("Tags") + .HasColumnType("TEXT"); + + b.Property("TopParentId") + .HasColumnType("TEXT"); + + b.Property("TotalBitrate") + .HasColumnType("INTEGER"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UnratedType") + .HasColumnType("TEXT"); + + b.Property("UserDataKey") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.HasIndex("Path"); + + b.HasIndex("PresentationUniqueKey"); + + b.HasIndex("TopParentId", "Id"); + + b.HasIndex("UserDataKey", "Type"); + + b.HasIndex("Type", "TopParentId", "Id"); + + b.HasIndex("Type", "TopParentId", "PresentationUniqueKey"); + + b.HasIndex("Type", "TopParentId", "StartDate"); + + b.HasIndex("Id", "Type", "IsFolder", "IsVirtualItem"); + + b.HasIndex("MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey"); + + b.HasIndex("Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem"); + + b.HasIndex("Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName"); + + b.HasIndex("IsFolder", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated"); + + b.HasIndex("Type", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated"); + + b.ToTable("BaseItems"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Blurhash") + .HasColumnType("BLOB"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("ImageType") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ItemId"); + + b.ToTable("BaseItemImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemMetadataField", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.HasKey("Id", "ItemId"); + + b.HasIndex("ItemId"); + + b.ToTable("BaseItemMetadataFields"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderValue") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ItemId", "ProviderId"); + + b.HasIndex("ProviderId", "ProviderValue", "ItemId"); + + b.ToTable("BaseItemProviders"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemTrailerType", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.HasKey("Id", "ItemId"); + + b.HasIndex("ItemId"); + + b.ToTable("BaseItemTrailerTypes"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ChapterIndex") + .HasColumnType("INTEGER"); + + b.Property("ImageDateModified") + .HasColumnType("TEXT"); + + b.Property("ImagePath") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("StartPositionTicks") + .HasColumnType("INTEGER"); + + b.HasKey("ItemId", "ChapterIndex"); + + b.ToTable("Chapters"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client", "Key") + .IsUnique(); + + b.ToTable("CustomItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DashboardTheme") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property("TvHome") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("IndexBy") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property("SortBy") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemValue", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.Property("CleanValue") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ItemId", "Type", "Value"); + + b.HasIndex("ItemId", "Type", "CleanValue"); + + b.ToTable("ItemValues"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaSegment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndTicks") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("SegmentProviderId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartTicks") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaSegments"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaStreamInfo", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("StreamIndex") + .HasColumnType("INTEGER"); + + b.Property("AspectRatio") + .HasColumnType("TEXT"); + + b.Property("AverageFrameRate") + .HasColumnType("REAL"); + + b.Property("BitDepth") + .HasColumnType("INTEGER"); + + b.Property("BitRate") + .HasColumnType("INTEGER"); + + b.Property("BlPresentFlag") + .HasColumnType("INTEGER"); + + b.Property("ChannelLayout") + .HasColumnType("TEXT"); + + b.Property("Channels") + .HasColumnType("INTEGER"); + + b.Property("Codec") + .HasColumnType("TEXT"); + + b.Property("CodecTag") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CodecTimeBase") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ColorPrimaries") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ColorSpace") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ColorTransfer") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Comment") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DvBlSignalCompatibilityId") + .HasColumnType("INTEGER"); + + b.Property("DvLevel") + .HasColumnType("INTEGER"); + + b.Property("DvProfile") + .HasColumnType("INTEGER"); + + b.Property("DvVersionMajor") + .HasColumnType("INTEGER"); + + b.Property("DvVersionMinor") + .HasColumnType("INTEGER"); + + b.Property("ElPresentFlag") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("IsAnamorphic") + .HasColumnType("INTEGER"); + + b.Property("IsAvc") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("IsExternal") + .HasColumnType("INTEGER"); + + b.Property("IsForced") + .HasColumnType("INTEGER"); + + b.Property("IsHearingImpaired") + .HasColumnType("INTEGER"); + + b.Property("IsInterlaced") + .HasColumnType("INTEGER"); + + b.Property("KeyFrames") + .HasColumnType("TEXT"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("REAL"); + + b.Property("NalLengthSize") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PixelFormat") + .HasColumnType("TEXT"); + + b.Property("Profile") + .HasColumnType("TEXT"); + + b.Property("RealFrameRate") + .HasColumnType("REAL"); + + b.Property("RefFrames") + .HasColumnType("INTEGER"); + + b.Property("Rotation") + .HasColumnType("INTEGER"); + + b.Property("RpuPresentFlag") + .HasColumnType("INTEGER"); + + b.Property("SampleRate") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .HasColumnType("TEXT"); + + b.Property("TimeBase") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("ItemId", "StreamIndex"); + + b.HasIndex("StreamIndex"); + + b.HasIndex("StreamType"); + + b.HasIndex("StreamIndex", "StreamType"); + + b.HasIndex("StreamIndex", "StreamType", "Language"); + + b.ToTable("MediaStreamInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.People", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Role") + .HasColumnType("TEXT"); + + b.Property("ListOrder") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PersonType") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.HasKey("ItemId", "Role", "ListOrder"); + + b.HasIndex("Name"); + + b.HasIndex("ItemId", "ListOrder"); + + b.ToTable("Peoples"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccessToken") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AppName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("AppVersion") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("AccessToken", "DateLastActivity"); + + b.HasIndex("DeviceId", "DateLastActivity"); + + b.HasIndex("UserId", "DeviceId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CustomName") + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.ToTable("DeviceOptions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.TrickplayInfo", b => + { + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.Property("Bandwidth") + .HasColumnType("INTEGER"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Interval") + .HasColumnType("INTEGER"); + + b.Property("ThumbnailCount") + .HasColumnType("INTEGER"); + + b.Property("TileHeight") + .HasColumnType("INTEGER"); + + b.Property("TileWidth") + .HasColumnType("INTEGER"); + + b.HasKey("ItemId", "Width"); + + b.ToTable("TrickplayInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("CastReceiverId") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxActiveSessions") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.UserData", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("AudioStreamIndex") + .HasColumnType("INTEGER"); + + b.Property("BaseItemEntityId") + .HasColumnType("TEXT"); + + b.Property("IsFavorite") + .HasColumnType("INTEGER"); + + b.Property("LastPlayedDate") + .HasColumnType("TEXT"); + + b.Property("Likes") + .HasColumnType("INTEGER"); + + b.Property("PlayCount") + .HasColumnType("INTEGER"); + + b.Property("PlaybackPositionTicks") + .HasColumnType("INTEGER"); + + b.Property("Played") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("SubtitleStreamIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Key", "UserId"); + + b.HasIndex("BaseItemEntityId"); + + b.HasIndex("UserId"); + + b.HasIndex("Key", "UserId", "IsFavorite"); + + b.HasIndex("Key", "UserId", "LastPlayedDate"); + + b.HasIndex("Key", "UserId", "PlaybackPositionTicks"); + + b.HasIndex("Key", "UserId", "Played"); + + b.ToTable("UserData"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AncestorId", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("AncestorIds") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AttachmentStreamInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany() + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("Images") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemMetadataField", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("LockedFields") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("Provider") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemTrailerType", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("TrailerTypes") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("Chapters") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemValue", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("ItemValues") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaStreamInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("MediaStreams") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.People", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("Peoples") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.HasOne("Jellyfin.Data.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.UserData", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", null) + .WithMany("UserData") + .HasForeignKey("BaseItemEntityId"); + + b.HasOne("Jellyfin.Data.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b => + { + b.Navigation("AncestorIds"); + + b.Navigation("Chapters"); + + b.Navigation("Images"); + + b.Navigation("ItemValues"); + + b.Navigation("LockedFields"); + + b.Navigation("MediaStreams"); + + b.Navigation("Peoples"); + + b.Navigation("Provider"); + + b.Navigation("TrailerTypes"); + + b.Navigation("UserData"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Navigation("HomeSections"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Navigation("AccessSchedules"); + + b.Navigation("DisplayPreferences"); + + b.Navigation("ItemDisplayPreferences"); + + b.Navigation("Permissions"); + + b.Navigation("Preferences"); + + b.Navigation("ProfileImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.cs b/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.cs new file mode 100644 index 000000000..f1238db82 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20241009225800_ExpandedBaseItemFields.cs @@ -0,0 +1,169 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Jellyfin.Server.Implementations.Migrations +{ + /// + public partial class ExpandedBaseItemFields : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Images", + table: "BaseItems"); + + migrationBuilder.DropColumn( + name: "LockedFields", + table: "BaseItems"); + + migrationBuilder.DropColumn( + name: "TrailerTypes", + table: "BaseItems"); + + migrationBuilder.AlterColumn( + name: "ExtraType", + table: "BaseItems", + type: "INTEGER", + nullable: true, + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Audio", + table: "BaseItems", + type: "INTEGER", + nullable: true, + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true); + + migrationBuilder.CreateTable( + name: "BaseItemImageInfos", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Path = table.Column(type: "TEXT", nullable: false), + DateModified = table.Column(type: "TEXT", nullable: false), + ImageType = table.Column(type: "INTEGER", nullable: false), + Width = table.Column(type: "INTEGER", nullable: false), + Height = table.Column(type: "INTEGER", nullable: false), + Blurhash = table.Column(type: "BLOB", nullable: true), + ItemId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BaseItemImageInfos", x => x.Id); + table.ForeignKey( + name: "FK_BaseItemImageInfos_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BaseItemMetadataFields", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false), + ItemId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BaseItemMetadataFields", x => new { x.Id, x.ItemId }); + table.ForeignKey( + name: "FK_BaseItemMetadataFields_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BaseItemTrailerTypes", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false), + ItemId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BaseItemTrailerTypes", x => new { x.Id, x.ItemId }); + table.ForeignKey( + name: "FK_BaseItemTrailerTypes_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItemImageInfos_ItemId", + table: "BaseItemImageInfos", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_BaseItemMetadataFields_ItemId", + table: "BaseItemMetadataFields", + column: "ItemId"); + + migrationBuilder.CreateIndex( + name: "IX_BaseItemTrailerTypes_ItemId", + table: "BaseItemTrailerTypes", + column: "ItemId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BaseItemImageInfos"); + + migrationBuilder.DropTable( + name: "BaseItemMetadataFields"); + + migrationBuilder.DropTable( + name: "BaseItemTrailerTypes"); + + migrationBuilder.AlterColumn( + name: "ExtraType", + table: "BaseItems", + type: "TEXT", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Audio", + table: "BaseItems", + type: "TEXT", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "Images", + table: "BaseItems", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "LockedFields", + table: "BaseItems", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "TrailerTypes", + table: "BaseItems", + type: "TEXT", + nullable: true); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index dd280489b..1a3a5910f 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -154,8 +154,8 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Artists") .HasColumnType("TEXT"); - b.Property("Audio") - .HasColumnType("TEXT"); + b.Property("Audio") + .HasColumnType("INTEGER"); b.Property("ChannelId") .HasColumnType("TEXT"); @@ -208,8 +208,8 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("ExtraIds") .HasColumnType("TEXT"); - b.Property("ExtraType") - .HasColumnType("TEXT"); + b.Property("ExtraType") + .HasColumnType("INTEGER"); b.Property("ForcedSortName") .HasColumnType("TEXT"); @@ -220,9 +220,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Height") .HasColumnType("INTEGER"); - b.Property("Images") - .HasColumnType("TEXT"); - b.Property("IndexNumber") .HasColumnType("INTEGER"); @@ -253,9 +250,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("LUFS") .HasColumnType("REAL"); - b.Property("LockedFields") - .HasColumnType("TEXT"); - b.Property("MediaType") .HasColumnType("TEXT"); @@ -352,9 +346,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("TotalBitrate") .HasColumnType("INTEGER"); - b.Property("TrailerTypes") - .HasColumnType("TEXT"); - b.Property("Type") .IsRequired() .HasColumnType("TEXT"); @@ -401,6 +392,56 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("BaseItems"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Blurhash") + .HasColumnType("BLOB"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("ImageType") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ItemId"); + + b.ToTable("BaseItemImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemMetadataField", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.HasKey("Id", "ItemId"); + + b.HasIndex("ItemId"); + + b.ToTable("BaseItemMetadataFields"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b => { b.Property("ItemId") @@ -420,6 +461,21 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("BaseItemProviders"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemTrailerType", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("TEXT"); + + b.HasKey("Id", "ItemId"); + + b.HasIndex("ItemId"); + + b.ToTable("BaseItemTrailerTypes"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => { b.Property("ItemId") @@ -1268,6 +1324,28 @@ namespace Jellyfin.Server.Implementations.Migrations b.Navigation("Item"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("Images") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemMetadataField", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("LockedFields") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b => { b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") @@ -1279,6 +1357,17 @@ namespace Jellyfin.Server.Implementations.Migrations b.Navigation("Item"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemTrailerType", b => + { + b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") + .WithMany("TrailerTypes") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => { b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") @@ -1406,14 +1495,20 @@ namespace Jellyfin.Server.Implementations.Migrations b.Navigation("Chapters"); + b.Navigation("Images"); + b.Navigation("ItemValues"); + b.Navigation("LockedFields"); + b.Navigation("MediaStreams"); b.Navigation("Peoples"); b.Navigation("Provider"); + b.Navigation("TrailerTypes"); + b.Navigation("UserData"); }); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs index 6c36a1591..ab5403271 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs @@ -27,6 +27,9 @@ public class BaseItemConfiguration : IEntityTypeConfiguration builder.HasMany(e => e.Chapters); builder.HasMany(e => e.Provider); builder.HasMany(e => e.AncestorIds); + builder.HasMany(e => e.LockedFields); + builder.HasMany(e => e.TrailerTypes); + builder.HasMany(e => e.Images); builder.HasIndex(e => e.Path); builder.HasIndex(e => e.ParentId); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemMetadataFieldConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemMetadataFieldConfiguration.cs new file mode 100644 index 000000000..137f4a883 --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemMetadataFieldConfiguration.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq; +using Jellyfin.Data.Entities; +using MediaBrowser.Model.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using SQLitePCL; + +namespace Jellyfin.Server.Implementations.ModelConfiguration; + +/// +/// Provides configuration for the BaseItemMetadataField entity. +/// +public class BaseItemMetadataFieldConfiguration : IEntityTypeConfiguration +{ + /// + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(e => new { e.Id, e.ItemId }); + builder.HasOne(e => e.Item); + } +} diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemTrailerTypeConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemTrailerTypeConfiguration.cs new file mode 100644 index 000000000..f03d99c29 --- /dev/null +++ b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemTrailerTypeConfiguration.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq; +using Jellyfin.Data.Entities; +using MediaBrowser.Model.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using SQLitePCL; + +namespace Jellyfin.Server.Implementations.ModelConfiguration; + +/// +/// Provides configuration for the BaseItemMetadataField entity. +/// +public class BaseItemTrailerTypeConfiguration : IEntityTypeConfiguration +{ + /// + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(e => new { e.Id, e.ItemId }); + builder.HasOne(e => e.Item); + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs index c4a15c64e..8ce423298 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs @@ -2,13 +2,17 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; +using System.Text; using Emby.Server.Implementations.Data; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Libraries; +using Jellyfin.Extensions; using Jellyfin.Server.Implementations; using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using Microsoft.Data.Sqlite; @@ -503,293 +507,308 @@ public class MigrateLibraryDb : IMigrationRoutine private BaseItemEntity GetItem(SqliteDataReader reader) { - var item = new BaseItemEntity() + var entity = new BaseItemEntity() { - Type = reader.GetString(0) + Type = reader.GetString(0), + Id = Guid.NewGuid() }; var index = 1; if (reader.TryGetString(index++, out var data)) { - item.Data = data; + entity.Data = data; } if (reader.TryReadDateTime(index++, out var startDate)) { - item.StartDate = startDate; + entity.StartDate = startDate; } if (reader.TryReadDateTime(index++, out var endDate)) { - item.EndDate = endDate; + entity.EndDate = endDate; } if (reader.TryGetGuid(index++, out var guid)) { - item.ChannelId = guid.ToString("N"); + entity.ChannelId = guid.ToString("N"); } if (reader.TryGetBoolean(index++, out var isMovie)) { - item.IsMovie = isMovie; + entity.IsMovie = isMovie; } if (reader.TryGetBoolean(index++, out var isSeries)) { - item.IsSeries = isSeries; + entity.IsSeries = isSeries; } if (reader.TryGetString(index++, out var episodeTitle)) { - item.EpisodeTitle = episodeTitle; + entity.EpisodeTitle = episodeTitle; } if (reader.TryGetBoolean(index++, out var isRepeat)) { - item.IsRepeat = isRepeat; + entity.IsRepeat = isRepeat; } if (reader.TryGetSingle(index++, out var communityRating)) { - item.CommunityRating = communityRating; + entity.CommunityRating = communityRating; } if (reader.TryGetString(index++, out var customRating)) { - item.CustomRating = customRating; + entity.CustomRating = customRating; } if (reader.TryGetInt32(index++, out var indexNumber)) { - item.IndexNumber = indexNumber; + entity.IndexNumber = indexNumber; } if (reader.TryGetBoolean(index++, out var isLocked)) { - item.IsLocked = isLocked; + entity.IsLocked = isLocked; } if (reader.TryGetString(index++, out var preferredMetadataLanguage)) { - item.PreferredMetadataLanguage = preferredMetadataLanguage; + entity.PreferredMetadataLanguage = preferredMetadataLanguage; } if (reader.TryGetString(index++, out var preferredMetadataCountryCode)) { - item.PreferredMetadataCountryCode = preferredMetadataCountryCode; + entity.PreferredMetadataCountryCode = preferredMetadataCountryCode; } if (reader.TryGetInt32(index++, out var width)) { - item.Width = width; + entity.Width = width; } if (reader.TryGetInt32(index++, out var height)) { - item.Height = height; + entity.Height = height; } if (reader.TryReadDateTime(index++, out var dateLastRefreshed)) { - item.DateLastRefreshed = dateLastRefreshed; + entity.DateLastRefreshed = dateLastRefreshed; } if (reader.TryGetString(index++, out var name)) { - item.Name = name; + entity.Name = name; } if (reader.TryGetString(index++, out var restorePath)) { - item.Path = restorePath; + entity.Path = restorePath; } if (reader.TryReadDateTime(index++, out var premiereDate)) { - item.PremiereDate = premiereDate; + entity.PremiereDate = premiereDate; } if (reader.TryGetString(index++, out var overview)) { - item.Overview = overview; + entity.Overview = overview; } if (reader.TryGetInt32(index++, out var parentIndexNumber)) { - item.ParentIndexNumber = parentIndexNumber; + entity.ParentIndexNumber = parentIndexNumber; } if (reader.TryGetInt32(index++, out var productionYear)) { - item.ProductionYear = productionYear; + entity.ProductionYear = productionYear; } if (reader.TryGetString(index++, out var officialRating)) { - item.OfficialRating = officialRating; + entity.OfficialRating = officialRating; } if (reader.TryGetString(index++, out var forcedSortName)) { - item.ForcedSortName = forcedSortName; + entity.ForcedSortName = forcedSortName; } if (reader.TryGetInt64(index++, out var runTimeTicks)) { - item.RunTimeTicks = runTimeTicks; + entity.RunTimeTicks = runTimeTicks; } if (reader.TryGetInt64(index++, out var size)) { - item.Size = size; + entity.Size = size; } if (reader.TryReadDateTime(index++, out var dateCreated)) { - item.DateCreated = dateCreated; + entity.DateCreated = dateCreated; } if (reader.TryReadDateTime(index++, out var dateModified)) { - item.DateModified = dateModified; + entity.DateModified = dateModified; } - item.Id = reader.GetGuid(index++); + entity.Id = reader.GetGuid(index++); if (reader.TryGetString(index++, out var genres)) { - item.Genres = genres; + entity.Genres = genres; } if (reader.TryGetGuid(index++, out var parentId)) { - item.ParentId = parentId; + entity.ParentId = parentId; } - if (reader.TryGetString(index++, out var audioString)) + if (reader.TryGetString(index++, out var audioString) && Enum.TryParse(audioString, out var audioType)) { - item.Audio = audioString; + entity.Audio = audioType; } if (reader.TryGetString(index++, out var serviceName)) { - item.ExternalServiceId = serviceName; + entity.ExternalServiceId = serviceName; } if (reader.TryGetBoolean(index++, out var isInMixedFolder)) { - item.IsInMixedFolder = isInMixedFolder; + entity.IsInMixedFolder = isInMixedFolder; } if (reader.TryReadDateTime(index++, out var dateLastSaved)) { - item.DateLastSaved = dateLastSaved; + entity.DateLastSaved = dateLastSaved; } if (reader.TryGetString(index++, out var lockedFields)) { - item.LockedFields = lockedFields; + entity.LockedFields = lockedFields.Split('|').Select(Enum.Parse) + .Select(e => new BaseItemMetadataField() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }) + .ToArray(); } if (reader.TryGetString(index++, out var studios)) { - item.Studios = studios; + entity.Studios = studios; } if (reader.TryGetString(index++, out var tags)) { - item.Tags = tags; + entity.Tags = tags; } if (reader.TryGetString(index++, out var trailerTypes)) { - item.TrailerTypes = trailerTypes; + entity.TrailerTypes = trailerTypes.Split('|').Select(Enum.Parse) + .Select(e => new BaseItemTrailerType() + { + Id = (int)e, + Item = entity, + ItemId = entity.Id + }) + .ToArray(); } if (reader.TryGetString(index++, out var originalTitle)) { - item.OriginalTitle = originalTitle; + entity.OriginalTitle = originalTitle; } if (reader.TryGetString(index++, out var primaryVersionId)) { - item.PrimaryVersionId = primaryVersionId; + entity.PrimaryVersionId = primaryVersionId; } if (reader.TryReadDateTime(index++, out var dateLastMediaAdded)) { - item.DateLastMediaAdded = dateLastMediaAdded; + entity.DateLastMediaAdded = dateLastMediaAdded; } if (reader.TryGetString(index++, out var album)) { - item.Album = album; + entity.Album = album; } if (reader.TryGetSingle(index++, out var lUFS)) { - item.LUFS = lUFS; + entity.LUFS = lUFS; } if (reader.TryGetSingle(index++, out var normalizationGain)) { - item.NormalizationGain = normalizationGain; + entity.NormalizationGain = normalizationGain; } if (reader.TryGetSingle(index++, out var criticRating)) { - item.CriticRating = criticRating; + entity.CriticRating = criticRating; } if (reader.TryGetBoolean(index++, out var isVirtualItem)) { - item.IsVirtualItem = isVirtualItem; + entity.IsVirtualItem = isVirtualItem; } if (reader.TryGetString(index++, out var seriesName)) { - item.SeriesName = seriesName; + entity.SeriesName = seriesName; } if (reader.TryGetString(index++, out var seasonName)) { - item.SeasonName = seasonName; + entity.SeasonName = seasonName; } if (reader.TryGetGuid(index++, out var seasonId)) { - item.SeasonId = seasonId; + entity.SeasonId = seasonId; } if (reader.TryGetGuid(index++, out var seriesId)) { - item.SeriesId = seriesId; + entity.SeriesId = seriesId; } if (reader.TryGetString(index++, out var presentationUniqueKey)) { - item.PresentationUniqueKey = presentationUniqueKey; + entity.PresentationUniqueKey = presentationUniqueKey; } if (reader.TryGetInt32(index++, out var parentalRating)) { - item.InheritedParentalRatingValue = parentalRating; + entity.InheritedParentalRatingValue = parentalRating; } if (reader.TryGetString(index++, out var externalSeriesId)) { - item.ExternalSeriesId = externalSeriesId; + entity.ExternalSeriesId = externalSeriesId; } if (reader.TryGetString(index++, out var tagLine)) { - item.Tagline = tagLine; + entity.Tagline = tagLine; } if (reader.TryGetString(index++, out var providerIds)) { - item.Provider = providerIds.Split('|').Select(e => e.Split("=")) + entity.Provider = providerIds.Split('|').Select(e => e.Split("=")) .Select(e => new BaseItemProvider() { Item = null!, @@ -800,59 +819,217 @@ public class MigrateLibraryDb : IMigrationRoutine if (reader.TryGetString(index++, out var imageInfos)) { - item.Images = imageInfos; + entity.Images = DeserializeImages(imageInfos).Select(f => Map(entity.Id, f)).ToArray(); } if (reader.TryGetString(index++, out var productionLocations)) { - item.ProductionLocations = productionLocations; + entity.ProductionLocations = productionLocations; } if (reader.TryGetString(index++, out var extraIds)) { - item.ExtraIds = extraIds; + entity.ExtraIds = extraIds; } if (reader.TryGetInt32(index++, out var totalBitrate)) { - item.TotalBitrate = totalBitrate; + entity.TotalBitrate = totalBitrate; } - if (reader.TryGetString(index++, out var extraTypeString)) + if (reader.TryGetString(index++, out var extraTypeString) && Enum.TryParse(extraTypeString, out var extraType)) { - item.ExtraType = extraTypeString; + entity.ExtraType = extraType; } if (reader.TryGetString(index++, out var artists)) { - item.Artists = artists; + entity.Artists = artists; } if (reader.TryGetString(index++, out var albumArtists)) { - item.AlbumArtists = albumArtists; + entity.AlbumArtists = albumArtists; } if (reader.TryGetString(index++, out var externalId)) { - item.ExternalId = externalId; + entity.ExternalId = externalId; } if (reader.TryGetString(index++, out var seriesPresentationUniqueKey)) { - item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; + entity.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; } if (reader.TryGetString(index++, out var showId)) { - item.ShowId = showId; + entity.ShowId = showId; } if (reader.TryGetGuid(index++, out var ownerId)) { - item.OwnerId = ownerId.ToString("N"); + entity.OwnerId = ownerId.ToString("N"); } - return item; + return entity; + } + + private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e) + { + return new BaseItemImageInfo() + { + ItemId = baseItemId, + Id = Guid.NewGuid(), + Path = e.Path, + Blurhash = e.BlurHash != null ? Encoding.UTF8.GetBytes(e.BlurHash) : null, + DateModified = e.DateModified, + Height = e.Height, + Width = e.Width, + ImageType = (ImageInfoImageType)e.Type, + Item = null! + }; + } + + internal ItemImageInfo[] DeserializeImages(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return Array.Empty(); + } + + // TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed + var valueSpan = value.AsSpan(); + var count = valueSpan.Count('|') + 1; + + var position = 0; + var result = new ItemImageInfo[count]; + foreach (var part in valueSpan.Split('|')) + { + var image = ItemImageInfoFromValueString(part); + + if (image is not null) + { + result[position++] = image; + } + } + + if (position == count) + { + return result; + } + + if (position == 0) + { + return Array.Empty(); + } + + // Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array. + return result[..position]; + } + + internal ItemImageInfo? ItemImageInfoFromValueString(ReadOnlySpan value) + { + const char Delimiter = '*'; + + var nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + return null; + } + + ReadOnlySpan path = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + return null; + } + + ReadOnlySpan dateModified = value[..nextSegment]; + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + nextSegment = value.Length; + } + + ReadOnlySpan imageType = value[..nextSegment]; + + var image = new ItemImageInfo + { + Path = path.ToString() + }; + + if (long.TryParse(dateModified, CultureInfo.InvariantCulture, out var ticks) + && ticks >= DateTime.MinValue.Ticks + && ticks <= DateTime.MaxValue.Ticks) + { + image.DateModified = new DateTime(ticks, DateTimeKind.Utc); + } + else + { + return null; + } + + if (Enum.TryParse(imageType, true, out ImageType type)) + { + image.Type = type; + } + else + { + return null; + } + + // Optional parameters: width*height*blurhash + if (nextSegment + 1 < value.Length - 1) + { + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1 || nextSegment == value.Length) + { + return image; + } + + ReadOnlySpan widthSpan = value[..nextSegment]; + + value = value[(nextSegment + 1)..]; + nextSegment = value.IndexOf(Delimiter); + if (nextSegment == -1) + { + nextSegment = value.Length; + } + + ReadOnlySpan heightSpan = value[..nextSegment]; + + if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) + && int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) + { + image.Width = width; + image.Height = height; + } + + if (nextSegment < value.Length - 1) + { + value = value[(nextSegment + 1)..]; + var length = value.Length; + + Span blurHashSpan = stackalloc char[length]; + for (int i = 0; i < length; i++) + { + var c = value[i]; + blurHashSpan[i] = c switch + { + '/' => Delimiter, + '\\' => '|', + _ => c + }; + } + + image.BlurHash = new string(blurHashSpan); + } + } + + return image; } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs index 1cf9e864d..105f5d7af 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -99,31 +99,6 @@ namespace Jellyfin.Server.Implementations.Tests.Data return data; } - [Theory] - [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))] - public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected) - { - var result = _sqliteItemRepository.ItemImageInfoFromValueString(value)!; - Assert.Equal(expected.Path, result.Path); - Assert.Equal(expected.Type, result.Type); - Assert.Equal(expected.DateModified, result.DateModified); - Assert.Equal(expected.Width, result.Width); - Assert.Equal(expected.Height, result.Height); - Assert.Equal(expected.BlurHash, result.BlurHash); - } - - [Theory] - [InlineData("")] - [InlineData("*")] - [InlineData("https://image.tmdb.org/t/p/original/zhB5CHEgqqh4wnEqDNJLfWXJlcL.jpg*0")] - [InlineData("/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*6374520964785129080*WjQbtJtSO8nhNZ%L_Io#R/oaS DeserializeImages_Valid_TestData() { var data = new TheoryData(); @@ -204,47 +179,6 @@ namespace Jellyfin.Server.Implementations.Tests.Data return data; } - [Theory] - [MemberData(nameof(DeserializeImages_Valid_TestData))] - public void DeserializeImages_Valid_Success(string value, ItemImageInfo[] expected) - { - var result = _sqliteItemRepository.DeserializeImages(value); - Assert.Equal(expected.Length, result.Length); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i].Path, result[i].Path); - Assert.Equal(expected[i].Type, result[i].Type); - Assert.Equal(expected[i].DateModified, result[i].DateModified); - Assert.Equal(expected[i].Width, result[i].Width); - Assert.Equal(expected[i].Height, result[i].Height); - Assert.Equal(expected[i].BlurHash, result[i].BlurHash); - } - } - - [Theory] - [MemberData(nameof(DeserializeImages_ValidAndInvalid_TestData))] - public void DeserializeImages_ValidAndInvalid_Success(string value, ItemImageInfo[] expected) - { - var result = _sqliteItemRepository.DeserializeImages(value); - Assert.Equal(expected.Length, result.Length); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i].Path, result[i].Path); - Assert.Equal(expected[i].Type, result[i].Type); - Assert.Equal(expected[i].DateModified, result[i].DateModified); - Assert.Equal(expected[i].Width, result[i].Width); - Assert.Equal(expected[i].Height, result[i].Height); - Assert.Equal(expected[i].BlurHash, result[i].BlurHash); - } - } - - [Theory] - [MemberData(nameof(DeserializeImages_Valid_TestData))] - public void SerializeImages_Valid_Success(string expected, ItemImageInfo[] value) - { - Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value)); - } - private sealed class ProviderIdsExtensionsTestsObject : IHasProviderIds { public Dictionary ProviderIds { get; set; } = new Dictionary(); -- cgit v1.2.3 From 441b99518988212a84070dffe1ef51d6faa4e0af Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 10 Oct 2024 15:27:13 +0000 Subject: Applied Review Suggestions --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 3 ++- Jellyfin.Data/Entities/BaseItemMetadataField.cs | 2 -- Jellyfin.Data/Entities/BaseItemTrailerType.cs | 3 +-- Jellyfin.Data/Entities/EnumLikeTable.cs | 14 -------------- Jellyfin.Data/Entities/MediaStreamTypeEntity.cs | 12 ++++++------ Jellyfin.Data/Entities/ProgramAudioEntity.cs | 12 ++++++------ 6 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 Jellyfin.Data/Entities/EnumLikeTable.cs (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index b66e7f5d9..df0f4ea20 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.Threading.Channels; using Emby.Server.Implementations.Playlists; @@ -116,5 +117,5 @@ public class ItemTypeLookup : IItemTypeLookup { BaseItemKind.UserView, typeof(UserView).FullName }, { BaseItemKind.Video, typeof(Video).FullName }, { BaseItemKind.Year, typeof(Year).FullName } - }.AsReadOnly(); + }.ToFrozenDictionary(); } diff --git a/Jellyfin.Data/Entities/BaseItemMetadataField.cs b/Jellyfin.Data/Entities/BaseItemMetadataField.cs index 2f8e910f2..c9d44c046 100644 --- a/Jellyfin.Data/Entities/BaseItemMetadataField.cs +++ b/Jellyfin.Data/Entities/BaseItemMetadataField.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; namespace Jellyfin.Data.Entities; -#pragma warning disable CA2227 /// /// Enum MetadataFields. diff --git a/Jellyfin.Data/Entities/BaseItemTrailerType.cs b/Jellyfin.Data/Entities/BaseItemTrailerType.cs index 7dee20c87..fb31fc8a4 100644 --- a/Jellyfin.Data/Entities/BaseItemTrailerType.cs +++ b/Jellyfin.Data/Entities/BaseItemTrailerType.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; namespace Jellyfin.Data.Entities; -#pragma warning disable CA2227 + /// /// Enum TrailerTypes. /// diff --git a/Jellyfin.Data/Entities/EnumLikeTable.cs b/Jellyfin.Data/Entities/EnumLikeTable.cs deleted file mode 100644 index 11e1d0aa9..000000000 --- a/Jellyfin.Data/Entities/EnumLikeTable.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Jellyfin.Data.Entities; - -/// -/// Defines an Entity that is modeled after an Enum. -/// -public abstract class EnumLikeTable -{ - /// - /// Gets or Sets Numerical ID of this enumeratable. - /// - public required int Id { get; set; } -} diff --git a/Jellyfin.Data/Entities/MediaStreamTypeEntity.cs b/Jellyfin.Data/Entities/MediaStreamTypeEntity.cs index d1f6f1b18..f57672a2c 100644 --- a/Jellyfin.Data/Entities/MediaStreamTypeEntity.cs +++ b/Jellyfin.Data/Entities/MediaStreamTypeEntity.cs @@ -8,30 +8,30 @@ public enum MediaStreamTypeEntity /// /// The audio. /// - Audio, + Audio = 0, /// /// The video. /// - Video, + Video = 1, /// /// The subtitle. /// - Subtitle, + Subtitle = 2, /// /// The embedded image. /// - EmbeddedImage, + EmbeddedImage = 3, /// /// The data. /// - Data, + Data = 4, /// /// The lyric. /// - Lyric + Lyric = 5 } diff --git a/Jellyfin.Data/Entities/ProgramAudioEntity.cs b/Jellyfin.Data/Entities/ProgramAudioEntity.cs index fafccb13c..5b225a002 100644 --- a/Jellyfin.Data/Entities/ProgramAudioEntity.cs +++ b/Jellyfin.Data/Entities/ProgramAudioEntity.cs @@ -8,30 +8,30 @@ public enum ProgramAudioEntity /// /// Mono. /// - Mono, + Mono = 0, /// /// Sterio. /// - Stereo, + Stereo = 1, /// /// Dolby. /// - Dolby, + Dolby = 2, /// /// DolbyDigital. /// - DolbyDigital, + DolbyDigital = 3, /// /// Thx. /// - Thx, + Thx = 4, /// /// Atmos. /// - Atmos + Atmos = 5 } -- cgit v1.2.3 From ae641b7f3af5117612b3917d93013d26191a71d8 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 10 Oct 2024 19:27:26 +0000 Subject: Applied review comments --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 73 ++++---- .../Item/BaseItemRepository.cs | 193 ++++++++++----------- .../Persistence/IItemTypeLookup.cs | 7 +- 3 files changed, 136 insertions(+), 137 deletions(-) (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index df0f4ea20..dc55211d8 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Threading.Channels; using Emby.Server.Implementations.Playlists; using Jellyfin.Data.Enums; @@ -82,40 +84,43 @@ public class ItemTypeLookup : IItemTypeLookup ]; /// - public IDictionary BaseItemKindNames { get; } = new Dictionary() + public IReadOnlyList MusicGenreTypes => BaseItemKindNames.Where(e => e.Key is BaseItemKind.Audio or BaseItemKind.MusicVideo or BaseItemKind.MusicAlbum or BaseItemKind.MusicArtist).Select(e => e.Value).ToImmutableArray(); + + /// + 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 } + { 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! } }.ToFrozenDictionary(); } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index d8ce4a135..5708391a5 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -23,6 +23,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -139,63 +140,57 @@ public sealed class BaseItemRepository( /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter) { - return GetItemValues(filter, [0, 1], typeof(MusicArtist).FullName!); + return GetItemValues(filter, [ItemValueType.Artist, ItemValueType.AlbumArtist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter) { - return GetItemValues(filter, [0], typeof(MusicArtist).FullName!); + return GetItemValues(filter, [ItemValueType.Artist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter) { - return GetItemValues(filter, [1], typeof(MusicArtist).FullName!); + return GetItemValues(filter, [ItemValueType.AlbumArtist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter) { - return GetItemValues(filter, [3], typeof(Studio).FullName!); + return GetItemValues(filter, [ItemValueType.Studios], itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter) { - return GetItemValues(filter, [2], typeof(Genre).FullName!); + return GetItemValues(filter, [ItemValueType.Genre], itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!); } /// public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter) { - return GetItemValues(filter, [2], typeof(MusicGenre).FullName!); + return GetItemValues(filter, [ItemValueType.Genre], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!); } /// public IReadOnlyList GetStudioNames() { - return GetItemValueNames([3], Array.Empty(), Array.Empty()); + return GetItemValueNames([ItemValueType.Studios], Array.Empty(), Array.Empty()); } /// public IReadOnlyList GetAllArtistNames() { - return GetItemValueNames([0, 1], Array.Empty(), Array.Empty()); + return GetItemValueNames([ItemValueType.Artist, ItemValueType.AlbumArtist], Array.Empty(), Array.Empty()); } /// public IReadOnlyList GetMusicGenreNames() { return GetItemValueNames( - [2], - new string[] - { - typeof(Audio).FullName!, - typeof(MusicVideo).FullName!, - typeof(MusicAlbum).FullName!, - typeof(MusicArtist).FullName! - }, + [ItemValueType.Genre], + itemTypeLookup.MusicGenreTypes, Array.Empty()); } @@ -203,15 +198,9 @@ public sealed class BaseItemRepository( public IReadOnlyList GetGenreNames() { return GetItemValueNames( - [2], + [ItemValueType.Genre], Array.Empty(), - new string[] - { - typeof(Audio).FullName!, - typeof(MusicVideo).FullName!, - typeof(MusicAlbum).FullName!, - typeof(MusicArtist).FullName! - }); + itemTypeLookup.MusicGenreTypes); } /// @@ -1084,7 +1073,7 @@ public sealed class BaseItemRepository( if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode) { baseQuery = baseQuery - .Where(e => e.ItemValues!.Where(e => e.ItemValue.Type == ItemValueType.InheritedTags) + .Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags) .Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)) || (e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value)!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags) @@ -1246,84 +1235,76 @@ public sealed class BaseItemRepository( tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags); } - try + using var context = dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + foreach (var item in tuples) { - using var context = dbProvider.CreateDbContext(); - using var transaction = context.Database.BeginTransaction(); - foreach (var item in tuples) + var entity = Map(item.Item); + if (!context.BaseItems.Any(e => e.Id == entity.Id)) { - var entity = Map(item.Item); - if (!context.BaseItems.Any(e => e.Id == entity.Id)) - { - context.BaseItems.Add(entity); - } - else - { - context.BaseItems.Attach(entity).State = EntityState.Modified; - } + context.BaseItems.Add(entity); + } + else + { + context.BaseItems.Attach(entity).State = EntityState.Modified; + } - context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete(); - if (item.Item.SupportsAncestors && item.AncestorIds != null) + context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + if (item.Item.SupportsAncestors && item.AncestorIds != null) + { + entity.AncestorIds = new List(); + foreach (var ancestorId in item.AncestorIds) { - entity.AncestorIds = new List(); - foreach (var ancestorId in item.AncestorIds) + entity.AncestorIds.Add(new AncestorId() { - entity.AncestorIds.Add(new AncestorId() - { - ParentItemId = ancestorId, - ItemId = entity.Id, - Item = null!, - ParentItem = null! - }); - } + ParentItemId = ancestorId, + ItemId = entity.Id, + Item = null!, + ParentItem = null! + }); } + } - var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags); - var itemValues = itemValuesToSave.Select(e => e.Value).ToArray(); - context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete(); - entity.ItemValues = new List(); - var referenceValues = context.ItemValues.Where(e => itemValues.Any(f => f == e.CleanValue)).ToArray(); + var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags); + var itemValues = itemValuesToSave.Select(e => e.Value).ToArray(); + context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + entity.ItemValues = new List(); + var referenceValues = context.ItemValues.Where(e => itemValues.Any(f => f == e.CleanValue)).ToArray(); - foreach (var itemValue in itemValuesToSave) + foreach (var itemValue in itemValuesToSave) + { + var refValue = referenceValues.FirstOrDefault(f => f.CleanValue == itemValue.Value && (int)f.Type == itemValue.MagicNumber); + if (refValue is not null) { - var refValue = referenceValues.FirstOrDefault(f => f.CleanValue == itemValue.Value && (int)f.Type == itemValue.MagicNumber); - if (refValue is not null) + entity.ItemValues.Add(new ItemValueMap() { - entity.ItemValues.Add(new ItemValueMap() - { - Item = entity, - ItemId = entity.Id, - ItemValue = null!, - ItemValueId = refValue.ItemValueId - }); - } - else + Item = entity, + ItemId = entity.Id, + ItemValue = null!, + ItemValueId = refValue.ItemValueId + }); + } + else + { + entity.ItemValues.Add(new ItemValueMap() { - entity.ItemValues.Add(new ItemValueMap() + Item = entity, + ItemId = entity.Id, + ItemValue = new ItemValue() { - Item = entity, - ItemId = entity.Id, - ItemValue = new ItemValue() - { - CleanValue = GetCleanValue(itemValue.Value), - Type = (ItemValueType)itemValue.MagicNumber, - ItemValueId = Guid.NewGuid(), - Value = itemValue.Value - }, - ItemValueId = Guid.Empty - }); - } + CleanValue = GetCleanValue(itemValue.Value), + Type = (ItemValueType)itemValue.MagicNumber, + ItemValueId = Guid.NewGuid(), + Value = itemValue.Value + }, + ItemValueId = Guid.Empty + }); } } - - context.SaveChanges(); - transaction.Commit(); - } - catch (System.Exception) - { - System.Console.WriteLine(); - throw; } + + context.SaveChanges(); + transaction.Commit(); } /// @@ -1665,7 +1646,7 @@ public sealed class BaseItemRepository( return entity; } - private IReadOnlyList GetItemValueNames(int[] itemValueTypes, IReadOnlyList withItemTypes, IReadOnlyList excludeItemTypes) + private IReadOnlyList GetItemValueNames(ItemValueType[] itemValueTypes, IReadOnlyList withItemTypes, IReadOnlyList excludeItemTypes) { using var context = dbProvider.CreateDbContext(); @@ -1725,7 +1706,7 @@ public sealed class BaseItemRepository( return Map(baseItemEntity, dto); } - private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, int[] itemValueTypes, string returnType) + private QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery filter, ItemValueType[] itemValueTypes, string returnType) { ArgumentNullException.ThrowIfNull(filter); @@ -1787,19 +1768,27 @@ public sealed class BaseItemRepository( result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count(); } + var seriesTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Series]; + var movieTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie]; + var episodeTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode]; + var musicAlbumTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum]; + var musicArtistTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]; + var audioTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio]; + var trailerTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Trailer]; + var resultQuery = query.Select(e => new { item = e, // TODO: This is bad refactor! itemCount = new ItemCounts() { - SeriesCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Series).FullName), - EpisodeCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Data.Entities.Libraries.Movie).FullName), - MovieCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Series).FullName), - AlbumCount = e.ItemValues!.Count(f => f.Item.Type == typeof(MusicAlbum).FullName), - ArtistCount = e.ItemValues!.Count(f => f.Item.Type == typeof(MusicArtist).FullName), - SongCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Audio).FullName), - TrailerCount = e.ItemValues!.Count(f => f.Item.Type == typeof(Trailer).FullName), + SeriesCount = e.ItemValues!.Count(f => f.Item.Type == seriesTypeName), + EpisodeCount = e.ItemValues!.Count(f => f.Item.Type == episodeTypeName), + MovieCount = e.ItemValues!.Count(f => f.Item.Type == movieTypeName), + AlbumCount = e.ItemValues!.Count(f => f.Item.Type == musicAlbumTypeName), + ArtistCount = e.ItemValues!.Count(f => f.Item.Type == musicArtistTypeName), + SongCount = e.ItemValues!.Count(f => f.Item.Type == audioTypeName), + TrailerCount = e.ItemValues!.Count(f => f.Item.Type == trailerTypeName), } }); @@ -1958,27 +1947,27 @@ public sealed class BaseItemRepository( if (IsTypeInQuery(BaseItemKind.Person, query)) { - list.Add(typeof(Person).FullName!); + list.Add(itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]!); } if (IsTypeInQuery(BaseItemKind.Genre, query)) { - list.Add(typeof(Genre).FullName!); + list.Add(itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!); } if (IsTypeInQuery(BaseItemKind.MusicGenre, query)) { - list.Add(typeof(MusicGenre).FullName!); + list.Add(itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!); } if (IsTypeInQuery(BaseItemKind.MusicArtist, query)) { - list.Add(typeof(MusicArtist).FullName!); + list.Add(itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); } if (IsTypeInQuery(BaseItemKind.Studio, query)) { - list.Add(typeof(Studio).FullName!); + list.Add(itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!); } return list; diff --git a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs index 6ad8380d7..343b95e9e 100644 --- a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs +++ b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs @@ -50,8 +50,13 @@ public interface IItemTypeLookup /// public IReadOnlyList ArtistsTypes { get; } + /// + /// Gets all serialisation target types for music related kinds. + /// + IReadOnlyList MusicGenreTypes { get; } + /// /// Gets mapping for all BaseItemKinds and their expected serialization target. /// - public IDictionary BaseItemKindNames { get; } + public IDictionary BaseItemKindNames { get; } } -- cgit v1.2.3 From 058a567e0025d2a3086de8530be613fdf2b08c8c Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 11 Oct 2024 11:46:43 +0000 Subject: Removed unused mapping tables --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 62 ---------------------- .../Persistence/IItemTypeLookup.cs | 40 -------------- 2 files changed, 102 deletions(-) (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index dc55211d8..5504012bf 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -21,68 +21,6 @@ namespace Emby.Server.Implementations.Data; /// public class ItemTypeLookup : IItemTypeLookup { - /// - public IReadOnlyList AllItemFields { get; } = Enum.GetValues(); - - /// - public IReadOnlyList ProgramTypes { get; } = - [ - BaseItemKind.Program, - BaseItemKind.TvChannel, - BaseItemKind.LiveTvProgram, - BaseItemKind.LiveTvChannel - ]; - - /// - public IReadOnlyList ProgramExcludeParentTypes { get; } = - [ - BaseItemKind.Series, - BaseItemKind.Season, - BaseItemKind.MusicAlbum, - BaseItemKind.MusicArtist, - BaseItemKind.PhotoAlbum - ]; - - /// - public IReadOnlyList ServiceTypes { get; } = - [ - BaseItemKind.TvChannel, - BaseItemKind.LiveTvChannel - ]; - - /// - public IReadOnlyList StartDateTypes { get; } = - [ - BaseItemKind.Program, - BaseItemKind.LiveTvProgram - ]; - - /// - public IReadOnlyList SeriesTypes { get; } = - [ - BaseItemKind.Book, - BaseItemKind.AudioBook, - BaseItemKind.Episode, - BaseItemKind.Season - ]; - - /// - public IReadOnlyList ArtistExcludeParentTypes { get; } = - [ - BaseItemKind.Series, - BaseItemKind.Season, - BaseItemKind.PhotoAlbum - ]; - - /// - public IReadOnlyList ArtistsTypes { get; } = - [ - BaseItemKind.Audio, - BaseItemKind.MusicAlbum, - BaseItemKind.MusicVideo, - BaseItemKind.AudioBook - ]; - /// public IReadOnlyList MusicGenreTypes => BaseItemKindNames.Where(e => e.Key is BaseItemKind.Audio or BaseItemKind.MusicVideo or BaseItemKind.MusicAlbum or BaseItemKind.MusicArtist).Select(e => e.Value).ToImmutableArray(); diff --git a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs index 343b95e9e..d2c6ff365 100644 --- a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs +++ b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs @@ -10,46 +10,6 @@ namespace MediaBrowser.Controller.Persistence; /// 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 all serialisation target types for music related kinds. /// -- cgit v1.2.3 From e20ecfc670c9ef8977b0795c85e35ce165fee46e Mon Sep 17 00:00:00 2001 From: JPVenson Date: Fri, 11 Oct 2024 14:16:42 +0000 Subject: applied review comments --- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 10 ++++++++-- Emby.Server.Implementations/Library/MusicManager.cs | 9 +-------- Jellyfin.Server.Implementations/Item/PeopleRepository.cs | 8 +++++--- MediaBrowser.Controller/Persistence/IItemTypeLookup.cs | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index 5504012bf..f5db28c7a 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -22,10 +22,16 @@ namespace Emby.Server.Implementations.Data; public class ItemTypeLookup : IItemTypeLookup { /// - public IReadOnlyList MusicGenreTypes => BaseItemKindNames.Where(e => e.Key is BaseItemKind.Audio or BaseItemKind.MusicVideo or BaseItemKind.MusicAlbum or BaseItemKind.MusicArtist).Select(e => e.Value).ToImmutableArray(); + public IReadOnlyList MusicGenreTypes { get; } = [ + + typeof(Audio).FullName!, + typeof(MusicVideo).FullName!, + typeof(MusicAlbum).FullName!, + typeof(MusicArtist).FullName!, + ]; /// - public IDictionary BaseItemKindNames { get; } = new Dictionary() + public IReadOnlyDictionary BaseItemKindNames { get; } = new Dictionary() { { BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName! }, { BaseItemKind.Audio, typeof(Audio).FullName! }, diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 3f2909947..71c69ec50 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -27,14 +27,7 @@ namespace Emby.Server.Implementations.Library public IReadOnlyList GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions) { - var list = new List - { - item - }; - - list.AddRange(GetInstantMixFromGenres(item.Genres, user, dtoOptions)); - - return [.. list]; + return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } /// diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs index dee87f48f..5f5bf09af 100644 --- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs +++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs @@ -16,10 +16,11 @@ namespace Jellyfin.Server.Implementations.Item; /// Manager for handling people. /// /// Efcore Factory. +/// Items lookup service. /// /// Initializes a new instance of the class. /// -public class PeopleRepository(IDbContextFactory dbProvider) : IPeopleRepository +public class PeopleRepository(IDbContextFactory dbProvider, IItemTypeLookup itemTypeLookup) : IPeopleRepository { private readonly IDbContextFactory _dbProvider = dbProvider; @@ -118,8 +119,9 @@ public class PeopleRepository(IDbContextFactory dbProvider) : { if (filter.User is not null && filter.IsFavorite.HasValue) { - query = query.Where(e => e.PersonType == typeof(Person).FullName) - .Where(e => context.BaseItems.Where(d => context.UserData.Where(e => e.IsFavorite == filter.IsFavorite && e.UserId.Equals(filter.User.Id)).Any(f => f.Key == d.UserDataKey)) + var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]; + query = query.Where(e => e.PersonType == personType) + .Where(e => context.BaseItems.Where(d => context.UserData.Where(w => w.IsFavorite == filter.IsFavorite && w.UserId.Equals(filter.User.Id)).Any(f => f.Key == d.UserDataKey)) .Select(f => f.Name).Contains(e.Name)); } diff --git a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs index d2c6ff365..9507f79d3 100644 --- a/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs +++ b/MediaBrowser.Controller/Persistence/IItemTypeLookup.cs @@ -18,5 +18,5 @@ public interface IItemTypeLookup /// /// Gets mapping for all BaseItemKinds and their expected serialization target. /// - public IDictionary BaseItemKindNames { get; } + public IReadOnlyDictionary BaseItemKindNames { get; } } -- cgit v1.2.3 From b39553611d0d6702ef657f76573cefa2ee437745 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Sun, 17 Nov 2024 11:03:43 +0000 Subject: Applied coding style --- .../Data/CleanDatabaseScheduledTask.cs | 4 ++-- Emby.Server.Implementations/Data/ItemTypeLookup.cs | 6 ------ Emby.Server.Implementations/Library/LibraryManager.cs | 7 ++++--- Emby.Server.Implementations/Library/MediaSourceManager.cs | 2 +- Jellyfin.Api/Controllers/InstantMixController.cs | 2 +- Jellyfin.Api/Controllers/YearsController.cs | 4 ++-- Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 12 ++++++------ Jellyfin.Server.Implementations/Item/ChapterRepository.cs | 2 +- .../Item/MediaAttachmentRepository.cs | 2 +- .../Item/MediaStreamRepository.cs | 2 +- Jellyfin.Server.Implementations/Item/PeopleRepository.cs | 4 ++-- .../MediaSegments/MediaSegmentManager.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 10 +++++----- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 4 ++-- MediaBrowser.Controller/Library/ILibraryManager.cs | 2 +- MediaBrowser.Providers/Music/ArtistMetadataService.cs | 2 +- src/Jellyfin.LiveTv/Guide/GuideManager.cs | 2 +- 18 files changed, 33 insertions(+), 38 deletions(-) (limited to 'Emby.Server.Implementations/Data/ItemTypeLookup.cs') diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 6ea7d9197..aceff8b53 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.Data _dbProvider = dbProvider; } - public Task Run(IProgress progress, CancellationToken cancellationToken) + public async Task Run(IProgress progress, CancellationToken cancellationToken) { - return CleanDeadItems(cancellationToken, progress); + await CleanDeadItems(cancellationToken, progress).ConfigureAwait(false); } private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress progress) diff --git a/Emby.Server.Implementations/Data/ItemTypeLookup.cs b/Emby.Server.Implementations/Data/ItemTypeLookup.cs index f5db28c7a..82c0a8b6c 100644 --- a/Emby.Server.Implementations/Data/ItemTypeLookup.cs +++ b/Emby.Server.Implementations/Data/ItemTypeLookup.cs @@ -1,12 +1,8 @@ -using System; using System.Collections.Frozen; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using System.Threading.Channels; using Emby.Server.Implementations.Playlists; using Jellyfin.Data.Enums; -using Jellyfin.Server.Implementations; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; @@ -14,7 +10,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Data; @@ -23,7 +18,6 @@ public class ItemTypeLookup : IItemTypeLookup { /// public IReadOnlyList MusicGenreTypes { get; } = [ - typeof(Audio).FullName!, typeof(MusicVideo).FullName!, typeof(MusicAlbum).FullName!, diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 7e059be23..7b37011cb 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1810,11 +1810,11 @@ namespace Emby.Server.Implementations.Library /// public void CreateItem(BaseItem item, BaseItem? parent) { - CreateItems(new[] { item }, parent, CancellationToken.None); + CreateOrUpdateItems(new[] { item }, parent, CancellationToken.None); } /// - public void CreateItems(IReadOnlyList items, BaseItem? parent, CancellationToken cancellationToken) + public void CreateOrUpdateItems(IReadOnlyList items, BaseItem? parent, CancellationToken cancellationToken) { _itemRepository.SaveItems(items, cancellationToken); @@ -2971,10 +2971,11 @@ namespace Emby.Server.Implementations.Library { if (createEntity) { - CreateItems([personEntity], null, CancellationToken.None); + CreateOrUpdateItems([personEntity], null, CancellationToken.None); } await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); + CreateOrUpdateItems([personEntity], null, CancellationToken.None); } } } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 2fb571a10..d0f5e60f7 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Library list.Add(source); } - return SortMediaSources(list).ToImmutableArray(); + return SortMediaSources(list).ToArray(); } /// > diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index e89e7ce26..87a856d38 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -397,7 +397,7 @@ public class InstantMixController : BaseJellyfinApiController if (limit.HasValue && limit < items.Count) { - items = items.Take(limit.Value).ToImmutableArray(); + items = items.Take(limit.Value).ToArray(); } var result = new QueryResult( diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 907724e04..e709e43e2 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -113,11 +113,11 @@ public class YearsController : BaseJellyfinApiController if (userId.IsNullOrEmpty()) { - items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToImmutableArray(); + items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToArray(); } else { - items = recursive ? folder.GetRecursiveChildren(user, query) : folder.GetChildren(user, true).Where(Filter).ToImmutableArray(); + items = recursive ? folder.GetRecursiveChildren(user, query) : folder.GetChildren(user, true).Where(Filter).ToArray(); } } else diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 0183685be..8670b06cc 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -117,7 +117,7 @@ public sealed class BaseItemRepository( PrepareFilterQuery(filter); using var context = dbProvider.CreateDbContext(); - return ApplyQueryFilter(context.BaseItems.AsNoTracking(), context, filter).Select(e => e.Id).ToImmutableArray(); + return ApplyQueryFilter(context.BaseItems.AsNoTracking(), context, filter).Select(e => e.Id).ToArray(); } /// @@ -216,7 +216,7 @@ public sealed class BaseItemRepository( dbQuery = ApplyGroupingFilter(dbQuery, filter); dbQuery = ApplyQueryPageing(dbQuery, filter); - result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray(); + result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToArray(); result.StartIndex = filter.StartIndex ?? 0; return result; } @@ -235,7 +235,7 @@ public sealed class BaseItemRepository( dbQuery = ApplyGroupingFilter(dbQuery, filter); dbQuery = ApplyQueryPageing(dbQuery, filter); - return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray(); + return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToArray(); } private IQueryable ApplyGroupingFilter(IQueryable dbQuery, InternalItemsQuery filter) @@ -831,7 +831,7 @@ public sealed class BaseItemRepository( } // query = query.DistinctBy(e => e.CleanValue); - return query.Select(e => e.ItemValue.CleanValue).ToImmutableArray(); + return query.Select(e => e.ItemValue.CleanValue).ToArray(); } private static bool TypeRequiresDeserialization(Type type) @@ -976,10 +976,10 @@ public sealed class BaseItemRepository( }); result.StartIndex = filter.StartIndex ?? 0; - result.Items = resultQuery.ToImmutableArray().Where(e => e is not null).Select(e => + result.Items = resultQuery.ToArray().Where(e => e is not null).Select(e => { return (DeserialiseBaseItem(e.item, filter.SkipDeserialization), e.itemCount); - }).ToImmutableArray(); + }).ToArray(); return result; } diff --git a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs index dc55484c9..16e8c205d 100644 --- a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs +++ b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs @@ -73,7 +73,7 @@ public class ChapterRepository : IChapterRepository }) .ToList() .Select(e => Map(e.chapter, e.baseItemPath!)) - .ToImmutableArray(); + .ToArray(); } /// diff --git a/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs b/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs index c6488f321..155798209 100644 --- a/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs +++ b/Jellyfin.Server.Implementations/Item/MediaAttachmentRepository.cs @@ -40,7 +40,7 @@ public class MediaAttachmentRepository(IDbContextFactory dbPr query = query.Where(e => e.Index == filter.Index); } - return query.AsEnumerable().Select(Map).ToImmutableArray(); + return query.AsEnumerable().Select(Map).ToArray(); } private MediaAttachment Map(AttachmentStreamInfo attachment) diff --git a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs index 0617dd81e..d6bfc1a8f 100644 --- a/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs +++ b/Jellyfin.Server.Implementations/Item/MediaStreamRepository.cs @@ -51,7 +51,7 @@ public class MediaStreamRepository : IMediaStreamRepository public IReadOnlyList GetMediaStreams(MediaStreamQuery filter) { using var context = _dbProvider.CreateDbContext(); - return TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter).AsEnumerable().Select(Map).ToImmutableArray(); + return TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter).AsEnumerable().Select(Map).ToArray(); } private string? GetPathToSave(string? path) diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs index 417212ba4..d1823514a 100644 --- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs +++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs @@ -36,7 +36,7 @@ public class PeopleRepository(IDbContextFactory dbProvider, I dbQuery = dbQuery.Take(filter.Limit); } - return dbQuery.AsEnumerable().Select(Map).ToImmutableArray(); + return dbQuery.AsEnumerable().Select(Map).ToArray(); } /// @@ -51,7 +51,7 @@ public class PeopleRepository(IDbContextFactory dbProvider, I dbQuery = dbQuery.Take(filter.Limit); } - return dbQuery.Select(e => e.Name).ToImmutableArray(); + return dbQuery.Select(e => e.Name).ToArray(); } /// diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index 151b616f7..d0f41c6fa 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -154,7 +154,7 @@ public class MediaSegmentManager : IMediaSegmentManager return query .OrderBy(e => e.StartTicks) .AsNoTracking() - .ToImmutableArray() + .ToArray() .Select(Map); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d92407a3f..a6bc35a9f 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1092,7 +1092,7 @@ namespace MediaBrowser.Controller.Entities return 1; }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) .ThenByDescending(i => i, new MediaSourceWidthComparator()) - .ToImmutableArray(); + .ToArray(); } protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8fff7dbc4..a13f04614 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -452,7 +452,7 @@ namespace MediaBrowser.Controller.Entities if (newItems.Count > 0) { - LibraryManager.CreateItems(newItems, this, cancellationToken); + LibraryManager.CreateOrUpdateItems(newItems, this, cancellationToken); } } else @@ -1306,7 +1306,7 @@ namespace MediaBrowser.Controller.Entities AddChildren(user, includeLinkedChildren, result, false, query); - return result.Values.ToImmutableArray(); + return result.Values.ToArray(); } protected virtual IEnumerable GetEligibleChildrenForRecursiveChildren(User user) @@ -1379,7 +1379,7 @@ namespace MediaBrowser.Controller.Entities AddChildren(user, true, result, true, query); - return result.Values.ToImmutableArray(); + return result.Values.ToArray(); } /// @@ -1407,7 +1407,7 @@ namespace MediaBrowser.Controller.Entities AddChildrenToList(result, includeLinkedChildren, true, filter); - return result.Values.ToImmutableArray(); + return result.Values.ToArray(); } /// @@ -1563,7 +1563,7 @@ namespace MediaBrowser.Controller.Entities return LinkedChildren .Select(i => new Tuple(i, GetLinkedChild(i))) .Where(i => i.Item2 is not null) - .ToImmutableArray(); + .ToArray(); } protected override async Task RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList fileSystemChildren, CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index cb17e3faf..d0c9f049a 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -131,13 +131,13 @@ namespace MediaBrowser.Controller.Entities.Movies public override IReadOnlyList GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { var children = base.GetChildren(user, includeLinkedChildren, query); - return Sort(children, user).ToImmutableArray(); + return Sort(children, user).ToArray(); } public override IReadOnlyList GetRecursiveChildren(User user, InternalItemsQuery query) { var children = base.GetRecursiveChildren(user, query); - return Sort(children, user).ToImmutableArray(); + return Sort(children, user).ToArray(); } public BoxSetInfo GetLookupInfo() diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 47b1cb16e..8fcd5f605 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -258,7 +258,7 @@ namespace MediaBrowser.Controller.Library /// Items to create. /// Parent of new items. /// CancellationToken to use for operation. - void CreateItems(IReadOnlyList items, BaseItem? parent, CancellationToken cancellationToken); + void CreateOrUpdateItems(IReadOnlyList items, BaseItem? parent, CancellationToken cancellationToken); /// /// Updates the item. diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index 8af6de925..c47f9a500 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Music Recursive = true, IsFolder = false }) - : item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder).ToImmutableArray(); + : item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); } } } diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs index f657422a0..ff31b7123 100644 --- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -265,7 +265,7 @@ public class GuideManager : IGuideManager if (newPrograms.Count > 0) { - _libraryManager.CreateItems(newPrograms, null, cancellationToken); + _libraryManager.CreateOrUpdateItems(newPrograms, null, cancellationToken); await PrecacheImages(newPrograms, maxCacheDate).ConfigureAwait(false); } -- cgit v1.2.3