Merge pull request #16861 from JPVenson/fix/fixMigrations12.0

Reorder Migrations and fix failing migration
This commit is contained in:
Bond-009
2026-05-21 19:32:57 +02:00
committed by GitHub
47 changed files with 105 additions and 1889 deletions

View File

@@ -26,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Item;
/// <summary>
/// Handles mapping between BaseItemEntity (database) and BaseItemDto (domain) objects.
/// </summary>
internal static class BaseItemMapper
public static class BaseItemMapper
{
/// <summary>
/// This holds all the types in the running assemblies

View File

@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Migration files should follow the EFCore standard in regards to naming.", Scope = "namespaceanddescendants", Target = "~N:Jellyfin.Server.Migrations.Routines")]

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Server.Implementations.Item;
using Jellyfin.Server.Migrations.Stages;
using Jellyfin.Server.ServerSetupApp;
using MediaBrowser.Controller.Channels;
@@ -23,7 +24,7 @@ namespace Jellyfin.Server.Migrations.Routines;
/// Removes orphaned extras (items with OwnerId pointing to non-existent items).
/// Must run before EF migrations that add FK constraints on OwnerId.
/// </summary>
[JellyfinMigration("2026-01-13T23:00:00", nameof(CleanupOrphanedExtras), Stage = JellyfinMigrationStageTypes.CoreInitialisation)]
[JellyfinMigration("2026-01-13T23:00:00", nameof(CleanupOrphanedExtras), Stage = JellyfinMigrationStageTypes.AppInitialisation)]
[JellyfinMigrationBackup(JellyfinDb = true)]
public class CleanupOrphanedExtras : IAsyncMigrationRoutine
{
@@ -37,39 +38,14 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine
/// <param name="logger">The startup logger.</param>
/// <param name="dbContextFactory">The database context factory.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="itemRepository">The item repository.</param>
/// <param name="itemCountService">The item count service.</param>
/// <param name="channelManager">The channel manager.</param>
/// <param name="recordingsManager">The recordings manager.</param>
/// <param name="mediaSourceManager">The media source manager.</param>
/// <param name="mediaSegmentManager">The media segments manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="fileSystem">The file system.</param>
public CleanupOrphanedExtras(
IStartupLogger<CleanupOrphanedExtras> logger,
IDbContextFactory<JellyfinDbContext> dbContextFactory,
ILibraryManager libraryManager,
IItemRepository itemRepository,
IItemCountService itemCountService,
IChannelManager channelManager,
IRecordingsManager recordingsManager,
IMediaSourceManager mediaSourceManager,
IMediaSegmentManager mediaSegmentManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem)
ILibraryManager libraryManager)
{
_logger = logger;
_dbContextFactory = dbContextFactory;
_libraryManager = libraryManager;
BaseItem.LibraryManager ??= libraryManager;
BaseItem.ItemRepository ??= itemRepository;
BaseItem.ItemCountService ??= itemCountService;
BaseItem.ChannelManager ??= channelManager;
BaseItem.MediaSourceManager ??= mediaSourceManager;
BaseItem.MediaSegmentManager ??= mediaSegmentManager;
BaseItem.ConfigurationManager ??= configurationManager;
BaseItem.FileSystem ??= fileSystem;
Video.RecordingsManager ??= recordingsManager;
}
/// <inheritdoc/>
@@ -78,12 +54,19 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine
var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
await using (context.ConfigureAwait(false))
{
var placeholderOwner = Guid.Parse("00000000-0000-0000-0000-000000000001");
#pragma warning disable RS0030 // Do not use banned APIs
var orphanedItemIds = await context.BaseItems
.Where(b => b.OwnerId.HasValue && !b.OwnerId.Value.Equals(Guid.Empty))
.Where(b => !context.BaseItems.Any(parent => parent.Id.Equals(b.OwnerId!.Value)))
.Select(b => b.Id)
.Where(b => b.OwnerId.HasValue && b.OwnerId == placeholderOwner)
.Select(b => new
{
b.Id,
b.Path,
b.Type
})
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
#pragma warning restore RS0030 // Do not use banned APIs
if (orphanedItemIds.Count == 0)
{
@@ -97,11 +80,16 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine
var itemsToDelete = new List<BaseItem>();
foreach (var itemId in orphanedItemIds)
{
var item = _libraryManager.GetItemById(itemId);
if (item is not null)
{
itemsToDelete.Add(item);
}
itemsToDelete.Add(BaseItemMapper.DeserializeBaseItem(
new Database.Implementations.Entities.BaseItemEntity()
{
Id = itemId.Id,
Path = itemId.Path,
Type = itemId.Type
},
_logger,
null,
true)!);
}
_libraryManager.DeleteItemsUnsafeFast(itemsToDelete);

View File

@@ -284,10 +284,16 @@ public class MergeDuplicatePeople : IAsyncMigrationRoutine
return;
}
await context.Peoples
.Where(p => idsToDelete.Contains(p.Id))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
var idx = 0;
foreach (var item in idsToDelete.Chunk(200))
{
idx++; // humans count at one
_logger.LogInformation("Remove batch {BatchNo}/{MaxBatches} duplicate Peoples.", idx, idsToDelete.Count / 200);
await context.Peoples
.Where(p => item.Contains(p.Id))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
}
_logger.LogInformation("Removed {Count} duplicate Peoples rows.", idsToDelete.Count);
}

View File

@@ -270,6 +270,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -23,12 +23,40 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
name: "BaseItemEntityId",
table: "BaseItems");
migrationBuilder.Sql(
"""
UPDATE BaseItems
SET OwnerId = '00000000-0000-0000-0000-000000000001'
WHERE OwnerId IS NOT NULL
AND OwnerId NOT IN (SELECT Id FROM BaseItems);
""");
migrationBuilder.AddForeignKey(
name: "FK_BaseItems_BaseItems_OwnerId",
table: "BaseItems",
column: "OwnerId",
principalTable: "BaseItems",
principalColumn: "Id");
migrationBuilder.AddColumn<bool>(
name: "IsOriginal",
table: "MediaStreamInfos",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "OriginalLanguage",
table: "BaseItems",
type: "TEXT",
nullable: true);
migrationBuilder.UpdateData(
table: "BaseItems",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
column: "OriginalLanguage",
value: null);
}
/// <inheritdoc />
@@ -62,6 +90,14 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
column: "BaseItemEntityId",
principalTable: "BaseItems",
principalColumn: "Id");
migrationBuilder.DropColumn(
name: "IsOriginal",
table: "MediaStreamInfos");
migrationBuilder.DropColumn(
name: "OriginalLanguage",
table: "BaseItems");
}
}
}

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -267,6 +267,9 @@ namespace Jellyfin.Database.Providers.Sqlite.Migrations
b.Property<string>("OfficialRating")
.HasColumnType("TEXT");
b.Property<string>("OriginalLanguage")
.HasColumnType("TEXT");
b.Property<string>("OriginalTitle")
.HasColumnType("TEXT");

View File

@@ -1,47 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Database.Providers.Sqlite.Migrations
{
/// <inheritdoc />
public partial class AddOriginalLanguage : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsOriginal",
table: "MediaStreamInfos",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "OriginalLanguage",
table: "BaseItems",
type: "TEXT",
nullable: true);
migrationBuilder.UpdateData(
table: "BaseItems",
keyColumn: "Id",
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
column: "OriginalLanguage",
value: null);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsOriginal",
table: "MediaStreamInfos");
migrationBuilder.DropColumn(
name: "OriginalLanguage",
table: "BaseItems");
}
}
}