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.
recyclarr
Robert Dailey 4 years ago
parent 1a02cc0d4a
commit 173011b320

@ -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<string>? Config { get; }
}
[Test]
public void Resolve_NonServiceCommandType_NoActiveCommandSet()
{
var builder = new ContainerBuilder();
builder.RegisterType<NonServiceCommandType>();
var container = CompositionRoot.Setup(builder);
var createdType = CliTypeActivator.ResolveType(container, typeof(NonServiceCommandType));
Action act = () => _ = container.Resolve<IActiveServiceCommandProvider>().ActiveCommand;
createdType.Should().BeOfType<NonServiceCommandType>();
act.Should()
.Throw<InvalidOperationException>()
.WithMessage("The active command has not yet been determined");
}
[Test]
public void Resolve_ServiceCommandType_ActiveCommandSet()
{
var builder = new ContainerBuilder();
builder.RegisterType<StubCommand>();
var container = CompositionRoot.Setup(builder);
var createdType = CliTypeActivator.ResolveType(container, typeof(StubCommand));
var activeCommand = container.Resolve<IActiveServiceCommandProvider>().ActiveCommand;
activeCommand.Should().BeSameAs(createdType);
activeCommand.Should().BeOfType<StubCommand>();
}
}
}

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

@ -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<IServiceCommand>())
{
var activeServiceProvider = container.Resolve<IActiveServiceCommandProvider>();
activeServiceProvider.ActiveCommand = (IServiceCommand)instance;
}
return instance;
}
}
}

@ -0,0 +1,7 @@
namespace Trash.Command
{
public interface IActiveServiceCommandProvider
{
IServiceCommand ActiveCommand { get; set; }
}
}

