diff --git a/CHANGELOG.md b/CHANGELOG.md index 1baa3864..2a560b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Migration step added to delete old `repo` directory. Run `recyclarr migrate` to use. + ## [5.0.3] - 2023-06-25 ### Fixed diff --git a/src/Recyclarr.Cli/Console/Commands/MigrateCommand.cs b/src/Recyclarr.Cli/Console/Commands/MigrateCommand.cs index e35bc8a0..4a0980db 100644 --- a/src/Recyclarr.Cli/Console/Commands/MigrateCommand.cs +++ b/src/Recyclarr.Cli/Console/Commands/MigrateCommand.cs @@ -36,6 +36,7 @@ public class MigrateCommand : Command try { _migration.PerformAllMigrationSteps(settings.Debug); + _console.WriteLine("All migration steps completed"); } catch (MigrationException e) { diff --git a/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs b/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs index 44ce5170..68dcee95 100644 --- a/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs +++ b/src/Recyclarr.Cli/Console/Commands/SyncCommand.cs @@ -62,6 +62,7 @@ public class SyncCommand : AsyncCommand { // Will throw if migration is required, otherwise just a warning is issued. _migration.CheckNeededMigrations(); + await _repoUpdater.Update(); return (int) await _syncProcessor.ProcessConfigs(settings); diff --git a/src/Recyclarr.Cli/Migration/IMigrationStep.cs b/src/Recyclarr.Cli/Migration/IMigrationStep.cs deleted file mode 100644 index a034ecd2..00000000 --- a/src/Recyclarr.Cli/Migration/IMigrationStep.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Spectre.Console; - -namespace Recyclarr.Cli.Migration; - -public interface IMigrationStep -{ - int Order { get; } - string Description { get; } - IReadOnlyCollection Remediation { get; } - bool Required { get; } - bool CheckIfNeeded(); - void Execute(IAnsiConsole? console); -} diff --git a/src/Recyclarr.Cli/Migration/MigrationAutofacModule.cs b/src/Recyclarr.Cli/Migration/MigrationAutofacModule.cs index 13835437..34f1ca67 100644 --- a/src/Recyclarr.Cli/Migration/MigrationAutofacModule.cs +++ b/src/Recyclarr.Cli/Migration/MigrationAutofacModule.cs @@ -1,4 +1,5 @@ using Autofac; +using Recyclarr.Cli.Migration.Steps; namespace Recyclarr.Cli.Migration; @@ -10,10 +11,8 @@ public class MigrationAutofacModule : Module builder.RegisterType().As(); // Migration Steps - builder.RegisterTypes - ( - // Add migration steps here in order of execution - ) + builder.RegisterAssemblyTypes(ThisAssembly) + .AssignableTo() .As(); } } diff --git a/src/Recyclarr.Cli/Migration/MigrationExecutor.cs b/src/Recyclarr.Cli/Migration/MigrationExecutor.cs index a11a16ed..fad43541 100644 --- a/src/Recyclarr.Cli/Migration/MigrationExecutor.cs +++ b/src/Recyclarr.Cli/Migration/MigrationExecutor.cs @@ -1,3 +1,4 @@ +using Recyclarr.Cli.Migration.Steps; using Spectre.Console; namespace Recyclarr.Cli.Migration; @@ -13,12 +14,10 @@ public class MigrationExecutor : IMigrationExecutor _migrationSteps = migrationSteps.OrderBy(x => x.Order).ToList(); } - public void PerformAllMigrationSteps(bool withDiagnostics) + private void PerformMigrationStepsImpl(bool withDiagnostics, IEnumerable migrationSteps) { - _console.WriteLine("Performing migration steps..."); - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var step in _migrationSteps) + foreach (var step in migrationSteps) { // Do not use LINQ to filter using CheckIfNeeded(). If it returns true, then 'Execute()' must be invoked to // cause the necessary changes to happen. Those changes may be required in order for the *next* step's @@ -41,6 +40,11 @@ public class MigrationExecutor : IMigrationExecutor } } + public void PerformAllMigrationSteps(bool withDiagnostics) + { + PerformMigrationStepsImpl(withDiagnostics, _migrationSteps); + } + public void CheckNeededMigrations() { var neededMigrationSteps = _migrationSteps.Where(x => x.CheckIfNeeded()).ToList(); diff --git a/src/Recyclarr.Cli/Migration/Steps/DeleteRepoDirMigrationStep.cs b/src/Recyclarr.Cli/Migration/Steps/DeleteRepoDirMigrationStep.cs new file mode 100644 index 00000000..9f126c0b --- /dev/null +++ b/src/Recyclarr.Cli/Migration/Steps/DeleteRepoDirMigrationStep.cs @@ -0,0 +1,40 @@ +using System.IO.Abstractions; +using JetBrains.Annotations; +using Recyclarr.Common.Extensions; +using Recyclarr.TrashLib.Startup; +using Spectre.Console; + +namespace Recyclarr.Cli.Migration.Steps; + +[UsedImplicitly] +public class DeleteRepoDirMigrationStep : IMigrationStep +{ + private readonly IAppPaths _paths; + + public DeleteRepoDirMigrationStep(IAppPaths paths) + { + _paths = paths; + } + + public int Order => 1; + public string Description => "Delete old repo directory"; + public IReadOnlyCollection Remediation => new[] + { + $"Ensure Recyclarr has permission to recursively delete {RepoDir}", + $"Delete {RepoDir} manually if Recyclarr can't do it" + }; + + public bool Required => false; + private IDirectoryInfo RepoDir => _paths.AppDataDirectory.SubDir("repo"); + + public bool CheckIfNeeded() + { + return RepoDir.Exists; + } + + public void Execute(IAnsiConsole? console) + { + RepoDir.RecursivelyDeleteReadOnly(); + console?.WriteLine($"Deleted repo dir: {RepoDir.FullName}"); + } +} diff --git a/src/Recyclarr.Cli/Migration/Steps/IMigrationStep.cs b/src/Recyclarr.Cli/Migration/Steps/IMigrationStep.cs new file mode 100644 index 00000000..ad3102b5 --- /dev/null +++ b/src/Recyclarr.Cli/Migration/Steps/IMigrationStep.cs @@ -0,0 +1,47 @@ +using Spectre.Console; + +namespace Recyclarr.Cli.Migration.Steps; + +public interface IMigrationStep +{ + /// + /// Determines the order in which this migration step will run. + /// + int Order { get; } + + /// + /// A description printed to the user so that they understand the purpose of this migration step, and + /// what it does. + /// + string Description { get; } + + /// + /// One or more strings that individually represent a distinct manual solution that a user can attempt + /// when this migration step fails. + /// + IReadOnlyCollection Remediation { get; } + + /// + /// Determines if this migration step is required. If so, it must be executed before the program may be + /// used normally. + /// + bool Required { get; } + + /// + /// Run logic to determine if this migration step is necessary + /// + /// + /// Return true if this migration step is needed and should be run. False means the migration step is + /// not needed and Execute() will not be called. + /// + bool CheckIfNeeded(); + + /// + /// Execute the logic necessary for this migration step. + /// + /// + /// Use the console to print additional debug diagnostics. If this parameter is null, that means those + /// diagnostics should not be printed. + /// + void Execute(IAnsiConsole? console); +} diff --git a/src/Recyclarr.Cli/Recyclarr.Cli.csproj b/src/Recyclarr.Cli/Recyclarr.Cli.csproj index fb5a6c83..43d28082 100644 --- a/src/Recyclarr.Cli/Recyclarr.Cli.csproj +++ b/src/Recyclarr.Cli/Recyclarr.Cli.csproj @@ -37,8 +37,4 @@ - - - - diff --git a/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs b/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs index d03b37eb..5d533de5 100644 --- a/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs +++ b/src/Recyclarr.Common/Extensions/FileSystemExtensions.cs @@ -95,4 +95,14 @@ public static class FileSystemExtensions return configs.FirstOrDefault(); } + + public static void RecursivelyDeleteReadOnly(this IDirectoryInfo dir) + { + foreach (var info in dir.GetFileSystemInfos("*", SearchOption.AllDirectories)) + { + info.Attributes = FileAttributes.Normal; + } + + dir.Delete(true); + } } diff --git a/src/tests/Recyclarr.Cli.Tests/Migration/MigrationExecutorTest.cs b/src/tests/Recyclarr.Cli.Tests/Migration/MigrationExecutorTest.cs index b3257260..f66c6600 100644 --- a/src/tests/Recyclarr.Cli.Tests/Migration/MigrationExecutorTest.cs +++ b/src/tests/Recyclarr.Cli.Tests/Migration/MigrationExecutorTest.cs @@ -1,4 +1,5 @@ using Recyclarr.Cli.Migration; +using Recyclarr.Cli.Migration.Steps; using Recyclarr.Cli.TestLibrary; using Spectre.Console.Testing;