diff --git a/CHANGELOG.md b/CHANGELOG.md index 654205b2..63049b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 application continues as if there was no cache at all. - Fix a bug that resulted in certain custom formats not having their scores set in quality profiles. +- Fixed an issue where multiple instance configuration was not working. ### Changed diff --git a/src/Trash.Tests/Cache/ServiceCacheTest.cs b/src/Trash.Tests/Cache/ServiceCacheTest.cs index 314151f4..d264b120 100644 --- a/src/Trash.Tests/Cache/ServiceCacheTest.cs +++ b/src/Trash.Tests/Cache/ServiceCacheTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.IO.Abstractions; using FluentAssertions; @@ -21,12 +22,17 @@ namespace Trash.Tests.Cache { Filesystem = fs ?? Substitute.For(); StoragePath = Substitute.For(); - ServiceConfig = Substitute.For(); - Cache = new ServiceCache(Filesystem, StoragePath, ServiceConfig, Substitute.For()); + ConfigProvider = Substitute.For(); + + // Set up a default for the active config's base URL. This is used to generate part of the path + ConfigProvider.ActiveConfiguration = Substitute.For(); + ConfigProvider.ActiveConfiguration.BaseUrl.Returns("http://localhost:1234"); + + Cache = new ServiceCache(Filesystem, StoragePath, ConfigProvider, Substitute.For()); } public ServiceCache Cache { get; } - public IServiceConfiguration ServiceConfig { get; } + public IConfigurationProvider ConfigProvider { get; } public ICacheStoragePath StoragePath { get; } public IFileSystem Filesystem { get; } } @@ -74,7 +80,7 @@ namespace Trash.Tests.Cache obj.Should().NotBeNull(); obj!.TestValue.Should().Be("Foo"); - ctx.Filesystem.File.Received().ReadAllText(Path.Combine("testpath", "c59d1c81", $"{ValidObjectName}.json")); + ctx.Filesystem.File.Received().ReadAllText(Path.Combine("testpath", "be8fbc8f", $"{ValidObjectName}.json")); } [Test] @@ -110,7 +116,7 @@ namespace Trash.Tests.Cache ctx.Cache.Save(new ObjectWithAttribute {TestValue = "Foo"}); - var expectedParentDirectory = Path.Combine("testpath", "c59d1c81"); + var expectedParentDirectory = Path.Combine("testpath", "be8fbc8f"); ctx.Filesystem.Directory.Received().CreateDirectory(expectedParentDirectory); dynamic expectedJson = new {TestValue = "Foo"}; @@ -143,6 +149,31 @@ namespace Trash.Tests.Cache .WithMessage("CacheObjectNameAttribute is missing*"); } + [Test] + public void Switching_config_and_base_url_should_yield_different_cache_paths() + { + var ctx = new Context(); + ctx.StoragePath.Path.Returns("testpath"); + + var actualPaths = new List(); + + dynamic testJson = new {TestValue = "Foo"}; + ctx.Filesystem.File.Exists(Arg.Any()).Returns(true); + ctx.Filesystem.File.ReadAllText(Arg.Do(s => actualPaths.Add(s))) + .Returns(_ => JsonConvert.SerializeObject(testJson)); + + ctx.Cache.Load(); + + // Change the active config & base URL so we get a different path + ctx.ConfigProvider.ActiveConfiguration = Substitute.For(); + ctx.ConfigProvider.ActiveConfiguration.BaseUrl.Returns("http://localhost:5678"); + + ctx.Cache.Load(); + + actualPaths.Count.Should().Be(2); + actualPaths.Should().OnlyHaveUniqueItems(); + } + [Test] public void When_cache_file_is_empty_do_not_throw() { diff --git a/src/Trash.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs b/src/Trash.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs index efae91d3..f5e00129 100644 --- a/src/Trash.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs +++ b/src/Trash.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; +using Trash.Config; using Trash.Radarr; using Trash.Radarr.CustomFormat.Api; using Trash.Radarr.CustomFormat.Models; @@ -22,13 +23,15 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors var steps = Substitute.For(); var cfApi = Substitute.For(); var qpApi = Substitute.For(); - var config = new RadarrConfiguration {DeleteOldCustomFormats = true}; + + var configProvider = Substitute.For(); + configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = true}; var guideCfs = Array.Empty(); var deletedCfsInCache = new Collection(); var profileScores = new Dictionary>(); - var processor = new PersistenceProcessor(cfApi, qpApi, config, () => steps); + var processor = new PersistenceProcessor(cfApi, qpApi, configProvider, () => steps); processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores); steps.JsonTransactionStep.Received().RecordDeletions(Arg.Is(deletedCfsInCache), Arg.Any>()); @@ -40,17 +43,43 @@ namespace Trash.Tests.Radarr.CustomFormat.Processors var steps = Substitute.For(); var cfApi = Substitute.For(); var qpApi = Substitute.For(); - var config = new RadarrConfiguration(); // DeleteOldCustomFormats should default to false + + var configProvider = Substitute.For(); + configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = false}; var guideCfs = Array.Empty(); var deletedCfsInCache = Array.Empty(); var profileScores = new Dictionary>(); - var processor = new PersistenceProcessor(cfApi, qpApi, config, () => steps); + var processor = new PersistenceProcessor(cfApi, qpApi, configProvider, () => steps); processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores); steps.JsonTransactionStep.DidNotReceive() .RecordDeletions(Arg.Any>(), Arg.Any>()); } + + [Test] + public void Different_active_configuration_is_properly_used() + { + var steps = Substitute.For(); + var cfApi = Substitute.For(); + var qpApi = Substitute.For(); + var configProvider = Substitute.For(); + + var guideCfs = Array.Empty(); + var deletedCfsInCache = Array.Empty(); + var profileScores = new Dictionary>(); + + var processor = new PersistenceProcessor(cfApi, qpApi, configProvider, () => steps); + + configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = false}; + processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores); + + configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = true}; + processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores); + + steps.JsonTransactionStep.Received(1) + .RecordDeletions(Arg.Any>(), Arg.Any>()); + } } } diff --git a/src/Trash/Cache/ServiceCache.cs b/src/Trash/Cache/ServiceCache.cs index 4cd79202..560520c1 100644 --- a/src/Trash/Cache/ServiceCache.cs +++ b/src/Trash/Cache/ServiceCache.cs @@ -15,17 +15,18 @@ namespace Trash.Cache public class ServiceCache : IServiceCache { private static readonly Regex AllowedObjectNameCharacters = new(@"^[\w-]+$", RegexOptions.Compiled); - private readonly IServiceConfiguration _config; + private readonly IConfigurationProvider _configProvider; private readonly IFileSystem _fileSystem; private readonly IFNV1a _hash; private readonly ICacheStoragePath _storagePath; - public ServiceCache(IFileSystem fileSystem, ICacheStoragePath storagePath, IServiceConfiguration config, + public ServiceCache(IFileSystem fileSystem, ICacheStoragePath storagePath, + IConfigurationProvider configProvider, ILogger log) { _fileSystem = fileSystem; _storagePath = storagePath; - _config = config; + _configProvider = configProvider; Log = log; _hash = FNV1aFactory.Instance.Create(FNVConfig.GetPredefinedConfig(32)); } @@ -74,7 +75,8 @@ namespace Trash.Cache private string BuildServiceGuid() { - return _hash.ComputeHash(Encoding.ASCII.GetBytes(_config.BaseUrl)).AsHexString(); + return _hash.ComputeHash(Encoding.ASCII.GetBytes(_configProvider.ActiveConfiguration.BaseUrl)) + .AsHexString(); } private string PathFromAttribute() diff --git a/src/Trash/CompositionRoot.cs b/src/Trash/CompositionRoot.cs index 9b5e2212..5b34f157 100644 --- a/src/Trash/CompositionRoot.cs +++ b/src/Trash/CompositionRoot.cs @@ -95,8 +95,10 @@ namespace Trash .As() .SingleInstance(); - builder.Register(c => c.Resolve().ActiveConfiguration) - .As(); + // note: Do not allow consumers to resolve IServiceConfiguration directly; if this gets cached + // they end up using the wrong configuration when multiple instances are used. + // builder.Register(c => c.Resolve().ActiveConfiguration) + // .As(); } private static void CommandRegistrations(ContainerBuilder builder) diff --git a/src/Trash/Radarr/CustomFormat/Api/CustomFormatService.cs b/src/Trash/Radarr/CustomFormat/Api/CustomFormatService.cs index 9cebd8b6..831e769d 100644 --- a/src/Trash/Radarr/CustomFormat/Api/CustomFormatService.cs +++ b/src/Trash/Radarr/CustomFormat/Api/CustomFormatService.cs @@ -10,11 +10,11 @@ namespace Trash.Radarr.CustomFormat.Api { internal class CustomFormatService : ICustomFormatService { - private readonly IServiceConfiguration _serviceConfig; + private readonly IConfigurationProvider _configProvider; - public CustomFormatService(IServiceConfiguration serviceConfig) + public CustomFormatService(IConfigurationProvider configProvider) { - _serviceConfig = serviceConfig; + _configProvider = configProvider; } public async Task> GetCustomFormats() @@ -51,7 +51,7 @@ namespace Trash.Radarr.CustomFormat.Api private string BaseUrl() { - return _serviceConfig.BuildUrl(); + return _configProvider.ActiveConfiguration.BuildUrl(); } } } diff --git a/src/Trash/Radarr/CustomFormat/Api/QualityProfileService.cs b/src/Trash/Radarr/CustomFormat/Api/QualityProfileService.cs index 7a306b38..f7fccf54 100644 --- a/src/Trash/Radarr/CustomFormat/Api/QualityProfileService.cs +++ b/src/Trash/Radarr/CustomFormat/Api/QualityProfileService.cs @@ -9,14 +9,14 @@ namespace Trash.Radarr.CustomFormat.Api { internal class QualityProfileService : IQualityProfileService { - private readonly IServiceConfiguration _serviceConfig; + private readonly IConfigurationProvider _configProvider; - public QualityProfileService(IServiceConfiguration serviceConfig) + public QualityProfileService(IConfigurationProvider configProvider) { - _serviceConfig = serviceConfig; + _configProvider = configProvider; } - private string BaseUrl => _serviceConfig.BuildUrl(); + private string BaseUrl => _configProvider.ActiveConfiguration.BuildUrl(); public async Task> GetQualityProfiles() { diff --git a/src/Trash/Radarr/CustomFormat/Models/Cache/CustomFormatMapping.cs b/src/Trash/Radarr/CustomFormat/Models/Cache/CustomFormatCache.cs similarity index 100% rename from src/Trash/Radarr/CustomFormat/Models/Cache/CustomFormatMapping.cs rename to src/Trash/Radarr/CustomFormat/Models/Cache/CustomFormatCache.cs diff --git a/src/Trash/Radarr/CustomFormat/Processors/PersistenceProcessor.cs b/src/Trash/Radarr/CustomFormat/Processors/PersistenceProcessor.cs index aaee8d99..8a92c1df 100644 --- a/src/Trash/Radarr/CustomFormat/Processors/PersistenceProcessor.cs +++ b/src/Trash/Radarr/CustomFormat/Processors/PersistenceProcessor.cs @@ -18,7 +18,7 @@ namespace Trash.Radarr.CustomFormat.Processors internal class PersistenceProcessor : IPersistenceProcessor { - private readonly RadarrConfiguration _config; + private readonly IConfigurationProvider _configProvider; private readonly ICustomFormatService _customFormatService; private readonly IQualityProfileService _qualityProfileService; private readonly Func _stepsFactory; @@ -27,13 +27,13 @@ namespace Trash.Radarr.CustomFormat.Processors public PersistenceProcessor( ICustomFormatService customFormatService, IQualityProfileService qualityProfileService, - IServiceConfiguration config, + IConfigurationProvider configProvider, Func stepsFactory) { _customFormatService = customFormatService; _qualityProfileService = qualityProfileService; _stepsFactory = stepsFactory; - _config = (RadarrConfiguration) config; + _configProvider = configProvider; _steps = _stepsFactory(); } @@ -63,7 +63,8 @@ namespace Trash.Radarr.CustomFormat.Processors _steps.JsonTransactionStep.Process(guideCfs, radarrCfs); // Step 1.1: Optionally record deletions of custom formats in cache but not in the guide - if (_config.DeleteOldCustomFormats) + var config = (RadarrConfiguration) _configProvider.ActiveConfiguration; + if (config.DeleteOldCustomFormats) { _steps.JsonTransactionStep.RecordDeletions(deletedCfsInCache, radarrCfs); } diff --git a/src/Trash/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs b/src/Trash/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs index e79228ff..d5ac8576 100644 --- a/src/Trash/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs +++ b/src/Trash/Radarr/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs @@ -21,8 +21,7 @@ namespace Trash.Radarr.CustomFormat.Processors.PersistenceSteps IDictionary> cfScores) { var radarrProfiles = (await api.GetQualityProfiles()) - .Select(p => (Name: p["name"].ToString(), Json: p)) - .ToList(); + .Select(p => (Name: p["name"].ToString(), Json: p)); var profileScores = cfScores .GroupJoin(radarrProfiles, diff --git a/src/Trash/Radarr/QualityDefinition/Api/QualityDefinitionService.cs b/src/Trash/Radarr/QualityDefinition/Api/QualityDefinitionService.cs index 2952770d..bd0730b6 100644 --- a/src/Trash/Radarr/QualityDefinition/Api/QualityDefinitionService.cs +++ b/src/Trash/Radarr/QualityDefinition/Api/QualityDefinitionService.cs @@ -9,14 +9,14 @@ namespace Trash.Radarr.QualityDefinition.Api { public class QualityDefinitionService : IQualityDefinitionService { - private readonly IServiceConfiguration _serviceConfig; + private readonly IConfigurationProvider _configProvider; - public QualityDefinitionService(IServiceConfiguration serviceConfig) + public QualityDefinitionService(IConfigurationProvider configProvider) { - _serviceConfig = serviceConfig; + _configProvider = configProvider; } - private string BaseUrl => _serviceConfig.BuildUrl(); + private string BaseUrl => _configProvider.ActiveConfiguration.BuildUrl(); public async Task> GetQualityDefinition() { diff --git a/src/Trash/Sonarr/Api/SonarrApi.cs b/src/Trash/Sonarr/Api/SonarrApi.cs index 17e45769..be02d4a9 100644 --- a/src/Trash/Sonarr/Api/SonarrApi.cs +++ b/src/Trash/Sonarr/Api/SonarrApi.cs @@ -10,11 +10,11 @@ namespace Trash.Sonarr.Api { public class SonarrApi : ISonarrApi { - private readonly IServiceConfiguration _config; + private readonly IConfigurationProvider _configProvider; - public SonarrApi(IServiceConfiguration config) + public SonarrApi(IConfigurationProvider configProvider) { - _config = config; + _configProvider = configProvider; } public async Task GetVersion() @@ -78,9 +78,6 @@ namespace Trash.Sonarr.Api .ReceiveJson>(); } - private string BaseUrl() - { - return _config.BuildUrl(); - } + private string BaseUrl() => _configProvider.ActiveConfiguration.BaseUrl; } }