@ -2,7 +2,7 @@
namespace Trash.Command namespace Trash.Command
{ {
public interface IBaseCommand public interface IServiceCommand
{ {
bool Preview { get; } bool Preview { get; }
bool Debug { get; } bool Debug { get; }

@ -17,11 +17,11 @@ using YamlDotNet.Core;
namespace Trash.Command namespace Trash.Command
{ {
public abstract class BaseCommand : ICommand, IBaseCommand public abstract class ServiceCommand : ICommand, IServiceCommand
{ {
private readonly LoggingLevelSwitch _loggingLevelSwitch; private readonly LoggingLevelSwitch _loggingLevelSwitch;
protected BaseCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch) protected ServiceCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch)
{ {
_loggingLevelSwitch = loggingLevelSwitch; _loggingLevelSwitch = loggingLevelSwitch;
Log = logger; Log = logger;

@ -4,6 +4,7 @@ using Autofac;
using CliFx; using CliFx;
using Serilog; using Serilog;
using Serilog.Core; using Serilog.Core;
using Trash.Command;
using Trash.Config; using Trash.Config;
using Trash.Radarr.Api; using Trash.Radarr.Api;
using Trash.Radarr.QualityDefinition; using Trash.Radarr.QualityDefinition;
@ -52,23 +53,43 @@ namespace Trash
builder.RegisterType<RadarrQualityDefinitionGuideParser>().As<IRadarrQualityDefinitionGuideParser>(); builder.RegisterType<RadarrQualityDefinitionGuideParser>().As<IRadarrQualityDefinitionGuideParser>();
} }
public static IContainer Setup() private static void ConfigurationRegistrations(ContainerBuilder builder)
{ {
var builder = new ContainerBuilder();
builder.RegisterType<FileSystem>().As<IFileSystem>();
// Configuration
builder.RegisterType<ObjectFactory>().As<IObjectFactory>(); builder.RegisterType<ObjectFactory>().As<IObjectFactory>();
builder.RegisterGeneric(typeof(ConfigurationLoader<>)).As(typeof(IConfigurationLoader<>)); builder.RegisterGeneric(typeof(ConfigurationLoader<>)).As(typeof(IConfigurationLoader<>));
builder.RegisterGeneric(typeof(ConfigurationProvider<>)) builder.RegisterGeneric(typeof(ConfigurationProvider<>))
.As(typeof(IConfigurationProvider<>)) .As(typeof(IConfigurationProvider<>))
.SingleInstance(); .SingleInstance();
}
private static void CommandRegistrations(ContainerBuilder builder)
{
// Register all types deriving from CliFx's ICommand. These are all of our supported subcommands. // Register all types deriving from CliFx's ICommand. These are all of our supported subcommands.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.IsAssignableTo(typeof(ICommand))); .Where(t => t.IsAssignableTo(typeof(ICommand)));
// Used to access the chosen command class. This is assigned from CliTypeActivator
builder.RegisterType<ActiveServiceCommandProvider>()
.As<IActiveServiceCommandProvider>()
.SingleInstance();
builder.Register(c => c.Resolve<IActiveServiceCommandProvider>().ActiveCommand)
.As<IServiceCommand>();
}
public static IContainer Setup()
{
return Setup(new ContainerBuilder());
}
public static IContainer Setup(ContainerBuilder builder)
{
builder.RegisterType<FileSystem>()
.As<IFileSystem>();
ConfigurationRegistrations(builder);
CommandRegistrations(builder);
SetupLogging(builder); SetupLogging(builder);
SonarrRegistrations(builder); SonarrRegistrations(builder);
RadarrRegistrations(builder); RadarrRegistrations(builder);

@ -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). " + "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 " + "File must not already exist. If not specified, uses the default path of `trash.yml` right next to the " +
"executable.")] "executable.")]
public string Path { get; [UsedImplicitly] set; } = BaseCommand.DefaultConfigPath; public string Path { get; [UsedImplicitly] set; } = ServiceCommand.DefaultConfigPath;
public ValueTask ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {

@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Autofac; using Autofac;
using CliFx; using CliFx;
using Trash.Command;
namespace Trash namespace Trash
{ {
@ -15,7 +16,7 @@ namespace Trash
.AddCommandsFromThisAssembly() .AddCommandsFromThisAssembly()
.SetExecutableName(ThisAssembly.AssemblyName) .SetExecutableName(ThisAssembly.AssemblyName)
.SetVersion($"v{ThisAssembly.AssemblyInformationalVersion}") .SetVersion($"v{ThisAssembly.AssemblyInformationalVersion}")
.UseTypeActivator(type => _container.Resolve(type)) .UseTypeActivator(type => CliTypeActivator.ResolveType(_container, type))
.Build() .Build()
.RunAsync(); .RunAsync();
} }

@ -2,7 +2,7 @@
namespace Trash.Radarr namespace Trash.Radarr
{ {
public interface IRadarrCommand : IBaseCommand public interface IRadarrCommand : IServiceCommand
{ {
} }
} }

@ -13,7 +13,7 @@ namespace Trash.Radarr
{ {
[Command("radarr", Description = "Perform operations on a Radarr instance")] [Command("radarr", Description = "Perform operations on a Radarr instance")]
[UsedImplicitly] [UsedImplicitly]
public class RadarrCommand : BaseCommand, IRadarrCommand public class RadarrCommand : ServiceCommand, IRadarrCommand
{ {
private readonly IConfigurationLoader<RadarrConfiguration> _configLoader; private readonly IConfigurationLoader<RadarrConfiguration> _configLoader;
private readonly Func<RadarrQualityDefinitionUpdater> _qualityUpdaterFactory; private readonly Func<RadarrQualityDefinitionUpdater> _qualityUpdaterFactory;

@ -2,7 +2,7 @@
namespace Trash.Sonarr namespace Trash.Sonarr
{ {
public interface ISonarrCommand : IBaseCommand public interface ISonarrCommand : IServiceCommand
{ {
} }
} }

@ -14,7 +14,7 @@ namespace Trash.Sonarr
{ {
[Command("sonarr", Description = "Perform operations on a Sonarr instance")] [Command("sonarr", Description = "Perform operations on a Sonarr instance")]
[UsedImplicitly] [UsedImplicitly]
public class SonarrCommand : BaseCommand, ISonarrCommand public class SonarrCommand : ServiceCommand, ISonarrCommand
{ {
private readonly IConfigurationLoader<SonarrConfiguration> _configLoader; private readonly IConfigurationLoader<SonarrConfiguration> _configLoader;
private readonly Func<ReleaseProfileUpdater> _profileUpdaterFactory; private readonly Func<ReleaseProfileUpdater> _profileUpdaterFactory;

Loading…
Cancel
Save