diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d734342..ea51fbd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - New `--app-data` option for overriding the location of the application data directory. +- New `migrate` subcommand which may be used to perform migration steps manually. ### Changed - The default location for the default YAML file (`recyclarr.yml`) has been changed to the [application data directory](appdata). This is the same location of the `settings.yml` file. +- Automatic migration has been removed. Instead, the `migrate` subcommand should be used. ### Deprecated diff --git a/src/Recyclarr/Command/Initialization/Init/CheckMigrationNeeded.cs b/src/Recyclarr/Command/Initialization/Init/CheckMigrationNeeded.cs new file mode 100644 index 00000000..b47d3e18 --- /dev/null +++ b/src/Recyclarr/Command/Initialization/Init/CheckMigrationNeeded.cs @@ -0,0 +1,18 @@ +using Recyclarr.Migration; + +namespace Recyclarr.Command.Initialization.Init; + +internal class CheckMigrationNeeded : IServiceInitializer +{ + private readonly IMigrationExecutor _migration; + + public CheckMigrationNeeded(IMigrationExecutor migration) + { + _migration = migration; + } + + public void Initialize(ServiceCommand cmd) + { + _migration.CheckNeededMigrations(); + } +} diff --git a/src/Recyclarr/Command/Initialization/InitializationAutofacModule.cs b/src/Recyclarr/Command/Initialization/InitializationAutofacModule.cs index 28a499e4..f72bd274 100644 --- a/src/Recyclarr/Command/Initialization/InitializationAutofacModule.cs +++ b/src/Recyclarr/Command/Initialization/InitializationAutofacModule.cs @@ -16,7 +16,7 @@ public class InitializationAutofacModule : Module builder.RegisterTypes( typeof(InitializeAppDataPath), typeof(ServiceInitializer), - typeof(ServicePreInitializer)) + typeof(CheckMigrationNeeded)) .As() .OrderByRegistration(); diff --git a/src/Recyclarr/Command/Initialization/Init/ServicePreInitialization.cs b/src/Recyclarr/Command/MigrateCommand.cs similarity index 65% rename from src/Recyclarr/Command/Initialization/Init/ServicePreInitialization.cs rename to src/Recyclarr/Command/MigrateCommand.cs index 4448a5d7..653def72 100644 --- a/src/Recyclarr/Command/Initialization/Init/ServicePreInitialization.cs +++ b/src/Recyclarr/Command/MigrateCommand.cs @@ -1,24 +1,28 @@ using System.Text; +using CliFx; +using CliFx.Attributes; using CliFx.Exceptions; +using CliFx.Infrastructure; +using JetBrains.Annotations; using Recyclarr.Migration; -namespace Recyclarr.Command.Initialization.Init; +namespace Recyclarr.Command; -internal class ServicePreInitializer : IServiceInitializer +[Command("migrate", Description = "Perform any migration steps that may be needed between versions")] +[UsedImplicitly] +public class MigrateCommand : ICommand { private readonly IMigrationExecutor _migration; - public ServicePreInitializer(IMigrationExecutor migration) + public MigrateCommand(IMigrationExecutor migration) { _migration = migration; } - public void Initialize(ServiceCommand cmd) + public ValueTask ExecuteAsync(IConsole console) { - // Migrations are performed before we process command line arguments because we cannot instantiate any service - // objects via the DI container before migration logic is performed. This is due to the fact that migration - // steps may alter important files and directories which those services may depend on. PerformMigrations(); + return ValueTask.CompletedTask; } private void PerformMigrations() diff --git a/src/Recyclarr/Migration/IMigrationExecutor.cs b/src/Recyclarr/Migration/IMigrationExecutor.cs index 4487b2e4..2ee1a5b2 100644 --- a/src/Recyclarr/Migration/IMigrationExecutor.cs +++ b/src/Recyclarr/Migration/IMigrationExecutor.cs @@ -3,4 +3,5 @@ namespace Recyclarr.Migration; public interface IMigrationExecutor { void PerformAllMigrationSteps(); + void CheckNeededMigrations(); } diff --git a/src/Recyclarr/Migration/IMigrationStep.cs b/src/Recyclarr/Migration/IMigrationStep.cs index 927bf376..397e826c 100644 --- a/src/Recyclarr/Migration/IMigrationStep.cs +++ b/src/Recyclarr/Migration/IMigrationStep.cs @@ -5,6 +5,7 @@ public interface IMigrationStep int Order { get; } string Description { get; } IReadOnlyCollection Remediation { get; } + bool Required { get; } bool CheckIfNeeded(); void Execute(); } diff --git a/src/Recyclarr/Migration/MigrationExecutor.cs b/src/Recyclarr/Migration/MigrationExecutor.cs index f5b7a6c0..6bcaca40 100644 --- a/src/Recyclarr/Migration/MigrationExecutor.cs +++ b/src/Recyclarr/Migration/MigrationExecutor.cs @@ -1,3 +1,4 @@ +using CliFx.Exceptions; using CliFx.Infrastructure; namespace Recyclarr.Migration; @@ -40,4 +41,30 @@ public class MigrationExecutor : IMigrationExecutor _console.Output.WriteLine($"Migrate: {step.Description}"); } } + + public void CheckNeededMigrations() + { + var neededMigrationSteps = _migrationSteps.Where(x => x.CheckIfNeeded()).ToList(); + if (neededMigrationSteps.Count == 0) + { + return; + } + + var wereAnyRequired = false; + + foreach (var step in neededMigrationSteps) + { + var requiredText = step.Required ? "Required" : "Not Required"; + _console.Output.WriteLine($"Migration Needed ({requiredText}): {step.Description}"); + wereAnyRequired |= step.Required; + } + + _console.Output.WriteLine( + "\nRun the `migrate` subcommand to perform the above migration steps automatically\n"); + + if (wereAnyRequired) + { + throw new CommandException("Some migrations above are REQUIRED. Application will now exit."); + } + } } diff --git a/src/Recyclarr/Migration/Steps/MigrateTrashUpdaterAppDataDir.cs b/src/Recyclarr/Migration/Steps/MigrateTrashUpdaterAppDataDir.cs index 95cdb7be..cb2fe6af 100644 --- a/src/Recyclarr/Migration/Steps/MigrateTrashUpdaterAppDataDir.cs +++ b/src/Recyclarr/Migration/Steps/MigrateTrashUpdaterAppDataDir.cs @@ -3,9 +3,6 @@ using JetBrains.Annotations; namespace Recyclarr.Migration.Steps; -/// -/// Rename `trash.yml` to `recyclarr.yml`. -/// /// /// Implemented on 4/30/2022. /// @@ -24,6 +21,7 @@ public class MigrateTrashUpdaterAppDataDir : IMigrationStep public int Order => 20; public string Description { get; } public IReadOnlyCollection Remediation { get; } + public bool Required => true; public MigrateTrashUpdaterAppDataDir(IFileSystem fileSystem) { diff --git a/src/Recyclarr/Migration/Steps/MigrateTrashYml.cs b/src/Recyclarr/Migration/Steps/MigrateTrashYml.cs index a196e64d..456d7199 100644 --- a/src/Recyclarr/Migration/Steps/MigrateTrashYml.cs +++ b/src/Recyclarr/Migration/Steps/MigrateTrashYml.cs @@ -3,9 +3,6 @@ using JetBrains.Annotations; namespace Recyclarr.Migration.Steps; -/// -/// Rename `trash.yml` to `recyclarr.yml`. -/// /// /// Implemented on 4/30/2022. /// @@ -21,6 +18,7 @@ public class MigrateTrashYml : IMigrationStep public int Order => 10; public string Description { get; } public IReadOnlyCollection Remediation { get; } + public bool Required => true; public MigrateTrashYml(IFileSystem fileSystem) { @@ -32,7 +30,7 @@ public class MigrateTrashYml : IMigrationStep $"Ensure Recyclarr has permission to create {_newConfigPath}" }; - Description = $"Migration from `{_oldConfigPath}` to `{_newConfigPath}`"; + Description = $"Rename default YAML config from `{_oldConfigPath}` to `{_newConfigPath}`"; } public bool CheckIfNeeded() => _fileSystem.File.Exists(_oldConfigPath);