feat: Quality definition logic uses new JSON files

pull/113/head
Robert Dailey 2 years ago
parent 635e93b2db
commit 07c4b48578

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- Quality definition data is now pulled from JSON files.
## [2.3.1] - 2022-08-20 ## [2.3.1] - 2022-08-20
### Changed ### Changed

@ -68,7 +68,7 @@ public class SonarrCommand : ServiceCommand
await profileUpdaterFactory().Process(Preview, config); await profileUpdaterFactory().Process(Preview, config);
} }
if (config.QualityDefinition.HasValue) if (!string.IsNullOrEmpty(config.QualityDefinition))
{ {
await qualityUpdaterFactory().Process(Preview, config); await qualityUpdaterFactory().Process(Preview, config);
} }

@ -14,17 +14,17 @@ public class RadarrQualityDataTest
new object?[] {100m, 101m, true}, new object?[] {100m, 101m, true},
new object?[] {100m, 98m, true}, new object?[] {100m, 98m, true},
new object?[] {100m, null, true}, new object?[] {100m, null, true},
new object?[] {RadarrQualityData.PreferredUnlimitedThreshold, null, false}, new object?[] {RadarrQualityItem.PreferredUnlimitedThreshold, null, false},
new object?[] {RadarrQualityData.PreferredUnlimitedThreshold - 1, null, true}, new object?[] {RadarrQualityItem.PreferredUnlimitedThreshold - 1, null, true},
new object?[] new object?[]
{RadarrQualityData.PreferredUnlimitedThreshold, RadarrQualityData.PreferredUnlimitedThreshold, true} {RadarrQualityItem.PreferredUnlimitedThreshold, RadarrQualityItem.PreferredUnlimitedThreshold, true}
}; };
[TestCaseSource(nameof(PreferredTestValues))] [TestCaseSource(nameof(PreferredTestValues))]
public void PreferredDifferent_WithVariousValues_ReturnsExpectedResult(decimal guideValue, decimal? radarrValue, public void PreferredDifferent_WithVariousValues_ReturnsExpectedResult(decimal guideValue, decimal? radarrValue,
bool isDifferent) bool isDifferent)
{ {
var data = new RadarrQualityData {Preferred = guideValue}; var data = new RadarrQualityItem("", 0, 0, guideValue);
data.IsPreferredDifferent(radarrValue) data.IsPreferredDifferent(radarrValue)
.Should().Be(isDifferent); .Should().Be(isDifferent);
} }
@ -35,19 +35,19 @@ public class RadarrQualityDataTest
{ {
400m, 400m,
1.0m, 1.0m,
RadarrQualityData.PreferredUnlimitedThreshold RadarrQualityItem.PreferredUnlimitedThreshold
}, },
new[] new[]
{ {
RadarrQualityData.PreferredUnlimitedThreshold, RadarrQualityItem.PreferredUnlimitedThreshold,
1.0m, 1.0m,
RadarrQualityData.PreferredUnlimitedThreshold RadarrQualityItem.PreferredUnlimitedThreshold
}, },
new[] new[]
{ {
RadarrQualityData.PreferredUnlimitedThreshold - 1m, RadarrQualityItem.PreferredUnlimitedThreshold - 1m,
1.0m, 1.0m,
RadarrQualityData.PreferredUnlimitedThreshold - 1m RadarrQualityItem.PreferredUnlimitedThreshold - 1m
}, },
new[] new[]
{ {
@ -67,54 +67,54 @@ public class RadarrQualityDataTest
public void InterpolatedPreferred_VariousValues_ExpectedResults(decimal max, decimal ratio, public void InterpolatedPreferred_VariousValues_ExpectedResults(decimal max, decimal ratio,
decimal expectedResult) decimal expectedResult)
{ {
var data = new RadarrQualityData {Min = 0, Max = max}; var data = new RadarrQualityItem("", 0, max, 0);
data.InterpolatedPreferred(ratio).Should().Be(expectedResult); data.InterpolatedPreferred(ratio).Should().Be(expectedResult);
} }
[Test] [Test]
public void AnnotatedPreferred_OutsideThreshold_EqualsSameValueWithUnlimited() public void AnnotatedPreferred_OutsideThreshold_EqualsSameValueWithUnlimited()
{ {
const decimal testVal = RadarrQualityData.PreferredUnlimitedThreshold; const decimal testVal = RadarrQualityItem.PreferredUnlimitedThreshold;
var data = new RadarrQualityData {Preferred = testVal}; var data = new RadarrQualityItem("", 0, 0, testVal);
data.AnnotatedPreferred.Should().Be($"{testVal} (Unlimited)"); data.AnnotatedPreferred.Should().Be($"{testVal} (Unlimited)");
} }
[Test] [Test]
public void AnnotatedPreferred_WithinThreshold_EqualsSameStringValue() public void AnnotatedPreferred_WithinThreshold_EqualsSameStringValue()
{ {
const decimal testVal = RadarrQualityData.PreferredUnlimitedThreshold - 1; const decimal testVal = RadarrQualityItem.PreferredUnlimitedThreshold - 1;
var data = new RadarrQualityData {Preferred = testVal}; var data = new RadarrQualityItem("", 0, 0, testVal);
data.AnnotatedPreferred.Should().Be($"{testVal}"); data.AnnotatedPreferred.Should().Be($"{testVal}");
} }
[Test] [Test]
public void Preferred_AboveThreshold_EqualsSameValue() public void Preferred_AboveThreshold_EqualsSameValue()
{ {
const decimal testVal = RadarrQualityData.PreferredUnlimitedThreshold + 1; const decimal testVal = RadarrQualityItem.PreferredUnlimitedThreshold + 1;
var data = new RadarrQualityData {Preferred = testVal}; var data = new RadarrQualityItem("", 0, 0, testVal);
data.Preferred.Should().Be(testVal); data.Preferred.Should().Be(testVal);
} }
[Test] [Test]
public void PreferredForApi_AboveThreshold_EqualsNull() public void PreferredForApi_AboveThreshold_EqualsNull()
{ {
const decimal testVal = RadarrQualityData.PreferredUnlimitedThreshold + 1; const decimal testVal = RadarrQualityItem.PreferredUnlimitedThreshold + 1;
var data = new RadarrQualityData {Preferred = testVal}; var data = new RadarrQualityItem("", 0, 0, testVal);
data.PreferredForApi.Should().Be(null); data.PreferredForApi.Should().Be(null);
} }
[Test] [Test]
public void PreferredForApi_HighestWithinThreshold_EqualsSameValue() public void PreferredForApi_HighestWithinThreshold_EqualsSameValue()
{ {
const decimal testVal = RadarrQualityData.PreferredUnlimitedThreshold - 0.1m; const decimal testVal = RadarrQualityItem.PreferredUnlimitedThreshold - 0.1m;
var data = new RadarrQualityData {Preferred = testVal}; var data = new RadarrQualityItem("", 0, 0, testVal);
data.PreferredForApi.Should().Be(testVal).And.Be(data.Preferred); data.PreferredForApi.Should().Be(testVal).And.Be(data.Preferred);
} }
[Test] [Test]
public void PreferredForApi_LowestWithinThreshold_EqualsSameValue() public void PreferredForApi_LowestWithinThreshold_EqualsSameValue()
{ {
var data = new RadarrQualityData {Preferred = 0}; var data = new RadarrQualityItem("", 0, 0, 0);
data.PreferredForApi.Should().Be(0); data.PreferredForApi.Should().Be(0);
} }
} }

