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