Browse Source

Only consider migrations that have key set for migration.xml migration (#14061)

JPVenson 4 weeks ago
parent
commit
a7bb3ea214
27 changed files with 352 additions and 361 deletions
  1. 3 0
      Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs
  2. 3 2
      Jellyfin.Server/Migrations/JellyfinMigrationService.cs
  3. 1 1
      Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
  4. 1 1
      Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs
  5. 1 1
      Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs
  6. 1 1
      Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs
  7. 1 1
      Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs
  8. 1 1
      Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
  9. 1 1
      Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
  10. 1 1
      Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
  11. 1 1
      Jellyfin.Server/Migrations/Routines/FixAudioData.cs
  12. 1 1
      Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs
  13. 1 1
      Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
  14. 1 1
      Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
  15. 1 1
      Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
  16. 1 1
      Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs
  17. 1 11
      Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs
  18. 51 50
      Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
  19. 165 166
      Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
  20. 1 1
      Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs
  21. 1 1
      Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs
  22. 29 30
      Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs
  23. 1 1
      Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs
  24. 30 31
      Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs
  25. 51 52
      Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
  26. 1 1
      Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs
  27. 1 1
      Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs

+ 3 - 0
Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs

@@ -17,7 +17,9 @@ public sealed class JellyfinMigrationAttribute : Attribute
     /// </summary>
     /// <param name="order">The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string.</param>
     /// <param name="name">The name of this Migration.</param>
+#pragma warning disable CS0618 // Type or member is obsolete
     public JellyfinMigrationAttribute(string order, string name) : this(order, name, null)
+#pragma warning restore CS0618 // Type or member is obsolete
     {
     }
 
@@ -27,6 +29,7 @@ public sealed class JellyfinMigrationAttribute : Attribute
     /// <param name="order">The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string.</param>
     /// <param name="name">The name of this Migration.</param>
     /// <param name="key">[ONLY FOR LEGACY MIGRATIONS]The unique key of this migration. Must be a valid Guid formatted string.</param>
+    [Obsolete("This Constructor should only be used for Legacy migrations. Use the (Order,Name) one for all new ones instead.")]
     public JellyfinMigrationAttribute(string order, string name, string? key)
     {
         Order = DateTime.Parse(order, CultureInfo.InvariantCulture);

+ 3 - 2
Jellyfin.Server/Migrations/JellyfinMigrationService.cs

@@ -108,8 +108,9 @@ internal class JellyfinMigrationService
                 {
                     var historyRepository = dbContext.GetService<IHistoryRepository>();
                     var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
-                    var oldMigrations = Migrations.SelectMany(e => e)
-                        .Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value))) // this is a legacy migration that will always have its own ID.
+                    var oldMigrations = Migrations
+                        .SelectMany(e => e.Where(e => e.Metadata.Key is not null)) // only consider migrations that have the key set as its the reference marker for legacy migrations.
+                        .Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value)))
                         .Where(e => !appliedMigrations.Contains(e.BuildCodeMigrationId()))
                         .ToArray();
                     var startupScripts = oldMigrations.Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion()))));

+ 1 - 1
Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs

@@ -8,8 +8,8 @@ using Microsoft.Extensions.Logging;
 namespace Jellyfin.Server.Migrations.PreStartupRoutines;
 
 /// <inheritdoc />
