From 042fe618c1e1fa4d6497b20b7be23cefa3bf24a1 Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sun, 15 May 2022 18:34:49 -0500 Subject: [PATCH] refactor: Make AppPaths injectable This lays the groundwork to allow application paths to be overridden at runtime. --- src/Common/CommonAutofacModule.cs | 12 +++++ src/Common/DefaultEnvironment.cs | 9 ++++ src/Common/IEnvironment.cs | 6 +++ .../Command/CreateConfigCommandTest.cs | 46 ++++++++++--------- .../ServiceInitializationAndCleanupTest.cs | 4 +- src/Recyclarr.Tests/LogJanitorTest.cs | 42 ++++++++--------- src/Recyclarr/AppPaths.cs | 26 +++++++---- src/Recyclarr/Command/CreateConfigCommand.cs | 28 +++++++---- .../IServiceInitializationAndCleanup.cs | 2 +- .../Initialization/IServiceInitializer.cs | 2 +- .../ServiceInitializationAndCleanup.cs | 2 +- .../Initialization/ServiceInitializer.cs | 13 +++++- .../ServicePreInitialization.cs | 2 +- src/Recyclarr/Command/RadarrCommand.cs | 14 ++++-- src/Recyclarr/Command/ServiceCommand.cs | 5 +- .../Command/Services/RadarrService.cs | 14 +++++- .../Command/Services/SonarrService.cs | 14 +++++- src/Recyclarr/Command/SonarrCommand.cs | 20 ++++++-- src/Recyclarr/CompositionRoot.cs | 6 +-- src/Recyclarr/Logging/LogJanitor.cs | 13 ++++-- src/Recyclarr/Logging/LoggerFactory.cs | 7 ++- src/Recyclarr/Program.cs | 4 +- src/Recyclarr/ResourcePaths.cs | 9 ---- .../Config/Settings/SettingsPersisterTest.cs | 7 ++- .../LocalRepoCustomFormatJsonParserTest.cs | 5 +- .../LocalRepoReleaseProfileJsonParserTest.cs | 5 +- .../Config/Settings/SettingsPersister.cs | 5 +- src/TrashLib/IAppPaths.cs | 12 +++++ src/TrashLib/Radarr/Config/IResourcePaths.cs | 7 --- .../Guide/LocalRepoCustomFormatJsonParser.cs | 7 ++- src/TrashLib/Repo/RepoUpdater.cs | 5 +- .../LocalRepoReleaseProfileJsonParser.cs | 7 ++- 32 files changed, 223 insertions(+), 137 deletions(-) create mode 100644 src/Common/CommonAutofacModule.cs create mode 100644 src/Common/DefaultEnvironment.cs create mode 100644 src/Common/IEnvironment.cs delete mode 100644 src/Recyclarr/ResourcePaths.cs create mode 100644 src/TrashLib/IAppPaths.cs delete mode 100644 src/TrashLib/Radarr/Config/IResourcePaths.cs diff --git a/src/Common/CommonAutofacModule.cs b/src/Common/CommonAutofacModule.cs new file mode 100644 index 00000000..bdc02ced --- /dev/null +++ b/src/Common/CommonAutofacModule.cs @@ -0,0 +1,12 @@ +using Autofac; + +namespace Common; + +public class CommonAutofacModule : Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As(); + builder.RegisterType().As(); + } +} diff --git a/src/Common/DefaultEnvironment.cs b/src/Common/DefaultEnvironment.cs new file mode 100644 index 00000000..118d9591 --- /dev/null +++ b/src/Common/DefaultEnvironment.cs @@ -0,0 +1,9 @@ +namespace Common; + +internal class DefaultEnvironment : IEnvironment +{ + public string GetFolderPath(Environment.SpecialFolder folder) + { + return Environment.GetFolderPath(folder); + } +} diff --git a/src/Common/IEnvironment.cs b/src/Common/IEnvironment.cs new file mode 100644 index 00000000..f40fdd44 --- /dev/null +++ b/src/Common/IEnvironment.cs @@ -0,0 +1,6 @@ +namespace Common; + +public interface IEnvironment +{ + public string GetFolderPath(Environment.SpecialFolder folder); +} diff --git a/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs b/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs index 29f86b47..156af853 100644 --- a/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs +++ b/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs @@ -1,9 +1,12 @@ -using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using AutoFixture.NUnit3; using CliFx.Infrastructure; +using FluentAssertions; using NSubstitute; using NUnit.Framework; using Recyclarr.Command; -using Serilog; +using TestLibrary.AutoFixture; +using TrashLib; // ReSharper disable MethodHasAsyncOverload @@ -13,32 +16,33 @@ namespace Recyclarr.Tests.Command; [Parallelizable(ParallelScope.All)] public class CreateConfigCommandTest { - [Test] - public async Task CreateConfig_DefaultPath_FileIsCreated() + [Test, AutoMockData] + public async Task Config_file_created_when_using_default_path( + [Frozen] IAppPaths paths, + [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, + CreateConfigCommand cmd) { - var logger = Substitute.For(); - var filesystem = Substitute.For(); - var cmd = new CreateConfigCommand(logger, filesystem); + const string ymlPath = "path/recyclarr.yml"; + paths.ConfigPath.Returns(ymlPath); + await cmd.ExecuteAsync(Substitute.For()); - await cmd.ExecuteAsync(Substitute.For()).ConfigureAwait(false); - - filesystem.File.Received().Exists(Arg.Is(s => s.EndsWith("recyclarr.yml"))); - filesystem.File.Received().WriteAllText(Arg.Is(s => s.EndsWith("recyclarr.yml")), Arg.Any()); + var file = fs.GetFile(ymlPath); + file.Should().NotBeNull(); + file.Contents.Should().NotBeEmpty(); } - [Test] - public async Task CreateConfig_SpecifyPath_FileIsCreated() + [Test, AutoMockData] + public async Task CreateConfig_SpecifyPath_FileIsCreated( + [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, + CreateConfigCommand cmd) { - var logger = Substitute.For(); - var filesystem = Substitute.For(); - var cmd = new CreateConfigCommand(logger, filesystem) - { - Path = "some/other/path.yml" - }; + const string ymlPath = "some/other/path.yml"; + cmd.Path = ymlPath; await cmd.ExecuteAsync(Substitute.For()).ConfigureAwait(false); - filesystem.File.Received().Exists(Arg.Is("some/other/path.yml")); - filesystem.File.Received().WriteAllText(Arg.Is("some/other/path.yml"), Arg.Any()); + var file = fs.GetFile(ymlPath); + file.Should().NotBeNull(); + file.Contents.Should().NotBeEmpty(); } } diff --git a/src/Recyclarr.Tests/Command/Initialization/ServiceInitializationAndCleanupTest.cs b/src/Recyclarr.Tests/Command/Initialization/ServiceInitializationAndCleanupTest.cs index 37e4d78b..ab78b3d8 100644 --- a/src/Recyclarr.Tests/Command/Initialization/ServiceInitializationAndCleanupTest.cs +++ b/src/Recyclarr.Tests/Command/Initialization/ServiceInitializationAndCleanupTest.cs @@ -13,7 +13,7 @@ public class ServiceInitializationAndCleanupTest { [Test, AutoMockData] public async Task Cleanup_happens_when_exception_occurs_in_action( - IServiceCommand cmd, + ServiceCommand cmd, IServiceCleaner cleaner) { var sut = new ServiceInitializationAndCleanup( @@ -28,7 +28,7 @@ public class ServiceInitializationAndCleanupTest [Test, AutoMockData] public async Task Cleanup_happens_when_exception_occurs_in_init( - IServiceCommand cmd, + ServiceCommand cmd, IServiceInitializer init, IServiceCleaner cleaner) { diff --git a/src/Recyclarr.Tests/LogJanitorTest.cs b/src/Recyclarr.Tests/LogJanitorTest.cs index dd20fafb..a5b847c3 100644 --- a/src/Recyclarr.Tests/LogJanitorTest.cs +++ b/src/Recyclarr.Tests/LogJanitorTest.cs @@ -1,7 +1,12 @@ -using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using AutoFixture.NUnit3; +using FluentAssertions; +using MoreLinq.Extensions; using NSubstitute; using NUnit.Framework; using Recyclarr.Logging; +using TestLibrary.AutoFixture; +using TrashLib; namespace Recyclarr.Tests; @@ -9,33 +14,28 @@ namespace Recyclarr.Tests; [Parallelizable(ParallelScope.All)] public class LogJanitorTest { - [Test] - public void Keep_correct_number_of_newest_log_files() + [Test, AutoMockData] + public void Keep_correct_number_of_newest_log_files( + [Frozen] IAppPaths paths, + [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, + LogJanitor janitor) { - var fs = Substitute.For(); - var janitor = new LogJanitor(fs); + const string logDir = "C:\\logs"; + paths.LogDirectory.Returns(logDir); - var testFileInfoList = new[] + var testFiles = new[] { - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For() + $"{logDir}\\trash_2021-05-15_19-00-00", + $"{logDir}\\trash_2021-05-15_20-00-00", + $"{logDir}\\trash_2021-05-15_21-00-00", + $"{logDir}\\trash_2021-05-15_22-00-00" }; - testFileInfoList[0].Name.Returns("trash_2021-05-15_19-00-00"); - testFileInfoList[1].Name.Returns("trash_2021-05-15_20-00-00"); - testFileInfoList[2].Name.Returns("trash_2021-05-15_21-00-00"); - testFileInfoList[3].Name.Returns("trash_2021-05-15_22-00-00"); - - fs.DirectoryInfo.FromDirectoryName(Arg.Any()).GetFiles() - .Returns(testFileInfoList); + testFiles.ForEach(x => fs.AddFile(x, new MockFileData(""))); janitor.DeleteOldestLogFiles(2); - testFileInfoList[0].Received().Delete(); - testFileInfoList[1].Received().Delete(); - testFileInfoList[2].DidNotReceive().Delete(); - testFileInfoList[3].DidNotReceive().Delete(); + fs.FileExists(testFiles[2]).Should().BeTrue(); + fs.FileExists(testFiles[3]).Should().BeTrue(); } } diff --git a/src/Recyclarr/AppPaths.cs b/src/Recyclarr/AppPaths.cs index 4471a7ee..9160806f 100644 --- a/src/Recyclarr/AppPaths.cs +++ b/src/Recyclarr/AppPaths.cs @@ -1,15 +1,23 @@ -namespace Recyclarr; +using System.IO.Abstractions; +using TrashLib; -internal static class AppPaths -{ - public static string AppDataPath { get; } = - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "recyclarr"); +namespace Recyclarr; - public static string DefaultConfigPath { get; } = Path.Combine(AppContext.BaseDirectory, "recyclarr.yml"); +internal class AppPaths : IAppPaths +{ + private readonly IFileSystem _fs; - public static string DefaultSettingsPath { get; } = Path.Combine(AppDataPath, "settings.yml"); + public AppPaths(IFileSystem fs) + { + _fs = fs; + } - public static string LogDirectory { get; } = Path.Combine(AppDataPath, "logs"); + public void SetAppDataPath(string path) => AppDataPath = path; - public static string RepoDirectory { get; } = Path.Combine(AppDataPath, "repo"); + public string AppDataPath { get; private set; } = ""; + public string ConfigPath => _fs.Path.Combine(AppContext.BaseDirectory, "recyclarr.yml"); + public string SettingsPath => _fs.Path.Combine(AppDataPath, "settings.yml"); + public string LogDirectory => _fs.Path.Combine(AppDataPath, "logs"); + public string RepoDirectory => _fs.Path.Combine(AppDataPath, "repo"); + public string CacheDirectory => _fs.Path.Combine(AppDataPath, "cache"); } diff --git a/src/Recyclarr/Command/CreateConfigCommand.cs b/src/Recyclarr/Command/CreateConfigCommand.cs index 2ab3e57c..03baed00 100644 --- a/src/Recyclarr/Command/CreateConfigCommand.cs +++ b/src/Recyclarr/Command/CreateConfigCommand.cs @@ -6,6 +6,7 @@ using CliFx.Infrastructure; using Common; using JetBrains.Annotations; using Serilog; +using TrashLib; namespace Recyclarr.Command; @@ -13,35 +14,42 @@ namespace Recyclarr.Command; [UsedImplicitly] public class CreateConfigCommand : ICommand { - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fs; + private readonly IAppPaths _paths; + private readonly ILogger _log; + private string? _path; - public CreateConfigCommand(ILogger logger, IFileSystem fileSystem) + public CreateConfigCommand(ILogger logger, IFileSystem fs, IAppPaths paths) { - Log = logger; - _fileSystem = fileSystem; + _log = logger; + _fs = fs; + _paths = paths; } - private ILogger Log { get; } - [CommandOption("path", 'p', Description = "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 `recyclarr.yml` right next to the " + "executable.")] - public string Path { get; [UsedImplicitly] set; } = AppPaths.DefaultConfigPath; + public string Path + { + get => _path ?? _paths.ConfigPath; + set => _path = value; + } public ValueTask ExecuteAsync(IConsole console) { var reader = new ResourceDataReader(typeof(Program)); var ymlData = reader.ReadData("config-template.yml"); - if (_fileSystem.File.Exists(Path)) + if (_fs.File.Exists(Path)) { throw new CommandException($"The file {Path} already exists. Please choose another path or " + "delete/move the existing file and run this command again."); } - _fileSystem.File.WriteAllText(Path, ymlData); - Log.Information("Created configuration at: {Path}", Path); + _fs.Directory.CreateDirectory(_fs.Path.GetDirectoryName(Path)); + _fs.File.WriteAllText(Path, ymlData); + _log.Information("Created configuration at: {Path}", Path); return default; } } diff --git a/src/Recyclarr/Command/Initialization/IServiceInitializationAndCleanup.cs b/src/Recyclarr/Command/Initialization/IServiceInitializationAndCleanup.cs index 64697f04..214b4f02 100644 --- a/src/Recyclarr/Command/Initialization/IServiceInitializationAndCleanup.cs +++ b/src/Recyclarr/Command/Initialization/IServiceInitializationAndCleanup.cs @@ -2,5 +2,5 @@ public interface IServiceInitializationAndCleanup { - Task Execute(IServiceCommand cmd, Func logic); + Task Execute(ServiceCommand cmd, Func logic); } diff --git a/src/Recyclarr/Command/Initialization/IServiceInitializer.cs b/src/Recyclarr/Command/Initialization/IServiceInitializer.cs index 8e88ed76..5da3ba52 100644 --- a/src/Recyclarr/Command/Initialization/IServiceInitializer.cs +++ b/src/Recyclarr/Command/Initialization/IServiceInitializer.cs @@ -2,5 +2,5 @@ public interface IServiceInitializer { - void Initialize(IServiceCommand cmd); + void Initialize(ServiceCommand cmd); } diff --git a/src/Recyclarr/Command/Initialization/ServiceInitializationAndCleanup.cs b/src/Recyclarr/Command/Initialization/ServiceInitializationAndCleanup.cs index dcc1066f..6fbde20b 100644 --- a/src/Recyclarr/Command/Initialization/ServiceInitializationAndCleanup.cs +++ b/src/Recyclarr/Command/Initialization/ServiceInitializationAndCleanup.cs @@ -15,7 +15,7 @@ public class ServiceInitializationAndCleanup : IServiceInitializationAndCleanup _cleaners = cleaners; } - public async Task Execute(IServiceCommand cmd, Func logic) + public async Task Execute(ServiceCommand cmd, Func logic) { try { diff --git a/src/Recyclarr/Command/Initialization/ServiceInitializer.cs b/src/Recyclarr/Command/Initialization/ServiceInitializer.cs index 9015946b..5fe3b9d3 100644 --- a/src/Recyclarr/Command/Initialization/ServiceInitializer.cs +++ b/src/Recyclarr/Command/Initialization/ServiceInitializer.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Serilog; using Serilog.Core; using Serilog.Events; +using TrashLib; using TrashLib.Config.Settings; using TrashLib.Extensions; using TrashLib.Repo; @@ -18,22 +19,25 @@ internal class ServiceInitializer : IServiceInitializer private readonly ISettingsPersister _settingsPersister; private readonly ISettingsProvider _settingsProvider; private readonly IRepoUpdater _repoUpdater; + private readonly IAppPaths _paths; public ServiceInitializer( ILogger log, LoggingLevelSwitch loggingLevelSwitch, ISettingsPersister settingsPersister, ISettingsProvider settingsProvider, - IRepoUpdater repoUpdater) + IRepoUpdater repoUpdater, + IAppPaths paths) { _log = log; _loggingLevelSwitch = loggingLevelSwitch; _settingsPersister = settingsPersister; _settingsProvider = settingsProvider; _repoUpdater = repoUpdater; + _paths = paths; } - public void Initialize(IServiceCommand cmd) + public void Initialize(ServiceCommand cmd) { // Must happen first because everything can use the logger. _loggingLevelSwitch.MinimumLevel = cmd.Debug ? LogEventLevel.Debug : LogEventLevel.Information; @@ -43,6 +47,11 @@ internal class ServiceInitializer : IServiceInitializer SetupHttp(); _repoUpdater.UpdateRepo(); + + if (!cmd.Config.Any()) + { + cmd.Config = new[] {_paths.ConfigPath}; + } } private void SetupHttp() diff --git a/src/Recyclarr/Command/Initialization/ServicePreInitialization.cs b/src/Recyclarr/Command/Initialization/ServicePreInitialization.cs index a52091de..5581cc6f 100644 --- a/src/Recyclarr/Command/Initialization/ServicePreInitialization.cs +++ b/src/Recyclarr/Command/Initialization/ServicePreInitialization.cs @@ -13,7 +13,7 @@ internal class ServicePreInitializer : IServiceInitializer _migration = migration; } - public void Initialize(IServiceCommand cmd) + public void Initialize(ServiceCommand cmd) { // Migrations are performed before we process command line arguments because we cannot instantiate any service // objects via the DI container before migration logic is performed. This is due to the fact that migration diff --git a/src/Recyclarr/Command/RadarrCommand.cs b/src/Recyclarr/Command/RadarrCommand.cs index 9d853824..8ff830ba 100644 --- a/src/Recyclarr/Command/RadarrCommand.cs +++ b/src/Recyclarr/Command/RadarrCommand.cs @@ -10,13 +10,19 @@ namespace Recyclarr.Command; internal class RadarrCommand : ServiceCommand, IRadarrCommand { private readonly Lazy _service; - - public override string CacheStoragePath { get; } = - Path.Combine(AppPaths.AppDataPath, "cache", "radarr"); + private readonly string? _cacheStoragePath; public override string Name => "Radarr"; - public RadarrCommand(IServiceInitializationAndCleanup init, Lazy service) + public sealed override string CacheStoragePath + { + get => _cacheStoragePath ?? _service.Value.DefaultCacheStoragePath; + protected init => _cacheStoragePath = value; + } + + public RadarrCommand( + IServiceInitializationAndCleanup init, + Lazy service) : base(init) { _service = service; diff --git a/src/Recyclarr/Command/ServiceCommand.cs b/src/Recyclarr/Command/ServiceCommand.cs index b06a2f24..108d4fac 100644 --- a/src/Recyclarr/Command/ServiceCommand.cs +++ b/src/Recyclarr/Command/ServiceCommand.cs @@ -21,10 +21,9 @@ public abstract class ServiceCommand : ICommand, IServiceCommand [CommandOption("config", 'c', Description = "One or more YAML config files to use. All configs will be used and settings are additive. " + "If not specified, the script will look for `recyclarr.yml` in the same directory as the executable.")] - public ICollection Config { get; [UsedImplicitly] set; } = - new List {AppPaths.DefaultConfigPath}; + public ICollection Config { get; [UsedImplicitly] set; } = new List(); - public abstract string CacheStoragePath { get; } + public abstract string CacheStoragePath { get; protected init; } public abstract string Name { get; } protected ServiceCommand(IServiceInitializationAndCleanup init) diff --git a/src/Recyclarr/Command/Services/RadarrService.cs b/src/Recyclarr/Command/Services/RadarrService.cs index a42aa038..0b294794 100644 --- a/src/Recyclarr/Command/Services/RadarrService.cs +++ b/src/Recyclarr/Command/Services/RadarrService.cs @@ -1,5 +1,7 @@ -using Recyclarr.Config; +using System.IO.Abstractions; +using Recyclarr.Config; using Serilog; +using TrashLib; using TrashLib.Extensions; using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat; @@ -11,6 +13,8 @@ public class RadarrService : ServiceBase { private readonly IConfigurationLoader _configLoader; private readonly Func _customFormatUpdaterFactory; + private readonly IFileSystem _fs; + private readonly IAppPaths _paths; private readonly ILogger _log; private readonly Func _qualityUpdaterFactory; @@ -18,14 +22,20 @@ public class RadarrService : ServiceBase ILogger log, IConfigurationLoader configLoader, Func qualityUpdaterFactory, - Func customFormatUpdaterFactory) + Func customFormatUpdaterFactory, + IFileSystem fs, + IAppPaths paths) { _log = log; _configLoader = configLoader; _qualityUpdaterFactory = qualityUpdaterFactory; _customFormatUpdaterFactory = customFormatUpdaterFactory; + _fs = fs; + _paths = paths; } + public string DefaultCacheStoragePath => _fs.Path.Combine(_paths.CacheDirectory, "radarr"); + protected override async Task Process(IRadarrCommand cmd) { foreach (var config in _configLoader.LoadMany(cmd.Config, "radarr")) diff --git a/src/Recyclarr/Command/Services/SonarrService.cs b/src/Recyclarr/Command/Services/SonarrService.cs index 60920df2..c046c713 100644 --- a/src/Recyclarr/Command/Services/SonarrService.cs +++ b/src/Recyclarr/Command/Services/SonarrService.cs @@ -1,6 +1,8 @@ -using CliFx.Exceptions; +using System.IO.Abstractions; +using CliFx.Exceptions; using Recyclarr.Config; using Serilog; +using TrashLib; using TrashLib.Extensions; using TrashLib.Sonarr; using TrashLib.Sonarr.Config; @@ -16,21 +18,29 @@ public class SonarrService : ServiceBase private readonly Func _profileUpdaterFactory; private readonly Func _qualityUpdaterFactory; private readonly IReleaseProfileLister _lister; + private readonly IFileSystem _fs; + private readonly IAppPaths _paths; public SonarrService( ILogger log, IConfigurationLoader configLoader, Func profileUpdaterFactory, Func qualityUpdaterFactory, - IReleaseProfileLister lister) + IReleaseProfileLister lister, + IFileSystem fs, + IAppPaths paths) { _log = log; _configLoader = configLoader; _profileUpdaterFactory = profileUpdaterFactory; _qualityUpdaterFactory = qualityUpdaterFactory; _lister = lister; + _fs = fs; + _paths = paths; } + public string DefaultCacheStoragePath => _fs.Path.Combine(_paths.CacheDirectory, "sonarr"); + protected override async Task Process(ISonarrCommand cmd) { if (cmd.ListReleaseProfiles) diff --git a/src/Recyclarr/Command/SonarrCommand.cs b/src/Recyclarr/Command/SonarrCommand.cs index 343881c8..81f53217 100644 --- a/src/Recyclarr/Command/SonarrCommand.cs +++ b/src/Recyclarr/Command/SonarrCommand.cs @@ -1,15 +1,18 @@ -using CliFx.Attributes; +using System.IO.Abstractions; +using CliFx.Attributes; using JetBrains.Annotations; using Recyclarr.Command.Initialization; using Recyclarr.Command.Services; +using TrashLib; namespace Recyclarr.Command; [Command("sonarr", Description = "Perform operations on a Sonarr instance")] [UsedImplicitly] -internal class SonarrCommand : ServiceCommand, ISonarrCommand +public class SonarrCommand : ServiceCommand, ISonarrCommand { private readonly Lazy _service; + private readonly string? _cacheStoragePath; [CommandOption("list-release-profiles", Description = "List available release profiles from the guide in YAML format.")] @@ -22,12 +25,19 @@ internal class SonarrCommand : ServiceCommand, ISonarrCommand "Note that not every release profile has terms that may be filtered.")] public string? ListTerms { get; [UsedImplicitly] set; } = "empty"; - public override string CacheStoragePath { get; } = - Path.Combine(AppPaths.AppDataPath, "cache", "sonarr"); + public sealed override string CacheStoragePath + { + get => _cacheStoragePath ?? _service.Value.DefaultCacheStoragePath; + protected init => _cacheStoragePath = value; + } public override string Name => "Sonarr"; - public SonarrCommand(IServiceInitializationAndCleanup init, Lazy service) + public SonarrCommand( + IServiceInitializationAndCleanup init, + Lazy service, + IFileSystem fs, + IAppPaths paths) : base(init) { _service = service; diff --git a/src/Recyclarr/CompositionRoot.cs b/src/Recyclarr/CompositionRoot.cs index fbdd8c58..edf60670 100644 --- a/src/Recyclarr/CompositionRoot.cs +++ b/src/Recyclarr/CompositionRoot.cs @@ -14,10 +14,10 @@ using Recyclarr.Logging; using Recyclarr.Migration; using Serilog; using Serilog.Core; +using TrashLib; using TrashLib.Cache; using TrashLib.Config; using TrashLib.Radarr; -using TrashLib.Radarr.Config; using TrashLib.Repo; using TrashLib.Sonarr; using TrashLib.Startup; @@ -43,7 +43,7 @@ public static class CompositionRoot builder.RegisterModule(); builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As().SingleInstance(); builder.RegisterGeneric(typeof(ConfigurationLoader<>)) .WithProperty(new AutowiringParameter()) @@ -80,7 +80,6 @@ public static class CompositionRoot builder.RegisterSource(); builder.RegisterType().As(); - builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); builder.RegisterModule(); @@ -91,6 +90,7 @@ public static class CompositionRoot CommandRegistrations(builder); SetupLogging(builder); + builder.RegisterModule(); builder.RegisterModule(); builder.RegisterModule(); builder.RegisterModule(); diff --git a/src/Recyclarr/Logging/LogJanitor.cs b/src/Recyclarr/Logging/LogJanitor.cs index a50dbe9e..53516a59 100644 --- a/src/Recyclarr/Logging/LogJanitor.cs +++ b/src/Recyclarr/Logging/LogJanitor.cs @@ -1,19 +1,24 @@ using System.IO.Abstractions; +using TrashLib; namespace Recyclarr.Logging; public class LogJanitor : ILogJanitor { - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fs; + private readonly IAppPaths _paths; - public LogJanitor(IFileSystem fileSystem) + public LogJanitor(IFileSystem fs, IAppPaths paths) { - _fileSystem = fileSystem; + _fs = fs; + _paths = paths; } public void DeleteOldestLogFiles(int numberOfNewestToKeep) { - foreach (var file in _fileSystem.DirectoryInfo.FromDirectoryName(AppPaths.LogDirectory).GetFiles() + var dir = _fs.Directory.CreateDirectory(_paths.LogDirectory); + + foreach (var file in dir.GetFiles() .OrderByDescending(f => f.Name) .Skip(numberOfNewestToKeep)) { diff --git a/src/Recyclarr/Logging/LoggerFactory.cs b/src/Recyclarr/Logging/LoggerFactory.cs index 84a4a4e8..3af00220 100644 --- a/src/Recyclarr/Logging/LoggerFactory.cs +++ b/src/Recyclarr/Logging/LoggerFactory.cs @@ -1,23 +1,26 @@ using System.IO.Abstractions; using Serilog; using Serilog.Core; +using TrashLib; namespace Recyclarr.Logging; public class LoggerFactory { private readonly IFileSystem _fs; + private readonly IAppPaths _paths; private readonly LoggingLevelSwitch _logLevel; - public LoggerFactory(IFileSystem fs, LoggingLevelSwitch logLevel) + public LoggerFactory(IFileSystem fs, IAppPaths paths, LoggingLevelSwitch logLevel) { _fs = fs; + _paths = paths; _logLevel = logLevel; } public ILogger Create() { - var logPath = _fs.Path.Combine(AppPaths.LogDirectory, $"trash_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"); + var logPath = _fs.Path.Combine(_paths.LogDirectory, $"trash_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"); const string consoleTemplate = "[{Level:u3}] {Message:lj}{NewLine}{Exception}"; diff --git a/src/Recyclarr/Program.cs b/src/Recyclarr/Program.cs index e34888b6..e39848f4 100644 --- a/src/Recyclarr/Program.cs +++ b/src/Recyclarr/Program.cs @@ -18,14 +18,12 @@ internal static class Program { _container = CompositionRoot.Setup(); - var console = _container.Resolve(); - var status = await new CliApplicationBuilder() .AddCommands(GetRegisteredCommandTypes()) .SetExecutableName(ExecutableName) .SetVersion(BuildVersion()) .UseTypeActivator(type => CliTypeActivator.ResolveType(_container, type)) - .UseConsole(console) + .UseConsole(_container.Resolve()) .Build() .RunAsync(); diff --git a/src/Recyclarr/ResourcePaths.cs b/src/Recyclarr/ResourcePaths.cs deleted file mode 100644 index 2dd67b2b..00000000 --- a/src/Recyclarr/ResourcePaths.cs +++ /dev/null @@ -1,9 +0,0 @@ -using TrashLib.Radarr.Config; - -namespace Recyclarr; - -public class ResourcePaths : IResourcePaths -{ - public string RepoPath => AppPaths.RepoDirectory; - public string SettingsPath => AppPaths.DefaultSettingsPath; -} diff --git a/src/TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs b/src/TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs index 8f0e8545..9ba72a78 100644 --- a/src/TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs +++ b/src/TrashLib.Tests/Config/Settings/SettingsPersisterTest.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using TestLibrary.AutoFixture; using TrashLib.Config; using TrashLib.Config.Settings; -using TrashLib.Radarr.Config; using YamlDotNet.Serialization; namespace TrashLib.Tests.Config.Settings; @@ -18,7 +17,7 @@ public class SettingsPersisterTest [Test, AutoMockData] public void Load_should_create_settings_file_if_not_exists( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fileSystem, - [Frozen] IResourcePaths paths, + [Frozen] IAppPaths paths, SettingsPersister sut) { paths.SettingsPath.Returns("test_path"); @@ -33,7 +32,7 @@ public class SettingsPersisterTest [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fileSystem, [Frozen(Matching.ImplementedInterfaces)] YamlSerializerFactory serializerFactory, [Frozen(Matching.ImplementedInterfaces)] SettingsProvider settingsProvider, - [Frozen] IResourcePaths paths, + [Frozen] IAppPaths paths, SettingsPersister sut) { paths.SettingsPath.Returns("test_path"); @@ -48,7 +47,7 @@ public class SettingsPersisterTest public void Load_data_correctly_when_file_exists( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fileSystem, [Frozen] IYamlSerializerFactory serializerFactory, - [Frozen] IResourcePaths paths, + [Frozen] IAppPaths paths, SettingsPersister sut) { // For this test, it doesn't really matter if the YAML data matches what SettingsValue expects; diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs index 5ec8445e..89bca6b9 100644 --- a/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs +++ b/src/TrashLib.Tests/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParserTest.cs @@ -4,7 +4,6 @@ using FluentAssertions; using NSubstitute; using NUnit.Framework; using TestLibrary.AutoFixture; -using TrashLib.Radarr.Config; using TrashLib.Radarr.CustomFormat.Guide; namespace TrashLib.Tests.Radarr.CustomFormat.Guide; @@ -15,11 +14,11 @@ public class LocalRepoCustomFormatJsonParserTest { [Test, AutoMockData] public void Get_custom_format_json_works( - [Frozen] IResourcePaths paths, + [Frozen] IAppPaths paths, [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fileSystem, LocalRepoCustomFormatJsonParser sut) { - paths.RepoPath.Returns(""); + paths.RepoDirectory.Returns(""); fileSystem.AddFile("docs/json/radarr/first.json", new MockFileData("first")); fileSystem.AddFile("docs/json/radarr/second.json", new MockFileData("second")); diff --git a/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParserTest.cs b/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParserTest.cs index f440453a..3523a81d 100644 --- a/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParserTest.cs +++ b/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParserTest.cs @@ -5,7 +5,6 @@ using Newtonsoft.Json; using NSubstitute; using NUnit.Framework; using TestLibrary.AutoFixture; -using TrashLib.Radarr.Config; using TrashLib.Sonarr.ReleaseProfile; using TrashLib.Sonarr.ReleaseProfile.Guide; @@ -17,7 +16,7 @@ public class LocalRepoReleaseProfileJsonParserTest { [Test, AutoMockData] public void Get_custom_format_json_works( - [Frozen] IResourcePaths paths, + [Frozen] IAppPaths paths, [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fileSystem, LocalRepoReleaseProfileJsonParser sut) { @@ -37,7 +36,7 @@ public class LocalRepoReleaseProfileJsonParserTest var mockData1 = MakeMockObject("first"); var mockData2 = MakeMockObject("second"); - paths.RepoPath.Returns(""); + paths.RepoDirectory.Returns(""); fileSystem.AddFile("docs/json/sonarr/first.json", MockFileData(mockData1)); fileSystem.AddFile("docs/json/sonarr/second.json", MockFileData(mockData2)); diff --git a/src/TrashLib/Config/Settings/SettingsPersister.cs b/src/TrashLib/Config/Settings/SettingsPersister.cs index 3c998760..74888992 100644 --- a/src/TrashLib/Config/Settings/SettingsPersister.cs +++ b/src/TrashLib/Config/Settings/SettingsPersister.cs @@ -1,17 +1,16 @@ using System.IO.Abstractions; -using TrashLib.Radarr.Config; namespace TrashLib.Config.Settings; public class SettingsPersister : ISettingsPersister { - private readonly IResourcePaths _paths; + private readonly IAppPaths _paths; private readonly ISettingsProvider _settingsProvider; private readonly IYamlSerializerFactory _serializerFactory; private readonly IFileSystem _fileSystem; public SettingsPersister( - IResourcePaths paths, + IAppPaths paths, ISettingsProvider settingsProvider, IYamlSerializerFactory serializerFactory, IFileSystem fileSystem) diff --git a/src/TrashLib/IAppPaths.cs b/src/TrashLib/IAppPaths.cs new file mode 100644 index 00000000..9251ff17 --- /dev/null +++ b/src/TrashLib/IAppPaths.cs @@ -0,0 +1,12 @@ +namespace TrashLib; + +public interface IAppPaths +{ + void SetAppDataPath(string path); + string AppDataPath { get; } + string ConfigPath { get; } + string SettingsPath { get; } + string LogDirectory { get; } + string RepoDirectory { get; } + string CacheDirectory { get; } +} diff --git a/src/TrashLib/Radarr/Config/IResourcePaths.cs b/src/TrashLib/Radarr/Config/IResourcePaths.cs deleted file mode 100644 index 5a071f42..00000000 --- a/src/TrashLib/Radarr/Config/IResourcePaths.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TrashLib.Radarr.Config; - -public interface IResourcePaths -{ - string RepoPath { get; } - string SettingsPath { get; } -} diff --git a/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs b/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs index 855af5e2..b74719fc 100644 --- a/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs +++ b/src/TrashLib/Radarr/CustomFormat/Guide/LocalRepoCustomFormatJsonParser.cs @@ -1,14 +1,13 @@ using System.IO.Abstractions; -using TrashLib.Radarr.Config; namespace TrashLib.Radarr.CustomFormat.Guide; public class LocalRepoCustomFormatJsonParser : IRadarrGuideService { private readonly IFileSystem _fileSystem; - private readonly IResourcePaths _paths; + private readonly IAppPaths _paths; - public LocalRepoCustomFormatJsonParser(IFileSystem fileSystem, IResourcePaths paths) + public LocalRepoCustomFormatJsonParser(IFileSystem fileSystem, IAppPaths paths) { _fileSystem = fileSystem; _paths = paths; @@ -16,7 +15,7 @@ public class LocalRepoCustomFormatJsonParser : IRadarrGuideService public IEnumerable GetCustomFormatJson() { - var jsonDir = Path.Combine(_paths.RepoPath, "docs/json/radarr"); + var jsonDir = Path.Combine(_paths.RepoDirectory, "docs/json/radarr"); var tasks = _fileSystem.Directory.GetFiles(jsonDir, "*.json") .Select(f => _fileSystem.File.ReadAllTextAsync(f)); diff --git a/src/TrashLib/Repo/RepoUpdater.cs b/src/TrashLib/Repo/RepoUpdater.cs index 7b0bb227..55a96f67 100644 --- a/src/TrashLib/Repo/RepoUpdater.cs +++ b/src/TrashLib/Repo/RepoUpdater.cs @@ -2,7 +2,6 @@ using Common; using LibGit2Sharp; using Serilog; using TrashLib.Config.Settings; -using TrashLib.Radarr.Config; using VersionControl; namespace TrashLib.Repo; @@ -16,7 +15,7 @@ public class RepoUpdater : IRepoUpdater public RepoUpdater( ILogger log, - IResourcePaths paths, + IAppPaths paths, IGitRepositoryFactory repositoryFactory, IFileUtilities fileUtils, ISettingsProvider settingsProvider) @@ -25,7 +24,7 @@ public class RepoUpdater : IRepoUpdater _repositoryFactory = repositoryFactory; _fileUtils = fileUtils; _settingsProvider = settingsProvider; - RepoPath = paths.RepoPath; + RepoPath = paths.RepoDirectory; } public string RepoPath { get; } diff --git a/src/TrashLib/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParser.cs b/src/TrashLib/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParser.cs index 391fb3ef..7a504063 100644 --- a/src/TrashLib/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParser.cs +++ b/src/TrashLib/Sonarr/ReleaseProfile/Guide/LocalRepoReleaseProfileJsonParser.cs @@ -3,17 +3,16 @@ using Common.Extensions; using Common.FluentValidation; using MoreLinq; using Newtonsoft.Json; -using TrashLib.Radarr.Config; namespace TrashLib.Sonarr.ReleaseProfile.Guide; public class LocalRepoReleaseProfileJsonParser : ISonarrGuideService { private readonly IFileSystem _fileSystem; - private readonly IResourcePaths _paths; + private readonly IAppPaths _paths; private readonly Lazy> _data; - public LocalRepoReleaseProfileJsonParser(IFileSystem fileSystem, IResourcePaths paths) + public LocalRepoReleaseProfileJsonParser(IFileSystem fileSystem, IAppPaths paths) { _fileSystem = fileSystem; _paths = paths; @@ -23,7 +22,7 @@ public class LocalRepoReleaseProfileJsonParser : ISonarrGuideService private IEnumerable GetReleaseProfileDataImpl() { var converter = new TermDataConverter(); - var jsonDir = Path.Combine(_paths.RepoPath, "docs/json/sonarr"); + var jsonDir = _fileSystem.Path.Combine(_paths.RepoDirectory, "docs/json/sonarr"); var tasks = _fileSystem.Directory.GetFiles(jsonDir, "*.json") .Select(async f => {