| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 | 
							- using System;
 
- using System.Collections.Generic;
 
- using System.Globalization;
 
- using System.IO;
 
- using System.Linq;
 
- using System.Reflection;
 
- using System.Threading;
 
- using System.Threading.Tasks;
 
- using Emby.Server.Implementations.Serialization;
 
- using Jellyfin.Database.Implementations;
 
- using Jellyfin.Server.Implementations.SystemBackupService;
 
- using Jellyfin.Server.Migrations.Stages;
 
- using Jellyfin.Server.ServerSetupApp;
 
- using MediaBrowser.Common.Configuration;
 
- using MediaBrowser.Controller.SystemBackupService;
 
- using MediaBrowser.Model.Configuration;
 
- using Microsoft.EntityFrameworkCore;
 
- using Microsoft.EntityFrameworkCore.Infrastructure;
 
- using Microsoft.EntityFrameworkCore.Migrations;
 
- using Microsoft.EntityFrameworkCore.Storage;
 
- using Microsoft.Extensions.Logging;
 
- namespace Jellyfin.Server.Migrations;
 
- /// <summary>
 
- /// Handles Migration of the Jellyfin data structure.
 
- /// </summary>
 
- internal class JellyfinMigrationService
 
- {
 
-     private const string DbFilename = "library.db";
 
-     private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory;
 
-     private readonly ILoggerFactory _loggerFactory;
 
-     private readonly IStartupLogger _startupLogger;
 
-     private readonly IBackupService? _backupService;
 
-     private readonly IJellyfinDatabaseProvider? _jellyfinDatabaseProvider;
 
-     private readonly IApplicationPaths _applicationPaths;
 
-     private (string? LibraryDb, string? JellyfinDb, BackupManifestDto? FullBackup) _backupKey;
 
-     /// <summary>
 
-     /// Initializes a new instance of the <see cref="JellyfinMigrationService"/> class.
 
-     /// </summary>
 
-     /// <param name="dbContextFactory">Provides access to the jellyfin database.</param>
 
-     /// <param name="loggerFactory">The logger factory.</param>
 
-     /// <param name="startupLogger">The startup logger for Startup UI intigration.</param>
 
-     /// <param name="applicationPaths">Application paths for library.db backup.</param>
 
-     /// <param name="backupService">The jellyfin backup service.</param>
 
-     /// <param name="jellyfinDatabaseProvider">The jellyfin database provider.</param>
 
-     public JellyfinMigrationService(
 
-         IDbContextFactory<JellyfinDbContext> dbContextFactory,
 
-         ILoggerFactory loggerFactory,
 
-         IStartupLogger<JellyfinMigrationService> startupLogger,
 
-         IApplicationPaths applicationPaths,
 
-         IBackupService? backupService = null,
 
-         IJellyfinDatabaseProvider? jellyfinDatabaseProvider = null)
 
-     {
 
-         _dbContextFactory = dbContextFactory;
 
-         _loggerFactory = loggerFactory;
 
-         _startupLogger = startupLogger;
 
-         _backupService = backupService;
 
-         _jellyfinDatabaseProvider = jellyfinDatabaseProvider;
 
-         _applicationPaths = applicationPaths;
 
- #pragma warning disable CS0618 // Type or member is obsolete
 
-         Migrations = [.. typeof(IMigrationRoutine).Assembly.GetTypes().Where(e => typeof(IMigrationRoutine).IsAssignableFrom(e) || typeof(IAsyncMigrationRoutine).IsAssignableFrom(e))
 
-             .Select(e => (Type: e, Metadata: e.GetCustomAttribute<JellyfinMigrationAttribute>(), Backup: e.GetCustomAttributes<JellyfinMigrationBackupAttribute>()))
 
-             .Where(e => e.Metadata is not null)
 
-             .GroupBy(e => e.Metadata!.Stage)
 
-             .Select(f =>
 
-             {
 
-                 var stage = new MigrationStage(f.Key);
 
-                 foreach (var item in f)
 
-                 {
 
-                     JellyfinMigrationBackupAttribute? backupMetadata = null;
 
-                     if (item.Backup?.Any() == true)
 
-                     {
 
-                         backupMetadata = item.Backup.Aggregate(MergeBackupAttributes);
 
-                     }
 
-                     stage.Add(new(item.Type, item.Metadata!, backupMetadata));
 
-                 }
 
-                 return stage;
 
-             })];
 
- #pragma warning restore CS0618 // Type or member is obsolete
 
-     }
 
-     private interface IInternalMigration
 
-     {
 
-         Task PerformAsync(IStartupLogger logger);
 
-     }
 
-     private HashSet<MigrationStage> Migrations { get; set; }
 
-     public async Task CheckFirstTimeRunOrMigration(IApplicationPaths appPaths)
 
-     {
 
-         var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migration Startup");
 
-         logger.LogInformation("Initialise Migration service.");
 
-         var xmlSerializer = new MyXmlSerializer();
 
-         var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath)
 
-             ? (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigurationFilePath)!
 
-             : new ServerConfiguration();
 
-         if (!serverConfig.IsStartupWizardCompleted)
 
-         {
 
-             logger.LogInformation("System initialisation detected. Seed data.");
 
-             var flatApplyMigrations = Migrations.SelectMany(e => e.Where(f => !f.Metadata.RunMigrationOnSetup)).ToArray();
 
-             var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 
-             await using (dbContext.ConfigureAwait(false))
 
-             {
 
-                 var databaseCreator = dbContext.Database.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator
 
-                     ?? throw new InvalidOperationException("Jellyfin does only support relational databases.");
 
-                 if (!await databaseCreator.ExistsAsync().ConfigureAwait(false))
 
-                 {
 
-                     await databaseCreator.CreateAsync().ConfigureAwait(false);
 
-                 }
 
-                 var historyRepository = dbContext.GetService<IHistoryRepository>();
 
-                 await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false);
 
-                 var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
 
-                 var startupScripts = flatApplyMigrations
 
-                     .Where(e => !appliedMigrations.Any(f => f != e.BuildCodeMigrationId()))
 
-                     .Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion()))))
 