-[JellyfinMigration("2025-04-20T00:00:00", nameof(CreateNetworkConfiguration), "9B354818-94D5-4B68-AC49-E35CB85F9D84", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T00:00:00", nameof(CreateNetworkConfiguration), "9B354818-94D5-4B68-AC49-E35CB85F9D84", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 public class CreateNetworkConfiguration : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs

@@ -10,8 +10,8 @@ using Microsoft.Extensions.Logging;
 namespace Jellyfin.Server.Migrations.PreStartupRoutines;
 
 /// <inheritdoc />
-[JellyfinMigration("2025-04-20T03:00:00", nameof(MigrateEncodingOptions), "A8E61960-7726-4450-8F3D-82C12DAABBCB", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T03:00:00", nameof(MigrateEncodingOptions), "A8E61960-7726-4450-8F3D-82C12DAABBCB", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 public class MigrateEncodingOptions : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs

@@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging;
 namespace Jellyfin.Server.Migrations.PreStartupRoutines;
 
 /// <inheritdoc />
-[JellyfinMigration("2025-04-20T02:00:00", nameof(MigrateMusicBrainzTimeout), "A6DCACF4-C057-4Ef9-80D3-61CEF9DDB4F0", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T02:00:00", nameof(MigrateMusicBrainzTimeout), "A6DCACF4-C057-4Ef9-80D3-61CEF9DDB4F0", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 public class MigrateMusicBrainzTimeout : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs

@@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging;
 namespace Jellyfin.Server.Migrations.PreStartupRoutines;
 
 /// <inheritdoc />
-[JellyfinMigration("2025-04-20T04:00:00", nameof(RenameEnableGroupingIntoCollections), "E73B777D-CD5C-4E71-957A-B86B3660B7CF", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T04:00:00", nameof(RenameEnableGroupingIntoCollections), "E73B777D-CD5C-4E71-957A-B86B3660B7CF", Stage = Stages.JellyfinMigrationStageTypes.PreInitialisation)]
 public class RenameEnableGroupingIntoCollections : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs

@@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Migration to add the default cast receivers to the system config.
 /// </summary>
-[JellyfinMigration("2025-04-20T16:00:00", nameof(AddDefaultCastReceivers), "34A1A1C4-5572-418E-A2F8-32CDFE2668E8", RunMigrationOnSetup = true)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T16:00:00", nameof(AddDefaultCastReceivers), "34A1A1C4-5572-418E-A2F8-32CDFE2668E8", RunMigrationOnSetup = true)]
 public class AddDefaultCastReceivers : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs

@@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// <summary>
     /// Migration to initialize system configuration with the default plugin repository.
     /// </summary>
-    [JellyfinMigration("2025-04-20T09:00:00", nameof(AddDefaultPluginRepository), "EB58EBEE-9514-4B9B-8225-12E1A40020DF", RunMigrationOnSetup = true)]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T09:00:00", nameof(AddDefaultPluginRepository), "EB58EBEE-9514-4B9B-8225-12E1A40020DF", RunMigrationOnSetup = true)]
     public class AddDefaultPluginRepository : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs

@@ -12,8 +12,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json,
     /// otherwise a blank file will be created.
     /// </summary>
-    [JellyfinMigration("2025-04-20T06:00:00", nameof(CreateUserLoggingConfigFile), "EF103419-8451-40D8-9F34-D1A8E93A1679")]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T06:00:00", nameof(CreateUserLoggingConfigFile), "EF103419-8451-40D8-9F34-D1A8E93A1679")]
     internal class CreateUserLoggingConfigFile : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs

@@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// <summary>
     /// Disable transcode throttling for all installations since it is currently broken for certain video formats.
     /// </summary>
-    [JellyfinMigration("2025-04-20T05:00:00", nameof(DisableTranscodingThrottling), "4124C2CD-E939-4FFB-9BE9-9B311C413638")]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T05:00:00", nameof(DisableTranscodingThrottling), "4124C2CD-E939-4FFB-9BE9-9B311C413638")]
     internal class DisableTranscodingThrottling : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/FixAudioData.cs

@@ -16,8 +16,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// <summary>
     /// Fixes the data column of audio types to be deserializable.
     /// </summary>
-    [JellyfinMigration("2025-04-20T18:00:00", nameof(FixAudioData), "CF6FABC2-9FBE-4933-84A5-FFE52EF22A58")]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T18:00:00", nameof(FixAudioData), "CF6FABC2-9FBE-4933-84A5-FFE52EF22A58")]
     internal class FixAudioData : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs

@@ -13,8 +13,8 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Properly set playlist owner.
 /// </summary>
-[JellyfinMigration("2025-04-20T15:00:00", nameof(FixPlaylistOwner), "615DFA9E-2497-4DBB-A472-61938B752C5B")]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T15:00:00", nameof(FixPlaylistOwner), "615DFA9E-2497-4DBB-A472-61938B752C5B")]
 internal class FixPlaylistOwner : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs

@@ -14,8 +14,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// <summary>
     /// The migration routine for migrating the activity log database to EF Core.
     /// </summary>
-    [JellyfinMigration("2025-04-20T07:00:00", nameof(MigrateActivityLogDb), "3793eb59-bc8c-456c-8b9f-bd5a62a42978")]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T07:00:00", nameof(MigrateActivityLogDb), "3793eb59-bc8c-456c-8b9f-bd5a62a42978")]
     public class MigrateActivityLogDb : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs

@@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// <summary>
     /// A migration that moves data from the authentication database into the new schema.
     /// </summary>
-    [JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T14:00:00", nameof(MigrateAuthenticationDb), "5BD72F41-E6F3-4F60-90AA-09869ABE0E22")]
     public class MigrateAuthenticationDb : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs

@@ -20,8 +20,8 @@ namespace Jellyfin.Server.Migrations.Routines
     /// <summary>
     /// The migration routine for migrating the display preferences database to EF Core.
     /// </summary>
-    [JellyfinMigration("2025-04-20T12:00:00", nameof(MigrateDisplayPreferencesDb), "06387815-C3CC-421F-A888-FB5F9992BEA8")]
 #pragma warning disable CS0618 // Type or member is obsolete
+    [JellyfinMigration("2025-04-20T12:00:00", nameof(MigrateDisplayPreferencesDb), "06387815-C3CC-421F-A888-FB5F9992BEA8")]
     public class MigrateDisplayPreferencesDb : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
     {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs

@@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Migration to move extracted files to the new directories.
 /// </summary>
-[JellyfinMigration("2025-04-21T00:00:00", nameof(MigrateKeyframeData), "EA4bCAE1-09A4-428E-9B90-4B4FD2EA1B24")]
+[JellyfinMigration("2025-04-21T00:00:00", nameof(MigrateKeyframeData))]
 public class MigrateKeyframeData : IDatabaseMigrationRoutine
 {
     private readonly ILogger<MigrateKeyframeData> _logger;

+ 1 - 11
Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs

@@ -9,26 +9,16 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
-using System.Threading;
 using Emby.Server.Implementations.Data;
 using Jellyfin.Database.Implementations;
 using Jellyfin.Database.Implementations.Entities;
 using Jellyfin.Extensions;
 using Jellyfin.Server.Implementations.Item;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.Channels;
-using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
 using Microsoft.Data.Sqlite;
 using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 using Chapter = Jellyfin.Database.Implementations.Entities.Chapter;
@@ -38,7 +28,7 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// The migration routine for migrating the userdata database to EF Core.
 /// </summary>
-[JellyfinMigration("2025-04-20T20:00:00", nameof(MigrateLibraryDb), "36445464-849f-429f-9ad0-bb130efa0664")]
+[JellyfinMigration("2025-04-20T20:00:00", nameof(MigrateLibraryDb))]
 internal class MigrateLibraryDb : IDatabaseMigrationRoutine
 {
     private const string DbFilename = "library.db";

+ 51 - 50
Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs

@@ -5,62 +5,63 @@ using MediaBrowser.Model.Globalization;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Logging;
 
-namespace Jellyfin.Server.Migrations.Routines
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// Migrate rating levels.
+/// </summary>
+#pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels))]
+#pragma warning restore CS0618 // Type or member is obsolete
+internal class MigrateRatingLevels : IDatabaseMigrationRoutine
 {
-    /// <summary>
-    /// Migrate rating levels.
-    /// </summary>
-    [JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels), "98724538-EB11-40E3-931A-252C55BDDE7A")]
-    internal class MigrateRatingLevels : IDatabaseMigrationRoutine
-    {
-        private readonly ILogger<MigrateRatingLevels> _logger;
-        private readonly IDbContextFactory<JellyfinDbContext> _provider;
-        private readonly ILocalizationManager _localizationManager;
+    private readonly ILogger<MigrateRatingLevels> _logger;
+    private readonly IDbContextFactory<JellyfinDbContext> _provider;
+    private readonly ILocalizationManager _localizationManager;
 
-        public MigrateRatingLevels(
-            IDbContextFactory<JellyfinDbContext> provider,
-            ILoggerFactory loggerFactory,
-            ILocalizationManager localizationManager)
-        {
-            _provider = provider;
-            _localizationManager = localizationManager;
-            _logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
-        }
+    public MigrateRatingLevels(
+        IDbContextFactory<JellyfinDbContext> provider,
+        ILoggerFactory loggerFactory,
+        ILocalizationManager localizationManager)
+    {
+        _provider = provider;
+        _localizationManager = localizationManager;
+        _logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
+    }
 
-        /// <inheritdoc/>
-        public void Perform()
+    /// <inheritdoc/>
+    public void Perform()
+    {
+        _logger.LogInformation("Recalculating parental rating levels based on rating string.");
+        using var context = _provider.CreateDbContext();
+        using var transaction = context.Database.BeginTransaction();
+        var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct();
+        foreach (var rating in ratings)
         {
-            _logger.LogInformation("Recalculating parental rating levels based on rating string.");
-            using var context = _provider.CreateDbContext();
-            using var transaction = context.Database.BeginTransaction();
-            var ratings = context.BaseItems.AsNoTracking().Select(e => e.OfficialRating).Distinct();
-            foreach (var rating in ratings)
+            if (string.IsNullOrEmpty(rating))
             {
-                if (string.IsNullOrEmpty(rating))
-                {
-                    int? value = null;
-                    context.BaseItems
-                        .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
-                        .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value));
-                    context.BaseItems
-                        .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
-                        .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value));
-                }
-                else
-                {
-                    var ratingValue = _localizationManager.GetRatingScore(rating);
-                    var score = ratingValue?.Score;
-                    var subScore = ratingValue?.SubScore;
-                    context.BaseItems
-                        .Where(e => e.OfficialRating == rating)
-                        .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score));
-                    context.BaseItems
-                        .Where(e => e.OfficialRating == rating)
-                        .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore));
-                }
+                int? value = null;
+                context.BaseItems
+                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
+                    .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, value));
+                context.BaseItems
+                    .Where(e => e.OfficialRating == null || e.OfficialRating == string.Empty)
+                    .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, value));
+            }
+            else
+            {
+                var ratingValue = _localizationManager.GetRatingScore(rating);
+                var score = ratingValue?.Score;
+                var subScore = ratingValue?.SubScore;
+                context.BaseItems
+                    .Where(e => e.OfficialRating == rating)
+                    .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingValue, score));
+                context.BaseItems
+                    .Where(e => e.OfficialRating == rating)
+                    .ExecuteUpdate(f => f.SetProperty(e => e.InheritedParentalRatingSubValue, subScore));
             }
-
-            transaction.Commit();
         }
