< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.JellyfinMigrationService
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/JellyfinMigrationService.cs
Line coverage
43%
Covered lines: 108
Uncovered lines: 142
Coverable lines: 250
Total lines: 485
Line coverage: 43.2%
Branch coverage
26%
Covered branches: 26
Total branches: 100
Branch coverage: 26%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/6/2026 - 12:15:23 AM Line coverage: 41.4% (100/241) Branch coverage: 20.4% (18/88) Total lines: 4595/7/2026 - 12:15:44 AM Line coverage: 41.4% (100/241) Branch coverage: 20.6% (19/92) Total lines: 4595/22/2026 - 12:15:17 AM Line coverage: 41.4% (100/241) Branch coverage: 21.7% (20/92) Total lines: 4595/27/2026 - 12:15:38 AM Line coverage: 41.9% (102/243) Branch coverage: 25.5% (25/98) Total lines: 4646/28/2026 - 12:15:35 AM Line coverage: 42.4% (104/245) Branch coverage: 25.5% (25/98) Total lines: 4678/2/2026 - 12:17:28 AM Line coverage: 43.2% (108/250) Branch coverage: 26% (26/100) Total lines: 485 5/6/2026 - 12:15:23 AM Line coverage: 41.4% (100/241) Branch coverage: 20.4% (18/88) Total lines: 4595/7/2026 - 12:15:44 AM Line coverage: 41.4% (100/241) Branch coverage: 20.6% (19/92) Total lines: 4595/22/2026 - 12:15:17 AM Line coverage: 41.4% (100/241) Branch coverage: 21.7% (20/92) Total lines: 4595/27/2026 - 12:15:38 AM Line coverage: 41.9% (102/243) Branch coverage: 25.5% (25/98) Total lines: 4646/28/2026 - 12:15:35 AM Line coverage: 42.4% (104/245) Branch coverage: 25.5% (25/98) Total lines: 4678/2/2026 - 12:17:28 AM Line coverage: 43.2% (108/250) Branch coverage: 26% (26/100) Total lines: 485

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
CheckFirstTimeRunOrMigration()36.36%1012245.31%
MigrateStepAsync()47.05%1313456.25%
GetJellyfinVersion()100%11100%
CleanupSystemAfterMigration()0%210140%
PrepareSystemForMigration()0%342180%
MergeBackupAttributes(...)0%110100%
.ctor(...)100%11100%
PerformAsync()100%11100%
.ctor(...)100%11100%
PerformAsync()100%11100%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Migrations/JellyfinMigrationService.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.IO;
 5using System.Linq;
 6using System.Reflection;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Emby.Server.Implementations.Serialization;
 10using Jellyfin.Database.Implementations;
 11using Jellyfin.Server.Implementations.SystemBackupService;
 12using Jellyfin.Server.Migrations.Stages;
 13using Jellyfin.Server.ServerSetupApp;
 14using MediaBrowser.Common.Configuration;
 15using MediaBrowser.Controller.SystemBackupService;
 16using MediaBrowser.Model.Configuration;
 17using Microsoft.EntityFrameworkCore;
 18using Microsoft.EntityFrameworkCore.Infrastructure;
 19using Microsoft.EntityFrameworkCore.Migrations;
 20using Microsoft.EntityFrameworkCore.Storage;
 21using Microsoft.Extensions.Logging;
 22
 23namespace Jellyfin.Server.Migrations;
 24
 25/// <summary>
 26/// Handles Migration of the Jellyfin data structure.
 27/// </summary>
 28internal class JellyfinMigrationService
 29{
 30    private const string DbFilename = "library.db";
 31    private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory;
 32    private readonly ILoggerFactory _loggerFactory;
 33    private readonly IStartupLogger _startupLogger;
 34    private readonly IBackupService? _backupService;
 35    private readonly IJellyfinDatabaseProvider? _jellyfinDatabaseProvider;
 36    private readonly IApplicationPaths _applicationPaths;
 37    private (string? LibraryDb, string? JellyfinDb, BackupManifestDto? FullBackup) _backupKey;
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="JellyfinMigrationService"/> class.
 41    /// </summary>
 42    /// <param name="dbContextFactory">Provides access to the jellyfin database.</param>
 43    /// <param name="loggerFactory">The logger factory.</param>
 44    /// <param name="startupLogger">The startup logger for Startup UI intigration.</param>
 45    /// <param name="applicationPaths">Application paths for library.db backup.</param>
 46    /// <param name="backupService">The jellyfin backup service.</param>
 47    /// <param name="jellyfinDatabaseProvider">The jellyfin database provider.</param>
 48    public JellyfinMigrationService(
 49        IDbContextFactory<JellyfinDbContext> dbContextFactory,
 50        ILoggerFactory loggerFactory,
 51        IStartupLogger<JellyfinMigrationService> startupLogger,
 52        IApplicationPaths applicationPaths,
 53        IBackupService? backupService = null,
 54        IJellyfinDatabaseProvider? jellyfinDatabaseProvider = null)
 55    {
 6656        _dbContextFactory = dbContextFactory;
 6657        _loggerFactory = loggerFactory;
 6658        _startupLogger = startupLogger;
 6659        _backupService = backupService;
 6660        _jellyfinDatabaseProvider = jellyfinDatabaseProvider;
 6661        _applicationPaths = applicationPaths;
 62#pragma warning disable CS0618 // Type or member is obsolete
 6663        Migrations = [.. typeof(IMigrationRoutine).Assembly.GetTypes().Where(e => typeof(IMigrationRoutine).IsAssignable
 6664            .Select(e => (Type: e, Metadata: e.GetCustomAttribute<JellyfinMigrationAttribute>(), Backup: e.GetCustomAttr
 6665            .Where(e => e.Metadata is not null)
 6666            .GroupBy(e => e.Metadata!.Stage)
 6667            .Select(f =>
 6668            {
 6669                var stage = new MigrationStage(f.Key);
 6670                foreach (var item in f)
 6671                {
 6672                    JellyfinMigrationBackupAttribute? backupMetadata = null;
 6673                    if (item.Backup?.Any() == true)
 6674                    {
 6675                        backupMetadata = item.Backup.Aggregate(MergeBackupAttributes);
 6676                    }
 6677
 6678                    stage.Add(new(item.Type, item.Metadata!, backupMetadata));
 6679                }
 6680
 6681                return stage;
 6682            })];
 83#pragma warning restore CS0618 // Type or member is obsolete
 6684    }
 85
 86    private interface IInternalMigration
 87    {
 88        Task PerformAsync(IStartupLogger logger);
 89    }
 90
 91    private HashSet<MigrationStage> Migrations { get; set; }
 92
 93    public async Task CheckFirstTimeRunOrMigration(IApplicationPaths appPaths, StartupOptions startupOptions)
 94    {
 2295        var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migration
 2296        logger.LogInformation("Initialise Migration service.");
 2297        var xmlSerializer = new MyXmlSerializer();
 2298        var serverConfig = File.Exists(appPaths.SystemConfigurationFilePath)
 2299            ? (ServerConfiguration)xmlSerializer.DeserializeFromFile(typeof(ServerConfiguration), appPaths.SystemConfigu
 22100            : new ServerConfiguration();
 22101        if (!serverConfig.IsStartupWizardCompleted || startupOptions.StartupMode is Configuration.StartupMode.SeedSystem
 102        {
 22103            logger.LogInformation("System initialization detected. Seed data. Startup mode is: {StartupMode}", startupOp
 22104            var flatApplyMigrations = Migrations.SelectMany(e => e.Where(f => !f.Metadata.RunMigrationOnSetup)).ToArray(
 105
 22106            var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 22107            await using (dbContext.ConfigureAwait(false))
 108            {
 22109                var databaseCreator = dbContext.Database.GetService<IDatabaseCreator>() as IRelationalDatabaseCreator
 22110                    ?? throw new InvalidOperationException("Jellyfin does only support relational databases.");
 22111                if (!await databaseCreator.ExistsAsync().ConfigureAwait(false))
 112                {
 22113                    await databaseCreator.CreateAsync().ConfigureAwait(false);
 114                }
 115
 22116                var historyRepository = dbContext.GetService<IHistoryRepository>();
 117
 22118                await historyRepository.CreateIfNotExistsAsync().ConfigureAwait(false);
 22119                var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
 22120                var startupScripts = flatApplyMigrations
 22121                    .Where(e => !appliedMigrations.Any(f => f != e.BuildCodeMigrationId()))
 22122                    .Select(e => (Migration: e.Metadata, Script: historyRepository.GetInsertScript(new HistoryRow(e.Buil
 22123                    .ToArray();
 1716124                foreach (var item in startupScripts)
 125                {
 836126                    logger.LogInformation("Seed migration {Key}-{Name}.", item.Migration.Key, item.Migration.Name);
 836127                    await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false);
 128                }
 22129            }
 130
 22131            logger.LogInformation("Migration system initialisation completed.");
 22132        }
 133        else
 134        {
 135            // migrate any existing migration.xml files
 0136            var migrationConfigPath = Path.Join(appPaths.ConfigurationDirectoryPath, "migrations.xml");
 0137            var migrationOptions = File.Exists(migrationConfigPath)
 0138                 ? (MigrationOptions)xmlSerializer.DeserializeFromFile(typeof(MigrationOptions), migrationConfigPath)!
 0139                 : null;
 0140            if (migrationOptions is not null && migrationOptions.Applied.Count > 0)
 141            {
 0142                logger.LogInformation("Old migration style migration.xml detected. Migrate now.");
 143                try
 144                {
 0145                    var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 0146                    await using (dbContext.ConfigureAwait(false))
 147                    {
 0148                        var historyRepository = dbContext.GetService<IHistoryRepository>();
 0149                        var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync().ConfigureAwait(fals
 0150                        var lastOldAppliedMigration = Migrations
 0151                            .SelectMany(e => e.Where(e => e.Metadata.Key is not null)) // only consider migrations that 
 0152                            .Where(e => migrationOptions.Applied.Any(f => f.Id.Equals(e.Metadata.Key!.Value)))
 0153                            .Where(e => !appliedMigrations.Contains(e.BuildCodeMigrationId()))
 0154                            .OrderBy(e => e.BuildCodeMigrationId())
 0155                            .Last(); // this is the latest migration applied in the old migration.xml
 156
 0157                        IReadOnlyList<CodeMigration> oldMigrations = [
 0158                            .. Migrations
 0159                            .SelectMany(e => e)
 0160                            .OrderBy(e => e.BuildCodeMigrationId())
 0161                            .TakeWhile(e => e.BuildCodeMigrationId() != lastOldAppliedMigration.BuildCodeMigrationId()),
 0162                            lastOldAppliedMigration
 0163                        ];
 164                        // those are all migrations that had to run in the old migration system, even if not noted in th
 165
 0166                        var startupScripts = oldMigrations.Select(e => (Migration: e.Metadata, Script: historyRepository
 0167                        foreach (var item in startupScripts)
 168                        {
 0169                            logger.LogInformation("Migrate migration {Key}-{Name}.", item.Migration.Key, item.Migration.
 0170                            await dbContext.Database.ExecuteSqlRawAsync(item.Script).ConfigureAwait(false);
 171                        }
 172
 0173                        logger.LogInformation("Rename old migration.xml to migration.xml.backup");
 0174                        File.Move(migrationConfigPath, Path.ChangeExtension(migrationConfigPath, ".xml.backup"), true);
 0175                    }
 0176                }
 0177                catch (Exception ex)
 178                {
 0179                    logger.LogCritical(ex, "Failed to apply migrations");
 0180                    throw;
 181                }
 182            }
 0183        }
 22184    }
 185
 186    public async Task MigrateStepAsync(JellyfinMigrationStageTypes stage, IServiceProvider? serviceProvider)
 187    {
 66188        var logger = _startupLogger.With(_loggerFactory.CreateLogger<JellyfinMigrationService>()).BeginGroup($"Migrate s
 66189        ICollection<CodeMigration> migrationStage = (Migrations.FirstOrDefault(e => e.Stage == stage) as ICollection<Cod
 190
 66191        var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 66192        await using (dbContext.ConfigureAwait(false))
 193        {
 66194            var historyRepository = dbContext.GetService<IHistoryRepository>();
 66195            var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>();
 66196            var completedMigrations = 0;
 66197            string? lastMigrationKey = null;
 198
 1276199            while (true)
 200            {
 201                // A single migration can change which migrations still apply: IMigrator.MigrateAsync treats its argumen
 202                // state to end up in, so it reverts everything applied after it, and a reverted migration can take code
 203                // with it (AddNormalizedUsername.Down drops the UpdateNormalizedUsername history row). Anything compute
 204                // that point is stale, so only ever run the next migration and then work out the pending set again.
 1342205                var appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
 1342206                var pendingCodeMigrations = migrationStage
 1342207                    .Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId()))
 1342208                    .Select(e => (Key: e.BuildCodeMigrationId(), Migration: new InternalCodeMigration(e, serviceProvider
 1342209                    .ToArray();
 210
 1342211                (string Key, InternalDatabaseMigration Migration)[] pendingDatabaseMigrations = [];
 1342212                if (stage is JellyfinMigrationStageTypes.CoreInitialisation)
 213                {
 1188214                    pendingDatabaseMigrations = migrationsAssembly.Migrations.Where(f => appliedMigrations.All(e => e.Mi
 1188215                       .Select(e => (Key: e.Key, Migration: new InternalDatabaseMigration(e, dbContext)))
 1188216                       .ToArray();
 217                }
 218
 1342219                (string Key, IInternalMigration Migration)[] pendingMigrations = [.. pendingCodeMigrations, .. pendingDa
 1342220                if (pendingMigrations.Length == 0)
 221                {
 222                    break;
 223                }
 224
 1276225                if (completedMigrations == 0)
 226                {
 44227                    logger.LogInformation("There are {Pending} migrations for stage {Stage}.", pendingMigrations.Length,
 228                }
 229
 1276230                var item = pendingMigrations.OrderBy(e => e.Key, StringComparer.Ordinal).First();
 1276231                if (string.Equals(item.Key, lastMigrationKey, StringComparison.Ordinal))
 232                {
 0233                    throw new InvalidOperationException($"Migration {item.Key} ran but did not record itself as applied 
 234                }
 235
 1276236                lastMigrationKey = item.Key;
 237
 238                // Surface generic "Running migration X of Y" progress in the always-visible startup UI header.
 1276239                SetupServer.ReportActivity(StartupActivity.Migration(completedMigrations + 1, completedMigrations + pend
 1276240                var migrationLogger = logger.With(_loggerFactory.CreateLogger(item.Migration.GetType().Name)).BeginGroup
 241                try
 242                {
 1276243                    migrationLogger.LogInformation("Perform migration {Name}", item.Key);
 1276244                    await item.Migration.PerformAsync(migrationLogger).ConfigureAwait(false);
 1276245                    migrationLogger.LogInformation("Migration {Name} was successfully applied", item.Key);
 1276246                }
 0247                catch (Exception ex)
 248                {
 0249                    migrationLogger.LogCritical("Error: {Error}", ex.Message);
 0250                    migrationLogger.LogError(ex, "Migration {Name} failed", item.Key);
 251
 0252                    if (_backupKey != default && _backupService is not null && _jellyfinDatabaseProvider is not null)
 253                    {
 0254                        if (_backupKey.LibraryDb is not null)
 255                        {
 0256                            migrationLogger.LogInformation("Attempt to rollback librarydb.");
 257                            try
 258                            {
 0259                                var libraryDbPath = Path.Combine(_applicationPaths.DataPath, DbFilename);
 0260                                File.Move(_backupKey.LibraryDb, libraryDbPath, true);
 0261                            }
 0262                            catch (Exception inner)
 263                            {
 0264                                migrationLogger.LogCritical(inner, "Could not rollback {LibraryPath}. Manual interventio
 0265                            }
 266                        }
 267
 0268                        if (_backupKey.JellyfinDb is not null)
 269                        {
 0270                            migrationLogger.LogInformation("Attempt to rollback JellyfinDb.");
 271                            try
 272                            {
 0273                                await _jellyfinDatabaseProvider.RestoreBackupFast(_backupKey.JellyfinDb, CancellationTok
 0274                            }
 0275                            catch (Exception inner)
 276                            {
 0277                                migrationLogger.LogCritical(inner, "Could not rollback {LibraryPath}. Manual interventio
 0278                            }
 279                        }
 280
 0281                        if (_backupKey.FullBackup is not null)
 282                        {
 0283                            migrationLogger.LogInformation("Attempt to rollback from backup.");
 284                            try
 285                            {
 0286                                await _backupService.RestoreBackupAsync(_backupKey.FullBackup.Path).ConfigureAwait(false
 0287                            }
 0288                            catch (Exception inner)
 289                            {
 0290                                migrationLogger.LogCritical(inner, "Could not rollback from backup {Backup}. Manual inte
 0291                            }
 292                        }
 293                    }
 294
 0295                    throw;
 296                }
 297
 1276298                completedMigrations++;
 1276299            }
 66300        }
 66301    }
 302
 303    private static string GetJellyfinVersion()
 304    {
 946305        return Assembly.GetEntryAssembly()!.GetName().Version!.ToString();
 306    }
 307
 308    public async Task CleanupSystemAfterMigration(ILogger logger)
 309    {
 0310        if (_backupKey != default)
 311        {
 0312            if (_backupKey.LibraryDb is not null)
 313            {
 0314                logger.LogInformation("Attempt to cleanup librarydb backup.");
 315                try
 316                {
 0317                    File.Delete(_backupKey.LibraryDb);
 0318                }
 0319                catch (Exception inner)
 320                {
 0321                    logger.LogCritical(inner, "Could not cleanup {LibraryPath}.", _backupKey.LibraryDb);
 0322                }
 323            }
 324
 0325            if (_backupKey.JellyfinDb is not null && _jellyfinDatabaseProvider is not null)
 326            {
 0327                logger.LogInformation("Attempt to cleanup JellyfinDb backup.");
 328                try
 329                {
 0330                    await _jellyfinDatabaseProvider.DeleteBackup(_backupKey.JellyfinDb).ConfigureAwait(false);
 0331                }
 0332                catch (Exception inner)
 333                {
 0334                    logger.LogCritical(inner, "Could not cleanup {LibraryPath}.", _backupKey.JellyfinDb);
 0335                }
 336            }
 337
 0338            if (_backupKey.FullBackup is not null)
 339            {
 0340                logger.LogInformation("Attempt to cleanup from migration backup.");
 341                try
 342                {
 0343                    File.Delete(_backupKey.FullBackup.Path);
 0344                }
 0345                catch (Exception inner)
 346                {
 0347                    logger.LogCritical(inner, "Could not cleanup backup {Backup}.", _backupKey.FullBackup.Path);
 0348                }
 349            }
 350        }
 0351    }
 352
 353    public async Task PrepareSystemForMigration(ILogger logger)
 354    {
 0355        logger.LogInformation("Prepare system for possible migrations");
 356        JellyfinMigrationBackupAttribute backupInstruction;
 357        IReadOnlyList<HistoryRow> appliedMigrations;
 0358        var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
 0359        await using (dbContext.ConfigureAwait(false))
 360        {
 0361            var historyRepository = dbContext.GetService<IHistoryRepository>();
 0362            var migrationsAssembly = dbContext.GetService<IMigrationsAssembly>();
 0363            appliedMigrations = await historyRepository.GetAppliedMigrationsAsync().ConfigureAwait(false);
 0364            backupInstruction = new JellyfinMigrationBackupAttribute()
 0365            {
 0366                JellyfinDb = migrationsAssembly.Migrations.Any(f => appliedMigrations.All(e => e.MigrationId != f.Key))
 0367            };
 0368        }
 369
 0370        backupInstruction = Migrations.SelectMany(e => e)
 0371           .Where(e => appliedMigrations.All(f => f.MigrationId != e.BuildCodeMigrationId()))
 0372           .Select(e => e.BackupRequirements)
 0373           .Where(e => e is not null)
 0374           .Aggregate(backupInstruction, MergeBackupAttributes!);
 375
 0376        if (backupInstruction.LegacyLibraryDb)
 377        {
 0378            logger.LogInformation("A migration will attempt to modify the library.db, will attempt to backup the file no
 379            // for legacy migrations that still operates on the library.db
 0380            var libraryDbPath = Path.Combine(_applicationPaths.DataPath, DbFilename);
 0381            if (File.Exists(libraryDbPath))
 382            {
 0383                for (int i = 1; ; i++)
 384                {
 0385                    var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", libraryDbPath, i);
 0386                    if (!File.Exists(bakPath))
 387                    {
 388                        try
 389                        {
 0390                            logger.LogInformation("Backing up {Library} to {BackupPath}", DbFilename, bakPath);
 0391                            File.Copy(libraryDbPath, bakPath);
 0392                            _backupKey = (bakPath, _backupKey.JellyfinDb, _backupKey.FullBackup);
 0393                            logger.LogInformation("{Library} backed up to {BackupPath}", DbFilename, bakPath);
 0394                            break;
 395                        }
 0396                        catch (Exception ex)
 397                        {
 0398                            logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, ba
 0399                            throw;
 400                        }
 401                    }
 402                }
 403
 0404                logger.LogInformation("{Library} has been backed up as {BackupPath}", DbFilename, _backupKey.LibraryDb);
 405            }
 406            else
 407            {
 0408                logger.LogError("Cannot make a backup of {Library} at path {BackupPath} because file could not be found 
 409            }
 410        }
 411
 0412        if (backupInstruction.JellyfinDb && _jellyfinDatabaseProvider is not null)
 413        {
 0414            logger.LogInformation("A migration will attempt to modify the jellyfin.db, will attempt to backup the file n
 0415            _backupKey = (_backupKey.LibraryDb, await _jellyfinDatabaseProvider.MigrationBackupFast(CancellationToken.No
 0416            logger.LogInformation("Jellyfin database has been backed up as {BackupPath}", _backupKey.JellyfinDb);
 417        }
 418
 0419        if (_backupService is not null && (backupInstruction.Metadata || backupInstruction.Subtitles || backupInstructio
 420        {
 0421            logger.LogInformation("A migration will attempt to modify system resources. Will attempt to create backup no
 0422            _backupKey = (_backupKey.LibraryDb, _backupKey.JellyfinDb, await _backupService.CreateBackupAsync(new Backup
 0423            {
 0424                Metadata = backupInstruction.Metadata,
 0425                Subtitles = backupInstruction.Subtitles,
 0426                Trickplay = backupInstruction.Trickplay,
 0427                Database = false // database backups are explicitly handled by the provider itself as the backup service
 0428            }).ConfigureAwait(false));
 0429            logger.LogInformation("Pre-Migration backup successfully created as {BackupKey}", _backupKey.FullBackup.Path
 430        }
 0431    }
 432
 433    private static JellyfinMigrationBackupAttribute MergeBackupAttributes(JellyfinMigrationBackupAttribute left, Jellyfi
 434    {
 0435        return new JellyfinMigrationBackupAttribute()
 0436        {
 0437            JellyfinDb = left!.JellyfinDb || right!.JellyfinDb,
 0438            LegacyLibraryDb = left.LegacyLibraryDb || right!.LegacyLibraryDb,
 0439            Metadata = left.Metadata || right!.Metadata,
 0440            Subtitles = left.Subtitles || right!.Subtitles,
 0441            Trickplay = left.Trickplay || right!.Trickplay
 0442        };
 443    }
 444
 445    private class InternalCodeMigration : IInternalMigration
 446    {
 447        private readonly CodeMigration _codeMigration;
 448        private readonly IServiceProvider? _serviceProvider;
 449        private JellyfinDbContext _dbContext;
 450
 451        public InternalCodeMigration(CodeMigration codeMigration, IServiceProvider? serviceProvider, JellyfinDbContext d
 452        {
 330453            _codeMigration = codeMigration;
 330454            _serviceProvider = serviceProvider;
 330455            _dbContext = dbContext;
 330456        }
 457
 458        public async Task PerformAsync(IStartupLogger logger)
 459        {
 110460            await _codeMigration.Perform(_serviceProvider, logger, CancellationToken.None).ConfigureAwait(false);
 461
 110462            var historyRepository = _dbContext.GetService<IHistoryRepository>();
 110463            var createScript = historyRepository.GetInsertScript(new HistoryRow(_codeMigration.BuildCodeMigrationId(), G
 110464            await _dbContext.Database.ExecuteSqlRawAsync(createScript).ConfigureAwait(false);
 110465        }
 466    }
 467
 468    private class InternalDatabaseMigration : IInternalMigration
 469    {
 470        private readonly JellyfinDbContext _jellyfinDbContext;
 471        private KeyValuePair<string, TypeInfo> _databaseMigrationInfo;
 472
 473        public InternalDatabaseMigration(KeyValuePair<string, TypeInfo> databaseMigrationInfo, JellyfinDbContext jellyfi
 474        {
 31482475            _databaseMigrationInfo = databaseMigrationInfo;
 31482476            _jellyfinDbContext = jellyfinDbContext;
 31482477        }
 478
 479        public async Task PerformAsync(IStartupLogger logger)
 480        {
 1166481            var migrator = _jellyfinDbContext.GetService<IMigrator>();
 1166482            await migrator.MigrateAsync(_databaseMigrationInfo.Key).ConfigureAwait(false);
 1166483        }
 484    }
 485}