-                     .ToArray();
 
-                 foreach (var item in startupScripts)
 
-                 {
 
-                     logger.LogInformation("Seed migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name);
 
-                     await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false);
 
-                 }
 
-             }
 
-             logger.LogInformation("Migration system initialisation completed.");
 
-         }
 
-         else
 
-         {
 
-             // migrate any existing migration.xml files
 
-             var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, "migrations.xml");
 
-             var migrationOptions = File.Exists(migrationConfigPath)
 
-                  ? (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!
 
-                  : null;
 
-             if (migrationOptions is not null && migrationOptions.Applied.Count > 0)
 
-             {
 
-                 logger.LogInformation("Old migration style migration.xml detected. Migrate now.");
 
-                 try
 
-                 {
 
-                     var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 
-                     await using (dbContext.ConfigureAwait(false))
 
-                     {
 
-                         var historyRepository = dbContext.GetService<IHistoryRepository>();
 
-                         var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
 
-                         var lastOldAppliedMigration = 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()))
 
-                             .OrderBy(e => e.BuildCodeMigrationId())
 
-                             .Last(); // this is the latest migration applied in the old migration.xml
 
-                         IReadOnlyList<CodeMigration> oldMigrations = [
 
-                             .. Migrations
 
-                             .SelectMany(e => e)
 
-                             .OrderBy(e => e.BuildCodeMigrationId())
 
-                             .TakeWhile(e => e.BuildCodeMigrationId() != lastOldAppliedMigration.BuildCodeMigrationId()),
 
-                             lastOldAppliedMigration
 
-                         ];
 
-                         // those are all migrations that had to run in the old migration system, even if not noted in the migration.xml file.
 
-                         var startupScripts = oldMigrations.Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.BuildCodeMigrationId(), GetJellyfinVersion()))));
 
-                         foreach (var item in startupScripts)
 
-                         {
 
-                             logger.LogInformation("Migrate migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name);
 
-                             await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false);
 
-                         }
 
-                         logger.LogInformation("Rename old migration.xml to migration.xml.backup");
 
-                         File.Move(migrationConfigPath, Path.ChangeExtension(migrationConfigPath, ".xml.backup"), true);
 
-                     }
 
-                 }
 
-                 catch (Exception ex)
 
-                 {
 
-                     logger.LogCritical(ex, "Failed to apply migrations");
 
-                     throw;
 
-                 }
 
-             }
 
-         }
 
-     }
 
-     public async Task MigrateStepAsync(JellyfinMigrationStageTypes stage, IServiceProvider? serviceProvider)
 