+
+        transaction.Commit();
     }
 }

+ 165 - 166
Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs

@@ -17,201 +17,200 @@ using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Logging;
 using JsonSerializer = System.Text.Json.JsonSerializer;
 
-namespace Jellyfin.Server.Migrations.Routines
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// The migration routine for migrating the user database to EF Core.
+/// </summary>
+#pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")]
+public class MigrateUserDb : IMigrationRoutine
+#pragma warning restore CS0618 // Type or member is obsolete
 {
+    private const string DbFilename = "users.db";
+
+    private readonly ILogger<MigrateUserDb> _logger;
+    private readonly IServerApplicationPaths _paths;
+    private readonly IDbContextFactory<JellyfinDbContext> _provider;
+    private readonly IXmlSerializer _xmlSerializer;
+
     /// <summary>
-    /// The migration routine for migrating the user database to EF Core.
+    /// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
     /// </summary>
-    [JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")]
-#pragma warning disable CS0618 // Type or member is obsolete
-    public class MigrateUserDb : IMigrationRoutine
-#pragma warning restore CS0618 // Type or member is obsolete
+    /// <param name="logger">The logger.</param>
+    /// <param name="paths">The server application paths.</param>
+    /// <param name="provider">The database provider.</param>
+    /// <param name="xmlSerializer">The xml serializer.</param>
+    public MigrateUserDb(
+        ILogger<MigrateUserDb> logger,
+        IServerApplicationPaths paths,
+        IDbContextFactory<JellyfinDbContext> provider,
+        IXmlSerializer xmlSerializer)
     {
-        private const string DbFilename = "users.db";
-
-        private readonly ILogger<MigrateUserDb> _logger;
-        private readonly IServerApplicationPaths _paths;
-        private readonly IDbContextFactory<JellyfinDbContext> _provider;
-        private readonly IXmlSerializer _xmlSerializer;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="paths">The server application paths.</param>
-        /// <param name="provider">The database provider.</param>
-        /// <param name="xmlSerializer">The xml serializer.</param>
-        public MigrateUserDb(
-            ILogger<MigrateUserDb> logger,
-            IServerApplicationPaths paths,
-            IDbContextFactory<JellyfinDbContext> provider,
-            IXmlSerializer xmlSerializer)
-        {
-            _logger = logger;
-            _paths = paths;
-            _provider = provider;
-            _xmlSerializer = xmlSerializer;
-        }
+        _logger = logger;
+        _paths = paths;
+        _provider = provider;
+        _xmlSerializer = xmlSerializer;
+    }
 
-        /// <inheritdoc/>
-        public void Perform()
+    /// <inheritdoc/>
+    public void Perform()
+    {
+        var dataPath = _paths.DataPath;
+        _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
+
+        using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
         {
-            var dataPath = _paths.DataPath;
-            _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
+            connection.Open();
+            using var dbContext = _provider.CreateDbContext();
 
-            using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}"))
-            {
-                connection.Open();
-                using var dbContext = _provider.CreateDbContext();
+            var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
 
-                var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
+            dbContext.RemoveRange(dbContext.Users);
+            dbContext.SaveChanges();
 
-                dbContext.RemoveRange(dbContext.Users);
-                dbContext.SaveChanges();
+            foreach (var entry in queryResult)
+            {
+                UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
+                if (mockup is null)
+                {
+                    continue;
+                }
 
-                foreach (var entry in queryResult)
+                var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
+
+                var configPath = Path.Combine(userDataDir, "config.xml");
+                var config = File.Exists(configPath)
+                    ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration()
+                    : new UserConfiguration();
+
+                var policyPath = Path.Combine(userDataDir, "policy.xml");
+                var policy = File.Exists(policyPath)
+                    ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy()
+                    : new UserPolicy();
+                policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
+                    "Emby.Server.Implementations.Library",
+                    "Jellyfin.Server.Implementations.Users",
+                    StringComparison.Ordinal)
+                    ?? typeof(DefaultAuthenticationProvider).FullName;
+
+                policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
+                int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
                 {
-                    UserMockup? mockup = JsonSerializer.Deserialize<UserMockup>(entry.GetStream(2), JsonDefaults.Options);
-                    if (mockup is null)
-                    {
-                        continue;
-                    }
-
-                    var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
-
-                    var configPath = Path.Combine(userDataDir, "config.xml");
-                    var config = File.Exists(configPath)
-                        ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration()
-                        : new UserConfiguration();
-
-                    var policyPath = Path.Combine(userDataDir, "policy.xml");
-                    var policy = File.Exists(policyPath)
-                        ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy()
-                        : new UserPolicy();
-                    policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
-                        "Emby.Server.Implementations.Library",
-                        "Jellyfin.Server.Implementations.Users",
-                        StringComparison.Ordinal)
-                        ?? typeof(DefaultAuthenticationProvider).FullName;
-
-                    policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName;
-                    int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
-                    {
-                        -1 => null,
-                        0 => 3,
-                        _ => policy.LoginAttemptsBeforeLockout
-                    };
+                    -1 => null,
+                    0 => 3,
+                    _ => policy.LoginAttemptsBeforeLockout
+                };
 
-                    var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
+                var user = new User(mockup.Name, policy.AuthenticationProviderId!, policy.PasswordResetProviderId!)
+                {
+                    Id = entry.GetGuid(1),
+                    InternalId = entry.GetInt64(0),
+                    MaxParentalRatingScore = policy.MaxParentalRating,
+                    MaxParentalRatingSubScore = null,
+                    EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
+                    RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
+                    InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
+                    LoginAttemptsBeforeLockout = maxLoginAttempts,
+                    SubtitleMode = config.SubtitleMode,
+                    HidePlayedInLatest = config.HidePlayedInLatest,
+                    EnableLocalPassword = config.EnableLocalPassword,
+                    PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
+                    DisplayCollectionsView = config.DisplayCollectionsView,
+                    DisplayMissingEpisodes = config.DisplayMissingEpisodes,
+                    AudioLanguagePreference = config.AudioLanguagePreference,
+                    RememberAudioSelections = config.RememberAudioSelections,
+                    EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
+                    RememberSubtitleSelections = config.RememberSubtitleSelections,
+                    SubtitleLanguagePreference = config.SubtitleLanguagePreference,
+                    Password = mockup.Password,
+                    LastLoginDate = mockup.LastLoginDate,
+                    LastActivityDate = mockup.LastActivityDate
+                };
+
+                if (mockup.ImageInfos.Length > 0)
+                {
+                    ItemImageInfo info = mockup.ImageInfos[0];
+
+                    user.ProfileImage = new ImageInfo(info.Path)
                     {
-                        Id = entry.GetGuid(1),
-                        InternalId = entry.GetInt64(0),
-                        MaxParentalRatingScore = policy.MaxParentalRating,
-                        MaxParentalRatingSubScore = null,
-                        EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
-                        RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
-                        InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
-                        LoginAttemptsBeforeLockout = maxLoginAttempts,
-                        SubtitleMode = config.SubtitleMode,
-                        HidePlayedInLatest = config.HidePlayedInLatest,
-                        EnableLocalPassword = config.EnableLocalPassword,
-                        PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
-                        DisplayCollectionsView = config.DisplayCollectionsView,
-                        DisplayMissingEpisodes = config.DisplayMissingEpisodes,
-                        AudioLanguagePreference = config.AudioLanguagePreference,
-                        RememberAudioSelections = config.RememberAudioSelections,
-                        EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
-                        RememberSubtitleSelections = config.RememberSubtitleSelections,
-                        SubtitleLanguagePreference = config.SubtitleLanguagePreference,
-                        Password = mockup.Password,
-                        LastLoginDate = mockup.LastLoginDate,
-                        LastActivityDate = mockup.LastActivityDate
+                        LastModified = info.DateModified
                     };
+                }
 
-                    if (mockup.ImageInfos.Length > 0)
-                    {
-                        ItemImageInfo info = mockup.ImageInfos[0];
-
-                        user.ProfileImage = new ImageInfo(info.Path)
-                        {
-                            LastModified = info.DateModified
-                        };
-                    }
-
-                    user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
-                    user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
-                    user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
-                    user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
-                    user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
-                    user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
-                    user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
-                    user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
-                    user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
-                    user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
-                    user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
-                    user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
-                    user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
-                    user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
-                    user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
-                    user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
-                    user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
-                    user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
-                    user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
-                    user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
-                    user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
-                    user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
-
-                    foreach (var policyAccessSchedule in policy.AccessSchedules)
-                    {
-                        user.AccessSchedules.Add(policyAccessSchedule);
-                    }
-
-                    user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
-                    user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
-                    user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
-                    user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
-                    user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
-                    user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
-                    user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
-                    user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
-                    user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
-
-                    dbContext.Users.Add(user);
+                user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
+                user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
+                user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
+                user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
+                user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
+                user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
+                user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
+                user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
+                user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
+                user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
+                user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
+                user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
+                user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
+                user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
+                user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
+                user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
+                user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
+                user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
+                user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
+                user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
+                user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
+                user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
+
+                foreach (var policyAccessSchedule in policy.AccessSchedules)
+                {
+                    user.AccessSchedules.Add(policyAccessSchedule);
                 }
 
-                dbContext.SaveChanges();
+                user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
+                user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
+                user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
+                user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
+                user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
+                user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
+                user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
+                user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
+                user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
+
+                dbContext.Users.Add(user);
             }
 
-            try
-            {
-                File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
+            dbContext.SaveChanges();
+        }
 
-                var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
-                if (File.Exists(journalPath))
-                {
-                    File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
-                }
-            }
-            catch (IOException e)
+        try
+        {
+            File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
+
+            var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
+            if (File.Exists(journalPath))
             {
-                _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
+                File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
             }
         }
+        catch (IOException e)
+        {
+            _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
+        }
+    }
 
 #nullable disable