@ -6,7 +6,6 @@ using NUnit.Framework;
using TrashLib.Config; using TrashLib.Config;
using TrashLib.Services.Radarr; using TrashLib.Services.Radarr;
using TrashLib.Services.Radarr.Config; using TrashLib.Services.Radarr.Config;
using TrashLib.Services.Radarr.QualityDefinition;
namespace TrashLib.Tests.Radarr; namespace TrashLib.Tests.Radarr;
@ -95,7 +94,7 @@ public class RadarrConfigurationTest
}, },
QualityDefinition = new QualityDefinitionConfig QualityDefinition = new QualityDefinitionConfig
{ {
Type = RadarrQualityDefinitionType.Movie Type = "movie"
} }
}; };

@ -1,6 +1,6 @@
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using TrashLib.Services.Sonarr.QualityDefinition; using TrashLib.Services.Common.QualityDefinition;
namespace TrashLib.Tests.Sonarr.QualityDefinition; namespace TrashLib.Tests.Sonarr.QualityDefinition;
@ -14,9 +14,9 @@ public class SonarrQualityDataTest
new object?[] {100m, 101m, true}, new object?[] {100m, 101m, true},
new object?[] {100m, 98m, true}, new object?[] {100m, 98m, true},
new object?[] {100m, null, true}, new object?[] {100m, null, true},
new object?[] {SonarrQualityData.MaxUnlimitedThreshold, null, false}, new object?[] {QualityItem.MaxUnlimitedThreshold, null, false},
new object?[] {SonarrQualityData.MaxUnlimitedThreshold - 1, null, true}, new object?[] {QualityItem.MaxUnlimitedThreshold - 1, null, true},
new object?[] {SonarrQualityData.MaxUnlimitedThreshold, SonarrQualityData.MaxUnlimitedThreshold, true} new object?[] {QualityItem.MaxUnlimitedThreshold, QualityItem.MaxUnlimitedThreshold, true}
}; };
private static readonly object[] MinTestValues = private static readonly object[] MinTestValues =
@ -30,7 +30,7 @@ public class SonarrQualityDataTest
public void MaxDifferent_WithVariousValues_ReturnsExpectedResult(decimal guideValue, decimal? radarrValue, public void MaxDifferent_WithVariousValues_ReturnsExpectedResult(decimal guideValue, decimal? radarrValue,
bool isDifferent) bool isDifferent)
{ {
var data = new SonarrQualityData {Max = guideValue}; var data = new QualityItem("", 0, guideValue);
data.IsMaxDifferent(radarrValue) data.IsMaxDifferent(radarrValue)
.Should().Be(isDifferent); .Should().Be(isDifferent);
} }
@ -39,7 +39,7 @@ public class SonarrQualityDataTest
public void MinDifferent_WithVariousValues_ReturnsExpectedResult(decimal guideValue, decimal radarrValue, public void MinDifferent_WithVariousValues_ReturnsExpectedResult(decimal guideValue, decimal radarrValue,
bool isDifferent) bool isDifferent)
{ {
var data = new SonarrQualityData {Min = guideValue}; var data = new QualityItem("", guideValue, 0);
data.IsMinDifferent(radarrValue) data.IsMinDifferent(radarrValue)
.Should().Be(isDifferent); .Should().Be(isDifferent);
} }
@ -47,16 +47,16 @@ public class SonarrQualityDataTest
[Test] [Test]
public void AnnotatedMax_OutsideThreshold_EqualsSameValueWithUnlimited() public void AnnotatedMax_OutsideThreshold_EqualsSameValueWithUnlimited()
{ {
const decimal testVal = SonarrQualityData.MaxUnlimitedThreshold; const decimal testVal = QualityItem.MaxUnlimitedThreshold;
var data = new SonarrQualityData {Max = testVal}; var data = new QualityItem("", 0, testVal);
data.AnnotatedMax.Should().Be($"{testVal} (Unlimited)"); data.AnnotatedMax.Should().Be($"{testVal} (Unlimited)");
} }
[Test] [Test]
public void AnnotatedMax_WithinThreshold_EqualsSameStringValue() public void AnnotatedMax_WithinThreshold_EqualsSameStringValue()
{ {
const decimal testVal = SonarrQualityData.MaxUnlimitedThreshold - 1; const decimal testVal = QualityItem.MaxUnlimitedThreshold - 1;
var data = new SonarrQualityData {Max = testVal}; var data = new QualityItem("", 0, testVal);
data.AnnotatedMax.Should().Be($"{testVal}"); data.AnnotatedMax.Should().Be($"{testVal}");
} }
@ -64,38 +64,38 @@ public class SonarrQualityDataTest
public void AnnotatedMin_NoThreshold_EqualsSameValue() public void AnnotatedMin_NoThreshold_EqualsSameValue()
{ {
const decimal testVal = 10m; const decimal testVal = 10m;
var data = new SonarrQualityData {Max = testVal}; var data = new QualityItem("", 0, testVal);
data.AnnotatedMax.Should().Be($"{testVal}"); data.AnnotatedMax.Should().Be($"{testVal}");
} }
[Test] [Test]
public void Max_AboveThreshold_EqualsSameValue() public void Max_AboveThreshold_EqualsSameValue()
{ {
const decimal testVal = SonarrQualityData.MaxUnlimitedThreshold + 1; const decimal testVal = QualityItem.MaxUnlimitedThreshold + 1;
var data = new SonarrQualityData {Max = testVal}; var data = new QualityItem("", 0, testVal);
data.Max.Should().Be(testVal); data.Max.Should().Be(testVal);
} }
[Test] [Test]
public void MaxForApi_AboveThreshold_EqualsNull() public void MaxForApi_AboveThreshold_EqualsNull()
{ {
const decimal testVal = SonarrQualityData.MaxUnlimitedThreshold + 1; const decimal testVal = QualityItem.MaxUnlimitedThreshold + 1;
var data = new SonarrQualityData {Max = testVal}; var data = new QualityItem("", 0, testVal);
data.MaxForApi.Should().Be(null); data.MaxForApi.Should().Be(null);
} }
[Test] [Test]
public void MaxForApi_HighestWithinThreshold_EqualsSameValue() public void MaxForApi_HighestWithinThreshold_EqualsSameValue()
{ {
const decimal testVal = SonarrQualityData.MaxUnlimitedThreshold - 0.1m; const decimal testVal = QualityItem.MaxUnlimitedThreshold - 0.1m;
var data = new SonarrQualityData {Max = testVal}; var data = new QualityItem("", 0, testVal);
data.MaxForApi.Should().Be(testVal).And.Be(data.Max); data.MaxForApi.Should().Be(testVal).And.Be(data.Max);
} }
[Test] [Test]
public void MaxForApi_LowestWithinThreshold_EqualsSameValue() public void MaxForApi_LowestWithinThreshold_EqualsSameValue()
{ {
var data = new SonarrQualityData {Max = 0}; var data = new QualityItem("", 0, 0);
data.MaxForApi.Should().Be(0); data.MaxForApi.Should().Be(0);
} }
} }