-     {
 
-         var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migrate stage {stage}.");
 
-         ICollection<CodeMigration> migrationStage = (Migrations.FirstOrDefault(e => e.Stage == stage) as ICollection<CodeMigration>) ?? [];
 
-         var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 
-         await using (dbContext.ConfigureAwait(false))
 
-         {
 
-             var historyRepository = dbContext.GetService<IHistoryRepository>();
 
-             var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>();
 
-             var appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
 
-             var pendingCodeMigrations = migrationStage
 
-                 .Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId()))
 
-                 .Select(e => (Key: e.BuildCodeMigrationId(), Migration: new InternalCodeMigration(e, serviceProvider, dbContext)))
 
-                 .ToArray();
 
-             (string Key, InternalDatabaseMigration Migration)[] pendingDatabaseMigrations = [];
 
-             if (stage is JellyfinMigrationStageTypes.CoreInitialisation)
 
-             {
 
-                 pendingDatabaseMigrations = migrationsAssembly.Migrations.Where(f => appliedMigrations.All(e => e.MigrationId != f.Key))
 
-                    .Select(e => (Key: e.Key, Migration: new InternalDatabaseMigration(e, dbContext)))
 
-                    .ToArray();
 
-             }
 
-             (string Key, IInternalMigration Migration)[] pendingMigrations = [.. pendingCodeMigrations, .. pendingDatabaseMigrations];
 
-             logger.LogInformation("There are {Pending} migrations for stage {Stage}.", pendingCodeMigrations.Length, stage);
 
-             var migrations = pendingMigrations.OrderBy(e => e.Key).ToArray();
 
-             foreach (var item in migrations)
 
-             {
 
-                 var migrationLogger = logger.With(_loggerFactory.CreateLogger(item.Migration.GetType().Name)).BeginGroup($"{item.Key}");
 
-                 try
 
-                 {
 
-                     migrationLogger.LogInformation("Perform migration {Name}", item.Key);
 
-                     await item.Migration.PerformAsync(migrationLogger).ConfigureAwait(false);
 
-                     migrationLogger.LogInformation("Migration {Name} was successfully applied", item.Key);
 
-                 }
 
-                 catch (Exception ex)
 
-                 {
 
-                     migrationLogger.LogCritical("Error: {Error}", ex.Message);
 
-                     migrationLogger.LogError(ex, "Migration {Name} failed", item.Key);
 
-                     if (_backupKey != default && _backupService is not null && _jellyfinDatabaseProvider is not null)
 
-                     {
 
-                         if (_backupKey.LibraryDb is not null)
 
-                         {
 
-                             migrationLogger.LogInformation("Attempt to rollback librarydb.");
 
-                             try
 
-                             {
 
-                                 var libraryDbPath = Path.Combine(_applicationPaths.DataPath, DbFilename);
 
-                                 File.Move(_backupKey.LibraryDb, libraryDbPath, true);
 
-                             }
 
-                             catch (Exception inner)
 
-                             {
 
-                                 migrationLogger.LogCritical(inner, "Could not rollback {LibraryPath}. Manual intervention might be required to restore a operational state.", _backupKey.LibraryDb);
 
-                             }
 
-                         }
 
-                         if (_backupKey.JellyfinDb is not null)
 
-                         {
 
-                             migrationLogger.LogInformation("Attempt to rollback JellyfinDb.");
 
-                             try
 
-                             {
 
-                                 await _jellyfinDatabaseProvider.RestoreBackupFast(_backupKey.JellyfinDb, CancellationToken.None).ConfigureAwait(false);
 
-                             }
 
-                             catch (Exception inner)
 
-                             {
 
-                                 migrationLogger.LogCritical(inner, "Could not rollback {LibraryPath}. Manual intervention might be required to restore a operational state.", _backupKey.JellyfinDb);
 
-                             }
 
-                         }
 
-                         if (_backupKey.FullBackup is not null)
 
-                         {
 
-                             migrationLogger.LogInformation("Attempt to rollback from backup.");
 
-                             try
 
-                             {
 
-                                 await _backupService.RestoreBackupAsync(_backupKey.FullBackup.Path).ConfigureAwait(false);
 
-                             }
 
-                             catch (Exception inner)
 
-                             {
 
-                                 migrationLogger.LogCritical(inner, "Could not rollback from backup {Backup}. Manual intervention might be required to restore a operational state.", _backupKey.FullBackup.Path);
 
-                             }
 
-                         }
 
-                     }
 
-                     throw;
 
-                 }
 
-             }
 