-        internal class UserMockup
-        {
-            public string Password { get; set; }
+    internal class UserMockup
+    {
+        public string Password { get; set; }
 
-            public string EasyPassword { get; set; }
+        public string EasyPassword { get; set; }
 
-            public DateTime? LastLoginDate { get; set; }
+        public DateTime? LastLoginDate { get; set; }
 
-            public DateTime? LastActivityDate { get; set; }
+        public DateTime? LastActivityDate { get; set; }
 
-            public string Name { get; set; }
+        public string Name { get; set; }
 
-            public ItemImageInfo[] ImageInfos { get; set; }
-        }
+        public ItemImageInfo[] ImageInfos { get; set; }
     }
 }

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs

@@ -24,7 +24,7 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Migration to move extracted files to the new directories.
 /// </summary>
-[JellyfinMigration("2025-04-20T21:00:00", nameof(MoveExtractedFiles), "9063b0Ef-CFF1-4EDC-9A13-74093681A89B")]
+[JellyfinMigration("2025-04-20T21:00:00", nameof(MoveExtractedFiles))]
 #pragma warning disable CS0618 // Type or member is obsolete
 public class MoveExtractedFiles : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs

@@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Migration to move trickplay files to the new directory.
 /// </summary>
-[JellyfinMigration("2025-04-20T23:00:00", nameof(MoveTrickplayFiles), "9540D44A-D8DC-11EF-9CBB-B77274F77C52", RunMigrationOnSetup = true)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T23:00:00", nameof(MoveTrickplayFiles), RunMigrationOnSetup = true)]
 public class MoveTrickplayFiles : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 29 - 30
Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs

@@ -2,42 +2,41 @@ using System;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Model.Updates;
 
-namespace Jellyfin.Server.Migrations.Routines
-{
-    /// <summary>
-    /// Migration to initialize system configuration with the default plugin repository.
-    /// </summary>
-    [JellyfinMigration("2025-04-20T11:00:00", nameof(ReaddDefaultPluginRepository), "5F86E7F6-D966-4C77-849D-7A7B40B68C4E", RunMigrationOnSetup = true)]
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// Migration to initialize system configuration with the default plugin repository.
+/// </summary>
 #pragma warning disable CS0618 // Type or member is obsolete
-    public class ReaddDefaultPluginRepository : IMigrationRoutine
+[JellyfinMigration("2025-04-20T11:00:00", nameof(ReaddDefaultPluginRepository), "5F86E7F6-D966-4C77-849D-7A7B40B68C4E", RunMigrationOnSetup = true)]
+public class ReaddDefaultPluginRepository : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
-    {
-        private readonly IServerConfigurationManager _serverConfigurationManager;
+{
+    private readonly IServerConfigurationManager _serverConfigurationManager;
 
-        private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
-        {
-            Name = "Jellyfin Stable",
-            Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
-        };
+    private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
+    {
+        Name = "Jellyfin Stable",
+        Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
+    };
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
-        /// </summary>
-        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
-        public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
-        {
-            _serverConfigurationManager = serverConfigurationManager;
-        }
+    /// <summary>
+    /// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
+    /// </summary>
+    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+    public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
+    {
+        _serverConfigurationManager = serverConfigurationManager;
+    }
 
-        /// <inheritdoc/>
-        public void Perform()
+    /// <inheritdoc/>
+    public void Perform()
+    {
+        // Only add if repository list is empty
+        if (_serverConfigurationManager.Configuration.PluginRepositories.Length == 0)
         {
-            // Only add if repository list is empty
-            if (_serverConfigurationManager.Configuration.PluginRepositories.Length == 0)
-            {
-                _serverConfigurationManager.Configuration.PluginRepositories = new[] { _defaultRepositoryInfo };
-                _serverConfigurationManager.SaveConfiguration();
-            }
+            _serverConfigurationManager.Configuration.PluginRepositories = new[] { _defaultRepositoryInfo };
+            _serverConfigurationManager.SaveConfiguration();
         }
     }
 }

+ 1 - 1
Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs

@@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Migration to re-read creation dates for library items with internal metadata paths.
 /// </summary>
-[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified), "32E762EB-4918-45CE-A44C-C801F66B877D", RunMigrationOnSetup = false)]
+[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified))]
 public class RefreshInternalDateModified : IDatabaseMigrationRoutine
 {
     private readonly ILogger<RefreshInternalDateModified> _logger;

+ 30 - 31
Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs

@@ -3,44 +3,43 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using Microsoft.Extensions.Logging;
 
-namespace Jellyfin.Server.Migrations.Routines
-{
-    /// <summary>
-    /// Removes the old 'RemoveDownloadImagesInAdvance' from library options.
-    /// </summary>
-    [JellyfinMigration("2025-04-20T13:00:00", nameof(RemoveDownloadImagesInAdvance), "A81F75E0-8F43-416F-A5E8-516CCAB4D8CC")]
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// Removes the old 'RemoveDownloadImagesInAdvance' from library options.
+/// </summary>
 #pragma warning disable CS0618 // Type or member is obsolete
-    internal class RemoveDownloadImagesInAdvance : IMigrationRoutine
+[JellyfinMigration("2025-04-20T13:00:00", nameof(RemoveDownloadImagesInAdvance), "A81F75E0-8F43-416F-A5E8-516CCAB4D8CC")]
+internal class RemoveDownloadImagesInAdvance : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
-    {
-        private readonly ILogger<RemoveDownloadImagesInAdvance> _logger;
-        private readonly ILibraryManager _libraryManager;
+{
+    private readonly ILogger<RemoveDownloadImagesInAdvance> _logger;
+    private readonly ILibraryManager _libraryManager;
 
-        public RemoveDownloadImagesInAdvance(ILogger<RemoveDownloadImagesInAdvance> logger, ILibraryManager libraryManager)
-        {
-            _logger = logger;
-            _libraryManager = libraryManager;
-        }
+    public RemoveDownloadImagesInAdvance(ILogger<RemoveDownloadImagesInAdvance> logger, ILibraryManager libraryManager)
+    {
+        _logger = logger;
+        _libraryManager = libraryManager;
+    }
 
-        /// <inheritdoc/>
-        public void Perform()
+    /// <inheritdoc/>
+    public void Perform()
+    {
+        var virtualFolders = _libraryManager.GetVirtualFolders(false);
+        _logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries");
+        foreach (var virtualFolder in virtualFolders)
         {
-            var virtualFolders = _libraryManager.GetVirtualFolders(false);
-            _logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries");
-            foreach (var virtualFolder in virtualFolders)
+            // Some virtual folders don't have a proper item id.
+            if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
             {
-                // Some virtual folders don't have a proper item id.
-                if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
-                {
-                    continue;
-                }
-
-                var libraryOptions = virtualFolder.LibraryOptions;
-                var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId) ?? throw new InvalidOperationException("Failed to find CollectionFolder");
-                // The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed.
-                collectionFolder.UpdateLibraryOptions(libraryOptions);
-                _logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name);
+                continue;
             }
+
+            var libraryOptions = virtualFolder.LibraryOptions;
+            var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId) ?? throw new InvalidOperationException("Failed to find CollectionFolder");
+            // The property no longer exists in LibraryOptions, so we just re-save the options to get old data removed.
+            collectionFolder.UpdateLibraryOptions(libraryOptions);
+            _logger.LogInformation("Removed from '{VirtualFolder}'", virtualFolder.Name);
         }
     }
 }