@ -6,4 +6,6 @@ public interface IRepoPaths
{ {
IReadOnlyCollection<IDirectoryInfo> RadarrCustomFormatPaths { get; } IReadOnlyCollection<IDirectoryInfo> RadarrCustomFormatPaths { get; }
IReadOnlyCollection<IDirectoryInfo> SonarrReleaseProfilePaths { get; } IReadOnlyCollection<IDirectoryInfo> SonarrReleaseProfilePaths { get; }
IReadOnlyCollection<IDirectoryInfo> SonarrQualityPaths { get; }
IReadOnlyCollection<IDirectoryInfo> RadarrQualityPaths { get; }
} }

@ -1,11 +1,13 @@
namespace TrashLib.Repo; namespace TrashLib.Repo;
public record RadarrMetadata( public record RadarrMetadata(
IReadOnlyCollection<string> CustomFormats IReadOnlyCollection<string> CustomFormats,
IReadOnlyCollection<string> Qualities
); );
public record SonarrMetadata( public record SonarrMetadata(
IReadOnlyCollection<string> ReleaseProfiles IReadOnlyCollection<string> ReleaseProfiles,
IReadOnlyCollection<string> Qualities
); );
public record JsonPaths( public record JsonPaths(

@ -4,5 +4,7 @@ namespace TrashLib.Repo;
public record RepoPaths( public record RepoPaths(
IReadOnlyCollection<IDirectoryInfo> RadarrCustomFormatPaths, IReadOnlyCollection<IDirectoryInfo> RadarrCustomFormatPaths,
IReadOnlyCollection<IDirectoryInfo> SonarrReleaseProfilePaths IReadOnlyCollection<IDirectoryInfo> SonarrReleaseProfilePaths,
IReadOnlyCollection<IDirectoryInfo> RadarrQualityPaths,
IReadOnlyCollection<IDirectoryInfo> SonarrQualityPaths
) : IRepoPaths; ) : IRepoPaths;

@ -27,6 +27,9 @@ public class RepoPathsFactory : IRepoPathsFactory
var metadata = _metadata.Value; var metadata = _metadata.Value;
return new RepoPaths( return new RepoPaths(
ToDirectoryInfoList(metadata.JsonPaths.Radarr.CustomFormats), ToDirectoryInfoList(metadata.JsonPaths.Radarr.CustomFormats),
ToDirectoryInfoList(metadata.JsonPaths.Sonarr.ReleaseProfiles)); ToDirectoryInfoList(metadata.JsonPaths.Sonarr.ReleaseProfiles),
ToDirectoryInfoList(metadata.JsonPaths.Radarr.Qualities),
ToDirectoryInfoList(metadata.JsonPaths.Sonarr.Qualities)
);
} }
} }

@ -0,0 +1,46 @@
using System.IO.Abstractions;
using Common.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Serilog;
namespace TrashLib.Services.Common.QualityDefinition;
internal class QualityGuideParser<T> where T : class
{
private readonly ILogger _log;
public QualityGuideParser(ILogger log)
{
_log = log;
}
public ICollection<T> GetQualities(IEnumerable<IDirectoryInfo> jsonDirectories)
{
return jsonDirectories
.SelectMany(x => x.GetFiles("*.json"))
.Select(ParseQuality)
.NotNull()
.ToList();
}
private T? ParseQuality(IFileInfo jsonFile)
{
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
});
using var json = new JsonTextReader(jsonFile.OpenText());
var quality = serializer.Deserialize<T>(json);
if (quality is null)
{
_log.Debug("Failed to parse quality definition JSON file: {Filename}", jsonFile.FullName);
}
return quality;
}
}

@ -0,0 +1,46 @@
using System.Globalization;
using System.Text;
namespace TrashLib.Services.Common.QualityDefinition;
public class QualityItem
{
public QualityItem(string quality, decimal min, decimal max)
{
Quality = quality;
Min = min;
Max = max;
}
public const decimal MaxUnlimitedThreshold = 400;
public string Quality { get; }
public decimal Min { get; }
public decimal Max { get; }
public decimal? MaxForApi => Max < MaxUnlimitedThreshold ? Max : null;
public decimal MinForApi => Min;
public string AnnotatedMin => Min.ToString(CultureInfo.InvariantCulture);
public string AnnotatedMax => AnnotatedValue(Max, MaxUnlimitedThreshold);
protected static string AnnotatedValue(decimal value, decimal threshold)
{
var builder = new StringBuilder(value.ToString(CultureInfo.InvariantCulture));
if (value >= threshold)
{
builder.Append(" (Unlimited)");
}
return builder.ToString();
}
public bool IsMinDifferent(decimal serviceValue) => serviceValue != Min;
public bool IsMaxDifferent(decimal? serviceValue)
{
return serviceValue == null
? MaxUnlimitedThreshold != Max
: serviceValue != Max || MaxUnlimitedThreshold == Max;
}
}

