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 @@
+
+
+