+ 51 - 52
Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs

@@ -7,71 +7,70 @@ using MediaBrowser.Controller;
 using Microsoft.Data.Sqlite;
 using Microsoft.Extensions.Logging;
 
-namespace Jellyfin.Server.Migrations.Routines
-{
-    /// <summary>
-    /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
-    /// </summary>
-    [JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")]
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
+/// </summary>
 #pragma warning disable CS0618 // Type or member is obsolete
-    internal class RemoveDuplicateExtras : IMigrationRoutine
+[JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")]
+internal class RemoveDuplicateExtras : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
+{
+    private const string DbFilename = "library.db";
+    private readonly ILogger<RemoveDuplicateExtras> _logger;
+    private readonly IServerApplicationPaths _paths;
+
+    public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
     {
-        private const string DbFilename = "library.db";
-        private readonly ILogger<RemoveDuplicateExtras> _logger;
-        private readonly IServerApplicationPaths _paths;
+        _logger = logger;
+        _paths = paths;
+    }
 
-        public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
+    /// <inheritdoc/>
+    public void Perform()
+    {
+        var dataPath = _paths.DataPath;
+        var dbPath = Path.Combine(dataPath, DbFilename);
+        using var connection = new SqliteConnection($"Filename={dbPath}");
+        connection.Open();
+        using (var transaction = connection.BeginTransaction())
         {
-            _logger = logger;
-            _paths = paths;
-        }
+            // Query the database for the ids of duplicate extras
+            var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
+            var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
 
-        /// <inheritdoc/>
-        public void Perform()
-        {
-            var dataPath = _paths.DataPath;
-            var dbPath = Path.Combine(dataPath, DbFilename);
-            using var connection = new SqliteConnection($"Filename={dbPath}");
-            connection.Open();
-            using (var transaction = connection.BeginTransaction())
+            // Do nothing if no duplicate extras were detected
+            if (bads.Length == 0)
             {
-                // Query the database for the ids of duplicate extras
-                var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
-                var bads = string.Join(", ", queryResult.Select(x => x.GetString(0)));
-
-                // Do nothing if no duplicate extras were detected
-                if (bads.Length == 0)
-                {
-                    _logger.LogInformation("No duplicate extras detected, skipping migration.");
-                    return;
-                }
+                _logger.LogInformation("No duplicate extras detected, skipping migration.");
+                return;
+            }
 
-                // Back up the database before deleting any entries
-                for (int i = 1; ; i++)
+            // Back up the database before deleting any entries
+            for (int i = 1; ; i++)
+            {
+                var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
+                if (!File.Exists(bakPath))
                 {
-                    var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
-                    if (!File.Exists(bakPath))
+                    try
                     {
-                        try
-                        {
-                            File.Copy(dbPath, bakPath);
-                            _logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
-                            break;
-                        }
-                        catch (Exception ex)
-                        {
-                            _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
-                            throw;
-                        }
+                        File.Copy(dbPath, bakPath);
+                        _logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
+                        break;
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
+                        throw;
                     }
                 }
-
-                // Delete all duplicate extras
-                _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
-                connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
-                transaction.Commit();
             }
+
+            // Delete all duplicate extras
+            _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
+            connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
+            transaction.Commit();
         }
     }
 }

+ 1 - 1
Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs

@@ -11,8 +11,8 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Remove duplicate playlist entries.
 /// </summary>
-[JellyfinMigration("2025-04-20T19:00:00", nameof(RemoveDuplicatePlaylistChildren), "96C156A2-7A13-4B3B-A8B8-FB80C94D20C0")]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T19:00:00", nameof(RemoveDuplicatePlaylistChildren), "96C156A2-7A13-4B3B-A8B8-FB80C94D20C0")]
 internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {

+ 1 - 1
Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs

@@ -6,8 +6,8 @@ namespace Jellyfin.Server.Migrations.Routines;
 /// <summary>
 /// Migration to update the default Jellyfin plugin repository.
 /// </summary>
-[JellyfinMigration("2025-04-20T17:00:00", nameof(UpdateDefaultPluginRepository), "852816E0-2712-49A9-9240-C6FC5FCAD1A8", RunMigrationOnSetup = true)]
 #pragma warning disable CS0618 // Type or member is obsolete
+[JellyfinMigration("2025-04-20T17:00:00", nameof(UpdateDefaultPluginRepository), "852816E0-2712-49A9-9240-C6FC5FCAD1A8", RunMigrationOnSetup = true)]
 public class UpdateDefaultPluginRepository : IMigrationRoutine
 #pragma warning restore CS0618 // Type or member is obsolete
 {