@ -1,6 +1,5 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using TrashLib.Config.Services; using TrashLib.Config.Services;
using TrashLib.Services.Radarr.QualityDefinition;
namespace TrashLib.Services.Radarr.Config; namespace TrashLib.Services.Radarr.Config;
@ -31,9 +30,6 @@ public class QualityProfileConfig
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class QualityDefinitionConfig public class QualityDefinitionConfig
{ {
// -1 does not map to a valid enumerator. this is to force validation to fail if it is not set from YAML. public string Type { get; init; } = "";
// All of this craziness is to avoid making the enum type nullable.
public RadarrQualityDefinitionType Type { get; init; } = (RadarrQualityDefinitionType) (-1);
public decimal PreferredRatio { get; set; } = 1.0m; public decimal PreferredRatio { get; set; } = 1.0m;
} }

@ -46,6 +46,6 @@ internal class QualityDefinitionConfigValidator : AbstractValidator<QualityDefin
{ {
public QualityDefinitionConfigValidator(IRadarrValidationMessages messages) public QualityDefinitionConfigValidator(IRadarrValidationMessages messages)
{ {
RuleFor(x => x.Type).IsInEnum().WithMessage(messages.QualityDefinitionType); RuleFor(x => x.Type).NotEmpty().WithMessage(messages.QualityDefinitionType);
} }
} }

@ -1,7 +0,0 @@
namespace TrashLib.Services.Radarr.QualityDefinition;
public interface IRadarrQualityDefinitionGuideParser
{
Task<string> GetMarkdownData();
IDictionary<RadarrQualityDefinitionType, List<RadarrQualityData>> ParseMarkdown(string markdown);
}

@ -0,0 +1,6 @@
namespace TrashLib.Services.Radarr.QualityDefinition;
public interface IRadarrQualityGuideParser
{
ICollection<RadarrQualityData> GetQualities();
}

@ -1,25 +1,7 @@
using TrashLib.Services.Sonarr.QualityDefinition;
namespace TrashLib.Services.Radarr.QualityDefinition; namespace TrashLib.Services.Radarr.QualityDefinition;
public class RadarrQualityData : SonarrQualityData public record RadarrQualityData(
{ string TrashId,
public const decimal PreferredUnlimitedThreshold = 395; string Type,
IReadOnlyCollection<RadarrQualityItem> Qualities
public decimal Preferred { get; set; } );
public decimal? PreferredForApi => Preferred < PreferredUnlimitedThreshold ? Preferred : null;
public string AnnotatedPreferred => AnnotatedValue(Preferred, PreferredUnlimitedThreshold);
public decimal InterpolatedPreferred(decimal ratio)
{
var cappedMax = Math.Min(Max, PreferredUnlimitedThreshold);
return Math.Round(Min + (cappedMax - Min) * ratio, 1);
}
public bool IsPreferredDifferent(decimal? serviceValue)
{
return serviceValue == null
? PreferredUnlimitedThreshold != Preferred
: serviceValue != Preferred || PreferredUnlimitedThreshold == Preferred;
}
}

@ -1,78 +0,0 @@
using System.IO.Abstractions;
using System.Text.RegularExpressions;
using Common.Extensions;
using TrashLib.Startup;
namespace TrashLib.Services.Radarr.QualityDefinition;
internal class RadarrQualityDefinitionGuideParser : IRadarrQualityDefinitionGuideParser
{
private readonly IAppPaths _paths;
private readonly Regex _regexHeader = new(@"^#+", RegexOptions.Compiled);
private readonly Regex _regexTableRow =
new(@"\| *(.*?) *\| *([\d.]+) *\| *([\d.]+) *\|", RegexOptions.Compiled);
public RadarrQualityDefinitionGuideParser(IAppPaths paths)
{
_paths = paths;
}
public async Task<string> GetMarkdownData()
{
var repoDir = _paths.RepoDirectory;
var file = repoDir
.SubDirectory("docs")
.SubDirectory("Radarr")
.File("Radarr-Quality-Settings-File-Size.md").OpenText();
return await file.ReadToEndAsync();
}
public IDictionary<RadarrQualityDefinitionType, List<RadarrQualityData>> ParseMarkdown(string markdown)
{
var results = new Dictionary<RadarrQualityDefinitionType, List<RadarrQualityData>>();
List<RadarrQualityData>? table = null;
var reader = new StringReader(markdown);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
if (string.IsNullOrEmpty(line))
{
continue;
}
var match = _regexHeader.Match(line);
if (match.Success)
{
// todo: hard-coded for now since there's only one supported right now.
var type = RadarrQualityDefinitionType.Movie;
table = results.GetOrCreate(type);
// If we grab a table that isn't empty, that means for whatever reason *another* table
// in the markdown is trying to modify a previous table's data. For example, maybe there
// are two "Series" quality tables. That would be a weird edge case, but handle that
// here just in case.
if (table.Count > 0)
{
table = null;
}
}
else if (table != null)
{
match = _regexTableRow.Match(line);
if (match.Success)
{
table.Add(new RadarrQualityData
{
Name = match.Groups[1].Value,
Min = match.Groups[2].Value.ToDecimal(),
Max = match.Groups[3].Value.ToDecimal()
});
}
}
}
return results;
}
}

@ -1,6 +0,0 @@
namespace TrashLib.Services.Radarr.QualityDefinition;
public enum RadarrQualityDefinitionType
{
Movie
}

