From a7bb3ea21489a3a4c9a3e9f60c9521eaa84bad31 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Tue, 6 May 2025 02:23:23 +0300 Subject: [PATCH] Only consider migrations that have key set for migration.xml migration (#14061) --- .../Migrations/JellyfinMigrationAttribute.cs | 3 + .../Migrations/JellyfinMigrationService.cs | 5 +- .../CreateNetworkConfiguration.cs | 2 +- .../MigrateEncodingOptions.cs | 2 +- .../MigrateMusicBrainzTimeout.cs | 2 +- .../RenameEnableGroupingIntoCollections.cs | 2 +- .../Routines/AddDefaultCastReceivers.cs | 2 +- .../Routines/AddDefaultPluginRepository.cs | 2 +- .../Routines/CreateUserLoggingConfigFile.cs | 2 +- .../Routines/DisableTranscodingThrottling.cs | 2 +- .../Migrations/Routines/FixAudioData.cs | 2 +- .../Migrations/Routines/FixPlaylistOwner.cs | 2 +- .../Routines/MigrateActivityLogDb.cs | 2 +- .../Routines/MigrateAuthenticationDb.cs | 2 +- .../Routines/MigrateDisplayPreferencesDb.cs | 2 +- .../Routines/MigrateKeyframeData.cs | 2 +- .../Migrations/Routines/MigrateLibraryDb.cs | 12 +- .../Routines/MigrateRatingLevels.cs | 101 ++--- .../Migrations/Routines/MigrateUserDb.cs | 347 +++++++++--------- .../Migrations/Routines/MoveExtractedFiles.cs | 2 +- .../Migrations/Routines/MoveTrickplayFiles.cs | 2 +- .../Routines/ReaddDefaultPluginRepository.cs | 59 ++- .../Routines/RefreshInternalDateModified.cs | 2 +- .../Routines/RemoveDownloadImagesInAdvance.cs | 61 ++- .../Routines/RemoveDuplicateExtras.cs | 103 +++--- .../RemoveDuplicatePlaylistChildren.cs | 2 +- .../Routines/UpdateDefaultPluginRepository.cs | 2 +- 27 files changed, 360 insertions(+), 369 deletions(-) diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs b/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs index f523bc76c1..5c8322ef78 100644 --- a/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs +++ b/Jellyfin.Server/Migrations/JellyfinMigrationAttribute.cs @@ -17,7 +17,9 @@ public sealed class JellyfinMigrationAttribute : Attribute /// /// The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string. /// The name of this Migration. +#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 /// The ordering this migration should be applied to. Must be a valid DateTime ISO8601 formatted string. /// The name of this Migration. /// [ONLY FOR LEGACY MIGRATIONS]The unique key of this migration. Must be a valid Guid formatted string. + [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); diff --git a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs index 46c22d16cc..ebffab7ef0 100644 --- a/Jellyfin.Server/Migrations/JellyfinMigrationService.cs +++ b/Jellyfin.Server/Migrations/JellyfinMigrationService.cs @@ -108,8 +108,9 @@ internal class JellyfinMigrationService { var historyRepository = dbContext.GetService(); 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())))); diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs index a62523b88f..fd472cff7c 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs @@ -8,8 +8,8 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.PreStartupRoutines; /// -[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 { diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs index 3455696994..0141b43c96 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateEncodingOptions.cs @@ -10,8 +10,8 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.PreStartupRoutines; /// -[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 { diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs index bdbf0c1ce4..e8da9f515d 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs @@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.PreStartupRoutines; /// -[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 { diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs index c0ca7896fb..995b2bbf9b 100644 --- a/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/RenameEnableGroupingIntoCollections.cs @@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations.PreStartupRoutines; /// -[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 { diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs index 7e92433423..00d152b4b8 100644 --- a/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs +++ b/Jellyfin.Server/Migrations/Routines/AddDefaultCastReceivers.cs @@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Migration to add the default cast receivers to the system config. /// -[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 { diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs index 603e01c180..8c8398a161 100644 --- a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs +++ b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs @@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines /// /// Migration to initialize system configuration with the default plugin repository. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 9d2a901cd9..1326a6dc8d 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/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. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index ca9bf32648..acf2835fe0 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -7,8 +7,8 @@ namespace Jellyfin.Server.Migrations.Routines /// /// Disable transcode throttling for all installations since it is currently broken for certain video formats. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/FixAudioData.cs b/Jellyfin.Server/Migrations/Routines/FixAudioData.cs index 6ebb5000e4..af8787b955 100644 --- a/Jellyfin.Server/Migrations/Routines/FixAudioData.cs +++ b/Jellyfin.Server/Migrations/Routines/FixAudioData.cs @@ -16,8 +16,8 @@ namespace Jellyfin.Server.Migrations.Routines /// /// Fixes the data column of audio types to be deserializable. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs index f31c1afbd3..56614ece3c 100644 --- a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs +++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs @@ -13,8 +13,8 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Properly set playlist owner. /// -[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 { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index 14089cac75..a954d307e1 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -14,8 +14,8 @@ namespace Jellyfin.Server.Migrations.Routines /// /// The migration routine for migrating the activity log database to EF Core. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs index e4362f44da..c6699c21df 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs @@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations.Routines /// /// A migration that moves data from the authentication database into the new schema. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs index 49ed01d6bb..0d9952ce97 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs @@ -20,8 +20,8 @@ namespace Jellyfin.Server.Migrations.Routines /// /// The migration routine for migrating the display preferences database to EF Core. /// - [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 { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs index 1ee4c41c37..03a5212585 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Migration to move extracted files to the new directories. /// -[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 _logger; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs index 8374508e66..c9d2899407 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs +++ b/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; /// /// The migration routine for migrating the userdata database to EF Core. /// -[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"; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs b/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs index 96276e9b10..234965c0a5 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs +++ b/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; + +/// +/// Migrate rating levels. +/// +#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 { - /// - /// Migrate rating levels. - /// - [JellyfinMigration("2025-04-20T22:00:00", nameof(MigrateRatingLevels), "98724538-EB11-40E3-931A-252C55BDDE7A")] - internal class MigrateRatingLevels : IDatabaseMigrationRoutine + private readonly ILogger _logger; + private readonly IDbContextFactory _provider; + private readonly ILocalizationManager _localizationManager; + + public MigrateRatingLevels( + IDbContextFactory provider, + ILoggerFactory loggerFactory, + ILocalizationManager localizationManager) { - private readonly ILogger _logger; - private readonly IDbContextFactory _provider; - private readonly ILocalizationManager _localizationManager; + _provider = provider; + _localizationManager = localizationManager; + _logger = loggerFactory.CreateLogger(); + } - public MigrateRatingLevels( - IDbContextFactory provider, - ILoggerFactory loggerFactory, - ILocalizationManager localizationManager) + /// + 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) { - _provider = provider; - _localizationManager = localizationManager; - _logger = loggerFactory.CreateLogger(); - } - - /// - 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) + 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(); } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 7a23fcc98c..e5584fb947 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/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 -{ - /// - /// The migration routine for migrating the user database to EF Core. - /// - [JellyfinMigration("2025-04-20T10:00:00", nameof(MigrateUserDb), "5C4B82A2-F053-4009-BD05-B6FCAD82F14C")] +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// The migration routine for migrating the user database to EF Core. +/// #pragma warning disable CS0618 // Type or member is obsolete - public class MigrateUserDb : IMigrationRoutine +[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 _logger; + private readonly IServerApplicationPaths _paths; + private readonly IDbContextFactory _provider; + private readonly IXmlSerializer _xmlSerializer; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The server application paths. + /// The database provider. + /// The xml serializer. + public MigrateUserDb( + ILogger logger, + IServerApplicationPaths paths, + IDbContextFactory provider, + IXmlSerializer xmlSerializer) { - private const string DbFilename = "users.db"; + _logger = logger; + _paths = paths; + _provider = provider; + _xmlSerializer = xmlSerializer; + } - private readonly ILogger _logger; - private readonly IServerApplicationPaths _paths; - private readonly IDbContextFactory _provider; - private readonly IXmlSerializer _xmlSerializer; + /// + public void Perform() + { + var dataPath = _paths.DataPath; + _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The server application paths. - /// The database provider. - /// The xml serializer. - public MigrateUserDb( - ILogger logger, - IServerApplicationPaths paths, - IDbContextFactory provider, - IXmlSerializer xmlSerializer) + using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) { - _logger = logger; - _paths = paths; - _provider = provider; - _xmlSerializer = xmlSerializer; - } + connection.Open(); + using var dbContext = _provider.CreateDbContext(); - /// - public void Perform() - { - var dataPath = _paths.DataPath; - _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); + var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); - using (var connection = new SqliteConnection($"Filename={Path.Combine(dataPath, DbFilename)}")) + dbContext.RemoveRange(dbContext.Users); + dbContext.SaveChanges(); + + foreach (var entry in queryResult) { - connection.Open(); - using var dbContext = _provider.CreateDbContext(); - - var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); - - dbContext.RemoveRange(dbContext.Users); - dbContext.SaveChanges(); - - foreach (var entry in queryResult) + UserMockup? mockup = JsonSerializer.Deserialize(entry.GetStream(2), JsonDefaults.Options); + if (mockup is null) { - UserMockup? mockup = JsonSerializer.Deserialize(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 - }; - - 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) - { - 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); + continue; } - dbContext.SaveChanges(); - } + var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); - try - { - File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + var configPath = Path.Combine(userDataDir, "config.xml"); + var config = File.Exists(configPath) + ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration() + : new UserConfiguration(); - var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); - if (File.Exists(journalPath)) + 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 { - File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal")); + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; + + 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) + { + 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); } - catch (IOException e) - { - _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'"); - } + + dbContext.SaveChanges(); } -#nullable disable - internal class UserMockup + try { - public string Password { get; set; } + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); - public string EasyPassword { get; set; } - - public DateTime? LastLoginDate { get; set; } - - public DateTime? LastActivityDate { get; set; } - - public string Name { get; set; } - - public ItemImageInfo[] ImageInfos { get; set; } + var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); + if (File.Exists(journalPath)) + { + 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; } + + public string EasyPassword { get; set; } + + public DateTime? LastLoginDate { get; set; } + + public DateTime? LastActivityDate { get; set; } + + public string Name { get; set; } + + public ItemImageInfo[] ImageInfos { get; set; } + } } diff --git a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs index c6471b24c8..8b4abdfe59 100644 --- a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs +++ b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Migration to move extracted files to the new directories. /// -[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 diff --git a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs index 6077080439..63b0614fd4 100644 --- a/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs +++ b/Jellyfin.Server/Migrations/Routines/MoveTrickplayFiles.cs @@ -15,8 +15,8 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Migration to move trickplay files to the new directory. /// -[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 { diff --git a/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs index 1ef1dd45fe..ebf4a2780e 100644 --- a/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs +++ b/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 -{ - /// - /// Migration to initialize system configuration with the default plugin repository. - /// - [JellyfinMigration("2025-04-20T11:00:00", nameof(ReaddDefaultPluginRepository), "5F86E7F6-D966-4C77-849D-7A7B40B68C4E", RunMigrationOnSetup = true)] +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Migration to initialize system configuration with the default plugin repository. +/// #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 RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo { - private readonly IServerConfigurationManager _serverConfigurationManager; + 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" - }; + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager) + /// + public void Perform() + { + // Only add if repository list is empty + if (_serverConfigurationManager.Configuration.PluginRepositories.Length == 0) { - _serverConfigurationManager = serverConfigurationManager; - } - - /// - public void Perform() - { - // 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(); } } } diff --git a/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs b/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs index 9a95757a8c..b23a7dbc42 100644 --- a/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs +++ b/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Migration to re-read creation dates for library items with internal metadata paths. /// -[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 _logger; diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs b/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs index 477363e0de..b626c473e3 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDownloadImagesInAdvance.cs +++ b/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 -{ - /// - /// Removes the old 'RemoveDownloadImagesInAdvance' from library options. - /// - [JellyfinMigration("2025-04-20T13:00:00", nameof(RemoveDownloadImagesInAdvance), "A81F75E0-8F43-416F-A5E8-516CCAB4D8CC")] +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Removes the old 'RemoveDownloadImagesInAdvance' from library options. +/// #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 _logger; + private readonly ILibraryManager _libraryManager; + + public RemoveDownloadImagesInAdvance(ILogger logger, ILibraryManager libraryManager) { - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; + _logger = logger; + _libraryManager = libraryManager; + } - public RemoveDownloadImagesInAdvance(ILogger logger, ILibraryManager libraryManager) + /// + public void Perform() + { + var virtualFolders = _libraryManager.GetVirtualFolders(false); + _logger.LogInformation("Removing 'RemoveDownloadImagesInAdvance' settings in all the libraries"); + foreach (var virtualFolder in virtualFolders) { - _logger = logger; - _libraryManager = libraryManager; - } - - /// - public void Perform() - { - 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(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(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); } } } diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs index c80512deed..c9e66d0cfe 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs +++ b/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 -{ - /// - /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. - /// - [JellyfinMigration("2025-04-20T08:00:00", nameof(RemoveDuplicateExtras), "ACBE17B7-8435-4A83-8B64-6FCF162CB9BD")] +namespace Jellyfin.Server.Migrations.Routines; + +/// +/// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. +/// #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 _logger; + private readonly IServerApplicationPaths _paths; + + public RemoveDuplicateExtras(ILogger logger, IServerApplicationPaths paths) { - private const string DbFilename = "library.db"; - private readonly ILogger _logger; - private readonly IServerApplicationPaths _paths; + _logger = logger; + _paths = paths; + } - public RemoveDuplicateExtras(ILogger logger, IServerApplicationPaths paths) + /// + 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))); - /// - 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))); + _logger.LogInformation("No duplicate extras detected, skipping migration."); + return; + } - // Do nothing if no duplicate extras were detected - if (bads.Length == 0) + // 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)) { - _logger.LogInformation("No duplicate extras detected, skipping migration."); - return; - } - - // 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)) + 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(); } } } diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs index ce2be2755c..23f212424b 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs @@ -11,8 +11,8 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Remove duplicate playlist entries. /// -[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 { diff --git a/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs index cf3f5433b4..f58cf27413 100644 --- a/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs +++ b/Jellyfin.Server/Migrations/Routines/UpdateDefaultPluginRepository.cs @@ -6,8 +6,8 @@ namespace Jellyfin.Server.Migrations.Routines; /// /// Migration to update the default Jellyfin plugin repository. /// -[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 {