feat: New migrate subcommand

Automatic migration no longer takes place. Instead, the user must run
`recyclarr migrate` to have those migration steps executed
automatically, or do it manually.
pull/76/head
Robert Dailey 2 years ago
parent bba4d2a08a
commit 276f59ae8c

@ -11,11 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- New `--app-data` option for overriding the location of the application data directory. - 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 ### Changed
- The default location for the default YAML file (`recyclarr.yml`) has been changed to the - 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. [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 ### Deprecated

@ -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();
}
}

@ -16,7 +16,7 @@ public class InitializationAutofacModule : Module
builder.RegisterTypes( builder.RegisterTypes(
typeof(InitializeAppDataPath), typeof(InitializeAppDataPath),
typeof(ServiceInitializer), typeof(ServiceInitializer),
typeof(ServicePreInitializer)) typeof(CheckMigrationNeeded))
.As<IServiceInitializer>() .As<IServiceInitializer>()
.OrderByRegistration(); .OrderByRegistration();

@ -1,24 +1,28 @@
using System.Text; using System.Text;
using CliFx;
using CliFx.Attributes;
using CliFx.Exceptions; using CliFx.Exceptions;
using CliFx.Infrastructure;
using JetBrains.Annotations;
using Recyclarr.Migration; 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; private readonly IMigrationExecutor _migration;
public ServicePreInitializer(IMigrationExecutor migration) public MigrateCommand(IMigrationExecutor migration)
{ {
_migration = 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(); PerformMigrations();
return ValueTask.CompletedTask;
} }
private void PerformMigrations() private void PerformMigrations()

@ -3,4 +3,5 @@ namespace Recyclarr.Migration;
public interface IMigrationExecutor public interface IMigrationExecutor
{ {
void PerformAllMigrationSteps(); void PerformAllMigrationSteps();
void CheckNeededMigrations();
} }

@ -5,6 +5,7 @@ public interface IMigrationStep
int Order { get; } int Order { get; }
string Description { get; } string Description { get; }
IReadOnlyCollection<string> Remediation { get; } IReadOnlyCollection<string> Remediation { get; }
bool Required { get; }
bool CheckIfNeeded(); bool CheckIfNeeded();
void Execute(); void Execute();
} }

@ -1,3 +1,4 @@
using CliFx.Exceptions;
using CliFx.Infrastructure; using CliFx.Infrastructure;
namespace Recyclarr.Migration; namespace Recyclarr.Migration;
@ -40,4 +41,30 @@ public class MigrationExecutor : IMigrationExecutor
_console.Output.WriteLine($"Migrate: {step.Description}"); _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.");
}
}
} }

@ -3,9 +3,6 @@ using JetBrains.Annotations;
namespace Recyclarr.Migration.Steps; namespace Recyclarr.Migration.Steps;
/// <summary>
/// Rename `trash.yml` to `recyclarr.yml`.
/// </summary>
/// <remarks> /// <remarks>
/// Implemented on 4/30/2022. /// Implemented on 4/30/2022.
/// </remarks> /// </remarks>
@ -24,6 +21,7 @@ public class MigrateTrashUpdaterAppDataDir : IMigrationStep
public int Order => 20; public int Order => 20;
public string Description { get; } public string Description { get; }
public IReadOnlyCollection<string> Remediation { get; } public IReadOnlyCollection<string> Remediation { get; }
public bool Required => true;
public MigrateTrashUpdaterAppDataDir(IFileSystem fileSystem) public MigrateTrashUpdaterAppDataDir(IFileSystem fileSystem)
{ {

@ -3,9 +3,6 @@ using JetBrains.Annotations;
namespace Recyclarr.Migration.Steps; namespace Recyclarr.Migration.Steps;
/// <summary>
/// Rename `trash.yml` to `recyclarr.yml`.
/// </summary>
/// <remarks> /// <remarks>
/// Implemented on 4/30/2022. /// Implemented on 4/30/2022.
/// </remarks> /// </remarks>
@ -21,6 +18,7 @@ public class MigrateTrashYml : IMigrationStep
public int Order => 10; public int Order => 10;
public string Description { get; } public string Description { get; }
public IReadOnlyCollection<string> Remediation { get; } public IReadOnlyCollection<string> Remediation { get; }
public bool Required => true;
public MigrateTrashYml(IFileSystem fileSystem) public MigrateTrashYml(IFileSystem fileSystem)
{ {
@ -32,7 +30,7 @@ public class MigrateTrashYml : IMigrationStep
$"Ensure Recyclarr has permission to create {_newConfigPath}" $"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); public bool CheckIfNeeded() => _fileSystem.File.Exists(_oldConfigPath);

Loading…
Cancel
Save