@ -1,4 +1,5 @@
using CliFx.Infrastructure; using CliFx.Infrastructure;
using Common.Extensions;
using Serilog; using Serilog;
using TrashLib.Services.Radarr.Config; using TrashLib.Services.Radarr.Config;
using TrashLib.Services.Radarr.QualityDefinition.Api; using TrashLib.Services.Radarr.QualityDefinition.Api;
@ -8,58 +9,65 @@ namespace TrashLib.Services.Radarr.QualityDefinition;
internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
{ {
private readonly ILogger _log;
private readonly IQualityDefinitionService _api; private readonly IQualityDefinitionService _api;
private readonly IConsole _console; private readonly IConsole _console;
private readonly IRadarrQualityDefinitionGuideParser _parser; private readonly IRadarrQualityGuideParser _parser;
public RadarrQualityDefinitionUpdater( public RadarrQualityDefinitionUpdater(
ILogger logger, ILogger logger,
IRadarrQualityDefinitionGuideParser parser, IRadarrQualityGuideParser parser,
IQualityDefinitionService api, IQualityDefinitionService api,
IConsole console) IConsole console)
{ {
Log = logger; _log = logger;
_parser = parser; _parser = parser;
_api = api; _api = api;
_console = console; _console = console;
} }
private ILogger Log { get; }
public async Task Process(bool isPreview, RadarrConfiguration config) public async Task Process(bool isPreview, RadarrConfiguration config)
{ {
Log.Information("Processing Quality Definition: {QualityDefinition}", config.QualityDefinition!.Type); _log.Information("Processing Quality Definition: {QualityDefinition}", config.QualityDefinition!.Type);
var qualityDefinitions = _parser.ParseMarkdown(await _parser.GetMarkdownData()); var qualityDefinitions = _parser.GetQualities();
var qualityTypeInConfig = config.QualityDefinition!.Type;
var selectedQuality = qualityDefinitions
.FirstOrDefault(x => x.Type.EqualsIgnoreCase(qualityTypeInConfig));
var selectedQuality = qualityDefinitions[config.QualityDefinition!.Type]; if (selectedQuality == null)
{
_log.Error("The specified quality definition type does not exist: {Type}", qualityTypeInConfig);
return;
}
// Fix an out of range ratio and warn the user // Fix an out of range ratio and warn the user
if (config.QualityDefinition.PreferredRatio is < 0 or > 1) if (config.QualityDefinition.PreferredRatio is < 0 or > 1)
{ {
var clampedRatio = Math.Clamp(config.QualityDefinition.PreferredRatio, 0, 1); var clampedRatio = Math.Clamp(config.QualityDefinition.PreferredRatio, 0, 1);
Log.Warning("Your `preferred_ratio` of {CurrentRatio} is out of range. " + _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}", "It must be a decimal between 0.0 and 1.0. It has been clamped to {ClampedRatio}",
config.QualityDefinition.PreferredRatio, clampedRatio); config.QualityDefinition.PreferredRatio, clampedRatio);
config.QualityDefinition.PreferredRatio = clampedRatio; config.QualityDefinition.PreferredRatio = clampedRatio;
} }
// Apply a calculated preferred size // Apply a calculated preferred size
foreach (var quality in selectedQuality) foreach (var quality in selectedQuality.Qualities)
{ {
quality.Preferred = quality.InterpolatedPreferred(config.QualityDefinition.PreferredRatio); quality.Preferred = quality.InterpolatedPreferred(config.QualityDefinition.PreferredRatio);
} }
if (isPreview) if (isPreview)
{ {
PrintQualityPreview(selectedQuality); PrintQualityPreview(selectedQuality.Qualities);
return; return;
} }
await ProcessQualityDefinition(selectedQuality); await ProcessQualityDefinition(selectedQuality.Qualities);
} }
private void PrintQualityPreview(IEnumerable<RadarrQualityData> quality) private void PrintQualityPreview(IEnumerable<RadarrQualityItem> quality)
{ {
_console.Output.WriteLine(""); _console.Output.WriteLine("");
const string format = "{0,-20} {1,-10} {2,-15} {3,-15}"; const string format = "{0,-20} {1,-10} {2,-15} {3,-15}";
@ -68,22 +76,22 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
foreach (var q in quality) foreach (var q in quality)
{ {
_console.Output.WriteLine(format, q.Name, q.AnnotatedMin, q.AnnotatedMax, q.AnnotatedPreferred); _console.Output.WriteLine(format, q.Quality, q.AnnotatedMin, q.AnnotatedMax, q.AnnotatedPreferred);
} }
_console.Output.WriteLine(""); _console.Output.WriteLine("");
} }
private async Task ProcessQualityDefinition(IEnumerable<RadarrQualityData> guideQuality) private async Task ProcessQualityDefinition(IEnumerable<RadarrQualityItem> guideQuality)
{ {
var serverQuality = await _api.GetQualityDefinition(); var serverQuality = await _api.GetQualityDefinition();
await UpdateQualityDefinition(serverQuality, guideQuality); await UpdateQualityDefinition(serverQuality, guideQuality);
} }
private async Task UpdateQualityDefinition(IReadOnlyCollection<RadarrQualityDefinitionItem> serverQuality, private async Task UpdateQualityDefinition(IReadOnlyCollection<RadarrQualityDefinitionItem> serverQuality,
IEnumerable<RadarrQualityData> guideQuality) IEnumerable<RadarrQualityItem> guideQuality)
{ {
static bool QualityIsDifferent(RadarrQualityDefinitionItem a, RadarrQualityData b) static bool QualityIsDifferent(RadarrQualityDefinitionItem a, RadarrQualityItem b)
{ {
return b.IsMinDifferent(a.MinSize) || return b.IsMinDifferent(a.MinSize) ||
b.IsMaxDifferent(a.MaxSize) || b.IsMaxDifferent(a.MaxSize) ||
@ -93,10 +101,10 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
var newQuality = new List<RadarrQualityDefinitionItem>(); var newQuality = new List<RadarrQualityDefinitionItem>();
foreach (var qualityData in guideQuality) foreach (var qualityData in guideQuality)
{ {
var entry = serverQuality.FirstOrDefault(q => q.Quality?.Name == qualityData.Name); var entry = serverQuality.FirstOrDefault(q => q.Quality?.Name == qualityData.Quality);
if (entry == null) if (entry == null)
{ {
Log.Warning("Server lacks quality definition for {Quality}; it will be skipped", qualityData.Name); _log.Warning("Server lacks quality definition for {Quality}; it will be skipped", qualityData.Quality);
continue; continue;
} }
@ -111,12 +119,12 @@ internal class RadarrQualityDefinitionUpdater : IRadarrQualityDefinitionUpdater
entry.PreferredSize = qualityData.PreferredForApi; entry.PreferredSize = qualityData.PreferredForApi;
newQuality.Add(entry); newQuality.Add(entry);
Log.Debug("Setting Quality " + _log.Debug("Setting Quality " +
"[Name: {Name}] [Source: {Source}] [Min: {Min}] [Max: {Max}] [Preferred: {Preferred}]", "[Name: {Name}] [Source: {Source}] [Min: {Min}] [Max: {Max}] [Preferred: {Preferred}]",
entry.Quality?.Name, entry.Quality?.Source, entry.MinSize, entry.MaxSize, entry.PreferredSize); entry.Quality?.Name, entry.Quality?.Source, entry.MinSize, entry.MaxSize, entry.PreferredSize);
} }
await _api.UpdateQualityDefinition(newQuality); await _api.UpdateQualityDefinition(newQuality);
Log.Information("Number of updated qualities: {Count}", newQuality.Count); _log.Information("Number of updated qualities: {Count}", newQuality.Count);
} }
} }