-         }
 
-     }
 
-     private static string GetJellyfinVersion()
 
-     {
 
-         return Assembly.GetEntryAssembly()!.GetName().Version!.ToString();
 
-     }
 
-     public async Task CleanupSystemAfterMigration(ILogger logger)
 
-     {
 
-         if (_backupKey != default)
 
-         {
 
-             if (_backupKey.LibraryDb is not null)
 
-             {
 
-                 logger.LogInformation("Attempt to cleanup librarydb backup.");
 
-                 try
 
-                 {
 
-                     File.Delete(_backupKey.LibraryDb);
 
-                 }
 
-                 catch (Exception inner)
 
-                 {
 
-                     logger.LogCritical(inner, "Could not cleanup {LibraryPath}.", _backupKey.LibraryDb);
 
-                 }
 
-             }
 
-             if (_backupKey.JellyfinDb is not null && _jellyfinDatabaseProvider is not null)
 
-             {
 
-                 logger.LogInformation("Attempt to cleanup JellyfinDb backup.");
 
-                 try
 
-                 {
 
-                     await _jellyfinDatabaseProvider.DeleteBackup(_backupKey.JellyfinDb).ConfigureAwait(false);
 
-                 }
 
-                 catch (Exception inner)
 
-                 {
 
-                     logger.LogCritical(inner, "Could not cleanup {LibraryPath}.", _backupKey.JellyfinDb);
 
-                 }
 
-             }
 
-             if (_backupKey.FullBackup is not null)
 
-             {
 
-                 logger.LogInformation("Attempt to cleanup from migration backup.");
 
-                 try
 
-                 {
 
-                     File.Delete(_backupKey.FullBackup.Path);
 
-                 }
 
-                 catch (Exception inner)
 
-                 {
 
-                     logger.LogCritical(inner, "Could not cleanup backup {Backup}.", _backupKey.FullBackup.Path);
 
-                 }
 
-             }
 
-         }
 
-     }
 
-     public async Task PrepareSystemForMigration(ILogger logger)
 
-     {
 
-         logger.LogInformation("Prepare system for possible migrations");
 
-         JellyfinMigrationBackupAttribute backupInstruction;
 
-         IReadOnlyList<HistoryRow> appliedMigrations;
 
-         var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 
-         await using (dbContext.ConfigureAwait(false))
 
-         {
 
-             var historyRepository = dbContext.GetService<IHistoryRepository>();
 
-             var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>();
 
-             appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
 
-             backupInstruction = new JellyfinMigrationBackupAttribute()
 
-             {
 
-                 JellyfinDb = migrationsAssembly.Migrations.Any(f => appliedMigrations.All(e => e.MigrationId != f.Key))
 
-             };
 
-         }
 
-         backupInstruction = Migrations.SelectMany(e => e)
 
-            .Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId()))
 
-            .Select(e => e.BackupRequirements)
 
-            .Where(e => e is not null)
 
-            .Aggregate(backupInstruction, MergeBackupAttributes!);
 
-         if (backupInstruction.LegacyLibraryDb)
 
-         {
 
-             logger.LogInformation("A migration will attempt to modify the library.db, will attempt to backup the file now.");
 
-             // for legacy migrations that still operates on the library.db
 
-             var libraryDbPath = Path.Combine(_applicationPaths.DataPath, DbFilename);
 
-             if (File.Exists(libraryDbPath))
 
-             {
 
-                 for (int i = 1; ; i++)
 
-                 {
 
-                     var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", libraryDbPath, i);
 
-                     if (!File.Exists(bakPath))
 
-                     {
 
-                         try
 
-                         {
 
-                             logger.LogInformation("Backing up {Library} to {BackupPath}", DbFilename, bakPath);
 
-                             File.Copy(libraryDbPath, bakPath);
 
-                             _backupKey = (bakPath, _backupKey.JellyfinDb, _backupKey.FullBackup);
 
-                             logger.LogInformation("{Library} backed up to {BackupPath}", DbFilename, bakPath);
 
-                             break;
 
-                         }
 
-                         catch (Exception ex)
 
-                         {
 
-                             logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
 
-                             throw;
 
-                         }
 
-                     }
 
-                 }
 
-                 logger.LogInformation("{Library} has been backed up as {BackupPath}", DbFilename, _backupKey.LibraryDb);
 
-             }
 
-             else
 
-             {
 
-                 logger.LogError("Cannot make a backup of {Library} at path {BackupPath} because file could not be found at {LibraryPath}", DbFilename, libraryDbPath, _applicationPaths.DataPath);
 
-             }
 
-         }
 
