spectre-console-remove-di-hacks
Robert Dailey 4 months ago
parent 537f225542
commit b6a82b5bd1

@ -21,9 +21,9 @@
<PackageVersion Include="Serilog.Expressions" Version="4.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Spectre.Console" Version="0.48.0" />
<PackageVersion Include="Spectre.Console" Version="0.48.1-preview.0.7" />
<PackageVersion Include="Spectre.Console.Analyzer" Version="0.48.0" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.48.0" />
<PackageVersion Include="Spectre.Console.Cli" Version="0.48.1-preview.0.7" />
<PackageVersion Include="SuperLinq" Version="5.4.0" />
<PackageVersion Include="System.Data.HashFunction.FNV" Version="2.0.0" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
@ -54,7 +54,7 @@
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Serilog.Sinks.Observable" Version="2.0.2" />
<PackageVersion Include="Serilog.Sinks.NUnit" Version="1.0.3" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.48.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.48.1-preview.0.7" />
<PackageVersion Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="20.0.4" />
</ItemGroup>
<!-- Following found during vulerabilities Code Scan -->

@ -4,6 +4,7 @@ using Autofac;
using Autofac.Extras.Ordering;
using AutoMapper.Contrib.Autofac.DependencyInjection;
using Recyclarr.Cli.Cache;
using Recyclarr.Cli.Console.Interceptors;
using Recyclarr.Cli.Console.Setup;
using Recyclarr.Cli.Logging;
using Recyclarr.Cli.Migration;
@ -63,7 +64,7 @@ public static class CompositionRoot
builder.RegisterAutoMapper(thisAssembly);
CommandRegistrations(builder);
CliRegistrations(builder);
PipelineRegistrations(builder);
}
@ -93,28 +94,24 @@ public static class CompositionRoot
private static void RegisterLogger(ContainerBuilder builder)
{
builder.RegisterType<LogJanitor>().As<ILogJanitor>();
builder.RegisterType<LoggingLevelSwitch>().SingleInstance();
builder.RegisterType<LoggerFactory>();
builder.Register(c => c.Resolve<LoggerFactory>().Create()).As<ILogger>().SingleInstance();
}
private static void CommandRegistrations(ContainerBuilder builder)
private static void CliRegistrations(ContainerBuilder builder)
{
builder.RegisterType<BaseCommandSetupInterceptor>().As<ICommandInterceptor>();
builder.RegisterType<VersionLogInterceptor>().As<ICommandInterceptor>();
builder.RegisterType<GlobalTaskInterceptor>().As<ICommandInterceptor>();
builder.RegisterTypes(
typeof(AppPathSetupTask),
typeof(JanitorCleanupTask))
.As<IBaseCommandSetupTask>()
.As<IGlobalSetupTask>()
.OrderByRegistration();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.AssignableTo<CommandSettings>();
}
public static void RegisterExternal(
ContainerBuilder builder,
LoggingLevelSwitch logLevelSwitch,
AppDataPathProvider appDataPathProvider)
{
builder.RegisterInstance(logLevelSwitch);
builder.RegisterInstance(appDataPathProvider);
}
}

@ -3,7 +3,7 @@ using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Helpers;
internal class AutofacTypeRegistrar(ContainerBuilder builder, Action<ILifetimeScope> assignScope)
internal class AutofacTypeRegistrar(ContainerBuilder builder)
: ITypeRegistrar
{
public void Register(Type service, Type implementation)
@ -23,8 +23,6 @@ internal class AutofacTypeRegistrar(ContainerBuilder builder, Action<ILifetimeSc
public ITypeResolver Build()
{
var container = builder.Build();
assignScope(container);
return new AutofacTypeResolver(container);
return new AutofacTypeResolver(builder.Build());
}
}

@ -1,23 +1,17 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Recyclarr.Cli.Console.Commands;
using Recyclarr.Cli.Console.Commands;
using Recyclarr.Cli.Console.Helpers;
using Recyclarr.Platform;
using Serilog.Core;
using Serilog.Events;
using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Setup;
namespace Recyclarr.Cli.Console.Interceptors;
public class CliInterceptor(LoggingLevelSwitch loggingLevelSwitch, AppDataPathProvider appDataPathProvider)
public class BaseCommandSetupInterceptor(LoggingLevelSwitch loggingLevelSwitch, IAppDataSetup appDataSetup)
: ICommandInterceptor
{
private readonly Subject<Unit> _interceptedSubject = new();
private readonly ConsoleAppCancellationTokenSource _ct = new();
public IObservable<Unit> OnIntercepted => _interceptedSubject.AsObservable();
public void Intercept(CommandContext context, CommandSettings settings)
{
switch (settings)
@ -30,22 +24,17 @@ public class CliInterceptor(LoggingLevelSwitch loggingLevelSwitch, AppDataPathPr
HandleBaseCommand(cmd);
break;
}
_interceptedSubject.OnNext(Unit.Default);
_interceptedSubject.OnCompleted();
}
private void HandleServiceCommand(ServiceCommandSettings cmd)
{
HandleBaseCommand(cmd);
appDataPathProvider.AppDataPath = cmd.AppData;
appDataSetup.AppDataDirectoryOverride = cmd.AppData;
}
private void HandleBaseCommand(BaseCommandSettings cmd)
{
cmd.CancellationToken = _ct.Token;
loggingLevelSwitch.MinimumLevel = cmd.Debug switch
{
true => LogEventLevel.Debug,

@ -0,0 +1,17 @@
using Recyclarr.Cli.Console.Setup;
using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Interceptors;
public class GlobalTaskInterceptor(IOrderedEnumerable<IGlobalSetupTask> tasks) : ICommandInterceptor
{
public void Intercept(CommandContext context, CommandSettings settings)
{
tasks.ForEach(x => x.OnStart());
}
public void InterceptResult(CommandContext context, CommandSettings settings, ref int result)
{
tasks.Reverse().ForEach(x => x.OnFinish());
}
}

@ -0,0 +1,11 @@
using Spectre.Console.Cli;
namespace Recyclarr.Cli.Console.Interceptors;
public class VersionLogInterceptor(ILogger log) : ICommandInterceptor
{
public void Intercept(CommandContext context, CommandSettings settings)
{
log.Debug("Recyclarr Version: {Version}", GitVersionInformation.InformationalVersion);
}
}

@ -2,7 +2,7 @@ using Recyclarr.Platform;
namespace Recyclarr.Cli.Console.Setup;
public class AppPathSetupTask(ILogger log, IAppPaths paths) : IBaseCommandSetupTask
public class AppPathSetupTask(ILogger log, IAppPaths paths) : IGlobalSetupTask
{
public void OnStart()
{

@ -1,6 +1,6 @@
namespace Recyclarr.Cli.Console.Setup;
public interface IBaseCommandSetupTask
public interface IGlobalSetupTask
{
void OnStart();
void OnFinish();

@ -4,7 +4,7 @@ using Recyclarr.Settings;
namespace Recyclarr.Cli.Console.Setup;
public class JanitorCleanupTask(ILogJanitor janitor, ILogger log, ISettingsProvider settingsProvider)
: IBaseCommandSetupTask
: IGlobalSetupTask
{
public void OnStart()
{

@ -2,9 +2,6 @@ using System.Diagnostics.CodeAnalysis;
using Autofac;
using Recyclarr.Cli.Console;
using Recyclarr.Cli.Console.Helpers;
using Recyclarr.Cli.Console.Setup;
using Recyclarr.Platform;
using Serilog.Core;
using Spectre.Console;
using Spectre.Console.Cli;
@ -12,21 +9,13 @@ namespace Recyclarr.Cli;
internal static class Program
{
private static ILifetimeScope? _scope;
private static IBaseCommandSetupTask[] _tasks = Array.Empty<IBaseCommandSetupTask>();
private static ILogger? _log;
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
public static int Main(string[] args)
{
var builder = new ContainerBuilder();
CompositionRoot.Setup(builder);
var logLevelSwitch = new LoggingLevelSwitch();
var appDataPathProvider = new AppDataPathProvider();
CompositionRoot.RegisterExternal(builder, logLevelSwitch, appDataPathProvider);
var app = new CommandApp(new AutofacTypeRegistrar(builder, s => _scope = s));
var app = new CommandApp(new AutofacTypeRegistrar(builder));
app.Configure(config =>
{
#if DEBUG
@ -34,60 +23,29 @@ internal static class Program
config.ValidateExamples();
#endif
config.Settings.PropagateExceptions = true;
// config.Settings.PropagateExceptions = true;
config.Settings.StrictParsing = true;
config.SetApplicationName("recyclarr");
config.SetApplicationVersion(
$"v{GitVersionInformation.SemVer} ({GitVersionInformation.FullBuildMetaData})");
var interceptor = new CliInterceptor(logLevelSwitch, appDataPathProvider);
interceptor.OnIntercepted.Subscribe(_ => OnAppInitialized());
config.SetInterceptor(interceptor);
config.SetExceptionHandler((ex, resolver) =>
{
var log = (ILogger?) resolver?.Resolve(typeof(ILogger));
if (log is null)
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
}
else
{
log.Error(ex, "Non-recoverable Exception");
}
});
CliSetup.Commands(config);
});
var result = 1;
try
{
result = app.Run(args);
}
catch (Exception ex)
{
if (_log is null)
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
}
else
{
_log.Error(ex, "Non-recoverable Exception");
}
}
finally
{
OnAppCleanup();
}
return result;
}
private static void OnAppInitialized()
{
if (_scope is null)
{
throw new InvalidProgramException("Composition root is not initialized");
}
_log = _scope.Resolve<ILogger>();
_log.Debug("Recyclarr Version: {Version}", GitVersionInformation.InformationalVersion);
_tasks = _scope.Resolve<IOrderedEnumerable<IBaseCommandSetupTask>>().ToArray();
_tasks.ForEach(x => x.OnStart());
}
private static void OnAppCleanup()
{
_tasks.Reverse().ForEach(x => x.OnFinish());
return app.Run(args);
}
}

@ -1,6 +0,0 @@
namespace Recyclarr.Platform;
public class AppDataPathProvider
{
public string? AppDataPath { get; set; }
}

@ -5,11 +5,13 @@ namespace Recyclarr.Platform;
public class DefaultAppDataSetup(
IEnvironment env,
IFileSystem fs,
IRuntimeInformation runtimeInfo)
IRuntimeInformation runtimeInfo) : IAppDataSetup
{
public IAppPaths CreateAppPaths(string? appDataDirectoryOverride = null)
public string? AppDataDirectoryOverride { get; set; }
public IAppPaths CreateAppPaths()
{
var appDir = GetAppDataDirectory(appDataDirectoryOverride);
var appDir = GetAppDataDirectory(AppDataDirectoryOverride);
return new AppPaths(fs.DirectoryInfo.New(appDir));
}

@ -0,0 +1,6 @@
namespace Recyclarr.Platform;
public interface IAppDataSetup
{
public string? AppDataDirectoryOverride { get; set; }
}

@ -12,16 +12,11 @@ public class PlatformAutofacModule : Module
private static void RegisterAppPaths(ContainerBuilder builder)
{
builder.RegisterType<DefaultAppDataSetup>();
builder.RegisterType<DefaultAppDataSetup>().As<IAppDataSetup>().AsSelf().SingleInstance();
builder.RegisterType<DefaultEnvironment>().As<IEnvironment>();
builder.RegisterType<DefaultRuntimeInformation>().As<IRuntimeInformation>();
builder.Register(c =>
{
var appData = c.Resolve<AppDataPathProvider>();
var dataSetup = c.Resolve<DefaultAppDataSetup>();
return dataSetup.CreateAppPaths(appData.AppDataPath);
})
builder.Register(c => c.Resolve<DefaultAppDataSetup>().CreateAppPaths())
.As<IAppPaths>()
.SingleInstance();
}

@ -11,7 +11,7 @@ internal class BaseCommandSetupIntegrationTest : CliIntegrationFixture
[Test]
public void Base_command_startup_tasks_are_registered()
{
var registrations = Resolve<IEnumerable<IBaseCommandSetupTask>>();
var registrations = Resolve<IEnumerable<IGlobalSetupTask>>();
registrations.Select(x => x.GetType()).Should().BeEquivalentTo(new[]
{
typeof(JanitorCleanupTask),

@ -3,9 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using Autofac;
using Autofac.Core;
using NUnit.Framework.Internal;
using Recyclarr.Platform;
using Recyclarr.TestLibrary.Autofac;
using Serilog.Core;
using Spectre.Console;
namespace Recyclarr.Cli.IntegrationTests;
@ -22,7 +20,6 @@ public class CompositionRootTest
{
var builder = new ContainerBuilder();
CompositionRoot.Setup(builder);
CompositionRoot.RegisterExternal(builder, new LoggingLevelSwitch(), new AppDataPathProvider());
// These are things that Spectre.Console normally registers for us, so they won't explicitly be
// in the CompositionRoot. Register mocks/stubs here.

@ -34,7 +34,8 @@ public class DefaultAppDataSetupTest
.SubDirectory("override")
.SubDirectory("path");
var paths = sut.CreateAppPaths(overridePath.FullName);
sut.AppDataDirectoryOverride = overridePath.FullName;
var paths = sut.CreateAppPaths();
paths.AppDataDirectory.FullName.Should().Be(overridePath.FullName);
}
@ -88,7 +89,8 @@ public class DefaultAppDataSetupTest
.SubDirectory("var")
.SubDirectory("path").FullName;
sut.CreateAppPaths(expectedPath);
sut.AppDataDirectoryOverride = expectedPath;
sut.CreateAppPaths();
env.DidNotReceiveWithAnyArgs().GetEnvironmentVariable(default!);
fs.AllDirectories.Should().Contain(expectedPath);

Loading…
Cancel
Save