@ -0,0 +1,20 @@
using Serilog;
using TrashLib.Repo;
using TrashLib.Services.Common.QualityDefinition;
namespace TrashLib.Services.Radarr.QualityDefinition;
internal class RadarrQualityGuideParser : IRadarrQualityGuideParser
{
private readonly QualityGuideParser<RadarrQualityData> _parser;
private readonly IRepoPathsFactory _pathFactory;
public RadarrQualityGuideParser(ILogger log, IRepoPathsFactory pathFactory)
{
_parser = new QualityGuideParser<RadarrQualityData>(log);
_pathFactory = pathFactory;
}
public ICollection<RadarrQualityData> GetQualities()
=> _parser.GetQualities(_pathFactory.Create().RadarrQualityPaths);
}

@ -0,0 +1,31 @@
using TrashLib.Services.Common.QualityDefinition;
namespace TrashLib.Services.Radarr.QualityDefinition;
public class RadarrQualityItem : QualityItem
{
public RadarrQualityItem(string quality, decimal min, decimal max, decimal preferred)
: base(quality, min, max)
{
Preferred = preferred;
}
public const decimal PreferredUnlimitedThreshold = 395;
public decimal Preferred { get; set; }
public decimal? PreferredForApi => Preferred < PreferredUnlimitedThreshold ? Preferred : null;
public string AnnotatedPreferred => AnnotatedValue(Preferred, PreferredUnlimitedThreshold);
public decimal InterpolatedPreferred(decimal ratio)
{
var cappedMax = Math.Min(Max, PreferredUnlimitedThreshold);
return Math.Round(Min + (cappedMax - Min) * ratio, 1);
}
public bool IsPreferredDifferent(decimal? serviceValue)
{
return serviceValue == null
? PreferredUnlimitedThreshold != Preferred
: serviceValue != Preferred || PreferredUnlimitedThreshold == Preferred;
}
}

@ -28,7 +28,7 @@ public class RadarrAutofacModule : Module
// Quality Definition Support // Quality Definition Support
builder.RegisterType<RadarrQualityDefinitionUpdater>().As<IRadarrQualityDefinitionUpdater>(); builder.RegisterType<RadarrQualityDefinitionUpdater>().As<IRadarrQualityDefinitionUpdater>();
builder.RegisterType<RadarrQualityDefinitionGuideParser>().As<IRadarrQualityDefinitionGuideParser>(); builder.RegisterType<RadarrQualityGuideParser>().As<IRadarrQualityGuideParser>();
// Custom Format Support // Custom Format Support
builder.RegisterType<CustomFormatUpdater>().As<ICustomFormatUpdater>(); builder.RegisterType<CustomFormatUpdater>().As<ICustomFormatUpdater>();

@ -1,12 +1,11 @@
using TrashLib.Config.Services; using TrashLib.Config.Services;
using TrashLib.Services.Sonarr.QualityDefinition;
namespace TrashLib.Services.Sonarr.Config; namespace TrashLib.Services.Sonarr.Config;
public class SonarrConfiguration : ServiceConfiguration public class SonarrConfiguration : ServiceConfiguration
{ {
public IList<ReleaseProfileConfig> ReleaseProfiles { get; init; } = Array.Empty<ReleaseProfileConfig>(); public IList<ReleaseProfileConfig> ReleaseProfiles { get; init; } = Array.Empty<ReleaseProfileConfig>();
public SonarrQualityDefinitionType? QualityDefinition { get; init; } public string QualityDefinition { get; init; } = "";
} }
public class ReleaseProfileConfig public class ReleaseProfileConfig

@ -1,7 +0,0 @@
namespace TrashLib.Services.Sonarr.QualityDefinition;
public interface ISonarrQualityDefinitionGuideParser
{
Task<string> GetMarkdownData();
IDictionary<SonarrQualityDefinitionType, List<SonarrQualityData>> ParseMarkdown(string markdown);
}

@ -0,0 +1,6 @@
namespace TrashLib.Services.Sonarr.QualityDefinition;
public interface ISonarrQualityGuideParser
{
ICollection<SonarrQualityData> GetQualities();
}

@ -1,39 +1,9 @@
using System.Globalization; using TrashLib.Services.Common.QualityDefinition;
using System.Text;
namespace TrashLib.Services.Sonarr.QualityDefinition; namespace TrashLib.Services.Sonarr.QualityDefinition;
public class SonarrQualityData public record SonarrQualityData(
{ string TrashId,
public const decimal MaxUnlimitedThreshold = 400; string Type,
IReadOnlyCollection<QualityItem> Qualities
public string Name { get; set; } = ""; );
public decimal Min { get; set; }
public decimal Max { get; set; }
public decimal? MaxForApi => Max < MaxUnlimitedThreshold ? Max : null;
public decimal MinForApi => Min;
public string AnnotatedMin => Min.ToString(CultureInfo.InvariantCulture);
public string AnnotatedMax => AnnotatedValue(Max, MaxUnlimitedThreshold);
protected static string AnnotatedValue(decimal value, decimal threshold)
{
var builder = new StringBuilder(value.ToString(CultureInfo.InvariantCulture));
if (value >= threshold)
{
builder.Append(" (Unlimited)");
}
return builder.ToString();
}
public bool IsMinDifferent(decimal serviceValue) => serviceValue != Min;
public bool IsMaxDifferent(decimal? serviceValue)
{
return serviceValue == null
? MaxUnlimitedThreshold != Max
: serviceValue != Max || MaxUnlimitedThreshold == Max;
}
}

