refactor!: Remove support for older versions of Sonarr

New minimum version is `3.0.9.1549`.
json-serializing-nullable-fields-issue
Robert Dailey 8 months ago
parent 19849b33b4
commit b6de1bf97e

@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
This release contains **BREAKING CHANGES**. See the [v6.0 Upgrade Guide][breaking6] for required
changes you may need to make.
[breaking6]: https://recyclarr.dev/wiki/upgrade-guide/v6.0/
### Changed
- **BREAKING**: Minimum required Sonarr version increased to `3.0.9.1549` (Previous minimum version
was `3.0.4.1098`).
## [5.4.3] - 2023-09-16
### Changed

@ -17,7 +17,6 @@
<PackageVersion Include="JetBrains.Annotations" Version="2023.2.0" />
<PackageVersion Include="MudBlazor" Version="6.9.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Newtonsoft.Json.Schema" Version="3.0.15" />
<PackageVersion Include="ReactiveUI.Blazor" Version="19.4.1" />
<PackageVersion Include="Serilog" Version="3.0.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
@ -64,4 +63,4 @@
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
</Project>
</Project>

@ -1,14 +0,0 @@
using Newtonsoft.Json.Linq;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Api.Objects;
using Recyclarr.TrashLib.Config;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Api;
public interface ISonarrReleaseProfileCompatibilityHandler
{
Task<object> CompatibleReleaseProfileForSending(
IServiceConfiguration config,
SonarrReleaseProfile profile);
SonarrReleaseProfile CompatibleReleaseProfileForReceiving(JObject profile);
}

