2020-03-05 18:09:33 +01:00
using System ;
2021-11-24 13:00:12 +01:00
using System.Collections.Generic ;
using System.IO ;
2020-03-05 18:09:33 +01:00
using System.Linq ;
2021-11-24 13:00:12 +01:00
using Emby.Server.Implementations ;
using Emby.Server.Implementations.Serialization ;
2020-03-05 18:09:33 +01:00
using MediaBrowser.Common.Configuration ;
2021-11-24 13:00:12 +01:00
using MediaBrowser.Model.Configuration ;
2020-04-17 05:40:32 +02:00
using Microsoft.Extensions.DependencyInjection ;
2020-03-05 16:21:27 +01:00
using Microsoft.Extensions.Logging ;
namespace Jellyfin.Server.Migrations
{
/// <summary>
2020-03-07 20:18:45 +01:00
/// The class that knows which migrations to apply and how to apply them.
2020-03-05 16:21:27 +01:00
/// </summary>
2020-03-05 18:09:33 +01:00
public sealed class MigrationRunner
2020-03-05 16:21:27 +01:00
{
2021-11-24 13:00:12 +01:00
/// <summary>
/// The list of known pre-startup migrations, in order of applicability.
/// </summary>
private static readonly Type [ ] _preStartupMigrationTypes =
{
2023-02-19 09:30:27 +01:00
typeof ( PreStartupRoutines . CreateNetworkConfiguration ) ,
typeof ( PreStartupRoutines . MigrateMusicBrainzTimeout )
2021-11-24 13:00:12 +01:00
} ;
2020-03-05 18:09:33 +01:00
/// <summary>
/// The list of known migrations, in order of applicability.
/// </summary>
2020-04-17 05:40:32 +02:00
private static readonly Type [ ] _migrationTypes =
2020-03-05 16:21:27 +01:00
{
2020-04-17 05:40:32 +02:00
typeof ( Routines . DisableTranscodingThrottling ) ,
2020-05-15 01:30:28 +02:00
typeof ( Routines . CreateUserLoggingConfigFile ) ,
2020-05-15 21:23:44 +02:00
typeof ( Routines . MigrateActivityLogDb ) ,
2020-06-05 21:23:38 +02:00
typeof ( Routines . RemoveDuplicateExtras ) ,
2020-06-16 19:16:17 +02:00
typeof ( Routines . AddDefaultPluginRepository ) ,
2020-07-01 03:44:41 +02:00
typeof ( Routines . MigrateUserDb ) ,
2020-07-24 01:50:12 +02:00
typeof ( Routines . ReaddDefaultPluginRepository ) ,
2020-11-10 00:20:12 +01:00
typeof ( Routines . MigrateDisplayPreferencesDb ) ,
2020-12-04 16:00:55 +01:00
typeof ( Routines . RemoveDownloadImagesInAdvance ) ,
2022-11-28 13:06:55 +01:00
typeof ( Routines . MigrateAuthenticationDb ) ,
typeof ( Routines . MigrateRatingLevels )
2020-03-05 16:21:27 +01:00
} ;
/// <summary>
/// Run all needed migrations.
/// </summary>
/// <param name="host">CoreAppHost that hosts current version.</param>
2020-03-05 18:09:33 +01:00
/// <param name="loggerFactory">Factory for making the logger.</param>
public static void Run ( CoreAppHost host , ILoggerFactory loggerFactory )
2020-03-05 16:21:27 +01:00
{
2020-03-05 18:09:33 +01:00
var logger = loggerFactory . CreateLogger < MigrationRunner > ( ) ;
2020-04-17 05:40:32 +02:00
var migrations = _migrationTypes
. Select ( m = > ActivatorUtilities . CreateInstance ( host . ServiceProvider , m ) )
. OfType < IMigrationRoutine > ( )
. ToArray ( ) ;
2021-11-24 13:00:12 +01:00
2021-08-04 14:40:09 +02:00
var migrationOptions = host . ConfigurationManager . GetConfiguration < MigrationOptions > ( MigrationsListStore . StoreKey ) ;
2021-11-24 13:00:12 +01:00
HandleStartupWizardCondition ( migrations , migrationOptions , host . ConfigurationManager . Configuration . IsStartupWizardCompleted , logger ) ;
PerformMigrations ( migrations , migrationOptions , options = > host . ConfigurationManager . SaveConfiguration ( MigrationsListStore . StoreKey , options ) , logger ) ;
}
2020-03-06 11:22:44 +01:00
2021-11-24 13:00:12 +01:00
/// <summary>
/// Run all needed pre-startup migrations.
/// </summary>
/// <param name="appPaths">Application paths.</param>
/// <param name="loggerFactory">Factory for making the logger.</param>
public static void RunPreStartup ( ServerApplicationPaths appPaths , ILoggerFactory loggerFactory )
{
var logger = loggerFactory . CreateLogger < MigrationRunner > ( ) ;
var migrations = _preStartupMigrationTypes
. Select ( m = > Activator . CreateInstance ( m , appPaths , loggerFactory ) )
. OfType < IMigrationRoutine > ( )
. ToArray ( ) ;
var xmlSerializer = new MyXmlSerializer ( ) ;
var migrationConfigPath = Path . Join ( appPaths . ConfigurationDirectoryPath , MigrationsListStore . StoreKey . ToLowerInvariant ( ) + ".xml" ) ;
2021-12-14 08:57:20 +01:00
var migrationOptions = File . Exists ( migrationConfigPath )
? ( MigrationOptions ) xmlSerializer . DeserializeFromFile ( typeof ( MigrationOptions ) , migrationConfigPath ) !
: new MigrationOptions ( ) ;
2021-11-24 13:00:12 +01:00
// We have to deserialize it manually since the configuration manager may overwrite it
2021-12-14 19:27:23 +01:00
var serverConfig = File . Exists ( appPaths . SystemConfigurationFilePath )
? ( ServerConfiguration ) xmlSerializer . DeserializeFromFile ( typeof ( ServerConfiguration ) , appPaths . SystemConfigurationFilePath ) !
: new ServerConfiguration ( ) ;
2021-11-24 13:00:12 +01:00
HandleStartupWizardCondition ( migrations , migrationOptions , serverConfig . IsStartupWizardCompleted , logger ) ;
PerformMigrations ( migrations , migrationOptions , options = > xmlSerializer . SerializeToFile ( options , migrationConfigPath ) , logger ) ;
}
private static void HandleStartupWizardCondition ( IEnumerable < IMigrationRoutine > migrations , MigrationOptions migrationOptions , bool isStartWizardCompleted , ILogger logger )
{
if ( isStartWizardCompleted | | migrationOptions . Applied . Count ! = 0 )
2020-03-06 11:22:44 +01:00
{
2021-11-24 13:00:12 +01:00
return ;
2020-03-06 11:22:44 +01:00
}
2021-11-24 13:00:12 +01:00
// If startup wizard is not finished, this is a fresh install.
var onlyOldInstalls = migrations . Where ( m = > ! m . PerformOnNewInstall ) . ToArray ( ) ;
logger . LogInformation ( "Marking following migrations as applied because this is a fresh install: {@OnlyOldInstalls}" , onlyOldInstalls . Select ( m = > m . Name ) ) ;
migrationOptions . Applied . AddRange ( onlyOldInstalls . Select ( m = > ( m . Id , m . Name ) ) ) ;
}
private static void PerformMigrations ( IMigrationRoutine [ ] migrations , MigrationOptions migrationOptions , Action < MigrationOptions > saveConfiguration , ILogger logger )
{
2020-03-08 17:40:30 +01:00
var appliedMigrationIds = migrationOptions . Applied . Select ( m = > m . Id ) . ToHashSet ( ) ;
2020-04-17 05:40:32 +02:00
for ( var i = 0 ; i < migrations . Length ; i + + )
2020-03-05 16:21:27 +01:00
{
2020-04-17 05:40:32 +02:00
var migrationRoutine = migrations [ i ] ;
2020-03-08 17:40:30 +01:00
if ( appliedMigrationIds . Contains ( migrationRoutine . Id ) )
2020-03-05 16:21:27 +01:00
{
2020-03-08 15:02:59 +01:00
logger . LogDebug ( "Skipping migration '{Name}' since it is already applied" , migrationRoutine . Name ) ;
2020-03-05 16:21:27 +01:00
continue ;
}
2020-03-08 15:02:59 +01:00
logger . LogInformation ( "Applying migration '{Name}'" , migrationRoutine . Name ) ;
2020-03-08 03:19:24 +01:00
2020-03-05 18:09:33 +01:00
try
2020-03-05 16:21:27 +01:00
{
2020-04-17 05:40:32 +02:00
migrationRoutine . Perform ( ) ;
2020-03-05 18:09:33 +01:00
}
catch ( Exception ex )
{
2020-03-08 15:02:59 +01:00
logger . LogError ( ex , "Could not apply migration '{Name}'" , migrationRoutine . Name ) ;
2020-03-08 15:02:42 +01:00
throw ;
2020-03-05 16:21:27 +01:00
}
2020-03-08 15:02:42 +01:00
// Mark the migration as completed
2020-03-08 15:02:59 +01:00
logger . LogInformation ( "Migration '{Name}' applied successfully" , migrationRoutine . Name ) ;
2020-03-08 17:40:30 +01:00
migrationOptions . Applied . Add ( ( migrationRoutine . Id , migrationRoutine . Name ) ) ;
2021-11-24 13:00:12 +01:00
saveConfiguration ( migrationOptions ) ;
2020-03-08 15:02:59 +01:00
logger . LogDebug ( "Migration '{Name}' marked as applied in configuration." , migrationRoutine . Name ) ;
2020-03-05 18:09:33 +01:00
}
2020-03-05 16:21:27 +01:00
}
}
}