From 6f2d946902144962011107bbd6862da269f2bb90 Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sat, 16 Oct 2021 18:18:30 -0500 Subject: [PATCH] fix: sonarr devs broke the release profile api The Sonarr developers made a backward-breaking API change resulting in Trash Updater being unable to obtain, create, or update release profiles. This fix keeps backward compatibility with the previous and current schema at the cost of additional code complexity. The specific breakage was in the Ignored and Required properties of the Release Profile JSON schema. They were converted from string type to array. Offending change: https://github.com/Sonarr/Sonarr/blob/deed85d2f9147e6180014507ef4f5af3695b0c61/src/NzbDrone.Core/Datastore/Migration/162_release_profile_to_array.cs Fixes #16 --- CHANGELOG.md | 4 +- src/Common/Common.csproj | 2 + src/Directory.Build.targets | 4 + src/Trash/Command/Helpers/ServiceCommand.cs | 8 +- src/Trash/CompositionRoot.cs | 3 + ...rReleaseProfileCompatibilityHandlerTest.cs | 112 ++++++++++++++++++ .../Sonarr/ReleaseProfileUpdaterTest.cs | 6 +- src/TrashLib/Sonarr/Api/ISonarrApi.cs | 4 +- ...onarrReleaseProfileCompatibilityHandler.cs | 11 ++ .../Mappings/SonarrApiObjectMappingProfile.cs | 27 +++++ .../Api/Objects/SonarrReleaseProfile.cs | 20 +++- src/TrashLib/Sonarr/Api/SonarrApi.cs | 35 +++--- ...onarrReleaseProfileCompatibilityHandler.cs | 65 ++++++++++ src/TrashLib/Sonarr/ISonarrCompatibility.cs | 10 ++ .../ReleaseProfile/ReleaseProfileUpdater.cs | 27 +++-- src/TrashLib/Sonarr/SonarrAutofacModule.cs | 6 + src/TrashLib/Sonarr/SonarrCompatibility.cs | 38 ++++++ src/TrashLib/Startup/AutoMapperConfig.cs | 22 ++++ src/TrashLib/TrashLib.csproj | 3 + 19 files changed, 366 insertions(+), 41 deletions(-) create mode 100644 src/TrashLib.Tests/Sonarr/Api/SonarrReleaseProfileCompatibilityHandlerTest.cs create mode 100644 src/TrashLib/Sonarr/Api/ISonarrReleaseProfileCompatibilityHandler.cs create mode 100644 src/TrashLib/Sonarr/Api/Mappings/SonarrApiObjectMappingProfile.cs create mode 100644 src/TrashLib/Sonarr/Api/SonarrReleaseProfileCompatibilityHandler.cs create mode 100644 src/TrashLib/Sonarr/ISonarrCompatibility.cs create mode 100644 src/TrashLib/Sonarr/SonarrCompatibility.cs create mode 100644 src/TrashLib/Startup/AutoMapperConfig.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bcc74b9..a6d5c075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### FIXED - libgit2sharp PDB is no longer required with trash.exe on Windows ([#15]) +- Unexpected character error due to breaking change in Sonarr API ([#16]) [#15]: https://github.com/rcdailey/trash-updater/issues/15 +[#16]: https://github.com/rcdailey/trash-updater/issues/16 ## [1.6.3] - 2021-07-31 -### FIXED - - Fix "assembly not found" error on startup related to LibGit2Sharp (Windows only). Note that this introduces an additional file in the released ZIP files named `git2-6777db8.pdb`. This file must be next to `trash.exe`. In the future, I plan to have this extra file removed so it's just a diff --git a/src/Common/Common.csproj b/src/Common/Common.csproj index 62882e30..5f38880a 100644 --- a/src/Common/Common.csproj +++ b/src/Common/Common.csproj @@ -2,5 +2,7 @@ + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index cb37954e..e4a33da9 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,5 +1,6 @@ + @@ -21,6 +22,8 @@ + + @@ -28,6 +31,7 @@ + diff --git a/src/Trash/Command/Helpers/ServiceCommand.cs b/src/Trash/Command/Helpers/ServiceCommand.cs index 8efb0138..7566d18e 100644 --- a/src/Trash/Command/Helpers/ServiceCommand.cs +++ b/src/Trash/Command/Helpers/ServiceCommand.cs @@ -18,11 +18,13 @@ namespace Trash.Command.Helpers { public abstract class ServiceCommand : ICommand, IServiceCommand { - private readonly AutofacContractResolver _contractResolver; private readonly LoggingLevelSwitch _loggingLevelSwitch; private readonly ILogJanitor _logJanitor; - protected ServiceCommand(ILogger logger, LoggingLevelSwitch loggingLevelSwitch, ILogJanitor logJanitor) + protected ServiceCommand( + ILogger logger, + LoggingLevelSwitch loggingLevelSwitch, + ILogJanitor logJanitor) { _loggingLevelSwitch = loggingLevelSwitch; _logJanitor = logJanitor; @@ -90,7 +92,7 @@ namespace Trash.Command.Helpers Debug ? LogEventLevel.Debug : LogEventLevel.Information; } - private static void SetupHttp() + private void SetupHttp() { FlurlHttp.Configure(settings => { diff --git a/src/Trash/CompositionRoot.cs b/src/Trash/CompositionRoot.cs index 6b0396c5..01fa9b80 100644 --- a/src/Trash/CompositionRoot.cs +++ b/src/Trash/CompositionRoot.cs @@ -14,6 +14,7 @@ using TrashLib.Config; using TrashLib.Radarr; using TrashLib.Radarr.Config; using TrashLib.Sonarr; +using TrashLib.Startup; using YamlDotNet.Serialization; namespace Trash @@ -93,6 +94,8 @@ namespace Trash builder.RegisterModule(); builder.RegisterModule(); + builder.Register(_ => AutoMapperConfig.Setup()).SingleInstance(); + // builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); return builder.Build(); } diff --git a/src/TrashLib.Tests/Sonarr/Api/SonarrReleaseProfileCompatibilityHandlerTest.cs b/src/TrashLib.Tests/Sonarr/Api/SonarrReleaseProfileCompatibilityHandlerTest.cs new file mode 100644 index 00000000..c76a3d2d --- /dev/null +++ b/src/TrashLib.Tests/Sonarr/Api/SonarrReleaseProfileCompatibilityHandlerTest.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using AutoMapper; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using NUnit.Framework; +using TrashLib.Sonarr; +using TrashLib.Sonarr.Api; +using TrashLib.Sonarr.Api.Objects; +using TrashLib.Startup; + +namespace TrashLib.Tests.Sonarr.Api +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + public class SonarrReleaseProfileCompatibilityHandlerTest + { + private class TestContext : IDisposable + { + private readonly JsonSerializerSettings _jsonSettings; + + public TestContext() + { + _jsonSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + Mapper = AutoMapperConfig.Setup(); + } + + public IMapper Mapper { get; } + + public void Dispose() + { + } + + public string SerializeJson(T obj) + { + return JsonConvert.SerializeObject(obj, _jsonSettings); + } + } + + [Test] + public void Receive_v1_to_v2() + { + using var ctx = new TestContext(); + + var compat = Substitute.For(); + var dataV1 = new SonarrReleaseProfileV1 {Ignored = "one,two,three"}; + var sut = new SonarrReleaseProfileCompatibilityHandler(compat, ctx.Mapper); + + var result = sut.CompatibleReleaseProfileForReceiving(JObject.Parse(ctx.SerializeJson(dataV1))); + + _ = compat.DidNotReceive().ArraysNeededForReleaseProfileRequiredAndIgnored; + result.Should().BeEquivalentTo(new SonarrReleaseProfile + { + Ignored = new List {"one", "two", "three"} + }); + } + + [Test] + public void Receive_v2_to_v2() + { + using var ctx = new TestContext(); + + var compat = Substitute.For(); + var dataV2 = new SonarrReleaseProfile {Ignored = new List {"one", "two", "three"}}; + var sut = new SonarrReleaseProfileCompatibilityHandler(compat, ctx.Mapper); + + var result = sut.CompatibleReleaseProfileForReceiving(JObject.Parse(ctx.SerializeJson(dataV2))); + + _ = compat.DidNotReceive().ArraysNeededForReleaseProfileRequiredAndIgnored; + result.Should().BeEquivalentTo(dataV2); + } + + [Test] + public void Send_v2_to_v1() + { + using var ctx = new TestContext(); + + var compat = Substitute.For(); + compat.ArraysNeededForReleaseProfileRequiredAndIgnored.Returns(false); + + var data = new SonarrReleaseProfile {Ignored = new List {"one", "two", "three"}}; + var sut = new SonarrReleaseProfileCompatibilityHandler(compat, ctx.Mapper); + + var result = sut.CompatibleReleaseProfileForSending(data); + + result.Should().BeEquivalentTo(new SonarrReleaseProfileV1 {Ignored = "one,two,three"}); + } + + [Test] + public void Send_v2_to_v2() + { + using var ctx = new TestContext(); + + var compat = Substitute.For(); + compat.ArraysNeededForReleaseProfileRequiredAndIgnored.Returns(true); + + var data = new SonarrReleaseProfile {Ignored = new List {"one", "two", "three"}}; + var sut = new SonarrReleaseProfileCompatibilityHandler(compat, ctx.Mapper); + + var result = sut.CompatibleReleaseProfileForSending(data); + + result.Should().BeEquivalentTo(data); + } + } +} diff --git a/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs b/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs index db4e7384..54255f65 100644 --- a/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs +++ b/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs @@ -1,6 +1,7 @@ using NSubstitute; using NUnit.Framework; using Serilog; +using TrashLib.Sonarr; using TrashLib.Sonarr.Api; using TrashLib.Sonarr.Config; using TrashLib.Sonarr.ReleaseProfile; @@ -16,6 +17,7 @@ namespace TrashLib.Tests.Sonarr public IReleaseProfileGuideParser Parser { get; } = Substitute.For(); public ISonarrApi Api { get; } = Substitute.For(); public ILogger Logger { get; } = Substitute.For(); + public ISonarrCompatibility Compatibility { get; } = Substitute.For(); } [Test] @@ -23,7 +25,7 @@ namespace TrashLib.Tests.Sonarr { var context = new Context(); - var logic = new ReleaseProfileUpdater(context.Logger, context.Parser, context.Api); + var logic = new ReleaseProfileUpdater(context.Logger, context.Parser, context.Api, context.Compatibility); logic.Process(false, new SonarrConfiguration()); context.Parser.DidNotReceive().GetMarkdownData(Arg.Any()); @@ -40,7 +42,7 @@ namespace TrashLib.Tests.Sonarr ReleaseProfiles = new[] {new ReleaseProfileConfig {Type = ReleaseProfileType.Anime}} }; - var logic = new ReleaseProfileUpdater(context.Logger, context.Parser, context.Api); + var logic = new ReleaseProfileUpdater(context.Logger, context.Parser, context.Api, context.Compatibility); logic.Process(false, config); context.Parser.Received().ParseMarkdown(config.ReleaseProfiles[0], "theMarkdown"); diff --git a/src/TrashLib/Sonarr/Api/ISonarrApi.cs b/src/TrashLib/Sonarr/Api/ISonarrApi.cs index 23f06bfe..0eb70df7 100644 --- a/src/TrashLib/Sonarr/Api/ISonarrApi.cs +++ b/src/TrashLib/Sonarr/Api/ISonarrApi.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using TrashLib.Sonarr.Api.Objects; @@ -7,7 +6,6 @@ namespace TrashLib.Sonarr.Api { public interface ISonarrApi { - Task GetVersion(); Task> GetTags(); Task CreateTag(string tag); Task> GetReleaseProfiles(); diff --git a/src/TrashLib/Sonarr/Api/ISonarrReleaseProfileCompatibilityHandler.cs b/src/TrashLib/Sonarr/Api/ISonarrReleaseProfileCompatibilityHandler.cs new file mode 100644 index 00000000..18d8bf5a --- /dev/null +++ b/src/TrashLib/Sonarr/Api/ISonarrReleaseProfileCompatibilityHandler.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json.Linq; +using TrashLib.Sonarr.Api.Objects; + +namespace TrashLib.Sonarr.Api +{ + public interface ISonarrReleaseProfileCompatibilityHandler + { + object CompatibleReleaseProfileForSending(SonarrReleaseProfile profile); + SonarrReleaseProfile CompatibleReleaseProfileForReceiving(JObject profile); + } +} diff --git a/src/TrashLib/Sonarr/Api/Mappings/SonarrApiObjectMappingProfile.cs b/src/TrashLib/Sonarr/Api/Mappings/SonarrApiObjectMappingProfile.cs new file mode 100644 index 00000000..f58cfc11 --- /dev/null +++ b/src/TrashLib/Sonarr/Api/Mappings/SonarrApiObjectMappingProfile.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using AutoMapper; +using JetBrains.Annotations; +using TrashLib.Sonarr.Api.Objects; + +namespace TrashLib.Sonarr.Api.Mappings +{ + [UsedImplicitly] + public class SonarrApiObjectMappingProfile : Profile + { + public SonarrApiObjectMappingProfile() + { + CreateMap() + .ForMember(d => d.Ignored, x => x.MapFrom( + s => s.Ignored.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList())) + .ForMember(d => d.Required, x => x.MapFrom( + s => s.Required.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList())); + + CreateMap() + .ForMember(d => d.Ignored, x => x.MapFrom( + s => string.Join(',', s.Ignored))) + .ForMember(d => d.Required, x => x.MapFrom( + s => string.Join(',', s.Required))); + } + } +} diff --git a/src/TrashLib/Sonarr/Api/Objects/SonarrReleaseProfile.cs b/src/TrashLib/Sonarr/Api/Objects/SonarrReleaseProfile.cs index b563face..4cadfc84 100644 --- a/src/TrashLib/Sonarr/Api/Objects/SonarrReleaseProfile.cs +++ b/src/TrashLib/Sonarr/Api/Objects/SonarrReleaseProfile.cs @@ -20,8 +20,12 @@ namespace TrashLib.Sonarr.Api.Objects public int Score { get; set; } } + // Retained for supporting versions of Sonarr prior to v3.0.6.1355 + // Offending change is here: + // https://github.com/Sonarr/Sonarr/blob/deed85d2f9147e6180014507ef4f5af3695b0c61/src/NzbDrone.Core/Datastore/Migration/162_release_profile_to_array.cs + // The Ignored and Required JSON properties were converted from string -> array in a backward-breaking way. [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)] - public class SonarrReleaseProfile + public class SonarrReleaseProfileV1 { public int Id { get; set; } public bool Enabled { get; set; } @@ -33,4 +37,18 @@ namespace TrashLib.Sonarr.Api.Objects public int IndexerId { get; set; } public IReadOnlyCollection Tags { get; set; } = new List(); } + + [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)] + public class SonarrReleaseProfile + { + public int Id { get; set; } + public bool Enabled { get; set; } + public string Name { get; set; } = ""; + public IReadOnlyCollection Required { get; set; } = new List(); + public IReadOnlyCollection Ignored { get; set; } = new List(); + public IReadOnlyCollection Preferred { get; set; } = new List(); + public bool IncludePreferredWhenRenaming { get; set; } + public int IndexerId { get; set; } + public IReadOnlyCollection Tags { get; set; } = new List(); + } } diff --git a/src/TrashLib/Sonarr/Api/SonarrApi.cs b/src/TrashLib/Sonarr/Api/SonarrApi.cs index d79d7c2e..8048dcdf 100644 --- a/src/TrashLib/Sonarr/Api/SonarrApi.cs +++ b/src/TrashLib/Sonarr/Api/SonarrApi.cs @@ -1,8 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Flurl; using Flurl.Http; +using Newtonsoft.Json.Linq; using TrashLib.Config; using TrashLib.Sonarr.Api.Objects; @@ -10,19 +11,13 @@ namespace TrashLib.Sonarr.Api { public class SonarrApi : ISonarrApi { + private readonly ISonarrReleaseProfileCompatibilityHandler _profileHandler; private readonly IServerInfo _serverInfo; - public SonarrApi(IServerInfo serverInfo) + public SonarrApi(IServerInfo serverInfo, ISonarrReleaseProfileCompatibilityHandler profileHandler) { _serverInfo = serverInfo; - } - - public async Task GetVersion() - { - dynamic data = await BaseUrl() - .AppendPathSegment("system/status") - .GetJsonAsync(); - return new Version(data.version); + _profileHandler = profileHandler; } public async Task> GetTags() @@ -42,24 +37,30 @@ namespace TrashLib.Sonarr.Api public async Task> GetReleaseProfiles() { - return await BaseUrl() + var response = await BaseUrl() .AppendPathSegment("releaseprofile") - .GetJsonAsync>(); + .GetJsonAsync>(); + + return response + .Select(_profileHandler.CompatibleReleaseProfileForReceiving) + .ToList(); } public async Task UpdateReleaseProfile(SonarrReleaseProfile profileToUpdate) { await BaseUrl() .AppendPathSegment($"releaseprofile/{profileToUpdate.Id}") - .PutJsonAsync(profileToUpdate); + .PutJsonAsync(_profileHandler.CompatibleReleaseProfileForSending(profileToUpdate)); } public async Task CreateReleaseProfile(SonarrReleaseProfile newProfile) { - return await BaseUrl() + var response = await BaseUrl() .AppendPathSegment("releaseprofile") - .PostJsonAsync(newProfile) - .ReceiveJson(); + .PostJsonAsync(_profileHandler.CompatibleReleaseProfileForSending(newProfile)) + .ReceiveJson(); + + return _profileHandler.CompatibleReleaseProfileForReceiving(response); } public async Task> GetQualityDefinition() diff --git a/src/TrashLib/Sonarr/Api/SonarrReleaseProfileCompatibilityHandler.cs b/src/TrashLib/Sonarr/Api/SonarrReleaseProfileCompatibilityHandler.cs new file mode 100644 index 00000000..bcf3af31 --- /dev/null +++ b/src/TrashLib/Sonarr/Api/SonarrReleaseProfileCompatibilityHandler.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.IO; +using AutoMapper; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; +using Newtonsoft.Json.Schema.Generation; +using Newtonsoft.Json.Serialization; +using Serilog; +using TrashLib.Sonarr.Api.Objects; + +namespace TrashLib.Sonarr.Api +{ + public class SonarrReleaseProfileCompatibilityHandler : ISonarrReleaseProfileCompatibilityHandler + { + private readonly ISonarrCompatibility _compatibility; + private readonly JSchemaGenerator _generator; + private readonly IMapper _mapper; + + public SonarrReleaseProfileCompatibilityHandler( + ISonarrCompatibility compatibility, + IMapper mapper) + { + _compatibility = compatibility; + _mapper = mapper; + _generator = new JSchemaGenerator + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + DefaultRequired = Required.Default + }; + } + + public object CompatibleReleaseProfileForSending(SonarrReleaseProfile profile) + { + return _compatibility.ArraysNeededForReleaseProfileRequiredAndIgnored + ? profile + : _mapper.Map(profile); + } + + public SonarrReleaseProfile CompatibleReleaseProfileForReceiving(JObject profile) + { + JSchema? schema; + IList? errorMessages; + + schema = _generator.Generate(typeof(SonarrReleaseProfile)); + if (profile.IsValid(schema, out errorMessages)) + { + return profile.ToObject() + ?? throw new InvalidDataException("SonarrReleaseProfile V2 parsing failed"); + } + + Log.Debug("SonarrReleaseProfile is not a match for V2, proceeding to V1: {Reasons}", errorMessages); + + schema = _generator.Generate(typeof(SonarrReleaseProfileV1)); + if (profile.IsValid(schema, out errorMessages)) + { + // This will throw if there's an issue during mapping. + return _mapper.Map(profile.ToObject()); + } + + throw new InvalidDataException( + $"SonarrReleaseProfile expected, but no supported schema detected: {errorMessages}"); + } + } +} diff --git a/src/TrashLib/Sonarr/ISonarrCompatibility.cs b/src/TrashLib/Sonarr/ISonarrCompatibility.cs new file mode 100644 index 00000000..7dd9b958 --- /dev/null +++ b/src/TrashLib/Sonarr/ISonarrCompatibility.cs @@ -0,0 +1,10 @@ +namespace TrashLib.Sonarr +{ + public interface ISonarrCompatibility + { + bool SupportsNamedReleaseProfiles { get; } + bool ArraysNeededForReleaseProfileRequiredAndIgnored { get; } + string InformationalVersion { get; } + string MinimumVersion { get; } + } +} diff --git a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs index e2bece88..e6bd9bf5 100644 --- a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs +++ b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs @@ -13,13 +13,19 @@ namespace TrashLib.Sonarr.ReleaseProfile internal class ReleaseProfileUpdater : IReleaseProfileUpdater { private readonly ISonarrApi _api; + private readonly ISonarrCompatibility _compatibility; private readonly IReleaseProfileGuideParser _parser; - public ReleaseProfileUpdater(ILogger logger, IReleaseProfileGuideParser parser, ISonarrApi api) + public ReleaseProfileUpdater( + ILogger logger, + IReleaseProfileGuideParser parser, + ISonarrApi api, + ISonarrCompatibility compatibility) { Log = logger; _parser = parser; _api = api; + _compatibility = compatibility; } private ILogger Log { get; } @@ -47,18 +53,13 @@ namespace TrashLib.Sonarr.ReleaseProfile } } - private async Task DoVersionEnforcement() + private void DoVersionEnforcement() { - // Since this script requires a specific version of v3 Sonarr that implements name support for - // release profiles, we perform that version check here and bail out if it does not meet a minimum - // required version. - var minimumVersion = new Version("3.0.4.1098"); - var version = await _api.GetVersion(); - if (version < minimumVersion) + if (!_compatibility.SupportsNamedReleaseProfiles) { throw new VersionException( - $"Your Sonarr version {version} does not meet the minimum " + - $"required version of {minimumVersion} to use this program"); + $"Your Sonarr version {_compatibility.InformationalVersion} does not meet the minimum " + + $"required version of {_compatibility.MinimumVersion} to use this program"); } } @@ -92,8 +93,8 @@ namespace TrashLib.Sonarr.ReleaseProfile .SelectMany(kvp => kvp.Value.Select(term => new SonarrPreferredTerm(kvp.Key, term))) .ToList(); - profileToUpdate.Ignored = string.Join(',', profile.Ignored); - profileToUpdate.Required = string.Join(',', profile.Required); + profileToUpdate.Ignored = profile.Ignored.ToList(); //string.Join(',', profile.Ignored); + profileToUpdate.Required = profile.Required.ToList(); //string.Join(',', profile.Required); // Null means the guide didn't specify a value for this, so we leave the existing setting intact. if (profile.IncludePreferredWhenRenaming != null) @@ -127,7 +128,7 @@ namespace TrashLib.Sonarr.ReleaseProfile private async Task ProcessReleaseProfiles(IDictionary profiles, ReleaseProfileConfig config) { - await DoVersionEnforcement(); + DoVersionEnforcement(); List tagIds = new(); diff --git a/src/TrashLib/Sonarr/SonarrAutofacModule.cs b/src/TrashLib/Sonarr/SonarrAutofacModule.cs index 6126058f..cabc0725 100644 --- a/src/TrashLib/Sonarr/SonarrAutofacModule.cs +++ b/src/TrashLib/Sonarr/SonarrAutofacModule.cs @@ -13,9 +13,15 @@ namespace TrashLib.Sonarr builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType() + .As() + .SingleInstance(); + // Release Profile Support builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType() + .As(); // Quality Definition Support builder.RegisterType().As(); diff --git a/src/TrashLib/Sonarr/SonarrCompatibility.cs b/src/TrashLib/Sonarr/SonarrCompatibility.cs new file mode 100644 index 00000000..9c79fa9b --- /dev/null +++ b/src/TrashLib/Sonarr/SonarrCompatibility.cs @@ -0,0 +1,38 @@ +using System; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using Flurl; +using Flurl.Http; +using TrashLib.Config; + +namespace TrashLib.Sonarr +{ + public class SonarrCompatibility : ISonarrCompatibility + { + private Version _version = new(); + + public SonarrCompatibility(IServerInfo serverInfo) + { + var task = serverInfo.BuildUrl() + .AppendPathSegment("system/status") + .GetJsonAsync(); + + task.ToObservable() + .Select(x => new Version(x.version)) + .Subscribe(x => _version = x); + } + + public bool SupportsNamedReleaseProfiles => + _version >= new Version(MinimumVersion); + + // Background: Issue #16 filed which points to a backward-breaking API + // change made in Sonarr at commit [deed85d2f]. + // + // [deed85d2f]: https://github.com/Sonarr/Sonarr/commit/deed85d2f9147e6180014507ef4f5af3695b0c61 + public bool ArraysNeededForReleaseProfileRequiredAndIgnored => + _version >= new Version("3.0.6.1355"); + + public string InformationalVersion => _version.ToString(); + public string MinimumVersion => "3.0.4.1098"; + } +} diff --git a/src/TrashLib/Startup/AutoMapperConfig.cs b/src/TrashLib/Startup/AutoMapperConfig.cs new file mode 100644 index 00000000..49c2dcd7 --- /dev/null +++ b/src/TrashLib/Startup/AutoMapperConfig.cs @@ -0,0 +1,22 @@ +using AutoMapper; + +namespace TrashLib.Startup +{ + public static class AutoMapperConfig + { + public static IMapper Setup() + { + // todo: consider using AutoMapper.Contrib.Autofac.DependencyInjection + var mapperConfig = new MapperConfiguration(cfg => + { + cfg.AddMaps(typeof(AutoMapperConfig)); + }); + +#if DEBUG + mapperConfig.AssertConfigurationIsValid(); +#endif + + return mapperConfig.CreateMapper(); + } + } +} diff --git a/src/TrashLib/TrashLib.csproj b/src/TrashLib/TrashLib.csproj index fdeecf29..3c249bd3 100644 --- a/src/TrashLib/TrashLib.csproj +++ b/src/TrashLib/TrashLib.csproj @@ -1,14 +1,17 @@ + + +