From d0b4b2ddb31a54f0705303ab8461be1125d66eab Mon Sep 17 00:00:00 2001 From: JPVenson Date: Sat, 7 Sep 2024 19:07:34 +0000 Subject: Migrated UserData from library sqlite db to jellyfin.db --- tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index 5bc4abd06..20a8f6152 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -149,7 +149,7 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal(new DateTime(2019, 8, 6, 9, 1, 18), item.DateCreated); // userData - var userData = _userDataManager.GetUserData(_testUser, item); + var userData = _userDataManager.GetUserData(_testUser, item)!; Assert.Equal(2, userData.PlayCount); Assert.True(userData.Played); Assert.Equal(new DateTime(2021, 02, 11, 07, 47, 23), userData.LastPlayedDate); -- cgit v1.2.3 From 2014fa56b8ab0b0aec0b31ae0d2d9e2fce02ee53 Mon Sep 17 00:00:00 2001 From: JPVenson <6794763+JPVenson@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:41:54 +0000 Subject: Ported new Item Repository architecture --- .../Library/MediaSourceManager.cs | 17 +++---- Jellyfin.Api/Controllers/InstantMixController.cs | 6 ++- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- .../Library/IMediaSourceManager.cs | 6 +-- .../Data/SqliteItemRepositoryTests.cs | 58 ++-------------------- 5 files changed, 20 insertions(+), 69 deletions(-) (limited to 'tests') diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index a5a715721..3bf1a4cde 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using System.IO; using System.Linq; @@ -164,7 +165,7 @@ namespace Emby.Server.Implementations.Library }); } - public async Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) + public async Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); @@ -217,7 +218,7 @@ namespace Emby.Server.Implementations.Library list.Add(source); } - return SortMediaSources(list); + return SortMediaSources(list).ToImmutableList(); } /// > @@ -458,7 +459,7 @@ namespace Emby.Server.Implementations.Library } } - private static List SortMediaSources(IEnumerable sources) + private static IEnumerable SortMediaSources(IEnumerable sources) { return sources.OrderBy(i => { @@ -475,8 +476,7 @@ namespace Emby.Server.Implementations.Library return stream?.Width ?? 0; }) - .Where(i => i.Type != MediaSourceType.Placeholder) - .ToList(); + .Where(i => i.Type != MediaSourceType.Placeholder); } public async Task> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) @@ -811,7 +811,7 @@ namespace Emby.Server.Implementations.Library return result.Item1; } - public async Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken) + public async Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken) { var stream = new MediaSourceInfo { @@ -834,10 +834,7 @@ namespace Emby.Server.Implementations.Library await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths) .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false); - return new List - { - stream - }; + return [stream]; } public async Task CloseLiveStream(string id) diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index dcbacf1d7..e9dda19ca 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; +using System.Linq; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; @@ -389,7 +391,7 @@ public class InstantMixController : BaseJellyfinApiController return GetResult(items, user, limit, dtoOptions); } - private QueryResult GetResult(List items, User? user, int? limit, DtoOptions dtoOptions) + private QueryResult GetResult(IReadOnlyList items, User? user, int? limit, DtoOptions dtoOptions) { var list = items; @@ -397,7 +399,7 @@ public class InstantMixController : BaseJellyfinApiController if (limit.HasValue && limit < list.Count) { - list = list.GetRange(0, limit.Value); + list = list.Take(limit.Value).ToImmutableArray(); } var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 3a5db2f3f..60b8804f7 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -132,7 +132,7 @@ public static class StreamingHelpers mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) ? mediaSources[0] - : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); + : mediaSources.FirstOrDefault(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id)) { diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 5ed3a49c3..729b385cf 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Library /// Option to enable path substitution. /// CancellationToken to use for operation. /// List of media sources wrapped in an awaitable task. - Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken); + Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken); /// /// Gets the static media sources. @@ -70,7 +70,7 @@ namespace MediaBrowser.Controller.Library /// Option to enable path substitution. /// User to use for operation. /// List of media sources. - List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null); + IReadOnlyList GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null); /// /// Gets the static media source. @@ -123,7 +123,7 @@ namespace MediaBrowser.Controller.Library /// The . /// The . /// A task containing the 's for the recording. - Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken); + Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken); /// /// Closes the media source. diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs index 0d2b488bc..1cf9e864d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using AutoFixture; using AutoFixture.AutoMoq; using Emby.Server.Implementations.Data; +using Jellyfin.Server.Implementations.Item; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Configuration; using Moq; @@ -18,7 +20,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data public const string MetaDataPath = "/meta/data/path"; private readonly IFixture _fixture; - private readonly SqliteItemRepository _sqliteItemRepository; + private readonly BaseItemRepository _sqliteItemRepository; public SqliteItemRepositoryTests() { @@ -40,7 +42,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }); _fixture.Inject(appHost); _fixture.Inject(config); - _sqliteItemRepository = _fixture.Create(); + _sqliteItemRepository = _fixture.Create(); } public static TheoryData ItemImageInfoFromValueString_Valid_TestData() @@ -101,7 +103,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data [MemberData(nameof(ItemImageInfoFromValueString_Valid_TestData))] public void ItemImageInfoFromValueString_Valid_Success(string value, ItemImageInfo expected) { - var result = _sqliteItemRepository.ItemImageInfoFromValueString(value); + var result = _sqliteItemRepository.ItemImageInfoFromValueString(value)!; Assert.Equal(expected.Path, result.Path); Assert.Equal(expected.Type, result.Type); Assert.Equal(expected.DateModified, result.DateModified); @@ -243,56 +245,6 @@ namespace Jellyfin.Server.Implementations.Tests.Data Assert.Equal(expected, _sqliteItemRepository.SerializeImages(value)); } - public static TheoryData> DeserializeProviderIds_Valid_TestData() - { - var data = new TheoryData>(); - - data.Add( - "Imdb=tt0119567", - new Dictionary() - { - { "Imdb", "tt0119567" }, - }); - - data.Add( - "Imdb=tt0119567|Tmdb=330|TmdbCollection=328", - new Dictionary() - { - { "Imdb", "tt0119567" }, - { "Tmdb", "330" }, - { "TmdbCollection", "328" }, - }); - - data.Add( - "MusicBrainzAlbum=9d363e43-f24f-4b39-bc5a-7ef305c677c7|MusicBrainzReleaseGroup=63eba062-847c-3b73-8b0f-6baf27bba6fa|AudioDbArtist=111352|AudioDbAlbum=2116560|MusicBrainzAlbumArtist=20244d07-534f-4eff-b4d4-930878889970", - new Dictionary() - { - { "MusicBrainzAlbum", "9d363e43-f24f-4b39-bc5a-7ef305c677c7" }, - { "MusicBrainzReleaseGroup", "63eba062-847c-3b73-8b0f-6baf27bba6fa" }, - { "AudioDbArtist", "111352" }, - { "AudioDbAlbum", "2116560" }, - { "MusicBrainzAlbumArtist", "20244d07-534f-4eff-b4d4-930878889970" }, - }); - - return data; - } - - [Theory] - [MemberData(nameof(DeserializeProviderIds_Valid_TestData))] - public void DeserializeProviderIds_Valid_Success(string value, Dictionary expected) - { - var result = new ProviderIdsExtensionsTestsObject(); - SqliteItemRepository.DeserializeProviderIds(value, result); - Assert.Equal(expected, result.ProviderIds); - } - - [Theory] - [MemberData(nameof(DeserializeProviderIds_Valid_TestData))] - public void SerializeProviderIds_Valid_Success(string expected, Dictionary values) - { - Assert.Equal(expected, SqliteItemRepository.SerializeProviderIds(values)); - } - private sealed class ProviderIdsExtensionsTestsObject : IHasProviderIds { public Dictionary ProviderIds { get; set; } = new Dictionary(); -- cgit v1.2.3 From 01d834f21abcb65d246b18762b79001929fe845b Mon Sep 17 00:00:00 2001 From: JPVenson <6794763+JPVenson@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:20:42 +0000 Subject: Fixed (most) tests --- Jellyfin.Data/Entities/BaseItemEntity.cs | 26 +- .../Item/BaseItemRepository.cs | 134 +- .../JellyfinDbContext.cs | 26 +- .../20240907123425_UserDataInJfLib.Designer.cs | 775 ---------- .../Migrations/20240907123425_UserDataInJfLib.cs | 79 -- .../20241009112234_BaseItemRefactor.Designer.cs | 1484 -------------------- .../Migrations/20241009112234_BaseItemRefactor.cs | 514 ------- .../20241009132112_BaseItemRefactor.Designer.cs | 1445 +++++++++++++++++++ .../Migrations/20241009132112_BaseItemRefactor.cs | 501 +++++++ .../Migrations/DesignTimeJellyfinDbFactory.cs | 3 +- .../Migrations/JellyfinDbModelSnapshot.cs | 39 - .../ModelConfiguration/BaseItemConfiguration.cs | 9 +- .../Providers/MetadataResult.cs | 2 +- .../Manager/MetadataServiceTests.cs | 2 +- .../Controllers/LibraryStructureControllerTests.cs | 19 +- 15 files changed, 2067 insertions(+), 2991 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.cs (limited to 'tests') diff --git a/Jellyfin.Data/Entities/BaseItemEntity.cs b/Jellyfin.Data/Entities/BaseItemEntity.cs index 5348c8746..dbe5a5372 100644 --- a/Jellyfin.Data/Entities/BaseItemEntity.cs +++ b/Jellyfin.Data/Entities/BaseItemEntity.cs @@ -156,28 +156,12 @@ public class BaseItemEntity public Guid? ParentId { get; set; } - public BaseItemEntity? Parent { get; set; } - - public ICollection? DirectChildren { get; set; } - public Guid? TopParentId { get; set; } - public BaseItemEntity? TopParent { get; set; } - - public ICollection? AllChildren { get; set; } - public Guid? SeasonId { get; set; } - public BaseItemEntity? Season { get; set; } - - public ICollection? SeasonEpisodes { get; set; } - public Guid? SeriesId { get; set; } - public ICollection? SeriesEpisodes { get; set; } - - public BaseItemEntity? Series { get; set; } - public ICollection? Peoples { get; set; } public ICollection? UserData { get; set; } @@ -191,4 +175,14 @@ public class BaseItemEntity public ICollection? Provider { get; set; } public ICollection? AncestorIds { 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; } + // public BaseItemEntity? Season { get; set; } + // public BaseItemEntity? Parent { get; set; } + // public ICollection? DirectChildren { get; set; } + // public BaseItemEntity? TopParent { get; set; } + // public ICollection? AllChildren { get; set; } + // public ICollection? SeasonEpisodes { get; set; } } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index d5a1be679..480d83eb1 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -5,20 +5,16 @@ using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Linq.Expressions; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; -using System.Threading.Channels; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller; 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.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; @@ -26,6 +22,7 @@ using MediaBrowser.Model.Querying; using Microsoft.EntityFrameworkCore; using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem; using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity; +#pragma warning disable RS0030 // Do not use banned APIs namespace Jellyfin.Server.Implementations.Item; @@ -66,12 +63,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr using var context = dbProvider.CreateDbContext(); using var transaction = context.Database.BeginTransaction(); - context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); - context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); - context.MediaStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); - context.AncestorIds.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); - context.ItemValues.Where(e => e.ItemId.Equals(id)).ExecuteDelete(); - context.BaseItems.Where(e => e.Id.Equals(id)).ExecuteDelete(); + context.Peoples.Where(e => e.ItemId == id).ExecuteDelete(); + context.Chapters.Where(e => e.ItemId == id).ExecuteDelete(); + context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete(); + 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.SaveChanges(); transaction.Commit(); } @@ -113,8 +110,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr PrepareFilterQuery(filter); using var context = dbProvider.CreateDbContext(); - var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter) - .DistinctBy(e => e.Id); + var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter); + // .DistinctBy(e => e.Id); var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter); if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey) @@ -266,8 +263,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr PrepareFilterQuery(filter); using var context = dbProvider.CreateDbContext(); - var dbQuery = TranslateQuery(context.BaseItems, context, filter) - .DistinctBy(e => e.Id); + var dbQuery = TranslateQuery(context.BaseItems, context, filter); if (filter.Limit.HasValue || filter.StartIndex.HasValue) { var offset = filter.StartIndex ?? 0; @@ -299,6 +295,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr return dbQuery.Count(); } +#pragma warning disable CA1307 // Specify StringComparison for clarity private IQueryable TranslateQuery( IQueryable baseQuery, JellyfinDbContext context, @@ -419,7 +416,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (!string.IsNullOrEmpty(filter.SearchTerm)) { - baseQuery = baseQuery.Where(e => e.CleanName!.Contains(filter.SearchTerm, StringComparison.InvariantCultureIgnoreCase) || (e.OriginalTitle != null && e.OriginalTitle.Contains(filter.SearchTerm, StringComparison.InvariantCultureIgnoreCase))); + baseQuery = baseQuery.Where(e => e.CleanName!.Contains(filter.SearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.Contains(filter.SearchTerm))); } if (filter.IsFolder.HasValue) @@ -474,18 +471,15 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr baseQuery = baseQuery.Where(e => includeTypeName.Contains(e.Type)); } - if (filter.ChannelIds.Count == 1) + if (filter.ChannelIds.Count > 0) { - baseQuery = baseQuery.Where(e => e.ChannelId == filter.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); - } - else if (filter.ChannelIds.Count > 1) - { - baseQuery = baseQuery.Where(e => filter.ChannelIds.Select(f => f.ToString("N", CultureInfo.InvariantCulture)).Contains(e.ChannelId)); + var channelIds = filter.ChannelIds.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray(); + baseQuery = baseQuery.Where(e => channelIds.Contains(e.ChannelId)); } if (!filter.ParentId.IsEmpty()) { - baseQuery = baseQuery.Where(e => e.ParentId.Equals(filter.ParentId)); + baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId); } if (!string.IsNullOrWhiteSpace(filter.Path)) @@ -591,7 +585,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.TrailerTypes.Length > 0) { - baseQuery = baseQuery.Where(e => filter.TrailerTypes.Any(f => e.TrailerTypes!.Contains(f.ToString(), StringComparison.OrdinalIgnoreCase))); + var trailerTypes = filter.TrailerTypes.Select(e => e.ToString()).ToArray(); + baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Contains(f))); } if (filter.IsAiring.HasValue) @@ -611,7 +606,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr baseQuery = baseQuery .Where(e => context.Peoples.Where(w => context.BaseItems.Where(w => filter.PersonIds.Contains(w.Id)).Any(f => f.Name == w.Name)) - .Any(f => f.ItemId.Equals(e.Id))); + .Any(f => f.ItemId == e.Id)); } if (!string.IsNullOrWhiteSpace(filter.Person)) @@ -649,12 +644,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr { baseQuery = baseQuery.Where(e => e.CleanName == filter.NameContains - || e.OriginalTitle!.Contains(filter.NameContains!, StringComparison.Ordinal)); + || e.OriginalTitle!.Contains(filter.NameContains!)); } if (!string.IsNullOrWhiteSpace(filter.NameStartsWith)) { - baseQuery = baseQuery.Where(e => e.SortName!.Contains(filter.NameStartsWith, StringComparison.OrdinalIgnoreCase)); + baseQuery = baseQuery.Where(e => e.SortName!.Contains(filter.NameStartsWith)); } if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater)) @@ -671,31 +666,32 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.ImageTypes.Length > 0) { - baseQuery = baseQuery.Where(e => filter.ImageTypes.Any(f => e.Images!.Contains(f.ToString(), StringComparison.InvariantCulture))); + var imgTypes = filter.ImageTypes.Select(e => e.ToString()).ToArray(); + baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Contains(f))); } if (filter.IsLiked.HasValue) { baseQuery = baseQuery - .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue); + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue); } if (filter.IsFavoriteOrLiked.HasValue) { baseQuery = baseQuery - .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked); + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked); } if (filter.IsFavorite.HasValue) { baseQuery = baseQuery - .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite); + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite); } if (filter.IsPlayed.HasValue) { baseQuery = baseQuery - .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value); + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value); } if (filter.IsResumable.HasValue) @@ -703,12 +699,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.IsResumable.Value) { baseQuery = baseQuery - .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0); + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0); } else { baseQuery = baseQuery - .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0); + .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0); } } @@ -925,7 +921,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value) { baseQuery = baseQuery - .Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id.Equals(e.ParentId.Value))); + .Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id == e.ParentId.Value)); } if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value) @@ -1048,11 +1044,11 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; if (enableItemsByName && includedItemByNameTypes.Count > 0) { - baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w.Equals(e.TopParentId!.Value))); + baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value)); } else { - baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w.Equals(e.TopParentId!.Value))); + baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w == e.TopParentId!.Value)); } } @@ -1064,7 +1060,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey)) { baseQuery = baseQuery - .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId.Equals(f.Id)))); + .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId == f.Id))); } if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey)) @@ -1090,7 +1086,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr .Where(e => e.ItemValues!.Where(e => e.Type == 6) .Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue)) || - (e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId.Equals(e.ParentId.Value))!.Where(e => e.Type == 6) + (e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId == e.ParentId.Value)!.Where(e => e.Type == 6) .Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue)))); } @@ -1112,21 +1108,23 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.SeriesStatuses.Length > 0) { + var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray(); baseQuery = baseQuery - .Where(e => filter.SeriesStatuses.Any(f => e.Data!.Contains(f.ToString(), StringComparison.InvariantCultureIgnoreCase))); + .Where(e => seriesStatus.Any(f => e.Data!.Contains(f))); } if (filter.BoxSetLibraryFolders.Length > 0) { + var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray(); baseQuery = baseQuery - .Where(e => filter.BoxSetLibraryFolders.Any(f => e.Data!.Contains(f.ToString("N", CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase))); + .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f))); } if (filter.VideoTypes.Length > 0) { var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"" + e + "\""); baseQuery = baseQuery - .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f, StringComparison.InvariantCultureIgnoreCase))); + .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f))); } if (filter.Is3D.HasValue) @@ -1134,12 +1132,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.Is3D.Value) { baseQuery = baseQuery - .Where(e => e.Data!.Contains("Video3DFormat", StringComparison.InvariantCultureIgnoreCase)); + .Where(e => e.Data!.Contains("Video3DFormat")); } else { baseQuery = baseQuery - .Where(e => !e.Data!.Contains("Video3DFormat", StringComparison.InvariantCultureIgnoreCase)); + .Where(e => !e.Data!.Contains("Video3DFormat")); } } @@ -1148,12 +1146,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.IsPlaceHolder.Value) { baseQuery = baseQuery - .Where(e => e.Data!.Contains("IsPlaceHolder\":true", StringComparison.InvariantCultureIgnoreCase)); + .Where(e => e.Data!.Contains("IsPlaceHolder\":true")); } else { baseQuery = baseQuery - .Where(e => !e.Data!.Contains("IsPlaceHolder\":true", StringComparison.InvariantCultureIgnoreCase)); + .Where(e => !e.Data!.Contains("IsPlaceHolder\":true")); } } @@ -1212,7 +1210,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr using var db = dbProvider.CreateDbContext(); db.BaseItems - .Where(e => e.Id.Equals(item.Id)) + .Where(e => e.Id == item.Id) .ExecuteUpdate(e => e.SetProperty(f => f.Images, images)); } @@ -1246,11 +1244,20 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr } using var context = dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); foreach (var item in tuples) { var entity = Map(item.Item); - context.BaseItems.Add(entity); + if (!context.BaseItems.Any(e => e.Id == entity.Id)) + { + 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) { foreach (var ancestorId in item.AncestorIds) @@ -1260,13 +1267,13 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr Item = entity, AncestorIdText = ancestorId.ToString(), Id = ancestorId, - ItemId = entity.Id + ItemId = Guid.Empty }); } } var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags); - context.ItemValues.Where(e => e.ItemId.Equals(entity.Id)).ExecuteDelete(); + context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete(); foreach (var itemValue in itemValues) { context.ItemValues.Add(new() @@ -1275,12 +1282,13 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr Type = itemValue.MagicNumber, Value = itemValue.Value, CleanValue = GetCleanValue(itemValue.Value), - ItemId = entity.Id + ItemId = Guid.Empty }); } } - context.SaveChanges(true); + context.SaveChanges(); + transaction.Commit(); } /// @@ -1292,7 +1300,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr } using var context = dbProvider.CreateDbContext(); - var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id)); + var item = context.BaseItems.FirstOrDefault(e => e.Id == id); if (item is null) { return null; @@ -1380,7 +1388,7 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr dto.Audio = Enum.Parse(entity.Audio); } - dto.ExtraIds = entity.ExtraIds?.Split('|').Select(e => Guid.Parse(e)).ToArray(); + dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? null : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray(); dto.ProductionLocations = entity.ProductionLocations?.Split('|'); dto.Studios = entity.Studios?.Split('|'); dto.Tags = entity.Tags?.Split('|'); @@ -1535,8 +1543,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr entity.Audio = dto.Audio?.ToString(); entity.ExtraType = dto.ExtraType?.ToString(); - entity.ExtraIds = string.Join('|', dto.ExtraIds); - entity.ProductionLocations = string.Join('|', dto.ProductionLocations); + 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; @@ -1628,15 +1636,15 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr .Where(e => itemValueTypes.Contains(e.Type)); if (withItemTypes.Count > 0) { - query = query.Where(e => context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId.Equals(e.ItemId)))); + query = query.Where(e => context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId))); } if (excludeItemTypes.Count > 0) { - query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId.Equals(e.ItemId)))); + query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId))); } - query = query.DistinctBy(e => e.CleanValue); + // query = query.DistinctBy(e => e.CleanValue); return query.Select(e => e.CleanValue).ToImmutableArray(); } @@ -2131,12 +2139,12 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr ItemSortBy.AirTime => e => e.SortName, // TODO ItemSortBy.Runtime => e => e.RunTimeTicks, ItemSortBy.Random => e => EF.Functions.Random(), - ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.LastPlayedDate, - ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.PlayCount, - ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite, + ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.LastPlayedDate, + ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.PlayCount, + ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.IsFavorite, ItemSortBy.IsFolder => e => e.IsFolder, - ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played, - ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played, + ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.Played, + ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.Played, ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded, ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.Type == 0).Select(f => f.CleanValue), ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.Type == 1).Select(f => f.CleanValue), diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs index c1d6d58cd..a9eda1b64 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbContext.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs @@ -4,20 +4,18 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Interfaces; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations; /// -public class JellyfinDbContext : DbContext +/// +/// Initializes a new instance of the class. +/// +/// The database context options. +/// Logger. +public class JellyfinDbContext(DbContextOptions options, ILogger logger) : DbContext(options) { - /// - /// Initializes a new instance of the class. - /// - /// The database context options. - public JellyfinDbContext(DbContextOptions options) : base(options) - { - } - /// /// Gets the containing the access schedules. /// @@ -228,7 +226,15 @@ public class JellyfinDbContext : DbContext saveEntity.OnSavingChanges(); } - return base.SaveChanges(); + try + { + return base.SaveChanges(); + } + catch (Exception e) + { + logger.LogError(e, "Error trying to save changes."); + throw; + } } /// diff --git a/Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.Designer.cs deleted file mode 100644 index 609faa1e6..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.Designer.cs +++ /dev/null @@ -1,775 +0,0 @@ -// -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("20240907123425_UserDataInJfLib")] - partial class UserDataInJfLib - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); - - 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.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.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.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("AudioStreamIndex") - .HasColumnType("INTEGER"); - - b.Property("IsFavorite") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - - 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.Property("UserId") - .HasColumnType("TEXT"); - - b.HasIndex("UserId"); - - b.HasIndex("Key", "UserId") - .IsUnique(); - - 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.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.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.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - 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/20240907123425_UserDataInJfLib.cs b/Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.cs deleted file mode 100644 index cb9a01f5b..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Jellyfin.Server.Implementations.Migrations -{ - /// - public partial class UserDataInJfLib : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "UserData", - columns: table => new - { - Key = table.Column(type: "TEXT", nullable: false), - Rating = table.Column(type: "REAL", nullable: true), - PlaybackPositionTicks = table.Column(type: "INTEGER", nullable: false), - PlayCount = table.Column(type: "INTEGER", nullable: false), - IsFavorite = table.Column(type: "INTEGER", nullable: false), - LastPlayedDate = table.Column(type: "TEXT", nullable: true), - Played = table.Column(type: "INTEGER", nullable: false), - AudioStreamIndex = table.Column(type: "INTEGER", nullable: true), - SubtitleStreamIndex = table.Column(type: "INTEGER", nullable: true), - Likes = table.Column(type: "INTEGER", nullable: true), - UserId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.ForeignKey( - name: "FK_UserData_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_UserData_Key_UserId", - table: "UserData", - columns: new[] { "Key", "UserId" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_UserData_Key_UserId_IsFavorite", - table: "UserData", - columns: new[] { "Key", "UserId", "IsFavorite" }); - - migrationBuilder.CreateIndex( - name: "IX_UserData_Key_UserId_LastPlayedDate", - table: "UserData", - columns: new[] { "Key", "UserId", "LastPlayedDate" }); - - migrationBuilder.CreateIndex( - name: "IX_UserData_Key_UserId_PlaybackPositionTicks", - table: "UserData", - columns: new[] { "Key", "UserId", "PlaybackPositionTicks" }); - - migrationBuilder.CreateIndex( - name: "IX_UserData_Key_UserId_Played", - table: "UserData", - columns: new[] { "Key", "UserId", "Played" }); - - migrationBuilder.CreateIndex( - name: "IX_UserData_UserId", - table: "UserData", - column: "UserId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UserData"); - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.Designer.cs deleted file mode 100644 index b3e028298..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.Designer.cs +++ /dev/null @@ -1,1484 +0,0 @@ -// -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("20241009112234_BaseItemRefactor")] - partial class BaseItemRefactor - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); - - 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("TEXT"); - - 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("TEXT"); - - b.Property("ForcedSortName") - .HasColumnType("TEXT"); - - b.Property("Genres") - .HasColumnType("TEXT"); - - b.Property("Height") - .HasColumnType("INTEGER"); - - b.Property("Images") - .HasColumnType("TEXT"); - - 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("LockedFields") - .HasColumnType("TEXT"); - - 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("TrailerTypes") - .HasColumnType("TEXT"); - - 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("SeasonId"); - - b.HasIndex("SeriesId"); - - 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.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.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.BaseItemEntity", b => - { - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Parent") - .WithMany("DirectChildren") - .HasForeignKey("ParentId"); - - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Season") - .WithMany("SeasonEpisodes") - .HasForeignKey("SeasonId"); - - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Series") - .WithMany("SeriesEpisodes") - .HasForeignKey("SeriesId"); - - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "TopParent") - .WithMany("AllChildren") - .HasForeignKey("TopParentId"); - - b.Navigation("Parent"); - - b.Navigation("Season"); - - b.Navigation("Series"); - - b.Navigation("TopParent"); - }); - - 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.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("AllChildren"); - - b.Navigation("AncestorIds"); - - b.Navigation("Chapters"); - - b.Navigation("DirectChildren"); - - b.Navigation("ItemValues"); - - b.Navigation("MediaStreams"); - - b.Navigation("Peoples"); - - b.Navigation("Provider"); - - b.Navigation("SeasonEpisodes"); - - b.Navigation("SeriesEpisodes"); - - 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/20241009112234_BaseItemRefactor.cs b/Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.cs deleted file mode 100644 index f51e385e0..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.cs +++ /dev/null @@ -1,514 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Jellyfin.Server.Implementations.Migrations -{ - /// - public partial class BaseItemRefactor : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_UserData_Key_UserId", - table: "UserData"); - - migrationBuilder.AddColumn( - name: "BaseItemEntityId", - table: "UserData", - type: "TEXT", - nullable: true); - - migrationBuilder.AddPrimaryKey( - name: "PK_UserData", - table: "UserData", - columns: new[] { "Key", "UserId" }); - - migrationBuilder.CreateTable( - name: "BaseItems", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Type = table.Column(type: "TEXT", nullable: false), - Data = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - StartDate = table.Column(type: "TEXT", nullable: false), - EndDate = table.Column(type: "TEXT", nullable: false), - ChannelId = table.Column(type: "TEXT", nullable: true), - IsMovie = table.Column(type: "INTEGER", nullable: false), - CommunityRating = table.Column(type: "REAL", nullable: true), - CustomRating = table.Column(type: "TEXT", nullable: true), - IndexNumber = table.Column(type: "INTEGER", nullable: true), - IsLocked = table.Column(type: "INTEGER", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - OfficialRating = table.Column(type: "TEXT", nullable: true), - MediaType = table.Column(type: "TEXT", nullable: true), - Overview = table.Column(type: "TEXT", nullable: true), - ParentIndexNumber = table.Column(type: "INTEGER", nullable: true), - PremiereDate = table.Column(type: "TEXT", nullable: true), - ProductionYear = table.Column(type: "INTEGER", nullable: true), - Genres = table.Column(type: "TEXT", nullable: true), - SortName = table.Column(type: "TEXT", nullable: true), - ForcedSortName = table.Column(type: "TEXT", nullable: true), - RunTimeTicks = table.Column(type: "INTEGER", nullable: true), - DateCreated = table.Column(type: "TEXT", nullable: true), - DateModified = table.Column(type: "TEXT", nullable: true), - IsSeries = table.Column(type: "INTEGER", nullable: false), - EpisodeTitle = table.Column(type: "TEXT", nullable: true), - IsRepeat = table.Column(type: "INTEGER", nullable: false), - PreferredMetadataLanguage = table.Column(type: "TEXT", nullable: true), - PreferredMetadataCountryCode = table.Column(type: "TEXT", nullable: true), - DateLastRefreshed = table.Column(type: "TEXT", nullable: true), - DateLastSaved = table.Column(type: "TEXT", nullable: true), - IsInMixedFolder = table.Column(type: "INTEGER", nullable: false), - LockedFields = table.Column(type: "TEXT", nullable: true), - Studios = table.Column(type: "TEXT", nullable: true), - Audio = table.Column(type: "TEXT", nullable: true), - ExternalServiceId = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - IsFolder = table.Column(type: "INTEGER", nullable: false), - InheritedParentalRatingValue = table.Column(type: "INTEGER", nullable: true), - UnratedType = table.Column(type: "TEXT", nullable: true), - TrailerTypes = table.Column(type: "TEXT", nullable: true), - CriticRating = table.Column(type: "REAL", nullable: true), - CleanName = table.Column(type: "TEXT", nullable: true), - PresentationUniqueKey = table.Column(type: "TEXT", nullable: true), - OriginalTitle = table.Column(type: "TEXT", nullable: true), - PrimaryVersionId = table.Column(type: "TEXT", nullable: true), - DateLastMediaAdded = table.Column(type: "TEXT", nullable: true), - Album = table.Column(type: "TEXT", nullable: true), - LUFS = table.Column(type: "REAL", nullable: true), - NormalizationGain = table.Column(type: "REAL", nullable: true), - IsVirtualItem = table.Column(type: "INTEGER", nullable: false), - SeriesName = table.Column(type: "TEXT", nullable: true), - UserDataKey = table.Column(type: "TEXT", nullable: true), - SeasonName = table.Column(type: "TEXT", nullable: true), - ExternalSeriesId = table.Column(type: "TEXT", nullable: true), - Tagline = table.Column(type: "TEXT", nullable: true), - Images = table.Column(type: "TEXT", nullable: true), - ProductionLocations = table.Column(type: "TEXT", nullable: true), - ExtraIds = table.Column(type: "TEXT", nullable: true), - TotalBitrate = table.Column(type: "INTEGER", nullable: true), - ExtraType = table.Column(type: "TEXT", nullable: true), - Artists = table.Column(type: "TEXT", nullable: true), - AlbumArtists = table.Column(type: "TEXT", nullable: true), - ExternalId = table.Column(type: "TEXT", nullable: true), - SeriesPresentationUniqueKey = table.Column(type: "TEXT", nullable: true), - ShowId = table.Column(type: "TEXT", nullable: true), - OwnerId = table.Column(type: "TEXT", nullable: true), - Width = table.Column(type: "INTEGER", nullable: true), - Height = table.Column(type: "INTEGER", nullable: true), - Size = table.Column(type: "INTEGER", nullable: true), - ParentId = table.Column(type: "TEXT", nullable: true), - TopParentId = table.Column(type: "TEXT", nullable: true), - SeasonId = table.Column(type: "TEXT", nullable: true), - SeriesId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BaseItems", x => x.Id); - table.ForeignKey( - name: "FK_BaseItems_BaseItems_ParentId", - column: x => x.ParentId, - principalTable: "BaseItems", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_BaseItems_BaseItems_SeasonId", - column: x => x.SeasonId, - principalTable: "BaseItems", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_BaseItems_BaseItems_SeriesId", - column: x => x.SeriesId, - principalTable: "BaseItems", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_BaseItems_BaseItems_TopParentId", - column: x => x.TopParentId, - principalTable: "BaseItems", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "AncestorIds", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - ItemId = table.Column(type: "TEXT", nullable: false), - AncestorIdText = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AncestorIds", x => new { x.ItemId, x.Id }); - table.ForeignKey( - name: "FK_AncestorIds_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AttachmentStreamInfos", - columns: table => new - { - ItemId = table.Column(type: "TEXT", nullable: false), - Index = table.Column(type: "INTEGER", nullable: false), - Codec = table.Column(type: "TEXT", nullable: false), - CodecTag = table.Column(type: "TEXT", nullable: true), - Comment = table.Column(type: "TEXT", nullable: true), - Filename = table.Column(type: "TEXT", nullable: true), - MimeType = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AttachmentStreamInfos", x => new { x.ItemId, x.Index }); - table.ForeignKey( - name: "FK_AttachmentStreamInfos_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BaseItemProviders", - columns: table => new - { - ItemId = table.Column(type: "TEXT", nullable: false), - ProviderId = table.Column(type: "TEXT", nullable: false), - ProviderValue = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BaseItemProviders", x => new { x.ItemId, x.ProviderId }); - table.ForeignKey( - name: "FK_BaseItemProviders_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Chapters", - columns: table => new - { - ItemId = table.Column(type: "TEXT", nullable: false), - ChapterIndex = table.Column(type: "INTEGER", nullable: false), - StartPositionTicks = table.Column(type: "INTEGER", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - ImagePath = table.Column(type: "TEXT", nullable: true), - ImageDateModified = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Chapters", x => new { x.ItemId, x.ChapterIndex }); - table.ForeignKey( - name: "FK_Chapters_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "ItemValues", - columns: table => new - { - ItemId = table.Column(type: "TEXT", nullable: false), - Type = table.Column(type: "INTEGER", nullable: false), - Value = table.Column(type: "TEXT", nullable: false), - CleanValue = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ItemValues", x => new { x.ItemId, x.Type, x.Value }); - table.ForeignKey( - name: "FK_ItemValues_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "MediaStreamInfos", - columns: table => new - { - ItemId = table.Column(type: "TEXT", nullable: false), - StreamIndex = table.Column(type: "INTEGER", nullable: false), - StreamType = table.Column(type: "TEXT", nullable: true), - Codec = table.Column(type: "TEXT", nullable: true), - Language = table.Column(type: "TEXT", nullable: true), - ChannelLayout = table.Column(type: "TEXT", nullable: true), - Profile = table.Column(type: "TEXT", nullable: true), - AspectRatio = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - IsInterlaced = table.Column(type: "INTEGER", nullable: false), - BitRate = table.Column(type: "INTEGER", nullable: false), - Channels = table.Column(type: "INTEGER", nullable: false), - SampleRate = table.Column(type: "INTEGER", nullable: false), - IsDefault = table.Column(type: "INTEGER", nullable: false), - IsForced = table.Column(type: "INTEGER", nullable: false), - IsExternal = table.Column(type: "INTEGER", nullable: false), - Height = table.Column(type: "INTEGER", nullable: false), - Width = table.Column(type: "INTEGER", nullable: false), - AverageFrameRate = table.Column(type: "REAL", nullable: false), - RealFrameRate = table.Column(type: "REAL", nullable: false), - Level = table.Column(type: "REAL", nullable: false), - PixelFormat = table.Column(type: "TEXT", nullable: true), - BitDepth = table.Column(type: "INTEGER", nullable: false), - IsAnamorphic = table.Column(type: "INTEGER", nullable: false), - RefFrames = table.Column(type: "INTEGER", nullable: false), - CodecTag = table.Column(type: "TEXT", nullable: false), - Comment = table.Column(type: "TEXT", nullable: false), - NalLengthSize = table.Column(type: "TEXT", nullable: false), - IsAvc = table.Column(type: "INTEGER", nullable: false), - Title = table.Column(type: "TEXT", nullable: false), - TimeBase = table.Column(type: "TEXT", nullable: false), - CodecTimeBase = table.Column(type: "TEXT", nullable: false), - ColorPrimaries = table.Column(type: "TEXT", nullable: false), - ColorSpace = table.Column(type: "TEXT", nullable: false), - ColorTransfer = table.Column(type: "TEXT", nullable: false), - DvVersionMajor = table.Column(type: "INTEGER", nullable: false), - DvVersionMinor = table.Column(type: "INTEGER", nullable: false), - DvProfile = table.Column(type: "INTEGER", nullable: false), - DvLevel = table.Column(type: "INTEGER", nullable: false), - RpuPresentFlag = table.Column(type: "INTEGER", nullable: false), - ElPresentFlag = table.Column(type: "INTEGER", nullable: false), - BlPresentFlag = table.Column(type: "INTEGER", nullable: false), - DvBlSignalCompatibilityId = table.Column(type: "INTEGER", nullable: false), - IsHearingImpaired = table.Column(type: "INTEGER", nullable: false), - Rotation = table.Column(type: "INTEGER", nullable: false), - KeyFrames = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaStreamInfos", x => new { x.ItemId, x.StreamIndex }); - table.ForeignKey( - name: "FK_MediaStreamInfos_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Peoples", - columns: table => new - { - ItemId = table.Column(type: "TEXT", nullable: false), - Role = table.Column(type: "TEXT", nullable: false), - ListOrder = table.Column(type: "INTEGER", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - PersonType = table.Column(type: "TEXT", nullable: true), - SortOrder = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Peoples", x => new { x.ItemId, x.Role, x.ListOrder }); - table.ForeignKey( - name: "FK_Peoples_BaseItems_ItemId", - column: x => x.ItemId, - principalTable: "BaseItems", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_UserData_BaseItemEntityId", - table: "UserData", - column: "BaseItemEntityId"); - - migrationBuilder.CreateIndex( - name: "IX_AncestorIds_Id", - table: "AncestorIds", - column: "Id"); - - migrationBuilder.CreateIndex( - name: "IX_AncestorIds_ItemId_AncestorIdText", - table: "AncestorIds", - columns: new[] { "ItemId", "AncestorIdText" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItemProviders_ProviderId_ProviderValue_ItemId", - table: "BaseItemProviders", - columns: new[] { "ProviderId", "ProviderValue", "ItemId" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Id_Type_IsFolder_IsVirtualItem", - table: "BaseItems", - columns: new[] { "Id", "Type", "IsFolder", "IsVirtualItem" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_IsFolder_TopParentId_IsVirtualItem_PresentationUniqueKey_DateCreated", - table: "BaseItems", - columns: new[] { "IsFolder", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_MediaType_TopParentId_IsVirtualItem_PresentationUniqueKey", - table: "BaseItems", - columns: new[] { "MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_ParentId", - table: "BaseItems", - column: "ParentId"); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Path", - table: "BaseItems", - column: "Path"); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_PresentationUniqueKey", - table: "BaseItems", - column: "PresentationUniqueKey"); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_SeasonId", - table: "BaseItems", - column: "SeasonId"); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_SeriesId", - table: "BaseItems", - column: "SeriesId"); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_TopParentId_Id", - table: "BaseItems", - columns: new[] { "TopParentId", "Id" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Type_SeriesPresentationUniqueKey_IsFolder_IsVirtualItem", - table: "BaseItems", - columns: new[] { "Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Type_SeriesPresentationUniqueKey_PresentationUniqueKey_SortName", - table: "BaseItems", - columns: new[] { "Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Type_TopParentId_Id", - table: "BaseItems", - columns: new[] { "Type", "TopParentId", "Id" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Type_TopParentId_IsVirtualItem_PresentationUniqueKey_DateCreated", - table: "BaseItems", - columns: new[] { "Type", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Type_TopParentId_PresentationUniqueKey", - table: "BaseItems", - columns: new[] { "Type", "TopParentId", "PresentationUniqueKey" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_Type_TopParentId_StartDate", - table: "BaseItems", - columns: new[] { "Type", "TopParentId", "StartDate" }); - - migrationBuilder.CreateIndex( - name: "IX_BaseItems_UserDataKey_Type", - table: "BaseItems", - columns: new[] { "UserDataKey", "Type" }); - - migrationBuilder.CreateIndex( - name: "IX_ItemValues_ItemId_Type_CleanValue", - table: "ItemValues", - columns: new[] { "ItemId", "Type", "CleanValue" }); - - migrationBuilder.CreateIndex( - name: "IX_MediaStreamInfos_StreamIndex", - table: "MediaStreamInfos", - column: "StreamIndex"); - - migrationBuilder.CreateIndex( - name: "IX_MediaStreamInfos_StreamIndex_StreamType", - table: "MediaStreamInfos", - columns: new[] { "StreamIndex", "StreamType" }); - - migrationBuilder.CreateIndex( - name: "IX_MediaStreamInfos_StreamIndex_StreamType_Language", - table: "MediaStreamInfos", - columns: new[] { "StreamIndex", "StreamType", "Language" }); - - migrationBuilder.CreateIndex( - name: "IX_MediaStreamInfos_StreamType", - table: "MediaStreamInfos", - column: "StreamType"); - - migrationBuilder.CreateIndex( - name: "IX_Peoples_ItemId_ListOrder", - table: "Peoples", - columns: new[] { "ItemId", "ListOrder" }); - - migrationBuilder.CreateIndex( - name: "IX_Peoples_Name", - table: "Peoples", - column: "Name"); - - migrationBuilder.AddForeignKey( - name: "FK_UserData_BaseItems_BaseItemEntityId", - table: "UserData", - column: "BaseItemEntityId", - principalTable: "BaseItems", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_UserData_BaseItems_BaseItemEntityId", - table: "UserData"); - - migrationBuilder.DropTable( - name: "AncestorIds"); - - migrationBuilder.DropTable( - name: "AttachmentStreamInfos"); - - migrationBuilder.DropTable( - name: "BaseItemProviders"); - - migrationBuilder.DropTable( - name: "Chapters"); - - migrationBuilder.DropTable( - name: "ItemValues"); - - migrationBuilder.DropTable( - name: "MediaStreamInfos"); - - migrationBuilder.DropTable( - name: "Peoples"); - - migrationBuilder.DropTable( - name: "BaseItems"); - - migrationBuilder.DropPrimaryKey( - name: "PK_UserData", - table: "UserData"); - - migrationBuilder.DropIndex( - name: "IX_UserData_BaseItemEntityId", - table: "UserData"); - - migrationBuilder.DropColumn( - name: "BaseItemEntityId", - table: "UserData"); - - migrationBuilder.CreateIndex( - name: "IX_UserData_Key_UserId", - table: "UserData", - columns: new[] { "Key", "UserId" }, - unique: true); - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.Designer.cs new file mode 100644 index 000000000..8e8e6c125 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.Designer.cs @@ -0,0 +1,1445 @@ +// +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("20241009132112_BaseItemRefactor")] + partial class BaseItemRefactor + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + 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("TEXT"); + + 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("TEXT"); + + b.Property("ForcedSortName") + .HasColumnType("TEXT"); + + b.Property("Genres") + .HasColumnType("TEXT"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Images") + .HasColumnType("TEXT"); + + 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("LockedFields") + .HasColumnType("TEXT"); + + 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("TrailerTypes") + .HasColumnType("TEXT"); + + 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.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.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.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.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("ItemValues"); + + b.Navigation("MediaStreams"); + + b.Navigation("Peoples"); + + b.Navigation("Provider"); + + 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/20241009132112_BaseItemRefactor.cs b/Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.cs new file mode 100644 index 000000000..caa731c15 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.cs @@ -0,0 +1,501 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Jellyfin.Server.Implementations.Migrations +{ + /// + public partial class BaseItemRefactor : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BaseItems", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + Data = table.Column(type: "TEXT", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + StartDate = table.Column(type: "TEXT", nullable: false), + EndDate = table.Column(type: "TEXT", nullable: false), + ChannelId = table.Column(type: "TEXT", nullable: true), + IsMovie = table.Column(type: "INTEGER", nullable: false), + CommunityRating = table.Column(type: "REAL", nullable: true), + CustomRating = table.Column(type: "TEXT", nullable: true), + IndexNumber = table.Column(type: "INTEGER", nullable: true), + IsLocked = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + OfficialRating = table.Column(type: "TEXT", nullable: true), + MediaType = table.Column(type: "TEXT", nullable: true), + Overview = table.Column(type: "TEXT", nullable: true), + ParentIndexNumber = table.Column(type: "INTEGER", nullable: true), + PremiereDate = table.Column(type: "TEXT", nullable: true), + ProductionYear = table.Column(type: "INTEGER", nullable: true), + Genres = table.Column(type: "TEXT", nullable: true), + SortName = table.Column(type: "TEXT", nullable: true), + ForcedSortName = table.Column(type: "TEXT", nullable: true), + RunTimeTicks = table.Column(type: "INTEGER", nullable: true), + DateCreated = table.Column(type: "TEXT", nullable: true), + DateModified = table.Column(type: "TEXT", nullable: true), + IsSeries = table.Column(type: "INTEGER", nullable: false), + EpisodeTitle = table.Column(type: "TEXT", nullable: true), + IsRepeat = table.Column(type: "INTEGER", nullable: false), + PreferredMetadataLanguage = table.Column(type: "TEXT", nullable: true), + PreferredMetadataCountryCode = table.Column(type: "TEXT", nullable: true), + DateLastRefreshed = table.Column(type: "TEXT", nullable: true), + DateLastSaved = table.Column(type: "TEXT", nullable: true), + IsInMixedFolder = table.Column(type: "INTEGER", nullable: false), + LockedFields = table.Column(type: "TEXT", nullable: true), + Studios = table.Column(type: "TEXT", nullable: true), + Audio = table.Column(type: "TEXT", nullable: true), + ExternalServiceId = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + IsFolder = table.Column(type: "INTEGER", nullable: false), + InheritedParentalRatingValue = table.Column(type: "INTEGER", nullable: true), + UnratedType = table.Column(type: "TEXT", nullable: true), + TrailerTypes = table.Column(type: "TEXT", nullable: true), + CriticRating = table.Column(type: "REAL", nullable: true), + CleanName = table.Column(type: "TEXT", nullable: true), + PresentationUniqueKey = table.Column(type: "TEXT", nullable: true), + OriginalTitle = table.Column(type: "TEXT", nullable: true), + PrimaryVersionId = table.Column(type: "TEXT", nullable: true), + DateLastMediaAdded = table.Column(type: "TEXT", nullable: true), + Album = table.Column(type: "TEXT", nullable: true), + LUFS = table.Column(type: "REAL", nullable: true), + NormalizationGain = table.Column(type: "REAL", nullable: true), + IsVirtualItem = table.Column(type: "INTEGER", nullable: false), + SeriesName = table.Column(type: "TEXT", nullable: true), + UserDataKey = table.Column(type: "TEXT", nullable: true), + SeasonName = table.Column(type: "TEXT", nullable: true), + ExternalSeriesId = table.Column(type: "TEXT", nullable: true), + Tagline = table.Column(type: "TEXT", nullable: true), + Images = table.Column(type: "TEXT", nullable: true), + ProductionLocations = table.Column(type: "TEXT", nullable: true), + ExtraIds = table.Column(type: "TEXT", nullable: true), + TotalBitrate = table.Column(type: "INTEGER", nullable: true), + ExtraType = table.Column(type: "TEXT", nullable: true), + Artists = table.Column(type: "TEXT", nullable: true), + AlbumArtists = table.Column(type: "TEXT", nullable: true), + ExternalId = table.Column(type: "TEXT", nullable: true), + SeriesPresentationUniqueKey = table.Column(type: "TEXT", nullable: true), + ShowId = table.Column(type: "TEXT", nullable: true), + OwnerId = table.Column(type: "TEXT", nullable: true), + Width = table.Column(type: "INTEGER", nullable: true), + Height = table.Column(type: "INTEGER", nullable: true), + Size = table.Column(type: "INTEGER", nullable: true), + ParentId = table.Column(type: "TEXT", nullable: true), + TopParentId = table.Column(type: "TEXT", nullable: true), + SeasonId = table.Column(type: "TEXT", nullable: true), + SeriesId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BaseItems", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AncestorIds", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + ItemId = table.Column(type: "TEXT", nullable: false), + AncestorIdText = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AncestorIds", x => new { x.ItemId, x.Id }); + table.ForeignKey( + name: "FK_AncestorIds_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AttachmentStreamInfos", + columns: table => new + { + ItemId = table.Column(type: "TEXT", nullable: false), + Index = table.Column(type: "INTEGER", nullable: false), + Codec = table.Column(type: "TEXT", nullable: false), + CodecTag = table.Column(type: "TEXT", nullable: true), + Comment = table.Column(type: "TEXT", nullable: true), + Filename = table.Column(type: "TEXT", nullable: true), + MimeType = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AttachmentStreamInfos", x => new { x.ItemId, x.Index }); + table.ForeignKey( + name: "FK_AttachmentStreamInfos_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BaseItemProviders", + columns: table => new + { + ItemId = table.Column(type: "TEXT", nullable: false), + ProviderId = table.Column(type: "TEXT", nullable: false), + ProviderValue = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BaseItemProviders", x => new { x.ItemId, x.ProviderId }); + table.ForeignKey( + name: "FK_BaseItemProviders_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Chapters", + columns: table => new + { + ItemId = table.Column(type: "TEXT", nullable: false), + ChapterIndex = table.Column(type: "INTEGER", nullable: false), + StartPositionTicks = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + ImagePath = table.Column(type: "TEXT", nullable: true), + ImageDateModified = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Chapters", x => new { x.ItemId, x.ChapterIndex }); + table.ForeignKey( + name: "FK_Chapters_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ItemValues", + columns: table => new + { + ItemId = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + Value = table.Column(type: "TEXT", nullable: false), + CleanValue = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ItemValues", x => new { x.ItemId, x.Type, x.Value }); + table.ForeignKey( + name: "FK_ItemValues_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MediaStreamInfos", + columns: table => new + { + ItemId = table.Column(type: "TEXT", nullable: false), + StreamIndex = table.Column(type: "INTEGER", nullable: false), + StreamType = table.Column(type: "TEXT", nullable: true), + Codec = table.Column(type: "TEXT", nullable: true), + Language = table.Column(type: "TEXT", nullable: true), + ChannelLayout = table.Column(type: "TEXT", nullable: true), + Profile = table.Column(type: "TEXT", nullable: true), + AspectRatio = table.Column(type: "TEXT", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + IsInterlaced = table.Column(type: "INTEGER", nullable: false), + BitRate = table.Column(type: "INTEGER", nullable: false), + Channels = table.Column(type: "INTEGER", nullable: false), + SampleRate = table.Column(type: "INTEGER", nullable: false), + IsDefault = table.Column(type: "INTEGER", nullable: false), + IsForced = table.Column(type: "INTEGER", nullable: false), + IsExternal = table.Column(type: "INTEGER", nullable: false), + Height = table.Column(type: "INTEGER", nullable: false), + Width = table.Column(type: "INTEGER", nullable: false), + AverageFrameRate = table.Column(type: "REAL", nullable: false), + RealFrameRate = table.Column(type: "REAL", nullable: false), + Level = table.Column(type: "REAL", nullable: false), + PixelFormat = table.Column(type: "TEXT", nullable: true), + BitDepth = table.Column(type: "INTEGER", nullable: false), + IsAnamorphic = table.Column(type: "INTEGER", nullable: false), + RefFrames = table.Column(type: "INTEGER", nullable: false), + CodecTag = table.Column(type: "TEXT", nullable: false), + Comment = table.Column(type: "TEXT", nullable: false), + NalLengthSize = table.Column(type: "TEXT", nullable: false), + IsAvc = table.Column(type: "INTEGER", nullable: false), + Title = table.Column(type: "TEXT", nullable: false), + TimeBase = table.Column(type: "TEXT", nullable: false), + CodecTimeBase = table.Column(type: "TEXT", nullable: false), + ColorPrimaries = table.Column(type: "TEXT", nullable: false), + ColorSpace = table.Column(type: "TEXT", nullable: false), + ColorTransfer = table.Column(type: "TEXT", nullable: false), + DvVersionMajor = table.Column(type: "INTEGER", nullable: false), + DvVersionMinor = table.Column(type: "INTEGER", nullable: false), + DvProfile = table.Column(type: "INTEGER", nullable: false), + DvLevel = table.Column(type: "INTEGER", nullable: false), + RpuPresentFlag = table.Column(type: "INTEGER", nullable: false), + ElPresentFlag = table.Column(type: "INTEGER", nullable: false), + BlPresentFlag = table.Column(type: "INTEGER", nullable: false), + DvBlSignalCompatibilityId = table.Column(type: "INTEGER", nullable: false), + IsHearingImpaired = table.Column(type: "INTEGER", nullable: false), + Rotation = table.Column(type: "INTEGER", nullable: false), + KeyFrames = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MediaStreamInfos", x => new { x.ItemId, x.StreamIndex }); + table.ForeignKey( + name: "FK_MediaStreamInfos_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Peoples", + columns: table => new + { + ItemId = table.Column(type: "TEXT", nullable: false), + Role = table.Column(type: "TEXT", nullable: false), + ListOrder = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + PersonType = table.Column(type: "TEXT", nullable: true), + SortOrder = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Peoples", x => new { x.ItemId, x.Role, x.ListOrder }); + table.ForeignKey( + name: "FK_Peoples_BaseItems_ItemId", + column: x => x.ItemId, + principalTable: "BaseItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserData", + columns: table => new + { + Key = table.Column(type: "TEXT", nullable: false), + UserId = table.Column(type: "TEXT", nullable: false), + Rating = table.Column(type: "REAL", nullable: true), + PlaybackPositionTicks = table.Column(type: "INTEGER", nullable: false), + PlayCount = table.Column(type: "INTEGER", nullable: false), + IsFavorite = table.Column(type: "INTEGER", nullable: false), + LastPlayedDate = table.Column(type: "TEXT", nullable: true), + Played = table.Column(type: "INTEGER", nullable: false), + AudioStreamIndex = table.Column(type: "INTEGER", nullable: true), + SubtitleStreamIndex = table.Column(type: "INTEGER", nullable: true), + Likes = table.Column(type: "INTEGER", nullable: true), + BaseItemEntityId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserData", x => new { x.Key, x.UserId }); + table.ForeignKey( + name: "FK_UserData_BaseItems_BaseItemEntityId", + column: x => x.BaseItemEntityId, + principalTable: "BaseItems", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_UserData_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AncestorIds_Id", + table: "AncestorIds", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_AncestorIds_ItemId_AncestorIdText", + table: "AncestorIds", + columns: new[] { "ItemId", "AncestorIdText" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItemProviders_ProviderId_ProviderValue_ItemId", + table: "BaseItemProviders", + columns: new[] { "ProviderId", "ProviderValue", "ItemId" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Id_Type_IsFolder_IsVirtualItem", + table: "BaseItems", + columns: new[] { "Id", "Type", "IsFolder", "IsVirtualItem" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_IsFolder_TopParentId_IsVirtualItem_PresentationUniqueKey_DateCreated", + table: "BaseItems", + columns: new[] { "IsFolder", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_MediaType_TopParentId_IsVirtualItem_PresentationUniqueKey", + table: "BaseItems", + columns: new[] { "MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_ParentId", + table: "BaseItems", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Path", + table: "BaseItems", + column: "Path"); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_PresentationUniqueKey", + table: "BaseItems", + column: "PresentationUniqueKey"); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_TopParentId_Id", + table: "BaseItems", + columns: new[] { "TopParentId", "Id" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Type_SeriesPresentationUniqueKey_IsFolder_IsVirtualItem", + table: "BaseItems", + columns: new[] { "Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Type_SeriesPresentationUniqueKey_PresentationUniqueKey_SortName", + table: "BaseItems", + columns: new[] { "Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Type_TopParentId_Id", + table: "BaseItems", + columns: new[] { "Type", "TopParentId", "Id" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Type_TopParentId_IsVirtualItem_PresentationUniqueKey_DateCreated", + table: "BaseItems", + columns: new[] { "Type", "TopParentId", "IsVirtualItem", "PresentationUniqueKey", "DateCreated" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Type_TopParentId_PresentationUniqueKey", + table: "BaseItems", + columns: new[] { "Type", "TopParentId", "PresentationUniqueKey" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_Type_TopParentId_StartDate", + table: "BaseItems", + columns: new[] { "Type", "TopParentId", "StartDate" }); + + migrationBuilder.CreateIndex( + name: "IX_BaseItems_UserDataKey_Type", + table: "BaseItems", + columns: new[] { "UserDataKey", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_ItemValues_ItemId_Type_CleanValue", + table: "ItemValues", + columns: new[] { "ItemId", "Type", "CleanValue" }); + + migrationBuilder.CreateIndex( + name: "IX_MediaStreamInfos_StreamIndex", + table: "MediaStreamInfos", + column: "StreamIndex"); + + migrationBuilder.CreateIndex( + name: "IX_MediaStreamInfos_StreamIndex_StreamType", + table: "MediaStreamInfos", + columns: new[] { "StreamIndex", "StreamType" }); + + migrationBuilder.CreateIndex( + name: "IX_MediaStreamInfos_StreamIndex_StreamType_Language", + table: "MediaStreamInfos", + columns: new[] { "StreamIndex", "StreamType", "Language" }); + + migrationBuilder.CreateIndex( + name: "IX_MediaStreamInfos_StreamType", + table: "MediaStreamInfos", + column: "StreamType"); + + migrationBuilder.CreateIndex( + name: "IX_Peoples_ItemId_ListOrder", + table: "Peoples", + columns: new[] { "ItemId", "ListOrder" }); + + migrationBuilder.CreateIndex( + name: "IX_Peoples_Name", + table: "Peoples", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_UserData_BaseItemEntityId", + table: "UserData", + column: "BaseItemEntityId"); + + migrationBuilder.CreateIndex( + name: "IX_UserData_Key_UserId_IsFavorite", + table: "UserData", + columns: new[] { "Key", "UserId", "IsFavorite" }); + + migrationBuilder.CreateIndex( + name: "IX_UserData_Key_UserId_LastPlayedDate", + table: "UserData", + columns: new[] { "Key", "UserId", "LastPlayedDate" }); + + migrationBuilder.CreateIndex( + name: "IX_UserData_Key_UserId_PlaybackPositionTicks", + table: "UserData", + columns: new[] { "Key", "UserId", "PlaybackPositionTicks" }); + + migrationBuilder.CreateIndex( + name: "IX_UserData_Key_UserId_Played", + table: "UserData", + columns: new[] { "Key", "UserId", "Played" }); + + migrationBuilder.CreateIndex( + name: "IX_UserData_UserId", + table: "UserData", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AncestorIds"); + + migrationBuilder.DropTable( + name: "AttachmentStreamInfos"); + + migrationBuilder.DropTable( + name: "BaseItemProviders"); + + migrationBuilder.DropTable( + name: "Chapters"); + + migrationBuilder.DropTable( + name: "ItemValues"); + + migrationBuilder.DropTable( + name: "MediaStreamInfos"); + + migrationBuilder.DropTable( + name: "Peoples"); + + migrationBuilder.DropTable( + name: "UserData"); + + migrationBuilder.DropTable( + name: "BaseItems"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs index 940cf7c5d..500c4a1c7 100644 --- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs +++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Logging.Abstractions; namespace Jellyfin.Server.Implementations.Migrations { @@ -14,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite("Data Source=jellyfin.db"); - return new JellyfinDbContext(optionsBuilder.Options); + return new JellyfinDbContext(optionsBuilder.Options, NullLogger.Instance); } } } diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index f74f7d791..dd280489b 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -376,10 +376,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasIndex("PresentationUniqueKey"); - b.HasIndex("SeasonId"); - - b.HasIndex("SeriesId"); - b.HasIndex("TopParentId", "Id"); b.HasIndex("UserDataKey", "Type"); @@ -1272,33 +1268,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Navigation("Item"); }); - modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b => - { - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Parent") - .WithMany("DirectChildren") - .HasForeignKey("ParentId"); - - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Season") - .WithMany("SeasonEpisodes") - .HasForeignKey("SeasonId"); - - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Series") - .WithMany("SeriesEpisodes") - .HasForeignKey("SeriesId"); - - b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "TopParent") - .WithMany("AllChildren") - .HasForeignKey("TopParentId"); - - b.Navigation("Parent"); - - b.Navigation("Season"); - - b.Navigation("Series"); - - b.Navigation("TopParent"); - }); - modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b => { b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") @@ -1433,14 +1402,10 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b => { - b.Navigation("AllChildren"); - b.Navigation("AncestorIds"); b.Navigation("Chapters"); - b.Navigation("DirectChildren"); - b.Navigation("ItemValues"); b.Navigation("MediaStreams"); @@ -1449,10 +1414,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Navigation("Provider"); - b.Navigation("SeasonEpisodes"); - - b.Navigation("SeriesEpisodes"); - b.Navigation("UserData"); }); diff --git a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs index d74b94784..6c36a1591 100644 --- a/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs +++ b/Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs @@ -15,10 +15,11 @@ public class BaseItemConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(e => e.Id); - builder.HasOne(e => e.Parent).WithMany(e => e.DirectChildren).HasForeignKey(e => e.ParentId); - builder.HasOne(e => e.TopParent).WithMany(e => e.AllChildren).HasForeignKey(e => e.TopParentId); - builder.HasOne(e => e.Season).WithMany(e => e.SeasonEpisodes).HasForeignKey(e => e.SeasonId); - builder.HasOne(e => e.Series).WithMany(e => e.SeriesEpisodes).HasForeignKey(e => e.SeriesId); + // TODO: See rant in entity file. + // builder.HasOne(e => e.Parent).WithMany(e => e.DirectChildren).HasForeignKey(e => e.ParentId); + // builder.HasOne(e => e.TopParent).WithMany(e => e.AllChildren).HasForeignKey(e => e.TopParentId); + // builder.HasOne(e => e.Season).WithMany(e => e.SeasonEpisodes).HasForeignKey(e => e.SeasonId); + // builder.HasOne(e => e.Series).WithMany(e => e.SeriesEpisodes).HasForeignKey(e => e.SeriesId); builder.HasMany(e => e.Peoples); builder.HasMany(e => e.UserData); builder.HasMany(e => e.ItemValues); diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index eabbe73cd..ef69885fc 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.Providers public IReadOnlyList People { get => _people; - set => _people = value.ToList(); + set => _people = value?.ToList(); } public bool HasMetadata { get; set; } diff --git a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs index cedcaf9c0..b32ecf6ec 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs @@ -330,7 +330,7 @@ namespace Jellyfin.Providers.Tests.Manager MetadataService.MergeBaseItemData(source, target, lockedFields, replaceData, false); actualValue = target.People; - return newValue?.Equals(actualValue) ?? actualValue is null; + return newValue?.SequenceEqual((IEnumerable)actualValue!) ?? actualValue is null; } /// diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index bf3bfdad4..02a77516f 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -13,7 +13,7 @@ using Xunit.Priority; namespace Jellyfin.Server.Integration.Tests.Controllers; -[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] +// [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] public sealed class LibraryStructureControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; @@ -62,12 +62,23 @@ public sealed class LibraryStructureControllerTests : IClassFixture 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 'tests') 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 7f03f39bcccca208a83fb6b2144b80e11d1d40ac Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 10 Oct 2024 00:49:06 +0000 Subject: Fixed tests --- .../Item/BaseItemRepository.cs | 89 ++++++++++++---------- .../Controllers/LibraryStructureControllerTests.cs | 14 ++-- 2 files changed, 56 insertions(+), 47 deletions(-) (limited to 'tests') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index d42581ef4..d82de097c 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -930,19 +930,19 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value) { baseQuery = baseQuery - .Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id == e.ParentId.Value)); + .Where(e => e.ParentId.HasValue && !context.BaseItems.Any(f => f.Id == e.ParentId.Value)); } if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value) { baseQuery = baseQuery - .Where(e => e.ItemValues!.Any(f => (f.Type == 0 || f.Type == ItemValueType.AlbumArtist) && f.CleanValue == e.CleanName)); + .Where(e => !e.ItemValues!.Any(f => (f.Type == ItemValueType.Artist || f.Type == ItemValueType.AlbumArtist) && f.CleanValue == e.CleanName)); } if (filter.IsDeadStudio.HasValue && filter.IsDeadStudio.Value) { baseQuery = baseQuery - .Where(e => e.ItemValues!.Any(f => f.Type == ItemValueType.Studios && f.CleanValue == e.CleanName)); + .Where(e => !e.ItemValues!.Any(f => f.Type == ItemValueType.Studios && f.CleanValue == e.CleanName)); } if (filter.IsDeadPerson.HasValue && filter.IsDeadPerson.Value) @@ -1252,53 +1252,61 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags); } - using var context = dbProvider.CreateDbContext(); - using var transaction = context.Database.BeginTransaction(); - foreach (var item in tuples) + try { - var entity = Map(item.Item); - if (!context.BaseItems.Any(e => e.Id == entity.Id)) + using var context = dbProvider.CreateDbContext(); + using var transaction = context.Database.BeginTransaction(); + foreach (var item in tuples) { - context.BaseItems.Add(entity); - } - else - { - context.BaseItems.Attach(entity).State = EntityState.Modified; - } + 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.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.Where(e => e.ItemId == entity.Id).ExecuteDelete(); + if (item.Item.SupportsAncestors && item.AncestorIds != null) { - entity.AncestorIds.Add(new AncestorId() + entity.AncestorIds = new List(); + foreach (var ancestorId in item.AncestorIds) { - ParentItemId = ancestorId, - ItemId = entity.Id - }); + entity.AncestorIds.Add(new AncestorId() + { + ParentItemId = ancestorId, + ItemId = entity.Id + }); + } } - } - var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags); - context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete(); - entity.ItemValues = new List(); + 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) - { - entity.ItemValues.Add(new() + foreach (var itemValue in itemValues) { - Item = entity, - Type = (ItemValueType)itemValue.MagicNumber, - Value = itemValue.Value, - CleanValue = GetCleanValue(itemValue.Value), - ItemId = entity.Id - }); + entity.ItemValues.Add(new() + { + Item = entity, + Type = (ItemValueType)itemValue.MagicNumber, + Value = itemValue.Value, + CleanValue = GetCleanValue(itemValue.Value), + ItemId = entity.Id + }); + } } - } - context.SaveChanges(); - transaction.Commit(); + context.SaveChanges(); + transaction.Commit(); + } + catch (System.Exception) + { + System.Console.WriteLine(); + throw; + } } /// @@ -1484,7 +1492,8 @@ public sealed class BaseItemRepository(IDbContextFactory dbPr Type = dto.GetType().ToString(), Id = dto.Id }; - entity.ParentId = dto.ParentId; + + entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null; entity.Path = GetPathToSave(dto.Path); entity.EndDate = dto.EndDate.GetValueOrDefault(); entity.CommunityRating = dto.CommunityRating; diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 02a77516f..190621085 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -13,7 +13,7 @@ using Xunit.Priority; namespace Jellyfin.Server.Integration.Tests.Controllers; -// [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] +[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] public sealed class LibraryStructureControllerTests : IClassFixture { private readonly JellyfinApplicationFactory _factory; @@ -62,13 +62,13 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Thu, 10 Oct 2024 15:23:34 +0000 Subject: Fixed tests --- .../Controllers/LibraryStructureControllerTests.cs | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'tests') diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 190621085..bf3bfdad4 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -68,17 +68,6 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Sun, 10 Nov 2024 20:18:36 +0000 Subject: Fixed out of order unittests --- .../Controllers/LibraryStructureControllerTests.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index bf3bfdad4..0376f57cc 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -62,12 +62,23 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Thu, 14 Nov 2024 08:48:53 +0000 Subject: Fixed Tests --- Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 3 ++- .../Probing/ProbeResultNormalizerTests.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 757c3ff37..f1afd3543 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -1300,7 +1300,8 @@ public sealed class BaseItemRepository( } } - var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags); + // Never save duplicate itemValues as they are now mapped anyway. + var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags).DistinctBy(e => (GetCleanValue(e.Value), e.MagicNumber)); context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete(); foreach (var itemValue in itemValuesToSave) { diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index df51d39cb..61282785f 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -65,7 +65,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.True(res.VideoStream.IsDefault); Assert.False(res.VideoStream.IsExternal); Assert.False(res.VideoStream.IsForced); - Assert.False(res.VideoStream.IsHearingImpaired); + Assert.False(res.VideoStream.IsHearingImpaired.GetValueOrDefault()); Assert.False(res.VideoStream.IsInterlaced); Assert.False(res.VideoStream.IsTextSubtitleStream); Assert.Equal(13d, res.VideoStream.Level); @@ -152,19 +152,19 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[3].Type); Assert.Equal("DVDSUB", res.MediaStreams[3].Codec); Assert.Null(res.MediaStreams[3].Title); - Assert.False(res.MediaStreams[3].IsHearingImpaired); + Assert.False(res.MediaStreams[3].IsHearingImpaired.GetValueOrDefault()); Assert.Equal("eng", res.MediaStreams[4].Language); Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[4].Type); Assert.Equal("mov_text", res.MediaStreams[4].Codec); Assert.Null(res.MediaStreams[4].Title); - Assert.True(res.MediaStreams[4].IsHearingImpaired); + Assert.True(res.MediaStreams[4].IsHearingImpaired.GetValueOrDefault()); Assert.Equal("eng", res.MediaStreams[5].Language); Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[5].Type); Assert.Equal("mov_text", res.MediaStreams[5].Codec); Assert.Equal("Commentary", res.MediaStreams[5].Title); - Assert.False(res.MediaStreams[5].IsHearingImpaired); + Assert.False(res.MediaStreams[5].IsHearingImpaired.GetValueOrDefault()); } [Fact] -- cgit v1.2.3 From 056dcf7e81049a9d92064d5f91dadfbcf9093a63 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Thu, 14 Nov 2024 09:04:35 +0000 Subject: Added Pipeline debug code --- Jellyfin.Api/Controllers/LibraryStructureController.cs | 10 +++++++++- .../Controllers/LibraryStructureControllerTests.cs | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 93c2393f3..c52068000 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -106,7 +106,15 @@ public class LibraryStructureController : BaseJellyfinApiController [FromQuery] string name, [FromQuery] bool refreshLibrary = false) { - await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false); + try + { + await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false); + } + catch (Exception ex) + { + return BadRequest(ex.ToString()); + } + return NoContent(); } diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 0376f57cc..9d39b4bfa 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -120,6 +120,14 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Thu, 14 Nov 2024 09:20:12 +0000 Subject: Updated test dbg message --- .../Controllers/LibraryStructureControllerTests.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'tests') diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 9d39b4bfa..acb330ca7 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -120,14 +120,6 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Thu, 14 Nov 2024 09:25:55 +0000 Subject: Fixed tests message --- .../Controllers/LibraryStructureControllerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index acb330ca7..dc8c33c73 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -120,6 +120,6 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Thu, 14 Nov 2024 10:14:41 +0000 Subject: Deterministic tests my *** --- Jellyfin.Api/Controllers/LibraryStructureController.cs | 9 ++++++++- Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 4 ++-- .../Controllers/LibraryStructureControllerTests.cs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index c3e3b659b..7838c2f61 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -88,7 +88,14 @@ public class LibraryStructureController : BaseJellyfinApiController libraryOptions.PathInfos = Array.ConvertAll(paths, i => new MediaPathInfo(i)); } - await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false); + try + { + await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false); + } + catch (System.Exception ex) + { + return BadRequest(ex.ToString()); + } return NoContent(); } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 14300d237..f62d6fc1a 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -83,7 +83,7 @@ public sealed class BaseItemRepository( context.Peoples.Where(e => e.BaseItems!.Count == 0).ExecuteDelete(); context.Chapters.Where(e => e.ItemId == id).ExecuteDelete(); context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete(); - context.AncestorIds.Where(e => e.ItemId == id).ExecuteDelete(); + context.AncestorIds.Where(e => e.ItemId == id || e.ParentItemId == id).ExecuteDelete(); context.ItemValuesMap.Where(e => e.ItemId == id).ExecuteDelete(); context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDelete(); context.BaseItemImageInfos.Where(e => e.ItemId == id).ExecuteDelete(); @@ -1292,7 +1292,7 @@ public sealed class BaseItemRepository( { if (!context.BaseItems.Any(f => f.Id == ancestorId)) { - throw new InvalidOperationException($"Cannot link non-existent parent: {ancestorId}"); + continue; } context.AncestorIds.Add(new AncestorId() diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index dc8c33c73..6b749f0a8 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -77,7 +77,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Thu, 14 Nov 2024 10:25:49 +0000 Subject: reverted dbg code --- Jellyfin.Api/Controllers/LibraryStructureController.cs | 11 +++-------- .../Controllers/LibraryStructureControllerTests.cs | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) (limited to 'tests') diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 7838c2f61..55000fc91 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -88,14 +88,7 @@ public class LibraryStructureController : BaseJellyfinApiController libraryOptions.PathInfos = Array.ConvertAll(paths, i => new MediaPathInfo(i)); } - try - { - await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false); - } - catch (System.Exception ex) - { - return BadRequest(ex.ToString()); - } + await _libraryManager.AddVirtualFolder(name, collectionType, libraryOptions, refreshLibrary).ConfigureAwait(false); return NoContent(); } @@ -106,6 +99,7 @@ public class LibraryStructureController : BaseJellyfinApiController /// The name of the folder. /// Whether to refresh the library. /// Folder removed. + /// Folder not found. /// A . [HttpDelete] [ProducesResponseType(StatusCodes.Status204NoContent)] @@ -113,6 +107,7 @@ public class LibraryStructureController : BaseJellyfinApiController [FromQuery] string name, [FromQuery] bool refreshLibrary = false) { + // TODO: refactor! this relies on an FileNotFound exception to return NotFound when attempting to remove a library that does not exist. await _libraryManager.RemoveVirtualFolder(name, refreshLibrary).ConfigureAwait(false); return NoContent(); diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 6b749f0a8..0376f57cc 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -77,7 +77,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Wed, 15 Jan 2025 20:12:41 +0000 Subject: Applied review comments --- .../Data/CleanDatabaseScheduledTask.cs | 14 ++++++++++---- .../Library/UserDataManager.cs | 12 +++++++++--- .../Item/BaseItemRepository.cs | 19 ++++++++++--------- .../Item/ChapterRepository.cs | 2 +- .../Migrations/Routines/MigrateLibraryDb.cs | 15 ++++++++------- src/Jellyfin.Drawing/ImageProcessor.cs | 10 +++++----- .../Controllers/LibraryStructureControllerTests.cs | 2 +- 7 files changed, 44 insertions(+), 30 deletions(-) (limited to 'tests') diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index aceff8b53..7ea863d76 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -67,10 +67,16 @@ namespace Emby.Server.Implementations.Data progress.Report(percent * 100); } - using var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); - using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false); - await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); - await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); + await using (context.ConfigureAwait(false)) + { + var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false); + await using (transaction.ConfigureAwait(false)) + { + await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); + } + } progress.Report(100); } diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 9b3a0c1f9..cc45f2fcb 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -146,8 +146,8 @@ namespace Emby.Server.Implementations.Library { ItemId = itemId, CustomDataKey = dto.Key, - Item = null!, - User = null!, + Item = null, + User = null, AudioStreamIndex = dto.AudioStreamIndex, IsFavorite = dto.IsFavorite, LastPlayedDate = dto.LastPlayedDate, @@ -181,7 +181,13 @@ namespace Emby.Server.Implementations.Library private UserItemData? GetUserData(User user, Guid itemId, List keys) { var cacheKey = GetCacheKey(user.InternalId, itemId); - var data = GetUserDataInternal(user.Id, itemId, keys); + + if (_userData.TryGetValue(cacheKey, out var data)) + { + return data; + } + + data = GetUserDataInternal(user.Id, itemId, keys); if (data is null) { diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 01e23f56d..1eca0713d 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -125,7 +125,7 @@ public sealed class BaseItemRepository transaction.Commit(); } - /// + /// public IReadOnlyList GetItemIdsList(InternalItemsQuery filter) { ArgumentNullException.ThrowIfNull(filter); @@ -201,7 +201,7 @@ public sealed class BaseItemRepository _itemTypeLookup.MusicGenreTypes); } - /// + /// public QueryResult GetItems(InternalItemsQuery filter) { ArgumentNullException.ThrowIfNull(filter); @@ -235,7 +235,7 @@ public sealed class BaseItemRepository return result; } - /// + /// public IReadOnlyList GetItemList(InternalItemsQuery filter) { ArgumentNullException.ThrowIfNull(filter); @@ -354,12 +354,14 @@ public sealed class BaseItemRepository { ArgumentException.ThrowIfNullOrEmpty(typeName); + // TODO: this isn't great. Refactor later to be both globally handled by a dedicated service not just an static variable and be loaded eagar. + // currently this is done so that plugins may introduce their own type of baseitems as we dont know when we are first called, before or after plugins are loaded return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(k)) .FirstOrDefault(t => t is not null)); } - /// + /// public void SaveImages(BaseItemDto item) { ArgumentNullException.ThrowIfNull(item); @@ -373,13 +375,13 @@ public sealed class BaseItemRepository transaction.Commit(); } - /// + /// public void SaveItems(IReadOnlyList items, CancellationToken cancellationToken) { UpdateOrInsertItems(items, cancellationToken); } - /// + /// public void UpdateOrInsertItems(IReadOnlyList items, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(items); @@ -479,7 +481,7 @@ public sealed class BaseItemRepository transaction.Commit(); } - /// + /// public BaseItemDto? RetrieveItem(Guid id) { if (id.IsEmpty()) @@ -890,8 +892,7 @@ public sealed class BaseItemRepository { try { - using var dataAsStream = new MemoryStream(Encoding.UTF8.GetBytes(baseItemEntity.Data!)); - dto = JsonSerializer.Deserialize(dataAsStream, type, JsonDefaults.Options) as BaseItemDto; + dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto; } catch (JsonException ex) { diff --git a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs index 16e8c205d..fc6f04d56 100644 --- a/Jellyfin.Server.Implementations/Item/ChapterRepository.cs +++ b/Jellyfin.Server.Implementations/Item/ChapterRepository.cs @@ -71,7 +71,7 @@ public class ChapterRepository : IChapterRepository chapter = e, baseItemPath = e.Item.Path }) - .ToList() + .AsEnumerable() .Select(e => Map(e.chapter, e.baseItemPath!)) .ToArray(); } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs index 8b2664ecd..d0360a56d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs @@ -1,3 +1,5 @@ +#pragma warning disable RS0030 // Do not use banned APIs + using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -21,7 +23,6 @@ using Microsoft.Extensions.Logging; using Chapter = Jellyfin.Data.Entities.Chapter; namespace Jellyfin.Server.Migrations.Routines; -#pragma warning disable RS0030 // Do not use banned APIs /// /// The migration routine for migrating the userdata database to EF Core. @@ -80,7 +81,7 @@ public class MigrateLibraryDb : IMigrationRoutine stopwatch.Restart(); _logger.LogInformation("Start moving TypedBaseItem."); - var typedBaseItemsQuery = """ + const string typedBaseItemsQuery = """ SELECT guid, type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, @@ -111,7 +112,7 @@ public class MigrateLibraryDb : IMigrationRoutine _logger.LogInformation("Start moving ItemValues."); // do not migrate inherited types as they are now properly mapped in search and lookup. - var itemValueQuery = + const string itemValueQuery = """ SELECT ItemId, Type, Value, CleanValue FROM ItemValues WHERE Type <> 6 AND EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = ItemValues.ItemId) @@ -187,7 +188,7 @@ public class MigrateLibraryDb : IMigrationRoutine dbContext.SaveChanges(); _logger.LogInformation("Start moving MediaStreamInfos."); - var mediaStreamQuery = """ + const string mediaStreamQuery = """ SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path, IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width, AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag, @@ -211,7 +212,7 @@ public class MigrateLibraryDb : IMigrationRoutine stopwatch.Restart(); _logger.LogInformation("Start moving People."); - var personsQuery = """ + const string personsQuery = """ SELECT ItemId, Name, Role, PersonType, SortOrder FROM People WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = People.ItemId) """; @@ -268,7 +269,7 @@ public class MigrateLibraryDb : IMigrationRoutine stopwatch.Restart(); _logger.LogInformation("Start moving Chapters."); - var chapterQuery = """ + const string chapterQuery = """ SELECT ItemId,StartPositionTicks,Name,ImagePath,ImageDateModified,ChapterIndex from Chapters2 WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = Chapters2.ItemId) """; @@ -287,7 +288,7 @@ public class MigrateLibraryDb : IMigrationRoutine stopwatch.Restart(); _logger.LogInformation("Start moving AncestorIds."); - var ancestorIdsQuery = """ + const string ancestorIdsQuery = """ SELECT ItemId, AncestorId, AncestorIdText FROM AncestorIds WHERE EXISTS(SELECT 1 FROM TypedBaseItems WHERE TypedBaseItems.guid = AncestorIds.ItemId) diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs index 7ba9ff172..0bd3b8920 100644 --- a/src/Jellyfin.Drawing/ImageProcessor.cs +++ b/src/Jellyfin.Drawing/ImageProcessor.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Mime; +using System.Reflection.Metadata.Ecma335; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -410,11 +411,11 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable /// public string GetImageCacheTag(BaseItem item, ItemImageInfo image) - => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + => GetImageCacheTag(item.Path, image.DateModified); /// public string GetImageCacheTag(BaseItemDto item, ItemImageInfo image) - => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + => GetImageCacheTag(item.Path, image.DateModified); /// public string? GetImageCacheTag(BaseItemDto item, ChapterInfo chapter) @@ -424,7 +425,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable return null; } - return (item.Path + chapter.ImageDateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); + return GetImageCacheTag(item.Path, chapter.ImageDateModified); } /// @@ -451,8 +452,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable return null; } - return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() - .ToString("N", CultureInfo.InvariantCulture); + return GetImageCacheTag(user.ProfileImage.Path, user.ProfileImage.LastModified); } private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 0376f57cc..e7166d424 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -45,7 +45,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Sun, 19 Jan 2025 12:29:14 +0000 Subject: Applied review comments --- Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 8 +------- .../Controllers/LibraryStructureControllerTests.cs | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) (limited to 'tests') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 970eaa3ba..a4e3f75ec 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -1216,13 +1216,7 @@ public sealed class BaseItemRepository if (hasSearch) { - List<(ItemSortBy, SortOrder)> prepend = new List<(ItemSortBy, SortOrder)>(4); - if (hasSearch) - { - prepend.Add((ItemSortBy.SortName, SortOrder.Ascending)); - } - - orderBy = filter.OrderBy = [.. prepend, .. orderBy]; + orderBy = filter.OrderBy = [(ItemSortBy.SortName, SortOrder.Ascending), .. orderBy]; } else if (orderBy.Count == 0) { diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index e7166d424..0f318f5a0 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -62,7 +62,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture Date: Sun, 19 Jan 2025 12:41:11 +0000 Subject: Fixed tests again --- .../Controllers/LibraryStructureControllerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs index 0f318f5a0..e7166d424 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs @@ -62,7 +62,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture