diff --git a/Directory.Packages.props b/Directory.Packages.props index 20d604e4..3beca96a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,9 +21,9 @@ - + - + @@ -54,7 +54,7 @@ - + diff --git a/src/Recyclarr.Cli/CompositionRoot.cs b/src/Recyclarr.Cli/CompositionRoot.cs index 59a8a01b..11f22db0 100644 --- a/src/Recyclarr.Cli/CompositionRoot.cs +++ b/src/Recyclarr.Cli/CompositionRoot.cs @@ -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().As(); + builder.RegisterType().SingleInstance(); builder.RegisterType(); builder.Register(c => c.Resolve().Create()).As().SingleInstance(); } - private static void CommandRegistrations(ContainerBuilder builder) + private static void CliRegistrations(ContainerBuilder builder) { + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterTypes( typeof(AppPathSetupTask), typeof(JanitorCleanupTask)) - .As() + .As() .OrderByRegistration(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .AssignableTo(); } - - public static void RegisterExternal( - ContainerBuilder builder, - LoggingLevelSwitch logLevelSwitch, - AppDataPathProvider appDataPathProvider) - { - builder.RegisterInstance(logLevelSwitch); - builder.RegisterInstance(appDataPathProvider); - } } diff --git a/src/Recyclarr.Cli/Console/Helpers/AutofacTypeRegistrar.cs b/src/Recyclarr.Cli/Console/Helpers/AutofacTypeRegistrar.cs index f4eec4c1..96d3213e 100644 --- a/src/Recyclarr.Cli/Console/Helpers/AutofacTypeRegistrar.cs +++ b/src/Recyclarr.Cli/Console/Helpers/AutofacTypeRegistrar.cs @@ -3,7 +3,7 @@ using Spectre.Console.Cli; namespace Recyclarr.Cli.Console.Helpers; -internal class AutofacTypeRegistrar(ContainerBuilder builder, Action assignScope) +internal class AutofacTypeRegistrar(ContainerBuilder builder) : ITypeRegistrar { public void Register(Type service, Type implementation) @@ -23,8 +23,6 @@ internal class AutofacTypeRegistrar(ContainerBuilder builder, Action _interceptedSubject = new(); private readonly ConsoleAppCancellationTokenSource _ct = new(); - public IObservable 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, diff --git a/src/Recyclarr.Cli/Console/Interceptors/GlobalTaskInterceptor.cs b/src/Recyclarr.Cli/Console/Interceptors/GlobalTaskInterceptor.cs new file mode 100644 index 00000000..5aba1fdc --- /dev/null +++ b/src/Recyclarr.Cli/Console/Interceptors/GlobalTaskInterceptor.cs @@ -0,0 +1,17 @@ +using Recyclarr.Cli.Console.Setup; +using Spectre.Console.Cli; + +namespace Recyclarr.Cli.Console.Interceptors; + +public class GlobalTaskInterceptor(IOrderedEnumerable 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()); + } +} diff --git a/src/Recyclarr.Cli/Console/Interceptors/VersionLogInterceptor.cs b/src/Recyclarr.Cli/Console/Interceptors/VersionLogInterceptor.cs new file mode 100644 index 00000000..2e42efc3 --- /dev/null +++ b/src/Recyclarr.Cli/Console/Interceptors/VersionLogInterceptor.cs @@ -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); + } +} diff --git a/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs b/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs index 07eeaed5..d15a2be1 100644 --- a/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs +++ b/src/Recyclarr.Cli/Console/Setup/AppPathSetupTask.cs @@ -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() { diff --git a/src/Recyclarr.Cli/Console/Setup/IBaseCommandSetupTask.cs b/src/Recyclarr.Cli/Console/Setup/IGlobalSetupTask.cs similarity index 68% rename from src/Recyclarr.Cli/Console/Setup/IBaseCommandSetupTask.cs rename to src/Recyclarr.Cli/Console/Setup/IGlobalSetupTask.cs index 99ffbee3..7c45c7cd 100644 --- a/src/Recyclarr.Cli/Console/Setup/IBaseCommandSetupTask.cs +++ b/src/Recyclarr.Cli/Console/Setup/IGlobalSetupTask.cs @@ -1,6 +1,6 @@ namespace Recyclarr.Cli.Console.Setup; -public interface IBaseCommandSetupTask +public interface IGlobalSetupTask { void OnStart(); void OnFinish(); diff --git a/src/Recyclarr.Cli/Console/Setup/JanitorCleanupTask.cs b/src/Recyclarr.Cli/Console/Setup/JanitorCleanupTask.cs index df7e4b45..2d236b3b 100644 --- a/src/Recyclarr.Cli/Console/Setup/JanitorCleanupTask.cs +++ b/src/Recyclarr.Cli/Console/Setup/JanitorCleanupTask.cs @@ -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() { diff --git a/src/Recyclarr.Cli/Program.cs b/src/Recyclarr.Cli/Program.cs index f53d93f2..e1681deb 100644 --- a/src/Recyclarr.Cli/Program.cs +++ b/src/Recyclarr.Cli/Program.cs @@ -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(); - 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(); - _log.Debug("Recyclarr Version: {Version}", GitVersionInformation.InformationalVersion); - - _tasks = _scope.Resolve>().ToArray(); - _tasks.ForEach(x => x.OnStart()); - } - - private static void OnAppCleanup() - { - _tasks.Reverse().ForEach(x => x.OnFinish()); + return app.Run(args); } } diff --git a/src/Recyclarr.Platform/AppDataPathProvider.cs b/src/Recyclarr.Platform/AppDataPathProvider.cs deleted file mode 100644 index 3bf11039..00000000 --- a/src/Recyclarr.Platform/AppDataPathProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Recyclarr.Platform; - -public class AppDataPathProvider -{ - public string? AppDataPath { get; set; } -} diff --git a/src/Recyclarr.Platform/DefaultAppDataSetup.cs b/src/Recyclarr.Platform/DefaultAppDataSetup.cs index 7e547469..975914a1 100644 --- a/src/Recyclarr.Platform/DefaultAppDataSetup.cs +++ b/src/Recyclarr.Platform/DefaultAppDataSetup.cs @@ -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)); } diff --git a/src/Recyclarr.Platform/IAppDataSetup.cs b/src/Recyclarr.Platform/IAppDataSetup.cs new file mode 100644 index 00000000..a6100ac3 --- /dev/null +++ b/src/Recyclarr.Platform/IAppDataSetup.cs @@ -0,0 +1,6 @@ +namespace Recyclarr.Platform; + +public interface IAppDataSetup +{ + public string? AppDataDirectoryOverride { get; set; } +} diff --git a/src/Recyclarr.Platform/PlatformAutofacModule.cs b/src/Recyclarr.Platform/PlatformAutofacModule.cs index 0ec06399..39bc5d9c 100644 --- a/src/Recyclarr.Platform/PlatformAutofacModule.cs +++ b/src/Recyclarr.Platform/PlatformAutofacModule.cs @@ -12,16 +12,11 @@ public class PlatformAutofacModule : Module private static void RegisterAppPaths(ContainerBuilder builder) { - builder.RegisterType(); + builder.RegisterType().As().AsSelf().SingleInstance(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.Register(c => - { - var appData = c.Resolve(); - var dataSetup = c.Resolve(); - return dataSetup.CreateAppPaths(appData.AppDataPath); - }) + builder.Register(c => c.Resolve().CreateAppPaths()) .As() .SingleInstance(); } diff --git a/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs b/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs index 5993ab29..9b97e8b4 100644 --- a/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs +++ b/tests/Recyclarr.Cli.IntegrationTests/BaseCommandSetupIntegrationTest.cs @@ -11,7 +11,7 @@ internal class BaseCommandSetupIntegrationTest : CliIntegrationFixture [Test] public void Base_command_startup_tasks_are_registered() { - var registrations = Resolve>(); + var registrations = Resolve>(); registrations.Select(x => x.GetType()).Should().BeEquivalentTo(new[] { typeof(JanitorCleanupTask), diff --git a/tests/Recyclarr.Cli.IntegrationTests/CompositionRootTest.cs b/tests/Recyclarr.Cli.IntegrationTests/CompositionRootTest.cs index 8f28574f..b510187e 100644 --- a/tests/Recyclarr.Cli.IntegrationTests/CompositionRootTest.cs +++ b/tests/Recyclarr.Cli.IntegrationTests/CompositionRootTest.cs @@ -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. diff --git a/tests/Recyclarr.Tests/Platform/DefaultAppDataSetupTest.cs b/tests/Recyclarr.Tests/Platform/DefaultAppDataSetupTest.cs index 283e36de..7039e6c3 100644 --- a/tests/Recyclarr.Tests/Platform/DefaultAppDataSetupTest.cs +++ b/tests/Recyclarr.Tests/Platform/DefaultAppDataSetupTest.cs @@ -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);