From 173011b32032b419c4c4f816443875ecdb0620ec Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sun, 25 Apr 2021 15:37:22 -0500 Subject: [PATCH] refactor: new active command provider semantics This allows for code to access the active IServiceCommand object, which represents a subcommand that provides an implementation for a service like Radarr or Sonarr. --- .../Command/CliTypeActivatorTest.cs | 56 +++++++++++++++++++ .../Command/ActiveServiceCommandProvider.cs | 16 ++++++ src/Trash/Command/CliTypeActivator.cs | 20 +++++++ .../Command/IActiveServiceCommandProvider.cs | 7 +++ .../{IBaseCommand.cs => IServiceCommand.cs} | 2 +- .../{BaseCommand.cs => ServiceCommand.cs} | 4 +- src/Trash/CompositionRoot.cs | 33 +++++++++-- src/Trash/CreateConfig/CreateConfigCommand.cs | 2 +- src/Trash/Program.cs | 3 +- src/Trash/Radarr/IRadarrCommand.cs | 2 +- src/Trash/Radarr/RadarrCommand.cs | 2 +- src/Trash/Sonarr/ISonarrCommand.cs | 2 +- src/Trash/Sonarr/SonarrCommand.cs | 2 +- 13 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 src/Trash.Tests/Command/CliTypeActivatorTest.cs create mode 100644 src/Trash/Command/ActiveServiceCommandProvider.cs create mode 100644 src/Trash/Command/CliTypeActivator.cs create mode 100644 src/Trash/Command/IActiveServiceCommandProvider.cs rename src/Trash/Command/{IBaseCommand.cs => IServiceCommand.cs} (82%) rename src/Trash/Command/{BaseCommand.cs => ServiceCommand.cs} (95%) diff --git a/src/Trash.Tests/Command/CliTypeActivatorTest.cs b/src/Trash.Tests/Command/CliTypeActivatorTest.cs new file mode 100644 index 00000000..e3a8c1c1 --- /dev/null +++ b/src/Trash.Tests/Command/CliTypeActivatorTest.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Autofac; +using FluentAssertions; +using NUnit.Framework; +using Trash.Command; + +namespace Trash.Tests.Command +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + public class CliTypeActivatorTest + { + private class NonServiceCommandType + { + } + + public class StubCommand : IServiceCommand + { + public bool Preview { get; } + public bool Debug { get; } + public List? Config { get; } + } + + [Test] + public void Resolve_NonServiceCommandType_NoActiveCommandSet() + { + var builder = new ContainerBuilder(); + builder.RegisterType(); + var container = CompositionRoot.Setup(builder); + + var createdType = CliTypeActivator.ResolveType(container, typeof(NonServiceCommandType)); + + Action act = () => _ = container.Resolve().ActiveCommand; + + createdType.Should().BeOfType(); + act.Should() + .Throw() + .WithMessage("The active command has not yet been determined"); + } + + [Test] + public void Resolve_ServiceCommandType_ActiveCommandSet() + { + var builder = new ContainerBuilder(); + builder.RegisterType(); + var container = CompositionRoot.Setup(builder); + + var createdType = CliTypeActivator.ResolveType(container, typeof(StubCommand)); + var activeCommand = container.Resolve().ActiveCommand; + + activeCommand.Should().BeSameAs(createdType); + activeCommand.Should().BeOfType(); + } + } +} diff --git a/src/Trash/Command/ActiveServiceCommandProvider.cs b/src/Trash/Command/ActiveServiceCommandProvider.cs new file mode 100644 index 00000000..31c2fa69 --- /dev/null +++ b/src/Trash/Command/ActiveServiceCommandProvider.cs @@ -0,0 +1,16 @@ +using System; + +namespace Trash.Command +{ + public class ActiveServiceCommandProvider : IActiveServiceCommandProvider + { + private IServiceCommand? _activeCommand; + + public IServiceCommand ActiveCommand + { + get => _activeCommand ?? + throw new InvalidOperationException("The active command has not yet been determined"); + set => _activeCommand = value; + } + } +} diff --git a/src/Trash/Command/CliTypeActivator.cs b/src/Trash/Command/CliTypeActivator.cs new file mode 100644 index 00000000..266317ae --- /dev/null +++ b/src/Trash/Command/CliTypeActivator.cs @@ -0,0 +1,20 @@ +using System; +using Autofac; + +namespace Trash.Command +{ + internal static class CliTypeActivator + { + public static object ResolveType(IContainer container, Type typeToResolve) + { + var instance = container.Resolve(typeToResolve); + if (instance.GetType().IsAssignableTo()) + { + var activeServiceProvider = container.Resolve(); + activeServiceProvider.ActiveCommand = (IServiceCommand)instance; + } + + return instance; + } + } +} diff --git a/src/Trash/Command/IActiveServiceCommandProvider.cs b/src/Trash/Command/IActiveServiceCommandProvider.cs new file mode 100644 index 00000000..7161ad31 --- /dev/null +++ b/src/Trash/Command/IActiveServiceCommandProvider.cs @@ -0,0 +1,7 @@ +namespace Trash.Command +{ + public interface IActiveServiceCommandProvider + { + IServiceCommand ActiveCommand { get; set; } + } +} diff --git a/src/Trash/Command/IBaseCommand.cs b/src/Trash/Command/IServiceCommand.cs similarity index 82% rename from src/Trash/Command/IBaseCommand.cs rename to src/Trash/Command/IServiceCommand.cs index bd5269af..13a2eced 100644 --- a/src/Trash/Command/IBaseCommand.cs +++ b/src/Trash/Command/IServiceCommand.cs @@ -2,7 +2,7 @@ namespace Trash.Command { - public interface IBaseCommand + public interface IServiceCommand { bool Preview { get; } bool Debug { get; } diff --git a/src/Trash/Command/BaseCommand.cs b/src/Trash/Command/ServiceCommand.cs similarity index 95% rename from src/Trash/Command/BaseCommand.cs rename to src/Trash/Command/ServiceCommand.cs index 408c0bb8..b06adc4b 100644 --- a/src/Trash/Command/BaseCommand.cs +++ b/src/Trash/Command/ServiceCommand.cs @@ -17,11 +17,11 @@ using YamlDotNet.Core; namespace Trash.Command { - public abstract class BaseCommand : ICommand, IBaseCommand + public abstract class ServiceCommand : ICommand, IServiceCommand { private readonly LoggingLevelSwitch _loggingLevelSwitch; - protected BaseCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch) + protected ServiceCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch) { _loggingLevelSwitch = loggingLevelSwitch; Log = logger; diff --git a/src/Trash/CompositionRoot.cs b/src/Trash/CompositionRoot.cs index 5d583072..dbc118e8 100644 --- a/src/Trash/CompositionRoot.cs +++ b/src/Trash/CompositionRoot.cs @@ -4,6 +4,7 @@ using Autofac; using CliFx; using Serilog; using Serilog.Core; +using Trash.Command; using Trash.Config; using Trash.Radarr.Api; using Trash.Radarr.QualityDefinition; @@ -52,23 +53,43 @@ namespace Trash builder.RegisterType().As(); } - public static IContainer Setup() + private static void ConfigurationRegistrations(ContainerBuilder builder) { - var builder = new ContainerBuilder(); - - builder.RegisterType().As(); - - // Configuration builder.RegisterType().As(); builder.RegisterGeneric(typeof(ConfigurationLoader<>)).As(typeof(IConfigurationLoader<>)); builder.RegisterGeneric(typeof(ConfigurationProvider<>)) .As(typeof(IConfigurationProvider<>)) .SingleInstance(); + } + private static void CommandRegistrations(ContainerBuilder builder) + { // Register all types deriving from CliFx's ICommand. These are all of our supported subcommands. builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(t => t.IsAssignableTo(typeof(ICommand))); + // Used to access the chosen command class. This is assigned from CliTypeActivator + builder.RegisterType() + .As() + .SingleInstance(); + + builder.Register(c => c.Resolve().ActiveCommand) + .As(); + } + + public static IContainer Setup() + { + return Setup(new ContainerBuilder()); + } + + public static IContainer Setup(ContainerBuilder builder) + { + builder.RegisterType() + .As(); + + ConfigurationRegistrations(builder); + CommandRegistrations(builder); + SetupLogging(builder); SonarrRegistrations(builder); RadarrRegistrations(builder); diff --git a/src/Trash/CreateConfig/CreateConfigCommand.cs b/src/Trash/CreateConfig/CreateConfigCommand.cs index 570bbf54..9977660c 100644 --- a/src/Trash/CreateConfig/CreateConfigCommand.cs +++ b/src/Trash/CreateConfig/CreateConfigCommand.cs @@ -29,7 +29,7 @@ namespace Trash.CreateConfig "Path where the new YAML file should be created. Must include the filename (e.g. path/to/config.yml). " + "File must not already exist. If not specified, uses the default path of `trash.yml` right next to the " + "executable.")] - public string Path { get; [UsedImplicitly] set; } = BaseCommand.DefaultConfigPath; + public string Path { get; [UsedImplicitly] set; } = ServiceCommand.DefaultConfigPath; public ValueTask ExecuteAsync(IConsole console) { diff --git a/src/Trash/Program.cs b/src/Trash/Program.cs index 037ad74c..c90ee2d0 100644 --- a/src/Trash/Program.cs +++ b/src/Trash/Program.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Autofac; using CliFx; +using Trash.Command; namespace Trash { @@ -15,7 +16,7 @@ namespace Trash .AddCommandsFromThisAssembly() .SetExecutableName(ThisAssembly.AssemblyName) .SetVersion($"v{ThisAssembly.AssemblyInformationalVersion}") - .UseTypeActivator(type => _container.Resolve(type)) + .UseTypeActivator(type => CliTypeActivator.ResolveType(_container, type)) .Build() .RunAsync(); } diff --git a/src/Trash/Radarr/IRadarrCommand.cs b/src/Trash/Radarr/IRadarrCommand.cs index b946aeb1..4b5cbd5e 100644 --- a/src/Trash/Radarr/IRadarrCommand.cs +++ b/src/Trash/Radarr/IRadarrCommand.cs @@ -2,7 +2,7 @@ namespace Trash.Radarr { - public interface IRadarrCommand : IBaseCommand + public interface IRadarrCommand : IServiceCommand { } } diff --git a/src/Trash/Radarr/RadarrCommand.cs b/src/Trash/Radarr/RadarrCommand.cs index b5cd415a..56294a18 100644 --- a/src/Trash/Radarr/RadarrCommand.cs +++ b/src/Trash/Radarr/RadarrCommand.cs @@ -13,7 +13,7 @@ namespace Trash.Radarr { [Command("radarr", Description = "Perform operations on a Radarr instance")] [UsedImplicitly] - public class RadarrCommand : BaseCommand, IRadarrCommand + public class RadarrCommand : ServiceCommand, IRadarrCommand { private readonly IConfigurationLoader _configLoader; private readonly Func _qualityUpdaterFactory; diff --git a/src/Trash/Sonarr/ISonarrCommand.cs b/src/Trash/Sonarr/ISonarrCommand.cs index b6a94431..85431fd9 100644 --- a/src/Trash/Sonarr/ISonarrCommand.cs +++ b/src/Trash/Sonarr/ISonarrCommand.cs @@ -2,7 +2,7 @@ namespace Trash.Sonarr { - public interface ISonarrCommand : IBaseCommand + public interface ISonarrCommand : IServiceCommand { } } diff --git a/src/Trash/Sonarr/SonarrCommand.cs b/src/Trash/Sonarr/SonarrCommand.cs index c738036b..3693665b 100644 --- a/src/Trash/Sonarr/SonarrCommand.cs +++ b/src/Trash/Sonarr/SonarrCommand.cs @@ -14,7 +14,7 @@ namespace Trash.Sonarr { [Command("sonarr", Description = "Perform operations on a Sonarr instance")] [UsedImplicitly] - public class SonarrCommand : BaseCommand, ISonarrCommand + public class SonarrCommand : ServiceCommand, ISonarrCommand { private readonly IConfigurationLoader _configLoader; private readonly Func _profileUpdaterFactory;