diff --git a/CHANGELOG.md b/CHANGELOG.md index e0750f9a..eaef2bb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use compact JSON for HTTP request/response body in debug log files. This makes logs much easier to scroll through. - Sonarr: Run version enforcement logic when using CFs instead of RPs. +- A warning is now displayed when the same custom format is assigned multiple times to the same + quality profile. ## [2.5.0] - 2022-09-11 diff --git a/src/TrashLib/Services/CustomFormat/CustomFormatUpdater.cs b/src/TrashLib/Services/CustomFormat/CustomFormatUpdater.cs index 2e0c973f..8d97e842 100644 --- a/src/TrashLib/Services/CustomFormat/CustomFormatUpdater.cs +++ b/src/TrashLib/Services/CustomFormat/CustomFormatUpdater.cs @@ -204,6 +204,25 @@ internal class CustomFormatUpdater : ICustomFormatUpdater _console.Output.WriteLine(""); } + if (_guideProcessor.DuplicateScores.Any()) + { + foreach (var (profileName, duplicates) in _guideProcessor.DuplicateScores) + foreach (var (trashId, dupeScores) in duplicates) + { + _log.Warning( + "Custom format with trash ID {TrashId} is duplicated {Count} times in quality profile " + + "{ProfileName} with the following scores: {Scores}", + trashId, dupeScores.Count, profileName, dupeScores); + } + + _log.Warning( + "When the same CF is specified multiple times with different scores in the same quality profile, " + + "only the score from the first occurrence is used. To resolve the duplication warnings above, " + + "remove the duplicate trash IDs from your YAML config"); + + _console.Output.WriteLine(""); + } + if (_guideProcessor.CustomFormatsWithOutdatedNames.Count > 0) { _log.Warning("One or more custom format names in your YAML config have been renamed in the guide and " + diff --git a/src/TrashLib/Services/CustomFormat/Processors/GuideProcessor.cs b/src/TrashLib/Services/CustomFormat/Processors/GuideProcessor.cs index f08d71e5..05e50336 100644 --- a/src/TrashLib/Services/CustomFormat/Processors/GuideProcessor.cs +++ b/src/TrashLib/Services/CustomFormat/Processors/GuideProcessor.cs @@ -40,6 +40,9 @@ internal class GuideProcessor : IGuideProcessor public IReadOnlyCollection<(string name, string trashId, string profileName)> CustomFormatsWithoutScore => _steps.QualityProfile.CustomFormatsWithoutScore; + public IReadOnlyDictionary>> DuplicateScores + => _steps.QualityProfile.DuplicateScores; + public IReadOnlyCollection DeletedCustomFormatsInCache => _steps.CustomFormat.DeletedCustomFormatsInCache; diff --git a/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/IQualityProfileStep.cs b/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/IQualityProfileStep.cs index dfd9da1d..b3314f7e 100644 --- a/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/IQualityProfileStep.cs +++ b/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/IQualityProfileStep.cs @@ -6,5 +6,6 @@ public interface IQualityProfileStep { IDictionary ProfileScores { get; } IReadOnlyCollection<(string name, string trashId, string profileName)> CustomFormatsWithoutScore { get; } + IReadOnlyDictionary>> DuplicateScores { get; } void Process(IEnumerable configData); } diff --git a/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/QualityProfileStep.cs b/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/QualityProfileStep.cs index f5931e50..bbbcdc4e 100644 --- a/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/QualityProfileStep.cs +++ b/src/TrashLib/Services/CustomFormat/Processors/GuideSteps/QualityProfileStep.cs @@ -1,3 +1,4 @@ +using Common.Extensions; using TrashLib.Services.CustomFormat.Models; namespace TrashLib.Services.CustomFormat.Processors.GuideSteps; @@ -6,12 +7,15 @@ internal class QualityProfileStep : IQualityProfileStep { private readonly Dictionary _profileScores = new(); private readonly List<(string name, string trashId, string profileName)> _customFormatsWithoutScore = new(); + private readonly Dictionary>> _duplicateScores = new(); public IDictionary ProfileScores => _profileScores; public IReadOnlyCollection<(string name, string trashId, string profileName)> CustomFormatsWithoutScore => _customFormatsWithoutScore; + public IReadOnlyDictionary>> DuplicateScores => _duplicateScores; + public void Process(IEnumerable configData) { foreach (var config in configData) @@ -46,6 +50,15 @@ internal class QualityProfileStep : IQualityProfileStep ProfileScores[profile.Name] = mapping; } + // Check if this score was specified multiple times for the same profile. For each duplicate, we record + // the score of the second and onward occurrences for logging/reporting purposes. + var dupe = mapping.Mapping.FirstOrDefault(x => x.CustomFormat.TrashId.EqualsIgnoreCase(cf.TrashId)); + if (dupe is not null) + { + _duplicateScores.GetOrCreate(profile.Name).GetOrCreate(cf.TrashId).Add(scoreToUse.Value); + continue; + } + mapping.Mapping.Add(new FormatMappingEntry(cf, scoreToUse.Value)); } } diff --git a/src/TrashLib/Services/CustomFormat/Processors/IGuideProcessor.cs b/src/TrashLib/Services/CustomFormat/Processors/IGuideProcessor.cs index b2380edd..78bd8988 100644 --- a/src/TrashLib/Services/CustomFormat/Processors/IGuideProcessor.cs +++ b/src/TrashLib/Services/CustomFormat/Processors/IGuideProcessor.cs @@ -15,6 +15,7 @@ internal interface IGuideProcessor IReadOnlyCollection DeletedCustomFormatsInCache { get; } IReadOnlyCollection<(string, string)> CustomFormatsWithOutdatedNames { get; } IDictionary> DuplicatedCustomFormats { get; } + IReadOnlyDictionary>> DuplicateScores { get; } Task BuildGuideDataAsync(IEnumerable config, CustomFormatCache? cache, IGuideService guideService);