@ -1,5 +1,4 @@
using Flurl.Http;
using Newtonsoft.Json.Linq;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Api.Objects;
using Recyclarr.TrashLib.Config;
using Recyclarr.TrashLib.Http;
@ -8,45 +7,32 @@ namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Api;
public class ReleaseProfileApiService : IReleaseProfileApiService
{
private readonly ISonarrReleaseProfileCompatibilityHandler _profileHandler;
private readonly IServiceRequestBuilder _service;
public ReleaseProfileApiService(
ISonarrReleaseProfileCompatibilityHandler profileHandler,
IServiceRequestBuilder service)
public ReleaseProfileApiService(IServiceRequestBuilder service)
{
_profileHandler = profileHandler;
_service = service;
}
public async Task UpdateReleaseProfile(IServiceConfiguration config, SonarrReleaseProfile profile)
{
var profileToSend = await _profileHandler.CompatibleReleaseProfileForSending(config, profile);
await _service.Request(config, "releaseprofile", profile.Id)
.PutJsonAsync(profileToSend);
.PutJsonAsync(profile);
}
public async Task<SonarrReleaseProfile> CreateReleaseProfile(
IServiceConfiguration config,
SonarrReleaseProfile profile)
{
var profileToSend = await _profileHandler.CompatibleReleaseProfileForSending(config, profile);
var response = await _service.Request(config, "releaseprofile")
.PostJsonAsync(profileToSend)
.ReceiveJson<JObject>();
return _profileHandler.CompatibleReleaseProfileForReceiving(response);
return await _service.Request(config, "releaseprofile")
.PostJsonAsync(profile)
.ReceiveJson<SonarrReleaseProfile>();
}
public async Task<IList<SonarrReleaseProfile>> GetReleaseProfiles(IServiceConfiguration config)
{
var response = await _service.Request(config, "releaseprofile")
.GetJsonAsync<List<JObject>>();
return response
.Select(_profileHandler.CompatibleReleaseProfileForReceiving)
.ToList();
return await _service.Request(config, "releaseprofile")
.GetJsonAsync<List<SonarrReleaseProfile>>();
}
public async Task DeleteReleaseProfile(IServiceConfiguration config, int releaseProfileId)

@ -1,59 +0,0 @@
using AutoMapper;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Api.Objects;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Api.Schemas;
using Recyclarr.TrashLib.Compatibility.Sonarr;
using Recyclarr.TrashLib.Config;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.Api;
public class SonarrReleaseProfileCompatibilityHandler : ISonarrReleaseProfileCompatibilityHandler
{
private readonly ILogger _log;
private readonly ISonarrCapabilityFetcher _capabilityFetcher;
private readonly IMapper _mapper;
public SonarrReleaseProfileCompatibilityHandler(
ILogger log,
ISonarrCapabilityFetcher capabilityFetcher,
IMapper mapper)
{
_log = log;
_capabilityFetcher = capabilityFetcher;
_mapper = mapper;
}
public async Task<object> CompatibleReleaseProfileForSending(
IServiceConfiguration config,
SonarrReleaseProfile profile)
{
var capabilities = await _capabilityFetcher.GetCapabilities(config);
return capabilities.ArraysNeededForReleaseProfileRequiredAndIgnored
? profile
: _mapper.Map<SonarrReleaseProfileV1>(profile);
}
public SonarrReleaseProfile CompatibleReleaseProfileForReceiving(JObject profile)
{
var schema = JSchema.Parse(SonarrReleaseProfileSchema.V2);
if (profile.IsValid(schema, out IList<string>? errorMessages))
{
return profile.ToObject<SonarrReleaseProfile>()
?? throw new InvalidDataException("SonarrReleaseProfile V2 parsing failed");
}
_log.Debug("SonarrReleaseProfile is not a match for V2, proceeding to V1: {Reasons}", errorMessages);
schema = JSchema.Parse(SonarrReleaseProfileSchema.V1);
if (profile.IsValid(schema, out errorMessages))
{
// This will throw if there's an issue during mapping.
return _mapper.Map<SonarrReleaseProfile>(profile.ToObject<SonarrReleaseProfileV1>());
}
throw new InvalidDataException(
$"SonarrReleaseProfile expected, but no supported schema detected: {errorMessages}");
}
}

@ -17,9 +17,6 @@ public class ReleaseProfileAutofacModule : Module
builder.RegisterType<ReleaseProfileFilterPipeline>().As<IReleaseProfileFilterPipeline>();
builder.RegisterType<ReleaseProfileDataLister>();
builder.RegisterType<SonarrReleaseProfileCompatibilityHandler>()
.As<ISonarrReleaseProfileCompatibilityHandler>();
builder.RegisterAggregateService<IReleaseProfilePipelinePhases>();
builder.RegisterType<ReleaseProfileConfigPhase>();
builder.RegisterType<ReleaseProfileApiFetchPhase>();

@ -1,22 +0,0 @@
using System.ComponentModel;
using System.Globalization;
namespace Recyclarr.TrashLib.Config.Parsing.BackwardCompatibility;
public class ResetUnmatchedScoresYamlTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(bool) || sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var enabledFlag = Convert.ToBoolean(value);
return new ResetUnmatchedScoresConfigYaml
{
FromBool = true,
Enabled = enabledFlag
};
}
}

@ -2,17 +2,9 @@ namespace Recyclarr.TrashLib.Compatibility.Sonarr;
public record SonarrCapabilities
{
public static Version MinimumVersion { get; } = new("3.0.4.1098");
public static Version MinimumVersion { get; } = new("3.0.9.1549");
public Version Version { get; init; } = new();
public bool SupportsNamedReleaseProfiles { get; init; }
// 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 { get; init; }
public bool SupportsCustomFormats { get; init; }
}