@ -1,81 +0,0 @@
using System.IO.Abstractions;
using System.Text.RegularExpressions;
using Common.Extensions;
using TrashLib.Startup;
namespace TrashLib.Services.Sonarr.QualityDefinition;
internal class SonarrQualityDefinitionGuideParser : ISonarrQualityDefinitionGuideParser
{
private readonly Regex _regexHeader = new(@"^#+", RegexOptions.Compiled);
private readonly Regex _regexTableRow =
new(@"\| *(.*?) *\| *([\d.]+) *\| *([\d.]+) *\|", RegexOptions.Compiled);
private readonly IAppPaths _paths;
public SonarrQualityDefinitionGuideParser(IAppPaths paths)
{
_paths = paths;
}
public async Task<string> GetMarkdownData()
{
var repoDir = _paths.RepoDirectory;
var file = repoDir
.SubDirectory("docs")
.SubDirectory("Sonarr")
.File("Sonarr-Quality-Settings-File-Size.md").OpenText();
return await file.ReadToEndAsync();
}
public IDictionary<SonarrQualityDefinitionType, List<SonarrQualityData>> ParseMarkdown(string markdown)
{
var results = new Dictionary<SonarrQualityDefinitionType, List<SonarrQualityData>>();
List<SonarrQualityData>? table = null;
var reader = new StringReader(markdown);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
if (string.IsNullOrEmpty(line))
{
continue;
}
var match = _regexHeader.Match(line);
if (match.Success)
{
var type = line.ContainsIgnoreCase("anime")
? SonarrQualityDefinitionType.Anime
: SonarrQualityDefinitionType.Series;
table = results.GetOrCreate(type);
// If we grab a table that isn't empty, that means for whatever reason *another* table
// in the markdown is trying to modify a previous table's data. For example, maybe there
// are two "Series" quality tables. That would be a weird edge case, but handle that
// here just in case.
if (table.Count > 0)
{
table = null;
}
}
else if (table != null)
{
match = _regexTableRow.Match(line);
if (match.Success)
{
table.Add(new SonarrQualityData
{
Name = match.Groups[1].Value,
Min = match.Groups[2].Value.ToDecimal(),
Max = match.Groups[3].Value.ToDecimal()
});
}
}
}
return results;
}
}

@ -1,8 +0,0 @@
namespace TrashLib.Services.Sonarr.QualityDefinition;
public enum SonarrQualityDefinitionType
{
Anime,
Series,
Hybrid
}

