diff --git a/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs b/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs index 7068836b..96d1dbc8 100644 --- a/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs +++ b/src/Recyclarr.Cli.TestLibrary/IntegrationFixture.cs @@ -1,7 +1,6 @@ using System.IO.Abstractions; using System.IO.Abstractions.Extensions; using System.IO.Abstractions.TestingHelpers; -using System.Reactive.Linq; using Autofac; using Autofac.Features.ResolveAnything; using NSubstitute; @@ -10,7 +9,6 @@ using Recyclarr.Common; using Recyclarr.Common.TestLibrary; using Recyclarr.TestLibrary; using Recyclarr.TrashLib; -using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Repo.VersionControl; using Recyclarr.TrashLib.Services.System; using Recyclarr.TrashLib.Startup; @@ -43,12 +41,11 @@ public abstract class IntegrationFixture : IDisposable builder.RegisterMockFor(); builder.RegisterMockFor(); - builder.RegisterMockFor(); builder.RegisterMockFor(); builder.RegisterMockFor(m => { // By default, choose some extremely high number so that all the newest features are enabled. - m.Version.Returns(_ => Observable.Return(new Version("99.0.0.0"))); + m.GetVersion(default!).ReturnsForAnyArgs(_ => new Version("99.0.0.0")); }); RegisterExtraTypes(builder); diff --git a/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs b/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs index 79770c13..d7a391df 100644 --- a/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs +++ b/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs @@ -1,49 +1,36 @@ -using Autofac; using FluentAssertions; using NSubstitute; using NUnit.Framework; using Recyclarr.Cli.Console.Helpers; -using Recyclarr.Cli.TestLibrary; +using Recyclarr.TestLibrary.AutoFixture; using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.Cli.Tests.Console.Helpers; [TestFixture] [Parallelizable(ParallelScope.All)] -public class CacheStoragePathTest : IntegrationFixture +public class CacheStoragePathTest { - [Test] - public void Use_guid_when_no_name() + [Test, AutoMockData] + public void Use_guid_when_no_name(CacheStoragePath sut) { var config = Substitute.ForPartsOf(); config.BaseUrl = new Uri("http://something"); config.InstanceName = null; - using var scope = Container.BeginLifetimeScope(builder => - { - builder.RegisterInstance(config).AsImplementedInterfaces(); - }); - - var sut = scope.Resolve(); - var result = sut.CalculatePath("obj"); + var result = sut.CalculatePath(config, "obj"); result.FullName.Should().MatchRegex(@".*[/\\][a-f0-9]+[/\\]obj\.json$"); } - [Test] - public void Use_name_when_not_null() + [Test, AutoMockData] + public void Use_name_when_not_null(CacheStoragePath sut) { var config = Substitute.ForPartsOf(); config.BaseUrl = new Uri("http://something"); config.InstanceName = "thename"; - using var scope = Container.BeginLifetimeScope(builder => - { - builder.RegisterInstance(config).AsImplementedInterfaces(); - }); - - var sut = scope.Resolve(); - var result = sut.CalculatePath("obj"); + var result = sut.CalculatePath(config, "obj"); result.FullName.Should().MatchRegex(@".*[/\\]thename_[a-f0-9]+[/\\]obj\.json$"); } diff --git a/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs b/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs index 104e99e3..44747a2c 100644 --- a/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs +++ b/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs @@ -11,39 +11,36 @@ namespace Recyclarr.Cli.Console.Helpers; public class CacheStoragePath : ICacheStoragePath { private readonly IAppPaths _paths; - private readonly IServiceConfiguration _config; private readonly IFNV1a _hash; public CacheStoragePath( - IAppPaths paths, - IServiceConfiguration config) + IAppPaths paths) { _paths = paths; - _config = config; _hash = FNV1aFactory.Instance.Create(FNVConfig.GetPredefinedConfig(32)); } - private string BuildUniqueServiceDir() + private string BuildUniqueServiceDir(IServiceConfiguration config) { // In the future, once array-style configurations are removed, the service name will no longer be optional // and the below condition can be removed and the logic simplified. var dirName = new StringBuilder(); - if (_config.InstanceName is not null) + if (config.InstanceName is not null) { - dirName.Append($"{_config.InstanceName}_"); + dirName.Append($"{config.InstanceName}_"); } - var url = _config.BaseUrl.OriginalString; + var url = config.BaseUrl.OriginalString; var guid = _hash.ComputeHash(Encoding.ASCII.GetBytes(url)).AsHexString(); dirName.Append(guid); return dirName.ToString(); } - public IFileInfo CalculatePath(string cacheObjectName) + public IFileInfo CalculatePath(IServiceConfiguration config, string cacheObjectName) { return _paths.CacheDirectory - .SubDirectory(_config.ServiceType.ToString().ToLower(CultureInfo.CurrentCulture)) - .SubDirectory(BuildUniqueServiceDir()) + .SubDirectory(config.ServiceType.ToString().ToLower(CultureInfo.CurrentCulture)) + .SubDirectory(BuildUniqueServiceDir(config)) .File(cacheObjectName + ".json"); } } diff --git a/src/Recyclarr.TrashLib.Tests/Cache/ServiceCacheTest.cs b/src/Recyclarr.TrashLib.Tests/Cache/ServiceCacheTest.cs index b852e8d0..a77552d5 100644 --- a/src/Recyclarr.TrashLib.Tests/Cache/ServiceCacheTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Cache/ServiceCacheTest.cs @@ -6,6 +6,7 @@ using NSubstitute; using NUnit.Framework; using Recyclarr.TestLibrary.AutoFixture; using Recyclarr.TrashLib.Cache; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; namespace Recyclarr.TrashLib.Tests.Cache; @@ -34,9 +35,10 @@ public class ServiceCacheTest [Test, AutoMockData] public void Load_returns_null_when_file_does_not_exist( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, + IServiceConfiguration config, ServiceCache sut) { - var result = sut.Load(); + var result = sut.Load(config); result.Should().BeNull(); } @@ -44,6 +46,7 @@ public class ServiceCacheTest public void Loading_with_attribute_parses_correctly( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, [Frozen] ICacheStoragePath storage, + IServiceConfiguration config, ServiceCache sut) { const string testJson = @"{'test_value': 'Foo'}"; @@ -51,18 +54,20 @@ public class ServiceCacheTest const string testJsonPath = "cacheFile.json"; fs.AddFile(testJsonPath, new MockFileData(testJson)); - storage.CalculatePath(default!).ReturnsForAnyArgs(fs.FileInfo.New(testJsonPath)); + storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New(testJsonPath)); - var obj = sut.Load(); + var obj = sut.Load(config); obj.Should().NotBeNull(); obj!.TestValue.Should().Be("Foo"); } [Test, AutoMockData] - public void Loading_with_invalid_object_name_throws(ServiceCache sut) + public void Loading_with_invalid_object_name_throws( + IServiceConfiguration config, + ServiceCache sut) { - Action act = () => sut.Load(); + Action act = () => sut.Load(config); act.Should() .Throw() @@ -70,9 +75,11 @@ public class ServiceCacheTest } [Test, AutoMockData] - public void Loading_without_attribute_throws(ServiceCache sut) + public void Loading_without_attribute_throws( + IServiceConfiguration config, + ServiceCache sut) { - Action act = () => sut.Load(); + Action act = () => sut.Load(config); act.Should() .Throw() @@ -83,15 +90,17 @@ public class ServiceCacheTest public void Properties_are_saved_using_snake_case( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, [Frozen] ICacheStoragePath storage, + IServiceConfiguration config, ServiceCache sut) { - storage.CalculatePath(default!).ReturnsForAnyArgs(_ => fs.FileInfo.New($"{ValidObjectName}.json")); + storage.CalculatePath(default!, default!) + .ReturnsForAnyArgs(_ => fs.FileInfo.New($"{ValidObjectName}.json")); - sut.Save(new ObjectWithAttribute {TestValue = "Foo"}); + sut.Save(new ObjectWithAttribute {TestValue = "Foo"}, config); fs.AllFiles.Should().ContainMatch($"*{ValidObjectName}.json"); - var file = fs.GetFile(storage.CalculatePath("").FullName); + var file = fs.GetFile(storage.CalculatePath(config, "").FullName); file.Should().NotBeNull(); file.TextContents.Should().Contain("\"test_value\""); } @@ -100,12 +109,13 @@ public class ServiceCacheTest public void Saving_with_attribute_parses_correctly( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, [Frozen] ICacheStoragePath storage, + IServiceConfiguration config, ServiceCache sut) { const string testJsonPath = "cacheFile.json"; - storage.CalculatePath(default!).ReturnsForAnyArgs(fs.FileInfo.New(testJsonPath)); + storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New(testJsonPath)); - sut.Save(new ObjectWithAttribute {TestValue = "Foo"}); + sut.Save(new ObjectWithAttribute {TestValue = "Foo"}, config); var expectedFile = fs.GetFile(testJsonPath); expectedFile.Should().NotBeNull(); @@ -115,9 +125,11 @@ public class ServiceCacheTest } [Test, AutoMockData] - public void Saving_with_invalid_object_name_throws(ServiceCache sut) + public void Saving_with_invalid_object_name_throws( + IServiceConfiguration config, + ServiceCache sut) { - var act = () => sut.Save(new ObjectWithAttributeInvalidChars()); + var act = () => sut.Save(new ObjectWithAttributeInvalidChars(), config); act.Should() .Throw() @@ -125,9 +137,11 @@ public class ServiceCacheTest } [Test, AutoMockData] - public void Saving_without_attribute_throws(ServiceCache sut) + public void Saving_without_attribute_throws( + IServiceConfiguration config, + ServiceCache sut) { - var act = () => sut.Save(new ObjectWithoutAttribute()); + var act = () => sut.Save(new ObjectWithoutAttribute(), config); act.Should() .Throw() @@ -138,13 +152,14 @@ public class ServiceCacheTest public void Switching_config_and_base_url_should_yield_different_cache_paths( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, [Frozen] ICacheStoragePath storage, + IServiceConfiguration config, ServiceCache sut) { - storage.CalculatePath(default!).ReturnsForAnyArgs(fs.FileInfo.New("Foo.json")); - sut.Save(new ObjectWithAttribute {TestValue = "Foo"}); + storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New("Foo.json")); + sut.Save(new ObjectWithAttribute {TestValue = "Foo"}, config); - storage.CalculatePath(default!).ReturnsForAnyArgs(fs.FileInfo.New("Bar.json")); - sut.Save(new ObjectWithAttribute {TestValue = "Bar"}); + storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New("Bar.json")); + sut.Save(new ObjectWithAttribute {TestValue = "Bar"}, config); var expectedFiles = new[] {"*Foo.json", "*Bar.json"}; foreach (var expectedFile in expectedFiles) @@ -157,12 +172,13 @@ public class ServiceCacheTest public void When_cache_file_is_empty_do_not_throw( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, [Frozen] ICacheStoragePath storage, + IServiceConfiguration config, ServiceCache sut) { - storage.CalculatePath(default!).ReturnsForAnyArgs(fs.FileInfo.New("cacheFile.json")); + storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New("cacheFile.json")); fs.AddFile("cacheFile.json", new MockFileData("")); - Action act = () => sut.Load(); + Action act = () => sut.Load(config); act.Should().NotThrow(); } @@ -171,6 +187,7 @@ public class ServiceCacheTest public void Name_properties_are_set_on_load( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, [Frozen] ICacheStoragePath storage, + IServiceConfiguration config, ServiceCache sut) { const string cacheJson = @" @@ -187,9 +204,9 @@ public class ServiceCacheTest "; fs.AddFile("cacheFile.json", new MockFileData(cacheJson)); - storage.CalculatePath(default!).ReturnsForAnyArgs(fs.FileInfo.New("cacheFile.json")); + storage.CalculatePath(default!, default!).ReturnsForAnyArgs(fs.FileInfo.New("cacheFile.json")); - var result = sut.Load(); + var result = sut.Load(config); result.Should().BeEquivalentTo(new CustomFormatCache { diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs index 824c270d..8e38e6de 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/CachePersisterTest.cs @@ -3,6 +3,7 @@ using FluentAssertions; using NSubstitute; using NUnit.Framework; using Recyclarr.TrashLib.Cache; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; @@ -33,14 +34,15 @@ public class CachePersisterTest public void Set_loaded_cache_to_null_if_versions_mismatch(int versionToTest) { var ctx = new Context(); + var config = Substitute.For(); var testCfObj = new CustomFormatCache { Version = versionToTest, TrashIdMappings = new Collection {new("", "", 5)} }; - ctx.ServiceCache.Load().Returns(testCfObj); - ctx.Persister.Load(); + ctx.ServiceCache.Load(config).Returns(testCfObj); + ctx.Persister.Load(config); ctx.Persister.CfCache.Should().BeNull(); } @@ -48,14 +50,15 @@ public class CachePersisterTest public void Accept_loaded_cache_when_versions_match() { var ctx = new Context(); + var config = Substitute.For(); var testCfObj = new CustomFormatCache { Version = CustomFormatCache.LatestVersion, TrashIdMappings = new Collection {new("", "", 5)} }; - ctx.ServiceCache.Load().Returns(testCfObj); - ctx.Persister.Load(); + ctx.ServiceCache.Load(config).Returns(testCfObj); + ctx.Persister.Load(config); ctx.Persister.CfCache.Should().NotBeNull(); } @@ -64,9 +67,10 @@ public class CachePersisterTest { var ctx = new Context(); var testCfObj = new CustomFormatCache(); - ctx.ServiceCache.Load().Returns(testCfObj); + var config = Substitute.For(); - ctx.Persister.Load(); + ctx.ServiceCache.Load(config).Returns(testCfObj); + ctx.Persister.Load(config); ctx.Persister.CfCache.Should().BeSameAs(testCfObj); } @@ -74,7 +78,9 @@ public class CachePersisterTest public void Cf_cache_returns_null_if_not_loaded() { var ctx = new Context(); - ctx.Persister.Load(); + var config = Substitute.For(); + + ctx.Persister.Load(config); ctx.Persister.CfCache.Should().BeNull(); } @@ -83,33 +89,39 @@ public class CachePersisterTest { var ctx = new Context(); var testCfObj = new CustomFormatCache(); - ctx.ServiceCache.Load().Returns(testCfObj); + var config = Substitute.For(); + + ctx.ServiceCache.Load(config).Returns(testCfObj); - ctx.Persister.Load(); - ctx.Persister.Save(); + ctx.Persister.Load(config); + ctx.Persister.Save(config); - ctx.ServiceCache.Received().Save(Arg.Is(testCfObj)); + ctx.ServiceCache.Received().Save(testCfObj, config); } [Test] public void Saving_without_loading_does_nothing() { var ctx = new Context(); - ctx.Persister.Save(); - ctx.ServiceCache.DidNotReceive().Save(Arg.Any()); + var config = Substitute.For(); + + ctx.Persister.Save(config); + ctx.ServiceCache.DidNotReceiveWithAnyArgs().Save(Arg.Any(), default!); } [Test] public void Updating_overwrites_previous_cf_cache_and_updates_cf_data() { var ctx = new Context(); + var config = Substitute.For(); // Load initial CfCache just to test that it gets replaced - ctx.ServiceCache.Load().Returns(new CustomFormatCache + ctx.ServiceCache.Load(config).Returns(new CustomFormatCache { TrashIdMappings = new Collection {new("trashid", "", 1)} }); - ctx.Persister.Load(); + + ctx.Persister.Load(config); // Update with new cached items var customFormatData = new List diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceProcessorTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceProcessorTest.cs index c3c7744b..50fdebf3 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceProcessorTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceProcessorTest.cs @@ -19,7 +19,6 @@ public class PersistenceProcessorTest { var steps = Substitute.For(); var cfApi = Substitute.For(); - var qpApi = Substitute.For(); var config = new RadarrConfiguration {DeleteOldCustomFormats = true}; @@ -27,8 +26,8 @@ public class PersistenceProcessorTest var deletedCfsInCache = new Collection(); var profileScores = new Dictionary(); - var processor = new PersistenceProcessor(cfApi, qpApi, config, steps); - await processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores); + var processor = new PersistenceProcessor(cfApi, steps); + await processor.PersistCustomFormats(config, guideCfs, deletedCfsInCache, profileScores); steps.JsonTransactionStep.Received().RecordDeletions(Arg.Is(deletedCfsInCache), Arg.Any>()); } @@ -38,7 +37,6 @@ public class PersistenceProcessorTest { var steps = Substitute.For(); var cfApi = Substitute.For(); - var qpApi = Substitute.For(); var config = new RadarrConfiguration {DeleteOldCustomFormats = false}; @@ -46,8 +44,8 @@ public class PersistenceProcessorTest var deletedCfsInCache = Array.Empty(); var profileScores = new Dictionary(); - var processor = new PersistenceProcessor(cfApi, qpApi, config, steps); - await processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores); + var processor = new PersistenceProcessor(cfApi, steps); + await processor.PersistCustomFormats(config, guideCfs, deletedCfsInCache, profileScores); steps.JsonTransactionStep.DidNotReceive() .RecordDeletions(Arg.Any>(), Arg.Any>()); diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs index ee5e0ddc..ebb4803a 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStepTest.cs @@ -1,5 +1,6 @@ using NSubstitute; using NUnit.Framework; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Api; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; @@ -28,14 +29,15 @@ public class CustomFormatApiPersistenceStepTest var api = Substitute.For(); - var processor = new CustomFormatApiPersistenceStep(); - await processor.Process(api, transactions); + var processor = new CustomFormatApiPersistenceStep(api); + var config = Substitute.For(); + await processor.Process(config, transactions); Received.InOrder(() => { - api.CreateCustomFormat(transactions.NewCustomFormats.First()); - api.UpdateCustomFormat(transactions.UpdatedCustomFormats.First()); - api.DeleteCustomFormat(4); + api.CreateCustomFormat(config, transactions.NewCustomFormats.First()); + api.UpdateCustomFormat(config, transactions.UpdatedCustomFormats.First()); + api.DeleteCustomFormat(config, 4); }); } } diff --git a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs index e1f36e73..963c6a88 100644 --- a/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs +++ b/src/Recyclarr.TrashLib.Tests/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStepTest.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; using Recyclarr.TestLibrary.NSubstitute; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Api; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; @@ -41,7 +42,8 @@ public class QualityProfileApiPersistenceStepTest }]"; var api = Substitute.For(); - api.GetQualityProfiles()!.Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); + api.GetQualityProfiles(default!)!.ReturnsForAnyArgs( + JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { @@ -50,10 +52,10 @@ public class QualityProfileApiPersistenceStepTest } }; - var processor = new QualityProfileApiPersistenceStep(); - await processor.Process(api, cfScores); + var processor = new QualityProfileApiPersistenceStep(api); + await processor.Process(Substitute.For(), cfScores); - await api.DidNotReceive().UpdateQualityProfile(Arg.Any(), Arg.Any()); + await api.DidNotReceiveWithAnyArgs().UpdateQualityProfile(default!, default!, default!); } [Test] @@ -62,15 +64,16 @@ public class QualityProfileApiPersistenceStepTest const string radarrQualityProfileData = @"[{'name': 'profile1'}]"; var api = Substitute.For(); - api.GetQualityProfiles()!.Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); + api.GetQualityProfiles(default!)!.ReturnsForAnyArgs( + JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { {"wrong_profile_name", CfTestUtils.NewMapping()} }; - var processor = new QualityProfileApiPersistenceStep(); - await processor.Process(api, cfScores); + var processor = new QualityProfileApiPersistenceStep(api); + await processor.Process(Substitute.For(), cfScores); processor.InvalidProfileNames.Should().Equal("wrong_profile_name"); processor.UpdatedScores.Should().BeEmpty(); @@ -101,7 +104,8 @@ public class QualityProfileApiPersistenceStepTest }]"; var api = Substitute.For(); - api.GetQualityProfiles()!.Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); + api.GetQualityProfiles(default!)!.ReturnsForAnyArgs( + JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { @@ -111,8 +115,8 @@ public class QualityProfileApiPersistenceStepTest } }; - var processor = new QualityProfileApiPersistenceStep(); - await processor.Process(api, cfScores); + var processor = new QualityProfileApiPersistenceStep(api); + await processor.Process(Substitute.For(), cfScores); processor.InvalidProfileNames.Should().BeEmpty(); processor.UpdatedScores.Should() @@ -125,6 +129,7 @@ public class QualityProfileApiPersistenceStepTest }); await api.Received().UpdateQualityProfile( + Arg.Any(), Verify.That(j => j["formatItems"]!.Children().Should().HaveCount(3)), Arg.Any()); } @@ -174,7 +179,8 @@ public class QualityProfileApiPersistenceStepTest }]"; var api = Substitute.For(); - api.GetQualityProfiles()!.Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); + api.GetQualityProfiles(default!)!.ReturnsForAnyArgs( + JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { @@ -189,8 +195,9 @@ public class QualityProfileApiPersistenceStepTest } }; - var processor = new QualityProfileApiPersistenceStep(); - await processor.Process(api, cfScores); + var processor = new QualityProfileApiPersistenceStep(api); + var config = Substitute.For(); + await processor.Process(config, cfScores); var expectedProfileJson = JObject.Parse(@"{ 'name': 'profile1', @@ -234,7 +241,10 @@ public class QualityProfileApiPersistenceStepTest }"); await api.Received() - .UpdateQualityProfile(Verify.That(a => a.Should().BeEquivalentTo(expectedProfileJson)), 1); + .UpdateQualityProfile( + config, + Verify.That(a => a.Should().BeEquivalentTo(expectedProfileJson)), + 1); processor.InvalidProfileNames.Should().BeEmpty(); processor.UpdatedScores.Should() .ContainKey("profile1").WhoseValue.Should() diff --git a/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCapabilityEnforcerTest.cs b/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCapabilityEnforcerTest.cs index 48d970ea..4c9b249e 100644 --- a/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCapabilityEnforcerTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCapabilityEnforcerTest.cs @@ -21,11 +21,11 @@ public class SonarrCapabilityEnforcerTest { var config = new SonarrConfiguration(); - checker.GetCapabilities().Returns((SonarrCapabilities?) null); + checker.GetCapabilities(default!).ReturnsForAnyArgs((SonarrCapabilities?) null); var act = () => sut.Check(config); - act.Should().Throw().WithMessage("*obtained*"); + act.Should().ThrowAsync().WithMessage("*obtained*"); } [Test, AutoMockData] @@ -35,14 +35,14 @@ public class SonarrCapabilityEnforcerTest { var config = new SonarrConfiguration(); - checker.GetCapabilities().Returns(new SonarrCapabilities(new Version()) + checker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities(new Version()) { SupportsNamedReleaseProfiles = false }); var act = () => sut.Check(config); - act.Should().Throw().WithMessage("*minimum*"); + act.Should().ThrowAsync().WithMessage("*minimum*"); } [Test, AutoMockData] @@ -58,7 +58,7 @@ public class SonarrCapabilityEnforcerTest } }; - checker.GetCapabilities().Returns(new SonarrCapabilities(new Version()) + checker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities(new Version()) { SupportsNamedReleaseProfiles = true, SupportsCustomFormats = true @@ -66,7 +66,7 @@ public class SonarrCapabilityEnforcerTest var act = () => sut.Check(config); - act.Should().Throw().WithMessage("*v3*"); + act.Should().ThrowAsync().WithMessage("*v3*"); } [Test, AutoMockData] @@ -82,7 +82,7 @@ public class SonarrCapabilityEnforcerTest } }; - checker.GetCapabilities().Returns(new SonarrCapabilities(new Version()) + checker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities(new Version()) { SupportsNamedReleaseProfiles = true, SupportsCustomFormats = false @@ -90,6 +90,6 @@ public class SonarrCapabilityEnforcerTest var act = () => sut.Check(config); - act.Should().Throw().WithMessage("*v4*"); + act.Should().ThrowAsync().WithMessage("*v4*"); } } diff --git a/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs b/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs index 91b826f0..e529415e 100644 --- a/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Sonarr/SonarrCompatibilityTest.cs @@ -7,6 +7,7 @@ using NSubstitute; using NUnit.Framework; using Recyclarr.Cli.TestLibrary; using Recyclarr.TestLibrary; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.ReleaseProfile.Api; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects; using Recyclarr.TrashLib.Services.Sonarr.Capabilities; @@ -58,10 +59,10 @@ public class SonarrCompatibilityTest : IntegrationFixture } [Test] - public void Send_v2_to_v1() + public async Task Send_v2_to_v1() { var capabilityChecker = Resolve(); - capabilityChecker.GetCapabilities().Returns(new SonarrCapabilities(new Version()) + capabilityChecker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities(new Version()) { ArraysNeededForReleaseProfileRequiredAndIgnored = false }); @@ -69,16 +70,16 @@ public class SonarrCompatibilityTest : IntegrationFixture var sut = Resolve(); var data = new SonarrReleaseProfile {Ignored = new List {"one", "two", "three"}}; - var result = sut.CompatibleReleaseProfileForSending(data); + var result = await sut.CompatibleReleaseProfileForSending(Substitute.For(), data); result.Should().BeEquivalentTo(new SonarrReleaseProfileV1 {Ignored = "one,two,three"}); } [Test] - public void Send_v2_to_v2() + public async Task Send_v2_to_v2() { var capabilityChecker = Resolve(); - capabilityChecker.GetCapabilities().Returns(new SonarrCapabilities(new Version()) + capabilityChecker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities(new Version()) { ArraysNeededForReleaseProfileRequiredAndIgnored = true }); @@ -86,7 +87,7 @@ public class SonarrCompatibilityTest : IntegrationFixture var sut = Resolve(); var data = new SonarrReleaseProfile {Ignored = new List {"one", "two", "three"}}; - var result = sut.CompatibleReleaseProfileForSending(data); + var result = await sut.CompatibleReleaseProfileForSending(Substitute.For(), data); result.Should().BeEquivalentTo(data); } diff --git a/src/Recyclarr.TrashLib/Cache/ICacheStoragePath.cs b/src/Recyclarr.TrashLib/Cache/ICacheStoragePath.cs index 180e98b1..5a0c4f4b 100644 --- a/src/Recyclarr.TrashLib/Cache/ICacheStoragePath.cs +++ b/src/Recyclarr.TrashLib/Cache/ICacheStoragePath.cs @@ -1,8 +1,9 @@ using System.IO.Abstractions; +using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Cache; public interface ICacheStoragePath { - IFileInfo CalculatePath(string cacheObjectName); + IFileInfo CalculatePath(IServiceConfiguration config, string cacheObjectName); } diff --git a/src/Recyclarr.TrashLib/Cache/IServiceCache.cs b/src/Recyclarr.TrashLib/Cache/IServiceCache.cs index dadd6598..84c55d18 100644 --- a/src/Recyclarr.TrashLib/Cache/IServiceCache.cs +++ b/src/Recyclarr.TrashLib/Cache/IServiceCache.cs @@ -1,7 +1,9 @@ +using Recyclarr.TrashLib.Config.Services; + namespace Recyclarr.TrashLib.Cache; public interface IServiceCache { - T? Load() where T : class; - void Save(T obj) where T : class; + T? Load(IServiceConfiguration config) where T : class; + void Save(T obj, IServiceConfiguration config) where T : class; } diff --git a/src/Recyclarr.TrashLib/Cache/ServiceCache.cs b/src/Recyclarr.TrashLib/Cache/ServiceCache.cs index 0a25fbb0..3df4749d 100644 --- a/src/Recyclarr.TrashLib/Cache/ServiceCache.cs +++ b/src/Recyclarr.TrashLib/Cache/ServiceCache.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Recyclarr.Common.Extensions; +using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Cache; @@ -29,9 +30,9 @@ public class ServiceCache : IServiceCache private ILogger Log { get; } - public T? Load() where T : class + public T? Load(IServiceConfiguration config) where T : class { - var path = PathFromAttribute(); + var path = PathFromAttribute(config); if (!path.Exists) { return null; @@ -52,9 +53,9 @@ public class ServiceCache : IServiceCache return null; } - public void Save(T obj) where T : class + public void Save(T obj, IServiceConfiguration config) where T : class { - var path = PathFromAttribute(); + var path = PathFromAttribute(config); path.CreateParentDirectory(); var serializer = JsonSerializer.Create(_jsonSettings); @@ -74,7 +75,7 @@ public class ServiceCache : IServiceCache return attribute.Name; } - private IFileInfo PathFromAttribute() + private IFileInfo PathFromAttribute(IServiceConfiguration config) { var objectName = GetCacheObjectNameAttribute(); if (!AllowedObjectNameCharacters.IsMatch(objectName)) @@ -82,6 +83,6 @@ public class ServiceCache : IServiceCache throw new ArgumentException($"Object name '{objectName}' has unacceptable characters"); } - return _storagePath.CalculatePath(objectName); + return _storagePath.CalculatePath(config, objectName); } } diff --git a/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs b/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs index 1074f6ea..d1825a8e 100644 --- a/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs +++ b/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs @@ -7,4 +7,6 @@ public interface IServiceConfiguration Uri BaseUrl { get; } string ApiKey { get; } bool DeleteOldCustomFormats { get; } + ICollection CustomFormats { get; init; } + QualityDefinitionConfig? QualityDefinition { get; init; } } diff --git a/src/Recyclarr.TrashLib/Http/IServiceRequestBuilder.cs b/src/Recyclarr.TrashLib/Http/IServiceRequestBuilder.cs index 3142cc34..13767f7c 100644 --- a/src/Recyclarr.TrashLib/Http/IServiceRequestBuilder.cs +++ b/src/Recyclarr.TrashLib/Http/IServiceRequestBuilder.cs @@ -1,8 +1,9 @@ using Flurl.Http; +using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Http; public interface IServiceRequestBuilder { - IFlurlRequest Request(params object[] path); + IFlurlRequest Request(IServiceConfiguration config, params object[] path); } diff --git a/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs b/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs index b3002b83..7d8c2096 100644 --- a/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs +++ b/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs @@ -5,19 +5,17 @@ namespace Recyclarr.TrashLib.Http; public class ServiceRequestBuilder : IServiceRequestBuilder { - private readonly IServiceConfiguration _config; private readonly IFlurlClientFactory _clientFactory; - public ServiceRequestBuilder(IServiceConfiguration config, IFlurlClientFactory clientFactory) + public ServiceRequestBuilder(IFlurlClientFactory clientFactory) { - _config = config; _clientFactory = clientFactory; } - public IFlurlRequest Request(params object[] path) + public IFlurlRequest Request(IServiceConfiguration config, params object[] path) { - var client = _clientFactory.BuildClient(_config.BaseUrl); + var client = _clientFactory.BuildClient(config.BaseUrl); return client.Request(new[] {"api", "v3"}.Concat(path).ToArray()) - .SetQueryParams(new {apikey = _config.ApiKey}); + .SetQueryParams(new {apikey = config.ApiKey}); } } diff --git a/src/Recyclarr.TrashLib/Services/Common/ServiceCapabilityChecker.cs b/src/Recyclarr.TrashLib/Services/Common/ServiceCapabilityChecker.cs index f4993728..cff6d27b 100644 --- a/src/Recyclarr.TrashLib/Services/Common/ServiceCapabilityChecker.cs +++ b/src/Recyclarr.TrashLib/Services/Common/ServiceCapabilityChecker.cs @@ -1,24 +1,21 @@ -using System.Reactive.Linq; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.System; namespace Recyclarr.TrashLib.Services.Common; public abstract class ServiceCapabilityChecker where T : class { - private readonly IObservable _capabilities; + private readonly IServiceInformation _info; - public T? GetCapabilities() + protected ServiceCapabilityChecker(IServiceInformation info) { - return _capabilities.Wait(); + _info = info; } - protected ServiceCapabilityChecker(IServiceInformation info) + public async Task GetCapabilities(IServiceConfiguration config) { - _capabilities = info.Version - .Select(x => x is null ? null : BuildCapabilitiesObject(x)) - .Replay(1) - .AutoConnect() - .LastAsync(); + var version = await _info.GetVersion(config); + return version is not null ? BuildCapabilitiesObject(version) : null; } protected abstract T BuildCapabilitiesObject(Version version); diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs index 38b350b4..2a7288e1 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/CustomFormatService.cs @@ -1,5 +1,6 @@ using Flurl.Http; using Newtonsoft.Json.Linq; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; using Recyclarr.TrashLib.Services.CustomFormat.Models; @@ -14,15 +15,15 @@ internal class CustomFormatService : ICustomFormatService _service = service; } - public async Task> GetCustomFormats() + public async Task> GetCustomFormats(IServiceConfiguration config) { - return await _service.Request("customformat") + return await _service.Request(config, "customformat") .GetJsonAsync>(); } - public async Task CreateCustomFormat(ProcessedCustomFormatData cf) + public async Task CreateCustomFormat(IServiceConfiguration config, ProcessedCustomFormatData cf) { - var response = await _service.Request("customformat") + var response = await _service.Request(config, "customformat") .PostJsonAsync(cf.Json) .ReceiveJson(); @@ -32,16 +33,16 @@ internal class CustomFormatService : ICustomFormatService } } - public async Task UpdateCustomFormat(ProcessedCustomFormatData cf) + public async Task UpdateCustomFormat(IServiceConfiguration config, ProcessedCustomFormatData cf) { - await _service.Request("customformat", cf.FormatId) + await _service.Request(config, "customformat", cf.FormatId) .PutJsonAsync(cf.Json) .ReceiveJson(); } - public async Task DeleteCustomFormat(int customFormatId) + public async Task DeleteCustomFormat(IServiceConfiguration config, int customFormatId) { - await _service.Request("customformat", customFormatId) + await _service.Request(config, "customformat", customFormatId) .DeleteAsync(); } } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/ICustomFormatService.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/ICustomFormatService.cs index d413d144..3ccee682 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/ICustomFormatService.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/ICustomFormatService.cs @@ -1,12 +1,13 @@ using Newtonsoft.Json.Linq; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Models; namespace Recyclarr.TrashLib.Services.CustomFormat.Api; public interface ICustomFormatService { - Task> GetCustomFormats(); - Task CreateCustomFormat(ProcessedCustomFormatData cf); - Task UpdateCustomFormat(ProcessedCustomFormatData cf); - Task DeleteCustomFormat(int customFormatId); + Task> GetCustomFormats(IServiceConfiguration config); + Task CreateCustomFormat(IServiceConfiguration config, ProcessedCustomFormatData cf); + Task UpdateCustomFormat(IServiceConfiguration config, ProcessedCustomFormatData cf); + Task DeleteCustomFormat(IServiceConfiguration config, int customFormatId); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/IQualityProfileService.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/IQualityProfileService.cs index b3fd141d..ef696f49 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/IQualityProfileService.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/IQualityProfileService.cs @@ -1,9 +1,10 @@ using Newtonsoft.Json.Linq; +using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Services.CustomFormat.Api; public interface IQualityProfileService { - Task> GetQualityProfiles(); - Task UpdateQualityProfile(JObject profileJson, int id); + Task> GetQualityProfiles(IServiceConfiguration config); + Task UpdateQualityProfile(IServiceConfiguration config, JObject profileJson, int id); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/QualityProfileService.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/QualityProfileService.cs index f44c838f..408e6a97 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Api/QualityProfileService.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Api/QualityProfileService.cs @@ -1,5 +1,6 @@ using Flurl.Http; using Newtonsoft.Json.Linq; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; namespace Recyclarr.TrashLib.Services.CustomFormat.Api; @@ -13,15 +14,15 @@ internal class QualityProfileService : IQualityProfileService _service = service; } - public async Task> GetQualityProfiles() + public async Task> GetQualityProfiles(IServiceConfiguration config) { - return await _service.Request("qualityprofile") + return await _service.Request(config, "qualityprofile") .GetJsonAsync>(); } - public async Task UpdateQualityProfile(JObject profileJson, int id) + public async Task UpdateQualityProfile(IServiceConfiguration config, JObject profileJson, int id) { - return await _service.Request("qualityprofile", id) + return await _service.Request(config, "qualityprofile", id) .PutJsonAsync(profileJson) .ReceiveJson(); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs index e48b97cd..5b73d89a 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/CachePersister.cs @@ -1,5 +1,6 @@ using Recyclarr.Common.Extensions; using Recyclarr.TrashLib.Cache; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; @@ -18,9 +19,9 @@ public class CachePersister : ICachePersister private ILogger Log { get; } public CustomFormatCache? CfCache { get; private set; } - public void Load() + public void Load(IServiceConfiguration config) { - CfCache = _cache.Load(); + CfCache = _cache.Load(config); // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (CfCache != null) { @@ -41,7 +42,7 @@ public class CachePersister : ICachePersister } } - public void Save() + public void Save(IServiceConfiguration config) { if (CfCache == null) { @@ -50,7 +51,7 @@ public class CachePersister : ICachePersister } Log.Debug("Saving Cache"); - _cache.Save(CfCache); + _cache.Save(CfCache, config); } public void Update(IEnumerable customFormats) diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/CustomFormatUpdater.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/CustomFormatUpdater.cs index 15f3b3d1..563c753d 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/CustomFormatUpdater.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/CustomFormatUpdater.cs @@ -1,5 +1,4 @@ using Recyclarr.Common.Extensions; -using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Processors; using Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; @@ -29,11 +28,11 @@ internal class CustomFormatUpdater : ICustomFormatUpdater _console = console; } - public async Task Process(bool isPreview, IEnumerable configs, SupportedServices serviceType) + public async Task Process(bool isPreview, IServiceConfiguration config) { - _cache.Load(); + _cache.Load(config); - await _guideProcessor.BuildGuideDataAsync(configs, _cache.CfCache, serviceType); + await _guideProcessor.BuildGuideDataAsync(config.CustomFormats, _cache.CfCache, config.ServiceType); if (!ValidateGuideDataAndCheckShouldProceed()) { @@ -48,6 +47,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater } await _persistenceProcessor.PersistCustomFormats( + config, _guideProcessor.ProcessedCustomFormats, _guideProcessor.DeletedCustomFormatsInCache, _guideProcessor.ProfileScores); @@ -57,7 +57,7 @@ internal class CustomFormatUpdater : ICustomFormatUpdater // Cache all the custom formats (using ID from API response). _cache.Update(_guideProcessor.ProcessedCustomFormats); - _cache.Save(); + _cache.Save(config); } private void PrintQualityProfileUpdates() diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/ICachePersister.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/ICachePersister.cs index c1ddc4e1..7a7cfe79 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/ICachePersister.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/ICachePersister.cs @@ -1,3 +1,4 @@ +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; @@ -6,7 +7,7 @@ namespace Recyclarr.TrashLib.Services.CustomFormat; public interface ICachePersister { CustomFormatCache? CfCache { get; } - void Load(); - void Save(); + void Load(IServiceConfiguration config); + void Save(IServiceConfiguration config); void Update(IEnumerable customFormats); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/ICustomFormatUpdater.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/ICustomFormatUpdater.cs index eaad2498..f830b958 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/ICustomFormatUpdater.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/ICustomFormatUpdater.cs @@ -1,9 +1,8 @@ -using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Services.CustomFormat; public interface ICustomFormatUpdater { - Task Process(bool isPreview, IEnumerable configs, SupportedServices serviceType); + Task Process(bool isPreview, IServiceConfiguration config); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/IPersistenceProcessor.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/IPersistenceProcessor.cs index a67b3a53..74a60269 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/IPersistenceProcessor.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/IPersistenceProcessor.cs @@ -1,3 +1,4 @@ +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Models; using Recyclarr.TrashLib.Services.CustomFormat.Models.Cache; using Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; @@ -10,7 +11,9 @@ public interface IPersistenceProcessor IReadOnlyCollection InvalidProfileNames { get; } CustomFormatTransactionData Transactions { get; } - Task PersistCustomFormats(IReadOnlyCollection guideCfs, + Task PersistCustomFormats( + IServiceConfiguration config, + IReadOnlyCollection guideCfs, IEnumerable deletedCfsInCache, IDictionary profileScores); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceProcessor.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceProcessor.cs index 4c1f10a9..5cc30d00 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceProcessor.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceProcessor.cs @@ -15,20 +15,14 @@ public interface IPersistenceProcessorSteps internal class PersistenceProcessor : IPersistenceProcessor { - private readonly IServiceConfiguration _config; private readonly ICustomFormatService _customFormatService; - private readonly IQualityProfileService _qualityProfileService; private readonly IPersistenceProcessorSteps _steps; public PersistenceProcessor( ICustomFormatService customFormatService, - IQualityProfileService qualityProfileService, - IServiceConfiguration config, IPersistenceProcessorSteps steps) { _customFormatService = customFormatService; - _qualityProfileService = qualityProfileService; - _config = config; _steps = steps; } @@ -42,11 +36,12 @@ internal class PersistenceProcessor : IPersistenceProcessor => _steps.ProfileQualityProfileApiPersister.InvalidProfileNames; public async Task PersistCustomFormats( + IServiceConfiguration config, IReadOnlyCollection guideCfs, IEnumerable deletedCfsInCache, IDictionary profileScores) { - var serviceCfs = await _customFormatService.GetCustomFormats(); + var serviceCfs = await _customFormatService.GetCustomFormats(config); // Step 1: Match CFs between the guide & Radarr and merge the data. The goal is to retain as much of the // original data from Radarr as possible. There are many properties in the response JSON that we don't @@ -54,17 +49,16 @@ internal class PersistenceProcessor : IPersistenceProcessor _steps.JsonTransactionStep.Process(guideCfs, serviceCfs); // Step 1.1: Optionally record deletions of custom formats in cache but not in the guide - if (_config.DeleteOldCustomFormats) + if (config.DeleteOldCustomFormats) { _steps.JsonTransactionStep.RecordDeletions(deletedCfsInCache, serviceCfs); } // Step 2: For each merged CF, persist it to Radarr via its API. This will involve a combination of updates // to existing CFs and creation of brand new ones, depending on what's already available in Radarr. - await _steps.CustomFormatCustomFormatApiPersister.Process(_customFormatService, - _steps.JsonTransactionStep.Transactions); + await _steps.CustomFormatCustomFormatApiPersister.Process(config, _steps.JsonTransactionStep.Transactions); // Step 3: Update all quality profiles with the scores from the guide for the uploaded custom formats - await _steps.ProfileQualityProfileApiPersister.Process(_qualityProfileService, profileScores); + await _steps.ProfileQualityProfileApiPersister.Process(config, profileScores); } } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStep.cs index 8dff3636..32107c38 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/CustomFormatApiPersistenceStep.cs @@ -1,24 +1,32 @@ +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Api; namespace Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; internal class CustomFormatApiPersistenceStep : ICustomFormatApiPersistenceStep { - public async Task Process(ICustomFormatService api, CustomFormatTransactionData transactions) + private readonly ICustomFormatService _api; + + public CustomFormatApiPersistenceStep(ICustomFormatService api) + { + _api = api; + } + + public async Task Process(IServiceConfiguration config, CustomFormatTransactionData transactions) { foreach (var cf in transactions.NewCustomFormats) { - await api.CreateCustomFormat(cf); + await _api.CreateCustomFormat(config, cf); } foreach (var cf in transactions.UpdatedCustomFormats) { - await api.UpdateCustomFormat(cf); + await _api.UpdateCustomFormat(config, cf); } foreach (var cfId in transactions.DeletedCustomFormatIds) { - await api.DeleteCustomFormat(cfId.CustomFormatId); + await _api.DeleteCustomFormat(config, cfId.CustomFormatId); } } } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/ICustomFormatApiPersistenceStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/ICustomFormatApiPersistenceStep.cs index 8c43afa2..ea2c7c75 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/ICustomFormatApiPersistenceStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/ICustomFormatApiPersistenceStep.cs @@ -1,8 +1,8 @@ -using Recyclarr.TrashLib.Services.CustomFormat.Api; +using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; public interface ICustomFormatApiPersistenceStep { - Task Process(ICustomFormatService api, CustomFormatTransactionData transactions); + Task Process(IServiceConfiguration config, CustomFormatTransactionData transactions); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/IQualityProfileApiPersistenceStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/IQualityProfileApiPersistenceStep.cs index 9e475b0d..e71a26b4 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/IQualityProfileApiPersistenceStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/IQualityProfileApiPersistenceStep.cs @@ -1,4 +1,4 @@ -using Recyclarr.TrashLib.Services.CustomFormat.Api; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Models; namespace Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; @@ -8,6 +8,7 @@ public interface IQualityProfileApiPersistenceStep IDictionary> UpdatedScores { get; } IReadOnlyCollection InvalidProfileNames { get; } - Task Process(IQualityProfileService api, + Task Process( + IServiceConfiguration config, IDictionary cfScores); } diff --git a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs index 55ee6a66..3612b3ff 100644 --- a/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs +++ b/src/Recyclarr.TrashLib/Services/CustomFormat/Processors/PersistenceSteps/QualityProfileApiPersistenceStep.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json.Linq; using Recyclarr.Common.Extensions; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.CustomFormat.Api; using Recyclarr.TrashLib.Services.CustomFormat.Models; @@ -7,16 +8,23 @@ namespace Recyclarr.TrashLib.Services.CustomFormat.Processors.PersistenceSteps; internal class QualityProfileApiPersistenceStep : IQualityProfileApiPersistenceStep { + private readonly IQualityProfileService _api; private readonly List _invalidProfileNames = new(); private readonly Dictionary> _updatedScores = new(); public IDictionary> UpdatedScores => _updatedScores; public IReadOnlyCollection InvalidProfileNames => _invalidProfileNames; - public async Task Process(IQualityProfileService api, + public QualityProfileApiPersistenceStep(IQualityProfileService api) + { + _api = api; + } + + public async Task Process( + IServiceConfiguration config, IDictionary cfScores) { - var serviceProfiles = await api.GetQualityProfiles(); + var serviceProfiles = await _api.GetQualityProfiles(config); // Match quality profiles in Radarr to ones the user put in their config. // For each match, we return a tuple including the list of custom format scores ("formatItems"). @@ -71,7 +79,7 @@ internal class QualityProfileApiPersistenceStep : IQualityProfileApiPersistenceS } var jsonRoot = (JObject) formatItems.First().Root; - await api.UpdateQualityProfile(jsonRoot, jsonRoot.Value("id")); + await _api.UpdateQualityProfile(config, jsonRoot, jsonRoot.Value("id")); } } diff --git a/src/Recyclarr.TrashLib/Services/Processors/RadarrProcessor.cs b/src/Recyclarr.TrashLib/Services/Processors/RadarrProcessor.cs index fcf3dad3..3fd89917 100644 --- a/src/Recyclarr.TrashLib/Services/Processors/RadarrProcessor.cs +++ b/src/Recyclarr.TrashLib/Services/Processors/RadarrProcessor.cs @@ -1,4 +1,3 @@ -using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Services.CustomFormat; using Recyclarr.TrashLib.Services.QualitySize; using Recyclarr.TrashLib.Services.Radarr.Config; @@ -7,18 +6,15 @@ namespace Recyclarr.TrashLib.Services.Processors; public class RadarrProcessor : IServiceProcessor { - private readonly ILogger _log; private readonly ICustomFormatUpdater _cfUpdater; private readonly IQualitySizeUpdater _qualityUpdater; private readonly RadarrConfiguration _config; public RadarrProcessor( - ILogger log, ICustomFormatUpdater cfUpdater, IQualitySizeUpdater qualityUpdater, RadarrConfiguration config) { - _log = log; _cfUpdater = cfUpdater; _qualityUpdater = qualityUpdater; _config = config; @@ -26,23 +22,14 @@ public class RadarrProcessor : IServiceProcessor public async Task Process(ISyncSettings settings) { - var didWork = false; - if (_config.QualityDefinition != null) { - await _qualityUpdater.Process(settings.Preview, _config.QualityDefinition, SupportedServices.Radarr); - didWork = true; + await _qualityUpdater.Process(settings.Preview, _config); } if (_config.CustomFormats.Count > 0) { - await _cfUpdater.Process(settings.Preview, _config.CustomFormats, SupportedServices.Radarr); - didWork = true; - } - - if (!didWork) - { - _log.Information("Nothing to do"); + await _cfUpdater.Process(settings.Preview, _config); } } } diff --git a/src/Recyclarr.TrashLib/Services/Processors/SonarrProcessor.cs b/src/Recyclarr.TrashLib/Services/Processors/SonarrProcessor.cs index d81b2cc9..0ef49f94 100644 --- a/src/Recyclarr.TrashLib/Services/Processors/SonarrProcessor.cs +++ b/src/Recyclarr.TrashLib/Services/Processors/SonarrProcessor.cs @@ -1,4 +1,3 @@ -using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Services.CustomFormat; using Recyclarr.TrashLib.Services.QualitySize; using Recyclarr.TrashLib.Services.ReleaseProfile; @@ -35,7 +34,7 @@ public class SonarrProcessor : IServiceProcessor public async Task Process(ISyncSettings settings) { // Any compatibility failures will be thrown as exceptions - _compatibilityEnforcer.Check(_config); + await _compatibilityEnforcer.Check(_config); var didWork = false; @@ -47,13 +46,13 @@ public class SonarrProcessor : IServiceProcessor if (_config.QualityDefinition != null) { - await _qualityUpdater.Process(settings.Preview, _config.QualityDefinition, SupportedServices.Sonarr); + await _qualityUpdater.Process(settings.Preview, _config); didWork = true; } if (_config.CustomFormats.Count > 0) { - await _cfUpdater.Process(settings.Preview, _config.CustomFormats, SupportedServices.Sonarr); + await _cfUpdater.Process(settings.Preview, _config); didWork = true; } diff --git a/src/Recyclarr.TrashLib/Services/QualitySize/Api/IQualityDefinitionService.cs b/src/Recyclarr.TrashLib/Services/QualitySize/Api/IQualityDefinitionService.cs index 6ac12d38..ad3578ce 100644 --- a/src/Recyclarr.TrashLib/Services/QualitySize/Api/IQualityDefinitionService.cs +++ b/src/Recyclarr.TrashLib/Services/QualitySize/Api/IQualityDefinitionService.cs @@ -1,7 +1,12 @@ +using Recyclarr.TrashLib.Config.Services; + namespace Recyclarr.TrashLib.Services.QualitySize.Api; public interface IQualityDefinitionService { - Task> GetQualityDefinition(); - Task> UpdateQualityDefinition(IList newQuality); + Task> GetQualityDefinition(IServiceConfiguration config); + + Task> UpdateQualityDefinition( + IServiceConfiguration config, + IList newQuality); } diff --git a/src/Recyclarr.TrashLib/Services/QualitySize/Api/QualityDefinitionService.cs b/src/Recyclarr.TrashLib/Services/QualitySize/Api/QualityDefinitionService.cs index 86c7f0a7..aa6bf923 100644 --- a/src/Recyclarr.TrashLib/Services/QualitySize/Api/QualityDefinitionService.cs +++ b/src/Recyclarr.TrashLib/Services/QualitySize/Api/QualityDefinitionService.cs @@ -1,4 +1,5 @@ using Flurl.Http; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; namespace Recyclarr.TrashLib.Services.QualitySize.Api; @@ -12,16 +13,17 @@ internal class QualityDefinitionService : IQualityDefinitionService _service = service; } - public async Task> GetQualityDefinition() + public async Task> GetQualityDefinition(IServiceConfiguration config) { - return await _service.Request("qualitydefinition") + return await _service.Request(config, "qualitydefinition") .GetJsonAsync>(); } public async Task> UpdateQualityDefinition( + IServiceConfiguration config, IList newQuality) { - return await _service.Request("qualityDefinition", "update") + return await _service.Request(config, "qualityDefinition", "update") .PutJsonAsync(newQuality) .ReceiveJson>(); } diff --git a/src/Recyclarr.TrashLib/Services/QualitySize/IQualitySizeUpdater.cs b/src/Recyclarr.TrashLib/Services/QualitySize/IQualitySizeUpdater.cs index b13b7e9a..6d8d438b 100644 --- a/src/Recyclarr.TrashLib/Services/QualitySize/IQualitySizeUpdater.cs +++ b/src/Recyclarr.TrashLib/Services/QualitySize/IQualitySizeUpdater.cs @@ -1,9 +1,8 @@ -using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config.Services; namespace Recyclarr.TrashLib.Services.QualitySize; public interface IQualitySizeUpdater { - Task Process(bool isPreview, QualityDefinitionConfig config, SupportedServices serviceType); + Task Process(bool isPreview, IServiceConfiguration config); } diff --git a/src/Recyclarr.TrashLib/Services/QualitySize/QualitySizeUpdater.cs b/src/Recyclarr.TrashLib/Services/QualitySize/QualitySizeUpdater.cs index 2fddac2e..30260e76 100644 --- a/src/Recyclarr.TrashLib/Services/QualitySize/QualitySizeUpdater.cs +++ b/src/Recyclarr.TrashLib/Services/QualitySize/QualitySizeUpdater.cs @@ -1,5 +1,4 @@ using Recyclarr.Common.Extensions; -using Recyclarr.TrashLib.Config; using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.QualitySize.Api; using Recyclarr.TrashLib.Services.QualitySize.Guide; @@ -26,40 +25,45 @@ internal class QualitySizeUpdater : IQualitySizeUpdater _guide = guide; } - public async Task Process(bool isPreview, QualityDefinitionConfig config, SupportedServices serviceType) + public async Task Process(bool isPreview, IServiceConfiguration config) { - _log.Information("Processing Quality Definition: {QualityDefinition}", config.Type); - var qualityDefinitions = _guide.GetQualitySizeData(serviceType); - var qualityTypeInConfig = config.Type; + var qualityDef = config.QualityDefinition; + if (qualityDef is null) + { + return; + } + var qualityDefinitions = _guide.GetQualitySizeData(config.ServiceType); var selectedQuality = qualityDefinitions - .FirstOrDefault(x => x.Type.EqualsIgnoreCase(qualityTypeInConfig)); + .FirstOrDefault(x => x.Type.EqualsIgnoreCase(qualityDef.Type)); if (selectedQuality == null) { - _log.Error("The specified quality definition type does not exist: {Type}", qualityTypeInConfig); + _log.Error("The specified quality definition type does not exist: {Type}", qualityDef.Type); return; } - if (config.PreferredRatio is not null) + _log.Information("Processing Quality Definition: {QualityDefinition}", qualityDef.Type); + + if (qualityDef.PreferredRatio is not null) { _log.Information("Using an explicit preferred ratio which will override values from the guide"); // Fix an out of range ratio and warn the user - if (config.PreferredRatio is < 0 or > 1) + if (qualityDef.PreferredRatio is < 0 or > 1) { - var clampedRatio = Math.Clamp(config.PreferredRatio.Value, 0, 1); + var clampedRatio = Math.Clamp(qualityDef.PreferredRatio.Value, 0, 1); _log.Warning("Your `preferred_ratio` of {CurrentRatio} is out of range. " + "It must be a decimal between 0.0 and 1.0. It has been clamped to {ClampedRatio}", - config.PreferredRatio, clampedRatio); + qualityDef.PreferredRatio, clampedRatio); - config.PreferredRatio = clampedRatio; + qualityDef.PreferredRatio = clampedRatio; } // Apply a calculated preferred size foreach (var quality in selectedQuality.Qualities) { - quality.Preferred = quality.InterpolatedPreferred(config.PreferredRatio.Value); + quality.Preferred = quality.InterpolatedPreferred(qualityDef.PreferredRatio.Value); } } @@ -69,7 +73,7 @@ internal class QualitySizeUpdater : IQualitySizeUpdater return; } - await ProcessQualityDefinition(selectedQuality.Qualities); + await ProcessQualityDefinition(config, selectedQuality.Qualities); } private void PrintQualityPreview(IReadOnlyCollection qualitySizeItems) @@ -98,9 +102,11 @@ internal class QualitySizeUpdater : IQualitySizeUpdater a.PreferredSize is not null && b.IsPreferredDifferent(a.PreferredSize); } - private async Task ProcessQualityDefinition(IReadOnlyCollection guideQuality) + private async Task ProcessQualityDefinition( + IServiceConfiguration config, + IReadOnlyCollection guideQuality) { - var serverQuality = await _api.GetQualityDefinition(); + var serverQuality = await _api.GetQualityDefinition(config); var newQuality = new List(); foreach (var qualityData in guideQuality) @@ -129,7 +135,7 @@ internal class QualitySizeUpdater : IQualitySizeUpdater serverEntry.PreferredSize); } - await _api.UpdateQualityDefinition(newQuality); + await _api.UpdateQualityDefinition(config, newQuality); _log.Information("Number of updated qualities: {Count}", newQuality.Count); } } diff --git a/src/Recyclarr.TrashLib/Services/Radarr/IRadarrCapabilityChecker.cs b/src/Recyclarr.TrashLib/Services/Radarr/IRadarrCapabilityChecker.cs deleted file mode 100644 index d7307613..00000000 --- a/src/Recyclarr.TrashLib/Services/Radarr/IRadarrCapabilityChecker.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Recyclarr.TrashLib.Services.Radarr; - -public interface IRadarrCapabilityChecker -{ - RadarrCapabilities? GetCapabilities(); -} diff --git a/src/Recyclarr.TrashLib/Services/Radarr/RadarrAutofacModule.cs b/src/Recyclarr.TrashLib/Services/Radarr/RadarrAutofacModule.cs index 286c3748..28828ede 100644 --- a/src/Recyclarr.TrashLib/Services/Radarr/RadarrAutofacModule.cs +++ b/src/Recyclarr.TrashLib/Services/Radarr/RadarrAutofacModule.cs @@ -6,7 +6,6 @@ public class RadarrAutofacModule : Module { protected override void Load(ContainerBuilder builder) { - builder.RegisterType().As() - .InstancePerLifetimeScope(); + builder.RegisterType().InstancePerLifetimeScope(); } } diff --git a/src/Recyclarr.TrashLib/Services/Radarr/RadarrCapabilityChecker.cs b/src/Recyclarr.TrashLib/Services/Radarr/RadarrCapabilityChecker.cs index 2464cfd6..7e2e21d0 100644 --- a/src/Recyclarr.TrashLib/Services/Radarr/RadarrCapabilityChecker.cs +++ b/src/Recyclarr.TrashLib/Services/Radarr/RadarrCapabilityChecker.cs @@ -3,7 +3,7 @@ using Recyclarr.TrashLib.Services.System; namespace Recyclarr.TrashLib.Services.Radarr; -public class RadarrCapabilityChecker : ServiceCapabilityChecker, IRadarrCapabilityChecker +public class RadarrCapabilityChecker : ServiceCapabilityChecker { public RadarrCapabilityChecker(IServiceInformation info) : base(info) diff --git a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/IReleaseProfileApiService.cs b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/IReleaseProfileApiService.cs index f14cdffa..09e34050 100644 --- a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/IReleaseProfileApiService.cs +++ b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/IReleaseProfileApiService.cs @@ -1,11 +1,12 @@ +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects; namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api; public interface IReleaseProfileApiService { - Task UpdateReleaseProfile(SonarrReleaseProfile profile); - Task CreateReleaseProfile(SonarrReleaseProfile profile); - Task> GetReleaseProfiles(); - Task DeleteReleaseProfile(int releaseProfileId); + Task UpdateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile); + Task CreateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile); + Task> GetReleaseProfiles(IServiceConfiguration config); + Task DeleteReleaseProfile(IServiceConfiguration config, int releaseProfileId); } diff --git a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ISonarrReleaseProfileCompatibilityHandler.cs b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ISonarrReleaseProfileCompatibilityHandler.cs index d1a9da94..7a87ecd9 100644 --- a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ISonarrReleaseProfileCompatibilityHandler.cs +++ b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ISonarrReleaseProfileCompatibilityHandler.cs @@ -1,10 +1,13 @@ using Newtonsoft.Json.Linq; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects; namespace Recyclarr.TrashLib.Services.ReleaseProfile.Api; public interface ISonarrReleaseProfileCompatibilityHandler { - object CompatibleReleaseProfileForSending(SonarrReleaseProfile profile); + Task CompatibleReleaseProfileForSending(IServiceConfiguration config, + SonarrReleaseProfile profile); + SonarrReleaseProfile CompatibleReleaseProfileForReceiving(JObject profile); } diff --git a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ReleaseProfileApiService.cs b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ReleaseProfileApiService.cs index 13ad1552..91ebe509 100644 --- a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ReleaseProfileApiService.cs +++ b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/ReleaseProfileApiService.cs @@ -1,5 +1,6 @@ using Flurl.Http; using Newtonsoft.Json.Linq; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects; @@ -18,27 +19,29 @@ public class ReleaseProfileApiService : IReleaseProfileApiService _service = service; } - public async Task UpdateReleaseProfile(SonarrReleaseProfile profile) + public async Task UpdateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile) { - var profileToSend = _profileHandler.CompatibleReleaseProfileForSending(profile); - await _service.Request("releaseprofile", profile.Id) + var profileToSend = await _profileHandler.CompatibleReleaseProfileForSending(config, profile); + await _service.Request(config, "releaseprofile", profile.Id) .PutJsonAsync(profileToSend); } - public async Task CreateReleaseProfile(SonarrReleaseProfile profile) + public async Task CreateReleaseProfile( + IServiceConfiguration config, + SonarrReleaseProfile profile) { - var profileToSend = _profileHandler.CompatibleReleaseProfileForSending(profile); + var profileToSend = _profileHandler.CompatibleReleaseProfileForSending(config, profile); - var response = await _service.Request("releaseprofile") + var response = await _service.Request(config, "releaseprofile") .PostJsonAsync(profileToSend) .ReceiveJson(); return _profileHandler.CompatibleReleaseProfileForReceiving(response); } - public async Task> GetReleaseProfiles() + public async Task> GetReleaseProfiles(IServiceConfiguration config) { - var response = await _service.Request("releaseprofile") + var response = await _service.Request(config, "releaseprofile") .GetJsonAsync>(); return response @@ -46,9 +49,9 @@ public class ReleaseProfileApiService : IReleaseProfileApiService .ToList(); } - public async Task DeleteReleaseProfile(int releaseProfileId) + public async Task DeleteReleaseProfile(IServiceConfiguration config, int releaseProfileId) { - await _service.Request("releaseprofile", releaseProfileId) + await _service.Request(config, "releaseprofile", releaseProfileId) .DeleteAsync(); } } diff --git a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/SonarrReleaseProfileCompatibilityHandler.cs b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/SonarrReleaseProfileCompatibilityHandler.cs index d00afbcf..043565ab 100644 --- a/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/SonarrReleaseProfileCompatibilityHandler.cs +++ b/src/Recyclarr.TrashLib/Services/ReleaseProfile/Api/SonarrReleaseProfileCompatibilityHandler.cs @@ -1,6 +1,7 @@ using AutoMapper; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.ExceptionTypes; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Schemas; @@ -24,9 +25,11 @@ public class SonarrReleaseProfileCompatibilityHandler : ISonarrReleaseProfileCom _mapper = mapper; } - public object CompatibleReleaseProfileForSending(SonarrReleaseProfile profile) + public async Task CompatibleReleaseProfileForSending( + IServiceConfiguration config, + SonarrReleaseProfile profile) { - var capabilities = _capabilityChecker.GetCapabilities(); + var capabilities = await _capabilityChecker.GetCapabilities(config); if (capabilities is null) { throw new ServiceIncompatibilityException("Capabilities could not be obtained"); diff --git a/src/Recyclarr.TrashLib/Services/ReleaseProfile/ReleaseProfileUpdater.cs b/src/Recyclarr.TrashLib/Services/ReleaseProfile/ReleaseProfileUpdater.cs index 9f461335..7d711354 100644 --- a/src/Recyclarr.TrashLib/Services/ReleaseProfile/ReleaseProfileUpdater.cs +++ b/src/Recyclarr.TrashLib/Services/ReleaseProfile/ReleaseProfileUpdater.cs @@ -1,4 +1,5 @@ using Recyclarr.Common.Extensions; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.ReleaseProfile.Api; using Recyclarr.TrashLib.Services.ReleaseProfile.Api.Objects; using Recyclarr.TrashLib.Services.ReleaseProfile.Filters; @@ -65,7 +66,7 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater return; } - await ProcessReleaseProfiles(filteredProfiles); + await ProcessReleaseProfiles(config, filteredProfiles); } private void PreviewReleaseProfiles(IEnumerable profiles) @@ -139,40 +140,42 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater } private async Task ProcessReleaseProfiles( + IServiceConfiguration config, List<(ReleaseProfileData Profile, IReadOnlyCollection Tags)> profilesAndTags) { // Obtain all of the existing release profiles first. If any were previously created by our program // here, we favor replacing those instead of creating new ones, which would just be mostly duplicates // (but with some differences, since there have likely been updates since the last run). - var existingProfiles = await _releaseProfileApi.GetReleaseProfiles(); + var existingProfiles = await _releaseProfileApi.GetReleaseProfiles(config); foreach (var (profile, tags) in profilesAndTags) { // If tags were provided, ensure they exist. Tags that do not exist are added first, so that we // may specify them with the release profile request payload. - var tagIds = await CreateTagsInSonarr(tags); + var tagIds = await CreateTagsInSonarr(config, tags); var title = BuildProfileTitle(profile.Name); var profileToUpdate = GetProfileToUpdate(existingProfiles, title); if (profileToUpdate != null) { _log.Information("Update existing profile: {ProfileName}", title); - await UpdateExistingProfile(profileToUpdate, profile, tagIds); + await UpdateExistingProfile(config, profileToUpdate, profile, tagIds); } else { _log.Information("Create new profile: {ProfileName}", title); - await CreateNewProfile(title, profile, tagIds); + await CreateNewProfile(config, title, profile, tagIds); } } // Any profiles with `[Trash]` in front of their name are managed exclusively by Recyclarr. As such, if // there are any still in Sonarr that we didn't update, those are most certainly old and shouldn't be kept // around anymore. - await DeleteOldManagedProfiles(profilesAndTags, existingProfiles); + await DeleteOldManagedProfiles(config, profilesAndTags, existingProfiles); } private async Task DeleteOldManagedProfiles( + IServiceConfiguration config, IEnumerable<(ReleaseProfileData Profile, IReadOnlyCollection Tags)> profilesAndTags, IEnumerable sonarrProfiles) { @@ -187,32 +190,37 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater foreach (var profile in sonarrProfilesToDelete) { _log.Information("Deleting old Trash release profile: {ProfileName}", profile.Name); - await _releaseProfileApi.DeleteReleaseProfile(profile.Id); + await _releaseProfileApi.DeleteReleaseProfile(config, profile.Id); } } - private async Task> CreateTagsInSonarr(IReadOnlyCollection tags) + private async Task> CreateTagsInSonarr( + IServiceConfiguration config, + IReadOnlyCollection tags) { if (!tags.Any()) { return Array.Empty(); } - var sonarrTags = await _tagApiService.GetTags(); - await CreateMissingTags(sonarrTags, tags); + var sonarrTags = await _tagApiService.GetTags(config); + await CreateMissingTags(config, sonarrTags, tags); return sonarrTags .Where(t => tags.Any(ct => ct.EqualsIgnoreCase(t.Label))) .Select(t => t.Id) .ToList(); } - private async Task CreateMissingTags(ICollection sonarrTags, IEnumerable configTags) + private async Task CreateMissingTags( + IServiceConfiguration config, + ICollection sonarrTags, + IEnumerable configTags) { var missingTags = configTags.Where(t => !sonarrTags.Any(t2 => t2.Label.EqualsIgnoreCase(t))); foreach (var tag in missingTags) { _log.Debug("Creating Tag: {Tag}", tag); - var newTag = await _tagApiService.CreateTag(tag); + var newTag = await _tagApiService.CreateTag(config, tag); sonarrTags.Add(newTag); } } @@ -243,18 +251,25 @@ public class ReleaseProfileUpdater : IReleaseProfileUpdater profileToUpdate.Tags = tagIds; } - private async Task UpdateExistingProfile(SonarrReleaseProfile profileToUpdate, ReleaseProfileData profile, + private async Task UpdateExistingProfile( + IServiceConfiguration config, + SonarrReleaseProfile profileToUpdate, + ReleaseProfileData profile, IReadOnlyCollection tagIds) { _log.Debug("Update existing profile with id {ProfileId}", profileToUpdate.Id); SetupProfileRequestObject(profileToUpdate, profile, tagIds); - await _releaseProfileApi.UpdateReleaseProfile(profileToUpdate); + await _releaseProfileApi.UpdateReleaseProfile(config, profileToUpdate); } - private async Task CreateNewProfile(string title, ReleaseProfileData profile, IReadOnlyCollection tagIds) + private async Task CreateNewProfile( + IServiceConfiguration config, + string title, + ReleaseProfileData profile, + IReadOnlyCollection tagIds) { var newProfile = new SonarrReleaseProfile {Name = title, Enabled = true}; SetupProfileRequestObject(newProfile, profile, tagIds); - await _releaseProfileApi.CreateReleaseProfile(newProfile); + await _releaseProfileApi.CreateReleaseProfile(config, newProfile); } } diff --git a/src/Recyclarr.TrashLib/Services/Sonarr/Api/ISonarrTagApiService.cs b/src/Recyclarr.TrashLib/Services/Sonarr/Api/ISonarrTagApiService.cs index e815e335..f281b80e 100644 --- a/src/Recyclarr.TrashLib/Services/Sonarr/Api/ISonarrTagApiService.cs +++ b/src/Recyclarr.TrashLib/Services/Sonarr/Api/ISonarrTagApiService.cs @@ -1,9 +1,10 @@ +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.Sonarr.Api.Objects; namespace Recyclarr.TrashLib.Services.Sonarr.Api; public interface ISonarrTagApiService { - Task> GetTags(); - Task CreateTag(string tag); + Task> GetTags(IServiceConfiguration config); + Task CreateTag(IServiceConfiguration config, string tag); } diff --git a/src/Recyclarr.TrashLib/Services/Sonarr/Api/SonarrTagApiService.cs b/src/Recyclarr.TrashLib/Services/Sonarr/Api/SonarrTagApiService.cs index 142f2618..2c9c5651 100644 --- a/src/Recyclarr.TrashLib/Services/Sonarr/Api/SonarrTagApiService.cs +++ b/src/Recyclarr.TrashLib/Services/Sonarr/Api/SonarrTagApiService.cs @@ -1,4 +1,5 @@ using Flurl.Http; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; using Recyclarr.TrashLib.Services.Sonarr.Api.Objects; @@ -13,15 +14,15 @@ public class SonarrTagApiService : ISonarrTagApiService _service = service; } - public async Task> GetTags() + public async Task> GetTags(IServiceConfiguration config) { - return await _service.Request("tag") + return await _service.Request(config, "tag") .GetJsonAsync>(); } - public async Task CreateTag(string tag) + public async Task CreateTag(IServiceConfiguration config, string tag) { - return await _service.Request("tag") + return await _service.Request(config, "tag") .PostJsonAsync(new {label = tag}) .ReceiveJson(); } diff --git a/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/ISonarrCapabilityChecker.cs b/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/ISonarrCapabilityChecker.cs index 36574b78..ccc4a64e 100644 --- a/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/ISonarrCapabilityChecker.cs +++ b/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/ISonarrCapabilityChecker.cs @@ -1,6 +1,8 @@ +using Recyclarr.TrashLib.Config.Services; + namespace Recyclarr.TrashLib.Services.Sonarr.Capabilities; public interface ISonarrCapabilityChecker { - SonarrCapabilities? GetCapabilities(); + Task GetCapabilities(IServiceConfiguration config); } diff --git a/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/SonarrCapabilityEnforcer.cs b/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/SonarrCapabilityEnforcer.cs index e43b0fc9..cf252565 100644 --- a/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/SonarrCapabilityEnforcer.cs +++ b/src/Recyclarr.TrashLib/Services/Sonarr/Capabilities/SonarrCapabilityEnforcer.cs @@ -13,9 +13,9 @@ public class SonarrCapabilityEnforcer _capabilityChecker = capabilityChecker; } - public void Check(SonarrConfiguration config) + public async Task Check(SonarrConfiguration config) { - var capabilities = _capabilityChecker.GetCapabilities(); + var capabilities = await _capabilityChecker.GetCapabilities(config); if (capabilities is null) { throw new ServiceIncompatibilityException("Capabilities could not be obtained"); diff --git a/src/Recyclarr.TrashLib/Services/System/IServiceInformation.cs b/src/Recyclarr.TrashLib/Services/System/IServiceInformation.cs index 26c2ac4c..4049b51b 100644 --- a/src/Recyclarr.TrashLib/Services/System/IServiceInformation.cs +++ b/src/Recyclarr.TrashLib/Services/System/IServiceInformation.cs @@ -1,6 +1,8 @@ +using Recyclarr.TrashLib.Config.Services; + namespace Recyclarr.TrashLib.Services.System; public interface IServiceInformation { - IObservable Version { get; } + public Task GetVersion(IServiceConfiguration config); } diff --git a/src/Recyclarr.TrashLib/Services/System/ISystemApiService.cs b/src/Recyclarr.TrashLib/Services/System/ISystemApiService.cs index ec07e798..dd1744ec 100644 --- a/src/Recyclarr.TrashLib/Services/System/ISystemApiService.cs +++ b/src/Recyclarr.TrashLib/Services/System/ISystemApiService.cs @@ -1,8 +1,9 @@ +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.System.Dto; namespace Recyclarr.TrashLib.Services.System; public interface ISystemApiService { - Task GetStatus(); + Task GetStatus(IServiceConfiguration config); } diff --git a/src/Recyclarr.TrashLib/Services/System/ServiceInformation.cs b/src/Recyclarr.TrashLib/Services/System/ServiceInformation.cs index 4e8aaa6c..b9ff77b5 100644 --- a/src/Recyclarr.TrashLib/Services/System/ServiceInformation.cs +++ b/src/Recyclarr.TrashLib/Services/System/ServiceInformation.cs @@ -1,40 +1,33 @@ -using System.Reactive.Concurrency; -using System.Reactive.Linq; using Flurl.Http; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; -using Recyclarr.TrashLib.Services.System.Dto; namespace Recyclarr.TrashLib.Services.System; public class ServiceInformation : IServiceInformation { + private readonly ISystemApiService _api; private readonly ILogger _log; public ServiceInformation(ISystemApiService api, ILogger log) { + _api = api; _log = log; - Version = Observable.FromAsync(async () => await api.GetStatus(), ThreadPoolScheduler.Instance) - .Timeout(TimeSpan.FromSeconds(15)) - .Do(LogServiceInfo) - .Select(x => new Version(x.Version)) - .Catch((Exception ex) => - { - log.Error("Exception trying to obtain service version: {Message}", ex switch - { - FlurlHttpException flex => flex.SanitizedExceptionMessage(), - _ => ex.Message - }); - - return Observable.Return((Version?) null); - }) - .Replay(1) - .AutoConnect(); } - public IObservable Version { get; } - - private void LogServiceInfo(SystemStatus status) + public async Task GetVersion(IServiceConfiguration config) { - _log.Debug("{Service} Version: {Version}", status.AppName, status.Version); + try + { + var status = await _api.GetStatus(config); + _log.Debug("{Service} Version: {Version}", status.AppName, status.Version); + return new Version(status.Version); + } + catch (FlurlHttpException ex) + { + _log.Error("Exception trying to obtain service version: {Message}", ex.SanitizedExceptionMessage()); + } + + return null; } } diff --git a/src/Recyclarr.TrashLib/Services/System/SystemApiService.cs b/src/Recyclarr.TrashLib/Services/System/SystemApiService.cs index 350f6d24..0ddb79f8 100644 --- a/src/Recyclarr.TrashLib/Services/System/SystemApiService.cs +++ b/src/Recyclarr.TrashLib/Services/System/SystemApiService.cs @@ -1,4 +1,5 @@ using Flurl.Http; +using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Http; using Recyclarr.TrashLib.Services.System.Dto; @@ -13,9 +14,9 @@ public class SystemApiService : ISystemApiService _service = service; } - public async Task GetStatus() + public async Task GetStatus(IServiceConfiguration config) { - return await _service.Request("system", "status") + return await _service.Request(config, "system", "status") .GetJsonAsync(); } } diff --git a/src/Recyclarr.sln.DotSettings b/src/Recyclarr.sln.DotSettings index 0fe82f0f..d86cf58c 100644 --- a/src/Recyclarr.sln.DotSettings +++ b/src/Recyclarr.sln.DotSettings @@ -112,5 +112,6 @@ &lt;/Language&gt; &lt;/profile&gt;</RIDER_SETTINGS><CSharpFormatDocComments>True</CSharpFormatDocComments><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSMakeFieldReadonly>True</CSMakeFieldReadonly></Profile> Recyclarr Cleanup + True True True \ No newline at end of file