From 05e7b90aab4750b2ac92b1fb55dd893a0f974ec6 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 19 Feb 2019 18:30:57 -0800 Subject: [PATCH] Fixed: Correct rejection message when profile does not allow upgrades Fixes #2958 --- .../UpgradeAllowedSpecificationFixture .cs | 292 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../Specifications/CutoffSpecification.cs | 13 +- .../Specifications/QueueSpecification.cs | 30 +- .../Specifications/UpgradableSpecification.cs | 32 +- .../UpgradeAllowedSpecification.cs | 53 ++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 7 files changed, 406 insertions(+), 16 deletions(-) create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs new file mode 100644 index 000000000..403405cbf --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeAllowedSpecificationFixture .cs @@ -0,0 +1,292 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Languages; +using NzbDrone.Core.Profiles.Languages; +using NzbDrone.Core.Test.Languages; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class UpgradeAllowedSpecificationFixture : CoreTest + { + [Test] + public void should_return_false_when_quality_are_the_same_language_is_better_and_upgrade_allowed_is_false_for_language_profile() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), + Cutoff = Language.French, + UpgradeAllowed = false + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.DVD), + Language.French + ).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_quality_is_better_languages_are_the_same_and_upgrade_allowed_is_false_for_quality_profile() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = false + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.Bluray1080p), + Language.English + ).Should().BeFalse(); + } + + [Test] + public void should_return_true_for_language_upgrade_when_upgrading_is_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), + Cutoff = Language.French, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.DVD), + Language.French + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_same_language_when_upgrading_is_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), + Cutoff = Language.French, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.DVD), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_same_language_when_upgrading_is_not_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), + Cutoff = Language.French, + UpgradeAllowed = false + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.DVD), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_lower_language_when_upgrading_is_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), + Cutoff = Language.French, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.French, + new QualityModel(Quality.DVD), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_lower_language_when_upgrading_is_not_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English, Language.French), + Cutoff = Language.French, + UpgradeAllowed = false + }, + new QualityModel(Quality.DVD), + Language.French, + new QualityModel(Quality.DVD), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_quality_upgrade_when_upgrading_is_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.Bluray1080p), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_same_quality_when_upgrading_is_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.DVD), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_same_quality_when_upgrading_is_not_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = false + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.DVD), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_lower_quality_when_upgrading_is_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.SDTV), + Language.English + ).Should().BeTrue(); + } + + [Test] + public void should_return_true_for_lower_quality_when_upgrading_is_not_allowed() + { + Subject.IsUpgradeAllowed( + new QualityProfile + { + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = false + }, + new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(Language.English), + Cutoff = Language.English, + UpgradeAllowed = true + }, + new QualityModel(Quality.DVD), + Language.English, + new QualityModel(Quality.SDTV), + Language.English + ).Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 3d81c628a..f6ff96763 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -159,6 +159,7 @@ + diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 0a5d7839f..65c8cbe14 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -24,7 +24,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) { - var profile = subject.Series.QualityProfile.Value; + var qualityProfile = subject.Series.QualityProfile.Value; + var languageProfile = subject.Series.LanguageProfile.Value; foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { @@ -36,8 +37,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); - if (!_upgradableSpecification.CutoffNotMet(profile, - subject.Series.LanguageProfile, + if (!_upgradableSpecification.CutoffNotMet(qualityProfile, + languageProfile, file.Quality, file.Language, _preferredWordServiceCalculator.Calculate(subject.Series, file.GetSceneOrFileName()), @@ -46,10 +47,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { _logger.Debug("Cutoff already met, rejecting."); - var qualityCutoffIndex = profile.GetIndex(profile.Cutoff); - var qualityCutoff = profile.Items[qualityCutoffIndex.Index]; + var qualityCutoffIndex = qualityProfile.GetIndex(qualityProfile.Cutoff); + var qualityCutoff = qualityProfile.Items[qualityCutoffIndex.Index]; - return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, subject.Series.LanguageProfile.Value.Cutoff); + return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, languageProfile.Cutoff); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 782e4df7c..e036cbf06 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -40,25 +40,27 @@ namespace NzbDrone.Core.DecisionEngine.Specifications foreach (var queueItem in matchingEpisode) { var remoteEpisode = queueItem.RemoteEpisode; + var qualityProfile = subject.Series.QualityProfile.Value; + var languageProfile = subject.Series.LanguageProfile.Value; - _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + _logger.Debug("Checking if existing release in queue meets cutoff. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); var queuedItemPreferredWordScore = _preferredWordServiceCalculator.Calculate(subject.Series, queueItem.Title); - if (!_upgradableSpecification.CutoffNotMet(subject.Series.QualityProfile, - subject.Series.LanguageProfile, + if (!_upgradableSpecification.CutoffNotMet(qualityProfile, + languageProfile, remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language, queuedItemPreferredWordScore, subject.ParsedEpisodeInfo.Quality, subject.PreferredWordScore)) { - return Decision.Reject("Quality for release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + return Decision.Reject("Release in queue already meets cutoff: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); } - _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + _logger.Debug("Checking if release is higher quality than queued release. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); - if (!_upgradableSpecification.IsUpgradable(subject.Series.QualityProfile, - subject.Series.LanguageProfile, + if (!_upgradableSpecification.IsUpgradable(qualityProfile, + languageProfile, remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language, queuedItemPreferredWordScore, @@ -66,7 +68,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications subject.ParsedEpisodeInfo.Language, subject.PreferredWordScore)) { - return Decision.Reject("Quality for release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + return Decision.Reject("Release in queue is of equal or higher preference: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + } + + _logger.Debug("Checking if profiles allow upgrading. Queued: {0} - {1}", remoteEpisode.ParsedEpisodeInfo.Quality, remoteEpisode.ParsedEpisodeInfo.Language); + + if (!_upgradableSpecification.IsUpgradeAllowed(subject.Series.QualityProfile, + subject.Series.LanguageProfile, + remoteEpisode.ParsedEpisodeInfo.Quality, + remoteEpisode.ParsedEpisodeInfo.Language, + subject.ParsedEpisodeInfo.Quality, + subject.ParsedEpisodeInfo.Language)) + { + return Decision.Reject("Another release is queued and the Quality or Language profile does not allow upgrades"); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index 32905f114..82656a2cc 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -13,6 +13,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications bool LanguageCutoffNotMet(LanguageProfile languageProfile, Language currentLanguage); bool CutoffNotMet(QualityProfile profile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality = null, int newScore = 0); bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); + bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage); } public class UpgradableSpecification : IUpgradableSpecification @@ -59,7 +60,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public bool IsUpgradable(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, int currentScore, QualityModel newQuality, Language newLanguage, int newScore) { - if (IsQualityUpgradable(qualityProfile, currentQuality, newQuality) && qualityProfile.UpgradeAllowed) + if (IsQualityUpgradable(qualityProfile, currentQuality, newQuality)) { return true; } @@ -70,7 +71,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return false; } - if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage) && languageProfile.UpgradeAllowed) + if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage)) { return true; } @@ -151,5 +152,32 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return false; } + + public bool IsUpgradeAllowed(QualityProfile qualityProfile, LanguageProfile languageProfile, QualityModel currentQuality, Language currentLanguage, QualityModel newQuality, Language newLanguage) + { + var isQualityUpgrade = new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) > 0; + var isLanguageUpgrade = new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) > 0; + + if (isQualityUpgrade && qualityProfile.UpgradeAllowed || + isLanguageUpgrade && languageProfile.UpgradeAllowed) + { + _logger.Debug("At least one profile allows upgrading"); + return true; + } + + if (isQualityUpgrade && !qualityProfile.UpgradeAllowed) + { + _logger.Debug("Quality profile allows upgrades, skipping"); + return false; + } + + if (isLanguageUpgrade && !languageProfile.UpgradeAllowed) + { + _logger.Debug("Language profile does not allow upgrades, skipping"); + return false; + } + + return true; + } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs new file mode 100644 index 000000000..63fc1b989 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs @@ -0,0 +1,53 @@ +using System.Linq; +using NLog; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class UpgradeAllowedSpecification : IDecisionEngineSpecification + { + private readonly UpgradableSpecification _upgradableSpecification; + private readonly Logger _logger; + + public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecification, Logger logger) + { + _upgradableSpecification = upgradableSpecification; + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Default; + public RejectionType Type => RejectionType.Permanent; + + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + var qualityProfile = subject.Series.QualityProfile.Value; + var languageProfile = subject.Series.LanguageProfile.Value; + + foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) + { + if (file == null) + { + _logger.Debug("File is no longer available, skipping this file."); + continue; + } + + _logger.Debug("Comparing file quality and language with report. Existing file is {0} - {1}", file.Quality, file.Language); + + if (!_upgradableSpecification.IsUpgradeAllowed(qualityProfile, + languageProfile, + file.Quality, + file.Language, + subject.ParsedEpisodeInfo.Quality, + subject.ParsedEpisodeInfo.Language)) + { + _logger.Debug("Upgrading is not allowed by the quality or language profile"); + + return Decision.Reject("Existing file and the Quality or Language profile does not allow upgrades"); + } + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 9e40df9b2..f31ad35d6 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -145,6 +145,7 @@ +