@ -1,6 +1,8 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using Common.Extensions;
using Serilog; using Serilog;
using TrashLib.Services.Common.QualityDefinition;
using TrashLib.Services.Sonarr.Api; using TrashLib.Services.Sonarr.Api;
using TrashLib.Services.Sonarr.Api.Objects; using TrashLib.Services.Sonarr.Api.Objects;
using TrashLib.Services.Sonarr.Config; using TrashLib.Services.Sonarr.Config;
@ -9,91 +11,117 @@ namespace TrashLib.Services.Sonarr.QualityDefinition;
internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
{ {
private readonly ILogger _log;
private readonly ISonarrApi _api; private readonly ISonarrApi _api;
private readonly IConsole _console; private readonly IConsole _console;
private readonly ISonarrQualityDefinitionGuideParser _parser; private readonly ISonarrQualityGuideParser _parser;
private readonly Regex _regexHybrid = new(@"720|1080", RegexOptions.Compiled); private readonly Regex _regexHybrid = new(@"720|1080", RegexOptions.Compiled);
public SonarrQualityDefinitionUpdater( public SonarrQualityDefinitionUpdater(
ILogger logger, ILogger logger,
ISonarrQualityDefinitionGuideParser parser, ISonarrQualityGuideParser parser,
ISonarrApi api, ISonarrApi api,
IConsole console) IConsole console)
{ {
Log = logger; _log = logger;
_parser = parser; _parser = parser;
_api = api; _api = api;
_console = console; _console = console;
} }
private ILogger Log { get; } private SonarrQualityData? GetQualityOrError(ICollection<SonarrQualityData> qualityDefinitions, string type)
{
var quality = qualityDefinitions.FirstOrDefault(x => x.Type.EqualsIgnoreCase(type));
if (quality is null)
{
_log.Error(
"The following quality definition is required for hybrid, but was not found in the guide: {Type}",
type);
}
return quality;
}
public async Task Process(bool isPreview, SonarrConfiguration config) public async Task Process(bool isPreview, SonarrConfiguration config)
{ {
Log.Information("Processing Quality Definition: {QualityDefinition}", config.QualityDefinition); _log.Information("Processing Quality Definition: {QualityDefinition}", config.QualityDefinition);
var qualityDefinitions = _parser.ParseMarkdown(await _parser.GetMarkdownData()); var qualityDefinitions = _parser.GetQualities();
List<SonarrQualityData> selectedQuality; var qualityTypeInConfig = config.QualityDefinition;
// var qualityDefinitions = _parser.ParseMarkdown(await _parser.GetMarkdownData());
SonarrQualityData? selectedQuality;
if (config.QualityDefinition == SonarrQualityDefinitionType.Hybrid) if (config.QualityDefinition.EqualsIgnoreCase("hybrid"))
{ {
selectedQuality = BuildHybridQuality(qualityDefinitions[SonarrQualityDefinitionType.Anime], var animeQuality = GetQualityOrError(qualityDefinitions, "anime");
qualityDefinitions[SonarrQualityDefinitionType.Series]); var seriesQuality = GetQualityOrError(qualityDefinitions, "series");
if (animeQuality is null || seriesQuality is null)
{
return;
}
selectedQuality = BuildHybridQuality(animeQuality.Qualities, seriesQuality.Qualities);
} }
else else
{ {
selectedQuality = qualityDefinitions[config.QualityDefinition!.Value]; selectedQuality = qualityDefinitions
.FirstOrDefault(x => x.Type.EqualsIgnoreCase(qualityTypeInConfig));
if (selectedQuality == null)
{
_log.Error("The specified quality definition type does not exist: {Type}", qualityTypeInConfig);
return;
}
} }
if (isPreview) if (isPreview)
{ {
PrintQualityPreview(selectedQuality); PrintQualityPreview(selectedQuality.Qualities);
return; return;
} }
await ProcessQualityDefinition(selectedQuality); await ProcessQualityDefinition(selectedQuality.Qualities);
} }
private List<SonarrQualityData> BuildHybridQuality(IReadOnlyCollection<SonarrQualityData> anime, private SonarrQualityData BuildHybridQuality(
IEnumerable<SonarrQualityData> series) IReadOnlyCollection<QualityItem> anime,
IReadOnlyCollection<QualityItem> series)
{ {
// todo Verify anime & series are the same length? Probably not, because we might not care about some rows anyway. // todo Verify anime & series are the same length? Probably not, because we might not care about some rows anyway.
Log.Information( _log.Information(
"Notice: Hybrid only functions on 720/1080 qualities and uses non-anime values for the rest (e.g. 2160)"); "Notice: Hybrid only functions on 720/1080 qualities and uses non-anime values for the rest (e.g. 2160)");
var hybrid = new List<SonarrQualityData>(); var hybrid = new List<QualityItem>();
foreach (var left in series) foreach (var left in series)
{ {
// Any qualities that anime doesn't care about get immediately added from Series quality // Any qualities that anime doesn't care about get immediately added from Series quality
var match = _regexHybrid.Match(left.Name); var match = _regexHybrid.Match(left.Quality);
if (!match.Success) if (!match.Success)
{ {
Log.Debug("Using 'Series' Quality For: {QualityName}", left.Name); _log.Debug("Using 'Series' Quality For: {QualityName}", left.Quality);
hybrid.Add(left); hybrid.Add(left);
continue; continue;
} }
// If there's a quality in Series that Anime doesn't know about, we add the Series quality // If there's a quality in Series that Anime doesn't know about, we add the Series quality
var right = anime.FirstOrDefault(row => row.Name == left.Name); var right = anime.FirstOrDefault(row => row.Quality == left.Quality);
if (right == null) if (right == null)
{ {
Log.Error("Could not find matching anime quality for series quality named {QualityName}", _log.Error("Could not find matching anime quality for series quality named {QualityName}",
left.Name); left.Quality);
hybrid.Add(left); hybrid.Add(left);
continue; continue;
} }
hybrid.Add(new SonarrQualityData hybrid.Add(new QualityItem(left.Quality,
{ Math.Min(left.Min, right.Min),
Name = left.Name, Math.Max(left.Max, right.Max)));
Min = Math.Min(left.Min, right.Min),
Max = Math.Max(left.Max, right.Max)
});
} }
return hybrid; return new SonarrQualityData("", "hybrid", hybrid);
} }
private void PrintQualityPreview(IEnumerable<SonarrQualityData> quality) private void PrintQualityPreview(IEnumerable<QualityItem> quality)
{ {
_console.Output.WriteLine(""); _console.Output.WriteLine("");
const string format = "{0,-20} {1,-10} {2,-15}"; const string format = "{0,-20} {1,-10} {2,-15}";
@ -102,22 +130,22 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
foreach (var q in quality) foreach (var q in quality)
{ {
_console.Output.WriteLine(format, q.Name, q.AnnotatedMin, q.AnnotatedMax); _console.Output.WriteLine(format, q.Quality, q.AnnotatedMin, q.AnnotatedMax);
} }
_console.Output.WriteLine(""); _console.Output.WriteLine("");
} }
private async Task ProcessQualityDefinition(IEnumerable<SonarrQualityData> guideQuality) private async Task ProcessQualityDefinition(IEnumerable<QualityItem> guideQuality)
{ {
var serverQuality = await _api.GetQualityDefinition(); var serverQuality = await _api.GetQualityDefinition();
await UpdateQualityDefinition(serverQuality, guideQuality); await UpdateQualityDefinition(serverQuality, guideQuality);
} }
private async Task UpdateQualityDefinition(IReadOnlyCollection<SonarrQualityDefinitionItem> serverQuality, private async Task UpdateQualityDefinition(IReadOnlyCollection<SonarrQualityDefinitionItem> serverQuality,
IEnumerable<SonarrQualityData> guideQuality) IEnumerable<QualityItem> guideQuality)
{ {
static bool QualityIsDifferent(SonarrQualityDefinitionItem a, SonarrQualityData b) static bool QualityIsDifferent(SonarrQualityDefinitionItem a, QualityItem b)
{ {
return b.IsMinDifferent(a.MinSize) || return b.IsMinDifferent(a.MinSize) ||
b.IsMaxDifferent(a.MaxSize); b.IsMaxDifferent(a.MaxSize);
@ -126,10 +154,10 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
var newQuality = new List<SonarrQualityDefinitionItem>(); var newQuality = new List<SonarrQualityDefinitionItem>();
foreach (var qualityData in guideQuality) foreach (var qualityData in guideQuality)
{ {
var entry = serverQuality.FirstOrDefault(q => q.Quality?.Name == qualityData.Name); var entry = serverQuality.FirstOrDefault(q => q.Quality?.Name == qualityData.Quality);
if (entry == null) if (entry == null)
{ {
Log.Warning("Server lacks quality definition for {Quality}; it will be skipped", qualityData.Name); _log.Warning("Server lacks quality definition for {Quality}; it will be skipped", qualityData.Quality);
continue; continue;
} }
@ -143,12 +171,12 @@ internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
entry.MaxSize = qualityData.MaxForApi; entry.MaxSize = qualityData.MaxForApi;
newQuality.Add(entry); newQuality.Add(entry);
Log.Debug("Setting Quality " + _log.Debug("Setting Quality " +
"[Name: {Name}] [Source: {Source}] [Min: {Min}] [Max: {Max}]", "[Name: {Name}] [Source: {Source}] [Min: {Min}] [Max: {Max}]",
entry.Quality?.Name, entry.Quality?.Source, entry.MinSize, entry.MaxSize); entry.Quality?.Name, entry.Quality?.Source, entry.MinSize, entry.MaxSize);
} }
await _api.UpdateQualityDefinition(newQuality); await _api.UpdateQualityDefinition(newQuality);
Log.Information("Number of updated qualities: {Count}", newQuality.Count); _log.Information("Number of updated qualities: {Count}", newQuality.Count);
} }
} }

@ -0,0 +1,20 @@
using Serilog;
using TrashLib.Repo;
using TrashLib.Services.Common.QualityDefinition;
namespace TrashLib.Services.Sonarr.QualityDefinition;
internal class SonarrQualityGuideParser : ISonarrQualityGuideParser
{
private readonly QualityGuideParser<SonarrQualityData> _parser;
private readonly IRepoPathsFactory _pathFactory;
public SonarrQualityGuideParser(ILogger log, IRepoPathsFactory pathFactory)
{
_parser = new QualityGuideParser<SonarrQualityData>(log);
_pathFactory = pathFactory;
}
public ICollection<SonarrQualityData> GetQualities()
=> _parser.GetQualities(_pathFactory.Create().SonarrQualityPaths);
}

@ -34,6 +34,6 @@ public class SonarrAutofacModule : Module
// Quality Definition Support // Quality Definition Support
builder.RegisterType<SonarrQualityDefinitionUpdater>().As<ISonarrQualityDefinitionUpdater>(); builder.RegisterType<SonarrQualityDefinitionUpdater>().As<ISonarrQualityDefinitionUpdater>();
builder.RegisterType<SonarrQualityDefinitionGuideParser>().As<ISonarrQualityDefinitionGuideParser>(); builder.RegisterType<SonarrQualityGuideParser>().As<ISonarrQualityGuideParser>();
} }
} }

@ -21,8 +21,4 @@
<ProjectReference Include="..\Common\Common.csproj" /> <ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\VersionControl\VersionControl.csproj" /> <ProjectReference Include="..\VersionControl\VersionControl.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Services" />
</ItemGroup>
</Project> </Project>

Loading…
Cancel
Save