You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
recyclarr/src/TrashLib/Services/Sonarr/QualityDefinition/SonarrQualityDefinitionUpda...

182 lines
6.4 KiB

using System.Text.RegularExpressions;
using CliFx.Infrastructure;
using Common.Extensions;
using Serilog;
using TrashLib.Services.Common.QualityDefinition;
using TrashLib.Services.Sonarr.Api;
using TrashLib.Services.Sonarr.Api.Objects;
using TrashLib.Services.Sonarr.Config;
using TrashLib.Services.Sonarr.ReleaseProfile.Guide;
namespace TrashLib.Services.Sonarr.QualityDefinition;
internal class SonarrQualityDefinitionUpdater : ISonarrQualityDefinitionUpdater
{
private readonly ILogger _log;
private readonly ISonarrApi _api;
private readonly IConsole _console;
private readonly ISonarrGuideService _guide;
private readonly Regex _regexHybrid = new(@"720|1080", RegexOptions.Compiled);
public SonarrQualityDefinitionUpdater(
ILogger logger,
ISonarrGuideService guide,
ISonarrApi api,
IConsole console)
{
_log = logger;
_guide = guide;
_api = api;
_console = console;
}
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)
{
_log.Information("Processing Quality Definition: {QualityDefinition}", config.QualityDefinition);
var qualityDefinitions = _guide.GetQualities();
var qualityTypeInConfig = config.QualityDefinition;
SonarrQualityData? selectedQuality;
if (config.QualityDefinition.EqualsIgnoreCase("hybrid"))
{
var animeQuality = GetQualityOrError(qualityDefinitions, "anime");
var seriesQuality = GetQualityOrError(qualityDefinitions, "series");
if (animeQuality is null || seriesQuality is null)
{
return;
}
selectedQuality = BuildHybridQuality(animeQuality.Qualities, seriesQuality.Qualities);
}
else
{
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)
{
PrintQualityPreview(selectedQuality.Qualities);
return;
}
await ProcessQualityDefinition(selectedQuality.Qualities);
}
private SonarrQualityData BuildHybridQuality(
IReadOnlyCollection<QualityItem> anime,
IReadOnlyCollection<QualityItem> series)
{
_log.Information(
"Notice: Hybrid only functions on 720/1080 qualities and uses non-anime values for the rest (e.g. 2160)");
var hybrid = new List<QualityItem>();
foreach (var left in series)
{
// Any qualities that anime doesn't care about get immediately added from Series quality
var match = _regexHybrid.Match(left.Quality);
if (!match.Success)
{
_log.Debug("Using 'Series' Quality For: {QualityName}", left.Quality);
hybrid.Add(left);
continue;
}
// If there's a quality in Series that Anime doesn't know about, we add the Series quality
var right = anime.FirstOrDefault(row => row.Quality == left.Quality);
if (right == null)
{
_log.Error("Could not find matching anime quality for series quality named {QualityName}",
left.Quality);
hybrid.Add(left);
continue;
}
hybrid.Add(new QualityItem(left.Quality,
Math.Min(left.Min, right.Min),
Math.Max(left.Max, right.Max)));
}
return new SonarrQualityData("", "hybrid", hybrid);
}
private void PrintQualityPreview(IEnumerable<QualityItem> quality)
{
_console.Output.WriteLine("");
const string format = "{0,-20} {1,-10} {2,-15}";
_console.Output.WriteLine(format, "Quality", "Min", "Max");
_console.Output.WriteLine(format, "-------", "---", "---");
foreach (var q in quality)
{
_console.Output.WriteLine(format, q.Quality, q.AnnotatedMin, q.AnnotatedMax);
}
_console.Output.WriteLine("");
}
private async Task ProcessQualityDefinition(IEnumerable<QualityItem> guideQuality)
{
var serverQuality = await _api.GetQualityDefinition();
await UpdateQualityDefinition(serverQuality, guideQuality);
}
private async Task UpdateQualityDefinition(IReadOnlyCollection<SonarrQualityDefinitionItem> serverQuality,
IEnumerable<QualityItem> guideQuality)
{
static bool QualityIsDifferent(SonarrQualityDefinitionItem a, QualityItem b)
{
return b.IsMinDifferent(a.MinSize) ||
b.IsMaxDifferent(a.MaxSize);
}
var newQuality = new List<SonarrQualityDefinitionItem>();
foreach (var qualityData in guideQuality)
{
var entry = serverQuality.FirstOrDefault(q => q.Quality?.Name == qualityData.Quality);
if (entry == null)
{
_log.Warning("Server lacks quality definition for {Quality}; it will be skipped", qualityData.Quality);
continue;
}
if (!QualityIsDifferent(entry, qualityData))
{
continue;
}
// Not using the original list again, so it's OK to modify the definition ref type objects in-place.
entry.MinSize = qualityData.MinForApi;
entry.MaxSize = qualityData.MaxForApi;
newQuality.Add(entry);
_log.Debug("Setting Quality " +
"[Name: {Name}] [Source: {Source}] [Min: {Min}] [Max: {Max}]",
entry.Quality?.Name, entry.Quality?.Source, entry.MinSize, entry.MaxSize);
}
await _api.UpdateQualityDefinition(newQuality);
_log.Information("Number of updated qualities: {Count}", newQuality.Count);
}
}