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