using Recyclarr.Common.Extensions; using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Services.QualitySize.Api; using Recyclarr.TrashLib.Services.QualitySize.Guide; using Spectre.Console; namespace Recyclarr.TrashLib.Services.QualitySize; internal class QualitySizeUpdater : IQualitySizeUpdater { private readonly ILogger _log; private readonly IQualityDefinitionService _api; private readonly IAnsiConsole _console; private readonly IQualityGuideService _guide; public QualitySizeUpdater( ILogger logger, IQualityDefinitionService api, IAnsiConsole console, IQualityGuideService guide) { _log = logger; _api = api; _console = console; _guide = guide; } public async Task Process(bool isPreview, IServiceConfiguration config) { var qualityDef = config.QualityDefinition; if (qualityDef is null) { return; } var qualityDefinitions = _guide.GetQualitySizeData(config.ServiceType); var selectedQuality = qualityDefinitions .FirstOrDefault(x => x.Type.EqualsIgnoreCase(qualityDef.Type)); if (selectedQuality == null) { _log.Error("The specified quality definition type does not exist: {Type}", qualityDef.Type); return; } _log.Information("Processing Quality Definition: {QualityDefinition}", qualityDef.Type); if (qualityDef.PreferredRatio is not null) { _log.Information("Using an explicit preferred ratio which will override values from the guide"); // Fix an out of range ratio and warn the user if (qualityDef.PreferredRatio is < 0 or > 1) { var clampedRatio = Math.Clamp(qualityDef.PreferredRatio.Value, 0, 1); _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}", qualityDef.PreferredRatio, clampedRatio); qualityDef.PreferredRatio = clampedRatio; } // Apply a calculated preferred size foreach (var quality in selectedQuality.Qualities) { quality.Preferred = quality.InterpolatedPreferred(qualityDef.PreferredRatio.Value); } } if (isPreview) { PrintQualityPreview(selectedQuality.Qualities); return; } await ProcessQualityDefinition(config, selectedQuality.Qualities); } private void PrintQualityPreview(IReadOnlyCollection qualitySizeItems) { var table = new Table(); table.Title("Quality Sizes [red](Preview)[/]"); table.AddColumn("[bold]Quality[/]"); table.AddColumn("[bold]Min[/]"); table.AddColumn("[bold]Max[/]"); table.AddColumn("[bold]Preferred[/]"); foreach (var q in qualitySizeItems) { var quality = $"[dodgerblue1]{q.Quality}[/]"; table.AddRow(quality, q.AnnotatedMin, q.AnnotatedMax, q.AnnotatedPreferred); } _console.WriteLine(); _console.Write(table); } private static bool QualityIsDifferent(ServiceQualityDefinitionItem a, QualitySizeItem b) { return b.IsMinDifferent(a.MinSize) || b.IsMaxDifferent(a.MaxSize) || a.PreferredSize is not null && b.IsPreferredDifferent(a.PreferredSize); } private async Task ProcessQualityDefinition( IServiceConfiguration config, IReadOnlyCollection guideQuality) { var serverQuality = await _api.GetQualityDefinition(config); var newQuality = new List(); foreach (var qualityData in guideQuality) { var serverEntry = serverQuality.FirstOrDefault(q => q.Quality?.Name == qualityData.Quality); if (serverEntry == null) { _log.Warning("Server lacks quality definition for {Quality}; it will be skipped", qualityData.Quality); continue; } if (!QualityIsDifferent(serverEntry, qualityData)) { continue; } // Not using the original list again, so it's OK to modify the definition ref type objects in-place. serverEntry.MinSize = qualityData.MinForApi; serverEntry.MaxSize = qualityData.MaxForApi; serverEntry.PreferredSize = qualityData.PreferredForApi; newQuality.Add(serverEntry); _log.Debug("Setting Quality " + "[Name: {Name}] [Source: {Source}] [Min: {Min}] [Max: {Max}] [Preferred: {Preferred}]", serverEntry.Quality?.Name, serverEntry.Quality?.Source, serverEntry.MinSize, serverEntry.MaxSize, serverEntry.PreferredSize); } await _api.UpdateQualityDefinition(config, newQuality); _log.Information("Number of updated qualities: {Count}", newQuality.Count); } }