Fixed AncestorIds

Fixed Sorting, NextUp and Continue Watching
This commit is contained in:
JPVenson 2024-11-12 15:37:01 +00:00
parent a7a2257ccb
commit 85b8b2573b
8 changed files with 1774 additions and 88 deletions

View File

@ -164,7 +164,9 @@ public class BaseItemEntity
public ICollection<BaseItemProvider>? Provider { get; set; } public ICollection<BaseItemProvider>? Provider { get; set; }
public ICollection<AncestorId>? AncestorIds { get; set; } public ICollection<AncestorId>? ParentAncestors { get; set; }
public ICollection<AncestorId>? Children { get; set; }
public ICollection<BaseItemMetadataField>? LockedFields { get; set; } public ICollection<BaseItemMetadataField>? LockedFields { get; set; }

View File

@ -117,37 +117,37 @@ public sealed class BaseItemRepository(
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter) public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery filter)
{ {
return GetItemValues(filter, [ItemValueType.Artist, ItemValueType.AlbumArtist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); return GetItemValues(filter, [ItemValueType.Artist, ItemValueType.AlbumArtist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter) public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery filter)
{ {
return GetItemValues(filter, [ItemValueType.Artist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); return GetItemValues(filter, [ItemValueType.Artist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter) public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery filter)
{ {
return GetItemValues(filter, [ItemValueType.AlbumArtist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!); return GetItemValues(filter, [ItemValueType.AlbumArtist], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicArtist]!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter) public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery filter)
{ {
return GetItemValues(filter, [ItemValueType.Studios], itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!); return GetItemValues(filter, [ItemValueType.Studios], itemTypeLookup.BaseItemKindNames[BaseItemKind.Studio]!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter) public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery filter)
{ {
return GetItemValues(filter, [ItemValueType.Genre], itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!); return GetItemValues(filter, [ItemValueType.Genre], itemTypeLookup.BaseItemKindNames[BaseItemKind.Genre]!);
} }
/// <inheritdoc /> /// <inheritdoc />
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter) public QueryResult<(BaseItemDto Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery filter)
{ {
return GetItemValues(filter, [ItemValueType.Genre], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!); return GetItemValues(filter, [ItemValueType.Genre], itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicGenre]!);
} }
@ -200,7 +200,7 @@ public sealed class BaseItemRepository(
using var context = dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking() IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking().AsSingleQuery()
.Include(e => e.TrailerTypes) .Include(e => e.TrailerTypes)
.Include(e => e.Provider) .Include(e => e.Provider)
.Include(e => e.LockedFields); .Include(e => e.LockedFields);
@ -212,28 +212,13 @@ public sealed class BaseItemRepository(
dbQuery = TranslateQuery(dbQuery, context, filter); dbQuery = TranslateQuery(dbQuery, context, filter);
dbQuery = dbQuery.Distinct(); dbQuery = dbQuery.Distinct();
// .DistinctBy(e => e.Id);
if (filter.EnableTotalRecordCount) if (filter.EnableTotalRecordCount)
{ {
result.TotalRecordCount = dbQuery.Count(); result.TotalRecordCount = dbQuery.Count();
} }
dbQuery = ApplyOrder(dbQuery, filter); dbQuery = ApplyOrder(dbQuery, filter);
dbQuery = ApplyQueryPageing(dbQuery, filter);
if (filter.Limit.HasValue || filter.StartIndex.HasValue)
{
var offset = filter.StartIndex ?? 0;
if (offset > 0)
{
dbQuery = dbQuery.Skip(offset);
}
if (filter.Limit.HasValue)
{
dbQuery = dbQuery.Take(filter.Limit.Value);
}
}
result.Items = dbQuery.AsEnumerable().Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray(); result.Items = dbQuery.AsEnumerable().Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray();
result.StartIndex = filter.StartIndex ?? 0; result.StartIndex = filter.StartIndex ?? 0;
@ -247,31 +232,43 @@ public sealed class BaseItemRepository(
PrepareFilterQuery(filter); PrepareFilterQuery(filter);
using var context = dbProvider.CreateDbContext(); using var context = dbProvider.CreateDbContext();
IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking().AsSingleQuery()
.Include(e => e.TrailerTypes)
.Include(e => e.Provider)
.Include(e => e.LockedFields);
return PrepareItemQuery(context, filter).AsEnumerable().Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray(); if (filter.DtoOptions.EnableImages)
{
dbQuery = dbQuery.Include(e => e.Images);
}
dbQuery = TranslateQuery(dbQuery, context, filter);
dbQuery = dbQuery.Distinct();
dbQuery = ApplyOrder(dbQuery, filter);
dbQuery = ApplyGroupingFilter(dbQuery, filter);
return dbQuery.AsEnumerable().Select(w => DeserialiseBaseItem(w, filter.SkipDeserialization)).ToImmutableArray();
} }
private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter) private IQueryable<BaseItemEntity> ApplyGroupingFilter(IQueryable<BaseItemEntity> dbQuery, InternalItemsQuery filter)
{ {
dbQuery = dbQuery.Distinct(); var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
// var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter); {
// if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey) dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.First());
// { }
// dbQuery = dbQuery.GroupBy(e => new { e.PresentationUniqueKey, e.SeriesPresentationUniqueKey }).Select(e => e.First()); else if (enableGroupByPresentationUniqueKey)
// } {
// else if (enableGroupByPresentationUniqueKey) dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First());
// { }
// dbQuery = dbQuery.GroupBy(e => e.PresentationUniqueKey).Select(e => e.First()); else if (filter.GroupBySeriesPresentationUniqueKey)
// } {
// else if (filter.GroupBySeriesPresentationUniqueKey) dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First());
// { }
// dbQuery = dbQuery.GroupBy(e => e.SeriesPresentationUniqueKey).Select(e => e.First()); else
// } {
// else dbQuery = dbQuery.Distinct();
// { }
// dbQuery = dbQuery.Distinct();
// }
return dbQuery; return dbQuery;
} }
@ -307,7 +304,7 @@ public sealed class BaseItemRepository(
private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter) private IQueryable<BaseItemEntity> PrepareItemQuery(JellyfinDbContext context, InternalItemsQuery filter)
{ {
IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking() IQueryable<BaseItemEntity> dbQuery = context.BaseItems.AsNoTracking().AsSingleQuery()
.Include(e => e.TrailerTypes) .Include(e => e.TrailerTypes)
.Include(e => e.Provider) .Include(e => e.Provider)
.Include(e => e.LockedFields); .Include(e => e.LockedFields);
@ -1086,13 +1083,13 @@ public sealed class BaseItemRepository(
if (filter.AncestorIds.Length > 0) if (filter.AncestorIds.Length > 0)
{ {
baseQuery = baseQuery.Where(e => e.AncestorIds!.Any(f => filter.AncestorIds.Contains(f.ParentItemId))); baseQuery = baseQuery.Where(e => e.Children!.Any(f => filter.AncestorIds.Contains(f.ParentItemId)));
} }
if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey)) if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
{ {
baseQuery = baseQuery baseQuery = baseQuery
.Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId == f.Id))); .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.ParentAncestors!.Any(w => w.ItemId == f.Id)));
} }
if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey)) if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
@ -1127,7 +1124,7 @@ public sealed class BaseItemRepository(
{ {
baseQuery = baseQuery baseQuery = baseQuery
.Where(e => .Where(e =>
e.AncestorIds! e.ParentAncestors!
.Any(f => .Any(f =>
f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(w.ItemValue.CleanValue)) f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(w.ItemValue.CleanValue))
|| e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""))); || e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\"")));
@ -1136,7 +1133,7 @@ public sealed class BaseItemRepository(
else else
{ {
baseQuery = baseQuery baseQuery = baseQuery
.Where(e => e.AncestorIds!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(w.ItemValue.CleanValue)))); .Where(e => e.ParentAncestors!.Any(f => f.ParentItem.ItemValues!.Any(w => w.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(w.ItemValue.CleanValue))));
} }
} }
@ -1236,7 +1233,7 @@ public sealed class BaseItemRepository(
} }
/// <inheritdoc cref="IItemRepository" /> /// <inheritdoc cref="IItemRepository" />
public void SaveImages(BaseItem item) public void SaveImages(BaseItemDto item)
{ {
ArgumentNullException.ThrowIfNull(item); ArgumentNullException.ThrowIfNull(item);
@ -1295,10 +1292,9 @@ public sealed class BaseItemRepository(
context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete(); context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
if (item.Item.SupportsAncestors && item.AncestorIds != null) if (item.Item.SupportsAncestors && item.AncestorIds != null)
{ {
entity.AncestorIds = new List<AncestorId>();
foreach (var ancestorId in item.AncestorIds) foreach (var ancestorId in item.AncestorIds)
{ {
entity.AncestorIds.Add(new AncestorId() context.AncestorIds.Add(new AncestorId()
{ {
ParentItemId = ancestorId, ParentItemId = ancestorId,
ItemId = entity.Id, ItemId = entity.Id,
@ -1378,7 +1374,7 @@ public sealed class BaseItemRepository(
/// <param name="entity">The entity.</param> /// <param name="entity">The entity.</param>
/// <param name="dto">The dto base instance.</param> /// <param name="dto">The dto base instance.</param>
/// <returns>The dto to map.</returns> /// <returns>The dto to map.</returns>
public BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto) public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto)
{ {
dto.Id = entity.Id; dto.Id = entity.Id;
dto.ParentId = entity.ParentId.GetValueOrDefault(); dto.ParentId = entity.ParentId.GetValueOrDefault();
@ -1416,10 +1412,10 @@ public sealed class BaseItemRepository(
dto.Genres = entity.Genres?.Split('|') ?? []; dto.Genres = entity.Genres?.Split('|') ?? [];
dto.DateCreated = entity.DateCreated.GetValueOrDefault(); dto.DateCreated = entity.DateCreated.GetValueOrDefault();
dto.DateModified = entity.DateModified.GetValueOrDefault(); dto.DateModified = entity.DateModified.GetValueOrDefault();
dto.ChannelId = string.IsNullOrWhiteSpace(entity.ChannelId) ? Guid.Empty : Guid.Parse(entity.ChannelId); dto.ChannelId = string.IsNullOrWhiteSpace(entity.ChannelId) ? Guid.Empty : (Guid.TryParse(entity.ChannelId, out var channelId) ? channelId : Guid.Empty);
dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault(); dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault(); dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : Guid.Parse(entity.OwnerId); dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty);
dto.Width = entity.Width.GetValueOrDefault(); dto.Width = entity.Width.GetValueOrDefault();
dto.Height = entity.Height.GetValueOrDefault(); dto.Height = entity.Height.GetValueOrDefault();
if (entity.Provider is not null) if (entity.Provider is not null)
@ -1720,21 +1716,29 @@ public sealed class BaseItemRepository(
return query.Select(e => e.ItemValue.CleanValue).ToImmutableArray(); return query.Select(e => e.ItemValue.CleanValue).ToImmutableArray();
} }
private bool TypeRequiresDeserialization(Type type) private static bool TypeRequiresDeserialization(Type type)
{ {
if (serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes)
{
if (type == typeof(Channel)
|| type == typeof(UserRootFolder))
{
return false;
}
}
return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null; return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
} }
private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false) private BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, bool skipDeserialization = false)
{
var typeToSerialise = GetType(baseItemEntity.Type);
return BaseItemRepository.DeserialiseBaseItem(
baseItemEntity,
logger,
skipDeserialization || (serverConfigurationManager.Configuration.SkipDeserializationForBasicTypes && (typeToSerialise == typeof(Channel) || typeToSerialise == typeof(UserRootFolder))));
}
/// <summary>
/// Deserialises a BaseItemEntity and sets all properties.
/// </summary>
/// <param name="baseItemEntity">The DB entity.</param>
/// <param name="logger">Logger.</param>
/// <param name="skipDeserialization">If only mapping should be processed.</param>
/// <returns>A mapped BaseItem.</returns>
/// <exception cref="InvalidOperationException">Will be thrown if an invalid serialisation is requested.</exception>
public static BaseItemDto DeserialiseBaseItem(BaseItemEntity baseItemEntity, ILogger logger, bool skipDeserialization = false)
{ {
var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unkown type."); var type = GetType(baseItemEntity.Type) ?? throw new InvalidOperationException("Cannot deserialise unkown type.");
BaseItemDto? dto = null; BaseItemDto? dto = null;
@ -1815,7 +1819,7 @@ public sealed class BaseItemRepository(
} }
} }
var result = new QueryResult<(BaseItem, ItemCounts)>(); var result = new QueryResult<(BaseItemDto, ItemCounts)>();
if (filter.EnableTotalRecordCount) if (filter.EnableTotalRecordCount)
{ {
result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count(); result.TotalRecordCount = query.DistinctBy(e => e.PresentationUniqueKey).Count();
@ -1877,7 +1881,7 @@ public sealed class BaseItemRepository(
return value.RemoveDiacritics().ToLowerInvariant(); return value.RemoveDiacritics().ToLowerInvariant();
} }
private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags) private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inheritedTags)
{ {
var list = new List<(int, string)>(); var list = new List<(int, string)>();
@ -2144,6 +2148,18 @@ public sealed class BaseItemRepository(
{ {
orderedQuery = query.OrderByDescending(expression); orderedQuery = query.OrderByDescending(expression);
} }
if (firstOrdering.OrderBy is ItemSortBy.Default or ItemSortBy.SortName)
{
if (firstOrdering.SortOrder is SortOrder.Ascending)
{
orderedQuery = orderedQuery.ThenBy(e => e.Name);
}
else
{
orderedQuery = orderedQuery.ThenByDescending(e => e.Name);
}
}
} }
foreach (var item in orderBy.Skip(1)) foreach (var item in orderBy.Skip(1))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
/// <inheritdoc />
public partial class FixAncestorIdConfig : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_AncestorIds_BaseItems_BaseItemEntityId",
table: "AncestorIds");
migrationBuilder.DropIndex(
name: "IX_AncestorIds_BaseItemEntityId",
table: "AncestorIds");
migrationBuilder.DropColumn(
name: "BaseItemEntityId",
table: "AncestorIds");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "BaseItemEntityId",
table: "AncestorIds",
type: "TEXT",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_AncestorIds_BaseItemEntityId",
table: "AncestorIds",
column: "BaseItemEntityId");
migrationBuilder.AddForeignKey(
name: "FK_AncestorIds_BaseItems_BaseItemEntityId",
table: "AncestorIds",
column: "BaseItemEntityId",
principalTable: "BaseItems",
principalColumn: "Id");
}
}
}

View File

@ -98,13 +98,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<Guid>("ParentItemId") b.Property<Guid>("ParentItemId")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<Guid?>("BaseItemEntityId")
.HasColumnType("TEXT");
b.HasKey("ItemId", "ParentItemId"); b.HasKey("ItemId", "ParentItemId");
b.HasIndex("BaseItemEntityId");
b.HasIndex("ParentItemId"); b.HasIndex("ParentItemId");
b.ToTable("AncestorIds"); b.ToTable("AncestorIds");
@ -1332,18 +1327,14 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.AncestorId", b => modelBuilder.Entity("Jellyfin.Data.Entities.AncestorId", b =>
{ {
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", null)
.WithMany("AncestorIds")
.HasForeignKey("BaseItemEntityId");
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item") b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
.WithMany() .WithMany("Children")
.HasForeignKey("ItemId") .HasForeignKey("ItemId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "ParentItem") b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "ParentItem")
.WithMany() .WithMany("ParentAncestors")
.HasForeignKey("ParentItemId") .HasForeignKey("ParentItemId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
@ -1551,10 +1542,10 @@ namespace Jellyfin.Server.Implementations.Migrations
modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b => modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
{ {
b.Navigation("AncestorIds");
b.Navigation("Chapters"); b.Navigation("Chapters");
b.Navigation("Children");
b.Navigation("Images"); b.Navigation("Images");
b.Navigation("ItemValues"); b.Navigation("ItemValues");
@ -1563,6 +1554,8 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Navigation("MediaStreams"); b.Navigation("MediaStreams");
b.Navigation("ParentAncestors");
b.Navigation("Peoples"); b.Navigation("Peoples");
b.Navigation("Provider"); b.Navigation("Provider");

View File

@ -15,7 +15,7 @@ public class AncestorIdConfiguration : IEntityTypeConfiguration<AncestorId>
{ {
builder.HasKey(e => new { e.ItemId, e.ParentItemId }); builder.HasKey(e => new { e.ItemId, e.ParentItemId });
builder.HasIndex(e => e.ParentItemId); builder.HasIndex(e => e.ParentItemId);
builder.HasOne(e => e.ParentItem); builder.HasOne(e => e.ParentItem).WithMany(e => e.ParentAncestors).HasForeignKey(f => f.ParentItemId);
builder.HasOne(e => e.Item); builder.HasOne(e => e.Item).WithMany(e => e.Children).HasForeignKey(f => f.ItemId);
} }
} }

View File

@ -26,7 +26,8 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
builder.HasMany(e => e.MediaStreams); builder.HasMany(e => e.MediaStreams);
builder.HasMany(e => e.Chapters); builder.HasMany(e => e.Chapters);
builder.HasMany(e => e.Provider); builder.HasMany(e => e.Provider);
builder.HasMany(e => e.AncestorIds); builder.HasMany(e => e.ParentAncestors);
builder.HasMany(e => e.Children);
builder.HasMany(e => e.LockedFields); builder.HasMany(e => e.LockedFields);
builder.HasMany(e => e.TrailerTypes); builder.HasMany(e => e.TrailerTypes);
builder.HasMany(e => e.Images); builder.HasMany(e => e.Images);

View File

@ -11,6 +11,7 @@ using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Item;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -79,7 +80,14 @@ public class MigrateLibraryDb : IMigrationRoutine
stopwatch.Restart(); stopwatch.Restart();
_logger.LogInformation("Start moving TypedBaseItem."); _logger.LogInformation("Start moving TypedBaseItem.");
var 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, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, Genres, ParentId, TopParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems"; var 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, " +
"ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, Genres, ParentId, TopParentId, " +
"Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, " +
"DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId, " +
"PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, " +
"ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId, MediaType FROM TypedBaseItems";
dbContext.BaseItems.ExecuteDelete(); dbContext.BaseItems.ExecuteDelete();
var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>(); var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>();
@ -87,7 +95,10 @@ public class MigrateLibraryDb : IMigrationRoutine
{ {
var baseItem = GetItem(dto); var baseItem = GetItem(dto);
dbContext.BaseItems.Add(baseItem.BaseItem); dbContext.BaseItems.Add(baseItem.BaseItem);
legacyBaseItemWithUserKeys[baseItem.LegacyUserDataKey] = baseItem.BaseItem; foreach (var dataKey in baseItem.LegacyUserDataKey)
{
legacyBaseItemWithUserKeys[dataKey] = baseItem.BaseItem;
}
} }
_logger.LogInformation("Try saving {0} BaseItem entries.", dbContext.BaseItems.Local.Count); _logger.LogInformation("Try saving {0} BaseItem entries.", dbContext.BaseItems.Local.Count);
@ -636,7 +647,7 @@ public class MigrateLibraryDb : IMigrationRoutine
return item; return item;
} }
private (BaseItemEntity BaseItem, string LegacyUserDataKey) GetItem(SqliteDataReader reader) private (BaseItemEntity BaseItem, string[] LegacyUserDataKey) GetItem(SqliteDataReader reader)
{ {
var entity = new BaseItemEntity() var entity = new BaseItemEntity()
{ {
@ -905,8 +916,10 @@ public class MigrateLibraryDb : IMigrationRoutine
entity.SeriesName = seriesName; entity.SeriesName = seriesName;
} }
if (reader.TryGetString(index++, out var userDataKey)) var userDataKeys = new List<string>();
if (reader.TryGetString(index++, out var directUserDataKey))
{ {
userDataKeys.Add(directUserDataKey);
} }
if (reader.TryGetString(index++, out var seasonName)) if (reader.TryGetString(index++, out var seasonName))
@ -1010,7 +1023,16 @@ public class MigrateLibraryDb : IMigrationRoutine
entity.OwnerId = ownerId; entity.OwnerId = ownerId;
} }
return (entity, userDataKey); if (reader.TryGetString(index++, out var mediaType))
{
entity.MediaType = mediaType;
}
var baseItem = BaseItemRepository.DeserialiseBaseItem(entity, _logger, false);
var dataKeys = baseItem.GetUserDataKeys();
userDataKeys.AddRange(dataKeys);
return (entity, userDataKeys.ToArray());
} }
private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e) private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)