-         if (backupInstruction.JellyfinDb && _jellyfinDatabaseProvider is not null)
 
-         {
 
-             logger.LogInformation("A migration will attempt to modify the jellyfin.db, will attempt to backup the file now.");
 
-             _backupKey = (_backupKey.LibraryDb, await _jellyfinDatabaseProvider.MigrationBackupFast(CancellationToken.None).ConfigureAwait(false), _backupKey.FullBackup);
 
-             logger.LogInformation("Jellyfin database has been backed up as {BackupPath}", _backupKey.JellyfinDb);
 
-         }
 
-         if (_backupService is not null && (backupInstruction.Metadata || backupInstruction.Subtitles || backupInstruction.Trickplay))
 
-         {
 
-             logger.LogInformation("A migration will attempt to modify system resources. Will attempt to create backup now.");
 
-             _backupKey = (_backupKey.LibraryDb, _backupKey.JellyfinDb, await _backupService.CreateBackupAsync(new BackupOptionsDto()
 
-             {
 
-                 Metadata = backupInstruction.Metadata,
 
-                 Subtitles = backupInstruction.Subtitles,
 
-                 Trickplay = backupInstruction.Trickplay,
 
-                 Database = false // database backups are explicitly handled by the provider itself as the backup service requires parity with the current model
 
-             }).ConfigureAwait(false));
 
-             logger.LogInformation("Pre-Migration backup successfully created as {BackupKey}", _backupKey.FullBackup.Path);
 
-         }
 
-     }
 
-     private static JellyfinMigrationBackupAttribute MergeBackupAttributes(JellyfinMigrationBackupAttribute left, JellyfinMigrationBackupAttribute right)
 
-     {
 
-         return new JellyfinMigrationBackupAttribute()
 
-         {
 
-             JellyfinDb = left!.JellyfinDb || right!.JellyfinDb,
 
-             LegacyLibraryDb = left.LegacyLibraryDb || right!.LegacyLibraryDb,
 
-             Metadata = left.Metadata || right!.Metadata,
 
-             Subtitles = left.Subtitles || right!.Subtitles,
 
-             Trickplay = left.Trickplay || right!.Trickplay
 
-         };
 
-     }
 
-     private class InternalCodeMigration : IInternalMigration
 
-     {
 
-         private readonly CodeMigration _codeMigration;
 
-         private readonly IServiceProvider? _serviceProvider;
 
-         private JellyfinDbContext _dbContext;
 
-         public InternalCodeMigration(CodeMigration codeMigration, IServiceProvider? serviceProvider, JellyfinDbContext dbContext)
 
-         {
 
-             _codeMigration = codeMigration;
 
-             _serviceProvider = serviceProvider;
 
-             _dbContext = dbContext;
 
-         }
 
-         public async Task PerformAsync(IStartupLogger logger)
 
-         {
 
-             await _codeMigration.Perform(_serviceProvider, logger, CancellationToken.None).ConfigureAwait(false);
 
-             var historyRepository = _dbContext.GetService<IHistoryRepository>();
 
-             var createScript = historyRepository.GetInsertScript(new HistoryRow(_codeMigration.BuildCodeMigrationId(), GetJellyfinVersion()));
 
-             await _dbContext.Database.ExecuteSqlRawAsync(createScript).ConfigureAwait(false);
 
-         }
 
-     }
 
-     private class InternalDatabaseMigration : IInternalMigration
 
-     {
 
-         private readonly JellyfinDbContext _jellyfinDbContext;
 
-         private KeyValuePair<string, TypeInfo> _databaseMigrationInfo;
 
-         public InternalDatabaseMigration(KeyValuePair<string, TypeInfo> databaseMigrationInfo, JellyfinDbContext jellyfinDbContext)
 
-         {
 
-             _databaseMigrationInfo = databaseMigrationInfo;
 
-             _jellyfinDbContext = jellyfinDbContext;
 
-         }
 
-         public async Task PerformAsync(IStartupLogger logger)
 
-         {
 
-             var migrator = _jellyfinDbContext.GetService<IMigrator>();
 
-             await migrator.MigrateAsync(_databaseMigrationInfo.Key).ConfigureAwait(false);
 
-         }
 
-     }
 
- }
 
 
  |