@ -17,7 +17,7 @@ public class SonarrCapabilityEnforcer
{
var capabilities = await _capabilityFetcher.GetCapabilities(config);
if (!capabilities.SupportsNamedReleaseProfiles)
if (capabilities.Version < SonarrCapabilities.MinimumVersion)
{
throw new ServiceIncompatibilityException(
$"Your Sonarr version {capabilities.Version} does not meet the minimum " +

@ -15,12 +15,6 @@ public class SonarrCapabilityFetcher : ServiceCapabilityFetcher<SonarrCapabiliti
{
Version = version,
SupportsNamedReleaseProfiles =
version >= SonarrCapabilities.MinimumVersion,
ArraysNeededForReleaseProfileRequiredAndIgnored =
version >= new Version("3.0.6.1355"),
SupportsCustomFormats =
version >= new Version(4, 0)
};

@ -11,7 +11,6 @@
<PackageReference Include="Flurl" />
<PackageReference Include="Flurl.Http" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Newtonsoft.Json.Schema" />
<PackageReference Include="Serilog" />
<PackageReference Include="SuperLinq" />
<PackageReference Include="System.Data.HashFunction.FNV" />

@ -1,92 +0,0 @@
using Autofac;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Api;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Api.Objects;
using Recyclarr.Cli.TestLibrary;
using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.Compatibility.Sonarr;
using Recyclarr.TrashLib.Config;
namespace Recyclarr.Cli.Tests.Pipelines.ReleaseProfile.Api;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class SonarrReleaseProfileCompatibilityHandlerTest : CliIntegrationFixture
{
private static JObject SerializeJson<T>(T obj)
{
JsonSerializerSettings jsonSettings = new()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
return JObject.Parse(JsonConvert.SerializeObject(obj, jsonSettings));
}
protected override void RegisterTypes(ContainerBuilder builder)
{
base.RegisterTypes(builder);
builder.RegisterMockFor<ISonarrCapabilityFetcher>();
}
[Test]
public void Receive_v1_to_v2()
{
var sut = Resolve<SonarrReleaseProfileCompatibilityHandler>();
var dataV1 = new SonarrReleaseProfileV1 {Ignored = "one,two,three"};
var result = sut.CompatibleReleaseProfileForReceiving(SerializeJson(dataV1));
result.Should().BeEquivalentTo(new SonarrReleaseProfile
{
Ignored = new List<string> {"one", "two", "three"}
});
}
[Test]
public void Receive_v2_to_v2()
{
var sut = Resolve<SonarrReleaseProfileCompatibilityHandler>();
var dataV2 = new SonarrReleaseProfile {Ignored = new List<string> {"one", "two", "three"}};
var result = sut.CompatibleReleaseProfileForReceiving(SerializeJson(dataV2));
result.Should().BeEquivalentTo(dataV2);
}
[Test]
public async Task Send_v2_to_v1()
{
var capabilityChecker = Resolve<ISonarrCapabilityFetcher>();
capabilityChecker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
ArraysNeededForReleaseProfileRequiredAndIgnored = false
});
var sut = Resolve<SonarrReleaseProfileCompatibilityHandler>();
var data = new SonarrReleaseProfile {Ignored = new List<string> {"one", "two", "three"}};
var result = await sut.CompatibleReleaseProfileForSending(Substitute.For<IServiceConfiguration>(), data);
result.Should().BeEquivalentTo(new SonarrReleaseProfileV1 {Ignored = "one,two,three"});
}
[Test]
public async Task Send_v2_to_v2()
{
var capabilityChecker = Resolve<ISonarrCapabilityFetcher>();
capabilityChecker.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
ArraysNeededForReleaseProfileRequiredAndIgnored = true
});
var sut = Resolve<SonarrReleaseProfileCompatibilityHandler>();
var data = new SonarrReleaseProfile {Ignored = new List<string> {"one", "two", "three"}};
var result = await sut.CompatibleReleaseProfileForSending(Substitute.For<IServiceConfiguration>(), data);
result.Should().BeEquivalentTo(data);
}
}

@ -15,10 +15,11 @@ public class SonarrCapabilityEnforcerTest
SonarrCapabilityEnforcer sut)
{
var config = NewConfig.Sonarr();
var min = SonarrCapabilities.MinimumVersion;
fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsNamedReleaseProfiles = false
Version = new Version(min.Major, min.Minor, min.Build, min.Revision - 1)
});
var act = () => sut.Check(config);
@ -41,7 +42,6 @@ public class SonarrCapabilityEnforcerTest
fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsNamedReleaseProfiles = true,
SupportsCustomFormats = true
});
@ -65,7 +65,6 @@ public class SonarrCapabilityEnforcerTest
fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsNamedReleaseProfiles = true,
SupportsCustomFormats = false
});
@ -95,7 +94,6 @@ public class SonarrCapabilityEnforcerTest
fetcher.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsNamedReleaseProfiles = true,
SupportsCustomFormats = false
});

Loading…
Cancel
Save