From eca95826c2a12bafbecd62decc4a1bfd503dc2a5 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 2 Sep 2024 13:27:21 -0700 Subject: [PATCH] Fixed: Respect Quality cutoff if Custom Format cutoff isn't met (cherry picked from commit 6f51e72d0073444b441bee5508322cc9e52e98e4) --- .../CutoffSpecificationFixture.cs | 207 ---------------- .../QualityUpgradeSpecificationFixture.cs | 120 ---------- .../RssSync/DelaySpecificationFixture.cs | 3 +- .../UpgradeDiskSpecificationFixture.cs | 220 +++++++++++++++++- .../UpgradeSpecificationFixture.cs | 164 +++++++++++++ .../Qualities/QualityFixture.cs | 1 + .../Specifications/CutoffSpecification.cs | 57 ----- .../Specifications/QueueSpecification.cs | 37 ++- .../RssSync/DelaySpecification.cs | 12 +- .../RssSync/HistorySpecification.cs | 55 +++-- .../Specifications/UpgradableSpecification.cs | 52 +++-- .../UpgradeDiskSpecification.cs | 60 +++-- .../DecisionEngine/UpgradeableRejectReason.cs | 12 + 13 files changed, 539 insertions(+), 461 deletions(-) delete mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs delete mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs delete mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/UpgradeableRejectReason.cs diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs deleted file mode 100644 index f4be69de6..000000000 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CutoffSpecificationFixture.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using System.Collections.Generic; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Serializer; -using NzbDrone.Core.CustomFormats; -using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Profiles.Qualities; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.CustomFormats; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.DecisionEngineTests -{ - [TestFixture] - public class CutoffSpecificationFixture : CoreTest - { - private CustomFormat _customFormat; - private RemoteMovie _remoteMovie; - - [SetUp] - public void Setup() - { - Mocker.SetConstant(Mocker.Resolve()); - - _remoteMovie = new RemoteMovie() - { - Movie = Builder.CreateNew().Build(), - ParsedMovieInfo = Builder.CreateNew().With(x => x.Quality = null).Build() - }; - - GivenOldCustomFormats(new List()); - } - - private void GivenProfile(QualityProfile profile) - { - CustomFormatsTestHelpers.GivenCustomFormats(); - profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(); - profile.MinFormatScore = 0; - _remoteMovie.Movie.QualityProfile = profile; - - Console.WriteLine(profile.ToJson()); - } - - private void GivenFileQuality(QualityModel quality) - { - _remoteMovie.Movie.MovieFile = Builder.CreateNew().With(x => x.Quality = quality).Build(); - } - - private void GivenNewQuality(QualityModel quality) - { - _remoteMovie.ParsedMovieInfo.Quality = quality; - } - - private void GivenOldCustomFormats(List formats) - { - Mocker.GetMock() - .Setup(x => x.ParseCustomFormat(It.IsAny())) - .Returns(formats); - } - - private void GivenNewCustomFormats(List formats) - { - _remoteMovie.CustomFormats = formats; - } - - private void GivenCustomFormatHigher() - { - _customFormat = new CustomFormat("My Format", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 }; - - CustomFormatsTestHelpers.GivenCustomFormats(_customFormat); - } - - [Test] - public void should_return_true_if_current_episode_is_less_than_cutoff() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.Bluray1080p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.DVD, new Revision(version: 2))); - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); - } - - [Test] - public void should_return_false_if_current_episode_is_equal_to_cutoff() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); - } - - [Test] - public void should_return_false_if_current_episode_is_greater_than_cutoff() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); - } - - [Test] - public void should_return_true_when_new_episode_is_proper_but_existing_is_not() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 1))); - GivenNewQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); - } - - [Test] - public void should_return_false_if_cutoff_is_met_and_quality_is_higher() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); - GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); - } - - [Test] - public void should_return_false_if_custom_formats_is_met_and_quality_and_format_higher() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - MinFormatScore = 0, - FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("My Format"), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.HDTV720p)); - GivenNewQuality(new QualityModel(Quality.Bluray1080p)); - - GivenCustomFormatHigher(); - - GivenOldCustomFormats(new List()); - GivenNewCustomFormats(new List { _customFormat }); - - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); - } - - [Test] - public void should_return_true_if_cutoffs_are_met_but_is_a_revision_upgrade() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.HDTV1080p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = true - }); - - GivenFileQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1))); - GivenNewQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 2))); - - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); - } - - [Test] - public void should_return_false_if_quality_profile_does_not_allow_upgrades_but_cutoff_is_set_to_highest_quality() - { - GivenProfile(new QualityProfile - { - Cutoff = Quality.RAWHD.Id, - Items = Qualities.QualityFixture.GetDefaultQualities(), - UpgradeAllowed = false - }); - - GivenFileQuality(new QualityModel(Quality.WEBDL1080p)); - GivenNewQuality(new QualityModel(Quality.Bluray1080p)); - - Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); - } - } -} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs deleted file mode 100644 index 374dc2288..000000000 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityUpgradeSpecificationFixture.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Collections.Generic; -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.CustomFormats; -using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Profiles.Qualities; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.CustomFormats; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.DecisionEngineTests -{ - [TestFixture] - - public class QualityUpgradeSpecificationFixture : CoreTest - { - private static CustomFormat _customFormat1 = new CustomFormat("My Format 1", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 }; - private static CustomFormat _customFormat2 = new CustomFormat("My Format 2", new ResolutionSpecification { Value = (int)Resolution.R480p }) { Id = 2 }; - - public static object[] IsUpgradeTestCases = - { - // Quality upgrade trumps custom format - new object[] { Quality.SDTV, 1, new List(), Quality.SDTV, 2, new List(), true }, - new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 2, new List { _customFormat1 }, true }, - new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 2, new List { _customFormat2 }, true }, - new object[] { Quality.SDTV, 1, new List { _customFormat2 }, Quality.SDTV, 2, new List { _customFormat1 }, true }, - - // Revision upgrade trumps custom format - new object[] { Quality.WEBDL720p, 1, new List(), Quality.WEBDL720p, 2, new List(), true }, - new object[] { Quality.WEBDL720p, 1, new List { _customFormat1 }, Quality.WEBDL720p, 2, new List { _customFormat1 }, true }, - new object[] { Quality.WEBDL720p, 1, new List { _customFormat1 }, Quality.WEBDL720p, 2, new List { _customFormat2 }, true }, - new object[] { Quality.WEBDL720p, 1, new List { _customFormat2 }, Quality.WEBDL720p, 2, new List { _customFormat1 }, true }, - - // Custom formats apply if quality same - new object[] { Quality.SDTV, 1, new List(), Quality.SDTV, 1, new List(), false }, - new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 1, new List { _customFormat1 }, false }, - new object[] { Quality.SDTV, 1, new List { _customFormat1 }, Quality.SDTV, 1, new List { _customFormat2 }, true }, - new object[] { Quality.SDTV, 1, new List { _customFormat2 }, Quality.SDTV, 1, new List { _customFormat1 }, false }, - - new object[] { Quality.WEBDL720p, 1, new List(), Quality.HDTV720p, 2, new List(), false }, - new object[] { Quality.WEBDL720p, 1, new List(), Quality.HDTV720p, 2, new List(), false }, - new object[] { Quality.WEBDL720p, 1, new List(), Quality.WEBDL720p, 1, new List(), false }, - new object[] { Quality.WEBDL1080p, 1, new List(), Quality.WEBDL1080p, 1, new List(), false } - }; - - [SetUp] - public void Setup() - { - CustomFormatsTestHelpers.GivenCustomFormats(_customFormat1, _customFormat2); - } - - private void GivenAutoDownloadPropers(ProperDownloadTypes type) - { - Mocker.GetMock() - .SetupGet(s => s.DownloadPropersAndRepacks) - .Returns(type); - } - - [Test] - [TestCaseSource("IsUpgradeTestCases")] - public void IsUpgradeTest(Quality current, - int currentVersion, - List currentFormats, - Quality newQuality, - int newVersion, - List newFormats, - bool expected) - { - GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade); - - var profile = new QualityProfile - { - Items = Qualities.QualityFixture.GetDefaultQualities(), - FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(_customFormat1.Name, _customFormat2.Name), - MinFormatScore = 0 - }; - - Subject.IsUpgradable(profile, - new QualityModel(current, new Revision(version: currentVersion)), - currentFormats, - new QualityModel(newQuality, new Revision(version: newVersion)), - newFormats) - .Should().Be(expected); - } - - [Test] - public void should_return_true_if_proper_and_download_propers_is_do_not_download() - { - GivenAutoDownloadPropers(ProperDownloadTypes.DoNotUpgrade); - - var profile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }; - - Subject.IsUpgradable(profile, - new QualityModel(Quality.DVD, new Revision(version: 2)), - new List(), - new QualityModel(Quality.DVD, new Revision(version: 1)), - new List()) - .Should().BeFalse(); - } - - [Test] - public void should_return_false_if_release_and_existing_file_are_the_same() - { - var profile = new QualityProfile - { - Items = Qualities.QualityFixture.GetDefaultQualities(), - }; - - Subject.IsUpgradable( - profile, - new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - new List(), - new QualityModel(Quality.HDTV720p, new Revision(version: 1)), - new List()) - .Should().BeFalse(); - } - } -} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs index 3e40b3759..108c6c881 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.DecisionEngine.Specifications.RssSync; using NzbDrone.Core.Download.Pending; @@ -75,7 +76,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync { Mocker.GetMock() .Setup(s => s.IsUpgradable(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny>())) - .Returns(true); + .Returns(UpgradeableRejectReason.None); } [Test] diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs index 807343b73..c26157576 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeDiskSpecificationFixture.cs @@ -4,10 +4,12 @@ using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; +using NzbDrone.Common.Serializer; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Movies; +using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; @@ -35,20 +37,22 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _firstFile = new MovieFile { Quality = new QualityModel(Quality.Bluray1080p, new Revision(version: 2)), DateAdded = DateTime.Now }; - var fakeSeries = Builder.CreateNew() + var fakeMovie = Builder.CreateNew() .With(c => c.QualityProfile = new QualityProfile { - Cutoff = Quality.Bluray1080p.Id, Items = Qualities.QualityFixture.GetDefaultQualities(), - FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(), - MinFormatScore = 0 + UpgradeAllowed = true, + Cutoff = Quality.Bluray1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("None"), + MinFormatScore = 0, }) .With(e => e.MovieFile = _firstFile) .Build(); _parseResultSingle = new RemoteMovie { - Movie = fakeSeries, - ParsedMovieInfo = new ParsedMovieInfo() { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, + Movie = fakeMovie, + ParsedMovieInfo = new ParsedMovieInfo { Quality = new QualityModel(Quality.DVD, new Revision(version: 2)) }, CustomFormats = new List() }; @@ -57,6 +61,38 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Returns(new List()); } + private void GivenProfile(QualityProfile profile) + { + CustomFormatsTestHelpers.GivenCustomFormats(); + profile.FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(); + profile.MinFormatScore = 0; + _parseResultSingle.Movie.QualityProfile = profile; + + Console.WriteLine(profile.ToJson()); + } + + private void GivenFileQuality(QualityModel quality) + { + _firstFile.Quality = quality; + } + + private void GivenNewQuality(QualityModel quality) + { + _parseResultSingle.ParsedMovieInfo.Quality = quality; + } + + private void GivenOldCustomFormats(List formats) + { + Mocker.GetMock() + .Setup(x => x.ParseCustomFormat(It.IsAny())) + .Returns(formats); + } + + private void GivenNewCustomFormats(List formats) + { + _parseResultSingle.CustomFormats = formats; + } + private void WithFirstFileUpgradable() { _firstFile.Quality = new QualityModel(Quality.SDTV); @@ -95,5 +131,177 @@ namespace NzbDrone.Core.Test.DecisionEngineTests _parseResultSingle.ParsedMovieInfo.Quality = new QualityModel(Quality.WEBDL1080p); _upgradeDisk.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); } + + [Test] + public void should_return_false_if_current_movie_is_equal_to_cutoff() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_false_if_current_movie_is_greater_than_cutoff() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_true_when_new_movie_is_proper_but_existing_is_not() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 1))); + GivenNewQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_cutoff_is_met_and_quality_is_higher() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_false_if_quality_cutoff_is_met_and_quality_is_higher_but_language_is_met() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_false_if_cutoff_is_met_and_quality_is_higher_and_language_is_higher() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p, new Revision(version: 2))); + GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_true_if_cutoff_is_not_met_and_new_quality_is_higher_and_language_is_higher() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.SDTV, new Revision(version: 2))); + GivenNewQuality(new QualityModel(Quality.Bluray1080p, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_if_cutoff_is_not_met_and_language_is_higher() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.SDTV, new Revision(version: 2))); + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_custom_formats_is_met_and_quality_and_format_higher() + { + var customFormat = new CustomFormat("My Format", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 }; + + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV720p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + MinFormatScore = 0, + FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems("My Format"), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.HDTV720p)); + GivenNewQuality(new QualityModel(Quality.Bluray1080p)); + + GivenOldCustomFormats(new List()); + GivenNewCustomFormats(new List { customFormat }); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_return_true_if_cutoffs_are_met_but_is_a_revision_upgrade() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.HDTV1080p.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true + }); + + GivenFileQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 1))); + GivenNewQuality(new QualityModel(Quality.WEBDL1080p, new Revision(version: 2))); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_false_if_quality_profile_does_not_allow_upgrades_but_cutoff_is_set_to_highest_quality() + { + GivenProfile(new QualityProfile + { + Cutoff = Quality.RAWHD.Id, + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = false + }); + + GivenFileQuality(new QualityModel(Quality.WEBDL1080p)); + GivenNewQuality(new QualityModel(Quality.Bluray1080p)); + + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeFalse(); + } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs new file mode 100644 index 000000000..d1f90577e --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs @@ -0,0 +1,164 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.CustomFormats; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Profiles.Qualities; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Test.CustomFormats; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + + public class UpgradeSpecificationFixture : CoreTest + { + private static readonly CustomFormat CustomFormat1 = new ("My Format 1", new ResolutionSpecification { Value = (int)Resolution.R1080p }) { Id = 1 }; + private static readonly CustomFormat CustomFormat2 = new ("My Format 2", new ResolutionSpecification { Value = (int)Resolution.R480p }) { Id = 2 }; + + public static object[] IsUpgradeTestCases = + { + // Quality upgrade trumps custom format + new object[] { Quality.SDTV, 1, new List(), Quality.SDTV, 2, new List(), UpgradeableRejectReason.None }, + new object[] { Quality.SDTV, 1, new List { CustomFormat1 }, Quality.SDTV, 2, new List { CustomFormat1 }, UpgradeableRejectReason.None }, + new object[] { Quality.SDTV, 1, new List { CustomFormat1 }, Quality.SDTV, 2, new List { CustomFormat2 }, UpgradeableRejectReason.None }, + new object[] { Quality.SDTV, 1, new List { CustomFormat2 }, Quality.SDTV, 2, new List { CustomFormat1 }, UpgradeableRejectReason.None }, + + // Revision upgrade trumps custom format + new object[] { Quality.WEBDL720p, 1, new List(), Quality.WEBDL720p, 2, new List(), UpgradeableRejectReason.None }, + new object[] { Quality.WEBDL720p, 1, new List { CustomFormat1 }, Quality.WEBDL720p, 2, new List { CustomFormat1 }, UpgradeableRejectReason.None }, + new object[] { Quality.WEBDL720p, 1, new List { CustomFormat1 }, Quality.WEBDL720p, 2, new List { CustomFormat2 }, UpgradeableRejectReason.None }, + new object[] { Quality.WEBDL720p, 1, new List { CustomFormat2 }, Quality.WEBDL720p, 2, new List { CustomFormat1 }, UpgradeableRejectReason.None }, + + // Custom formats apply if quality same + new object[] { Quality.SDTV, 1, new List(), Quality.SDTV, 1, new List(), UpgradeableRejectReason.CustomFormatScore }, + new object[] { Quality.SDTV, 1, new List { CustomFormat1 }, Quality.SDTV, 1, new List { CustomFormat1 }, UpgradeableRejectReason.CustomFormatScore }, + new object[] { Quality.SDTV, 1, new List { CustomFormat1 }, Quality.SDTV, 1, new List { CustomFormat2 }, UpgradeableRejectReason.CustomFormatCutoff }, + new object[] { Quality.SDTV, 1, new List { CustomFormat2 }, Quality.SDTV, 1, new List { CustomFormat1 }, UpgradeableRejectReason.CustomFormatScore }, + + new object[] { Quality.WEBDL720p, 1, new List(), Quality.HDTV720p, 2, new List(), UpgradeableRejectReason.BetterQuality }, + new object[] { Quality.WEBDL720p, 1, new List(), Quality.HDTV720p, 2, new List(), UpgradeableRejectReason.BetterQuality }, + new object[] { Quality.WEBDL720p, 1, new List(), Quality.WEBDL720p, 1, new List(), UpgradeableRejectReason.CustomFormatScore }, + new object[] { Quality.WEBDL1080p, 1, new List(), Quality.WEBDL1080p, 1, new List(), UpgradeableRejectReason.CustomFormatScore } + }; + + [SetUp] + public void Setup() + { + CustomFormatsTestHelpers.GivenCustomFormats(CustomFormat1, CustomFormat2); + } + + private void GivenAutoDownloadPropers(ProperDownloadTypes type) + { + Mocker.GetMock() + .SetupGet(s => s.DownloadPropersAndRepacks) + .Returns(type); + } + + [Test] + [TestCaseSource(nameof(IsUpgradeTestCases))] + public void IsUpgradeTest(Quality current, + int currentVersion, + List currentFormats, + Quality newQuality, + int newVersion, + List newFormats, + UpgradeableRejectReason expected) + { + GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade); + + var profile = new QualityProfile + { + UpgradeAllowed = true, + Items = Qualities.QualityFixture.GetDefaultQualities(), + FormatItems = CustomFormatsTestHelpers.GetSampleFormatItems(CustomFormat1.Name, CustomFormat2.Name) + }; + + Subject.IsUpgradable( + profile, + new QualityModel(current, new Revision(version: currentVersion)), + currentFormats, + new QualityModel(newQuality, new Revision(version: newVersion)), + newFormats) + .Should().Be(expected); + } + + [Test] + public void should_return_true_if_proper_and_download_propers_is_do_not_download() + { + GivenAutoDownloadPropers(ProperDownloadTypes.DoNotUpgrade); + + var profile = new QualityProfile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + Subject.IsUpgradable( + profile, + new QualityModel(Quality.DVD, new Revision(version: 1)), + new List(), + new QualityModel(Quality.DVD, new Revision(version: 2)), + new List()) + .Should().Be(UpgradeableRejectReason.None); + } + + [Test] + public void should_return_false_if_release_and_existing_file_are_the_same() + { + var profile = new QualityProfile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + Subject.IsUpgradable( + profile, + new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + new List(), + new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + new List()) + .Should().Be(UpgradeableRejectReason.CustomFormatScore); + } + + [Test] + public void should_return_true_if_release_has_higher_quality_and_cutoff_is_not_already_met() + { + var profile = new QualityProfile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true, + Cutoff = Quality.HDTV1080p.Id + }; + + Subject.IsUpgradable( + profile, + new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + new List(), + new QualityModel(Quality.HDTV1080p, new Revision(version: 1)), + new List()) + .Should().Be(UpgradeableRejectReason.None); + } + + [Test] + public void should_return_false_if_release_has_higher_quality_and_cutoff_is_already_met() + { + var profile = new QualityProfile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + UpgradeAllowed = true, + Cutoff = Quality.HDTV720p.Id + }; + + Subject.IsUpgradable( + profile, + new QualityModel(Quality.HDTV720p, new Revision(version: 1)), + new List(), + new QualityModel(Quality.HDTV1080p, new Revision(version: 1)), + new List()) + .Should().Be(UpgradeableRejectReason.QualityCutoff); + } + } +} diff --git a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs index 4e336c0f5..5b1057f28 100644 --- a/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs +++ b/src/NzbDrone.Core.Test/Qualities/QualityFixture.cs @@ -69,6 +69,7 @@ namespace NzbDrone.Core.Test.Qualities { var qualities = new List { + Quality.Unknown, Quality.CAM, Quality.TELECINE, Quality.DVDSCR, diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs deleted file mode 100644 index 331a1fa75..000000000 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ /dev/null @@ -1,57 +0,0 @@ -using NLog; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.CustomFormats; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.DecisionEngine.Specifications -{ - public class CutoffSpecification : IDecisionEngineSpecification - { - private readonly IUpgradableSpecification _upgradableSpecification; - private readonly ICustomFormatCalculationService _formatService; - private readonly Logger _logger; - - public CutoffSpecification(IUpgradableSpecification upgradableSpecification, - ICustomFormatCalculationService formatService, - Logger logger) - { - _upgradableSpecification = upgradableSpecification; - _formatService = formatService; - _logger = logger; - } - - public SpecificationPriority Priority => SpecificationPriority.Default; - public RejectionType Type => RejectionType.Permanent; - - public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) - { - var profile = subject.Movie.QualityProfile; - var file = subject.Movie.MovieFile; - - if (file != null) - { - file.Movie = subject.Movie; - _logger.Debug("Comparing file quality with report. Existing file is {0}", file.Quality); - - var customFormats = _formatService.ParseCustomFormat(file); - - if (!_upgradableSpecification.CutoffNotMet(profile, - file.Quality, - customFormats, - subject.ParsedMovieInfo.Quality)) - { - _logger.Debug("Existing custom formats {0} meet cutoff", - customFormats.ConcatToString()); - - var qualityCutoffIndex = profile.GetIndex(profile.Cutoff); - var qualityCutoff = profile.Items[qualityCutoffIndex.Index]; - - return Decision.Reject("Existing file meets cutoff: {0}", qualityCutoff); - } - } - - return Decision.Accept(); - } - } -} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index edb0fdda8..2bd79912e 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -55,29 +55,44 @@ namespace NzbDrone.Core.DecisionEngine.Specifications continue; } - var customFormats = _formatService.ParseCustomFormat(remoteMovie, (long)queueItem.Size); + var queuedItemCustomFormats = _formatService.ParseCustomFormat(remoteMovie, (long)queueItem.Size); _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0} - {1}", remoteMovie.ParsedMovieInfo.Quality, - customFormats.ConcatToString()); + queuedItemCustomFormats.ConcatToString()); if (!_upgradableSpecification.CutoffNotMet(qualityProfile, - remoteMovie.ParsedMovieInfo.Quality, - customFormats, - subject.ParsedMovieInfo.Quality)) + remoteMovie.ParsedMovieInfo.Quality, + queuedItemCustomFormats, + subject.ParsedMovieInfo.Quality)) { return Decision.Reject("Quality for release in queue already meets cutoff: {0}", remoteMovie.ParsedMovieInfo.Quality); } _logger.Debug("Checking if release is higher quality than queued release. Queued quality is: {0}", remoteMovie.ParsedMovieInfo.Quality); - if (!_upgradableSpecification.IsUpgradable(qualityProfile, - remoteMovie.ParsedMovieInfo.Quality, - remoteMovie.CustomFormats, - subject.ParsedMovieInfo.Quality, - subject.CustomFormats)) + var upgradeableRejectReason = _upgradableSpecification.IsUpgradable(qualityProfile, + remoteMovie.ParsedMovieInfo.Quality, + queuedItemCustomFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats); + + switch (upgradeableRejectReason) { - return Decision.Reject("Quality for release in queue is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality); + case UpgradeableRejectReason.BetterQuality: + return Decision.Reject("Release in queue on disk is of equal or higher preference: {0}", remoteMovie.ParsedMovieInfo.Quality); + + case UpgradeableRejectReason.BetterRevision: + return Decision.Reject("Release in queue on disk is of equal or higher revision: {0}", remoteMovie.ParsedMovieInfo.Quality.Revision); + + case UpgradeableRejectReason.QualityCutoff: + return Decision.Reject("Release in queue on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]); + + case UpgradeableRejectReason.CustomFormatCutoff: + return Decision.Reject("Release in queue on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore); + + case UpgradeableRejectReason.CustomFormatScore: + return Decision.Reject("Release in queue on disk has an equal or higher custom format score: {0}", qualityProfile.CalculateCustomFormatScore(queuedItemCustomFormats)); } _logger.Debug("Checking if profiles allow upgrading. Queued: {0}", remoteMovie.ParsedMovieInfo.Quality); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index 536e402ef..0d84dd114 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -58,13 +58,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync if (isPreferredProtocol && (subject.Movie.MovieFileId != 0 && file != null)) { var customFormats = _formatService.ParseCustomFormat(file); - var upgradable = _qualityUpgradableSpecification.IsUpgradable(profile, - file.Quality, - customFormats, - subject.ParsedMovieInfo.Quality, - subject.CustomFormats); + var upgradeableRejectReason = _qualityUpgradableSpecification.IsUpgradable(profile, + file.Quality, + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats); - if (upgradable) + if (upgradeableRejectReason == UpgradeableRejectReason.None) { var revisionUpgrade = _qualityUpgradableSpecification.IsRevisionUpgrade(subject.Movie.MovieFile.Quality, subject.ParsedMovieInfo.Quality); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs index f0bf029be..66ffa0cdb 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/HistorySpecification.cs @@ -42,32 +42,37 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync } var cdhEnabled = _configService.EnableCompletedDownloadHandling; + var qualityProfile = subject.Movie.QualityProfile; _logger.Debug("Performing history status check on report"); + _logger.Debug("Checking current status of movie [{0}] in history", subject.Movie.Id); var mostRecent = _historyService.MostRecentForMovie(subject.Movie.Id); if (mostRecent != null && mostRecent.EventType == MovieHistoryEventType.Grabbed) { - var customFormats = _formatService.ParseCustomFormat(mostRecent, subject.Movie); - - var cutoffUnmet = _upgradableSpecification.CutoffNotMet(subject.Movie.QualityProfile, - mostRecent.Quality, - customFormats, - subject.ParsedMovieInfo.Quality); - - var upgradeable = _upgradableSpecification.IsUpgradable(subject.Movie.QualityProfile, - mostRecent.Quality, - customFormats, - subject.ParsedMovieInfo.Quality, - subject.CustomFormats); - var recent = mostRecent.Date.After(DateTime.UtcNow.AddHours(-12)); + if (!recent && cdhEnabled) { return Decision.Accept(); } + var customFormats = _formatService.ParseCustomFormat(mostRecent, subject.Movie); + + var cutoffUnmet = _upgradableSpecification.CutoffNotMet( + subject.Movie.QualityProfile, + mostRecent.Quality, + customFormats, + subject.ParsedMovieInfo.Quality); + + var upgradeableRejectReason = _upgradableSpecification.IsUpgradable( + subject.Movie.QualityProfile, + mostRecent.Quality, + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats); + if (!cutoffUnmet) { if (recent) @@ -78,14 +83,26 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync return Decision.Reject("CDH is disabled and grab event in history already meets cutoff: {0}", mostRecent.Quality); } - if (!upgradeable) + var rejectionSubject = recent ? "Recent" : "CDH is disabled and"; + + switch (upgradeableRejectReason) { - if (recent) - { - return Decision.Reject("Recent grab event in history is of equal or higher quality: {0}", mostRecent.Quality); - } + case UpgradeableRejectReason.None: + return Decision.Accept(); + case UpgradeableRejectReason.BetterQuality: + return Decision.Reject("{0} grab event in history is of equal or higher preference: {1}", rejectionSubject, mostRecent.Quality); + + case UpgradeableRejectReason.BetterRevision: + return Decision.Reject("{0} grab event in history is of equal or higher revision: {1}", rejectionSubject, mostRecent.Quality.Revision); + + case UpgradeableRejectReason.QualityCutoff: + return Decision.Reject("{0} grab event in history meets quality cutoff: {1}", rejectionSubject, qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]); + + case UpgradeableRejectReason.CustomFormatCutoff: + return Decision.Reject("{0} grab event in history meets Custom Format cutoff: {1}", rejectionSubject, qualityProfile.CutoffFormatScore); - return Decision.Reject("CDH is disabled and grab event in history is of equal or higher quality: {0}", mostRecent.Quality); + case UpgradeableRejectReason.CustomFormatScore: + return Decision.Reject("{0} grab event in history has an equal or higher custom format score: {1}", rejectionSubject, qualityProfile.CalculateCustomFormatScore(customFormats)); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index 6e29985c8..af62c7f21 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -10,7 +10,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public interface IUpgradableSpecification { - bool IsUpgradable(QualityProfile profile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats); + UpgradeableRejectReason IsUpgradable(QualityProfile profile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats); bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null); bool CutoffNotMet(QualityProfile profile, QualityModel currentQuality, List currentFormats, QualityModel newQuality = null); bool IsRevisionUpgrade(QualityModel currentQuality, QualityModel newQuality); @@ -28,22 +28,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications _logger = logger; } - public bool IsUpgradable(QualityProfile qualityProfile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats) + public UpgradeableRejectReason IsUpgradable(QualityProfile qualityProfile, QualityModel currentQuality, List currentCustomFormats, QualityModel newQuality, List newCustomFormats) { var qualityComparer = new QualityModelComparer(qualityProfile); var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality); var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks; - if (qualityCompare > 0) + if (qualityCompare > 0 && QualityCutoffNotMet(qualityProfile, currentQuality, newQuality)) { _logger.Debug("New item has a better quality. Existing: {0}. New: {1}", currentQuality, newQuality); - return true; + return UpgradeableRejectReason.None; } if (qualityCompare < 0) { _logger.Debug("Existing item has better quality, skipping. Existing: {0}. New: {1}", currentQuality, newQuality); - return false; + return UpgradeableRejectReason.BetterQuality; } var qualityRevisionCompare = newQuality?.Revision.CompareTo(currentQuality.Revision); @@ -54,7 +54,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications qualityRevisionCompare > 0) { _logger.Debug("New item has a better quality revision, skipping. Existing: {0}. New: {1}", currentQuality, newQuality); - return true; + return UpgradeableRejectReason.None; } // Reject unless the user does not prefer propers/repacks and it's a revision downgrade. @@ -62,29 +62,37 @@ namespace NzbDrone.Core.DecisionEngine.Specifications qualityRevisionCompare < 0) { _logger.Debug("Existing item has a better quality revision, skipping. Existing: {0}. New: {1}", currentQuality, newQuality); - return false; + return UpgradeableRejectReason.BetterRevision; + } + + if (qualityCompare > 0) + { + _logger.Debug("Existing item meets cut-off for quality, skipping. Existing: {0}. Cutoff: {1}", + currentQuality, + qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]); + return UpgradeableRejectReason.QualityCutoff; } var currentFormatScore = qualityProfile.CalculateCustomFormatScore(currentCustomFormats); var newFormatScore = qualityProfile.CalculateCustomFormatScore(newCustomFormats); + if (newFormatScore <= currentFormatScore) + { + _logger.Debug("New item's custom formats [{0}] ({1}) do not improve on [{2}] ({3}), skipping", + newCustomFormats.ConcatToString(), + newFormatScore, + currentCustomFormats.ConcatToString(), + currentFormatScore); + return UpgradeableRejectReason.CustomFormatScore; + } + if (qualityProfile.UpgradeAllowed && currentFormatScore >= qualityProfile.CutoffFormatScore) { _logger.Debug("Existing item meets cut-off for custom formats, skipping. Existing: [{0}] ({1}). Cutoff score: {2}", currentCustomFormats.ConcatToString(), currentFormatScore, qualityProfile.CutoffFormatScore); - return false; - } - - if (newFormatScore <= currentFormatScore) - { - _logger.Debug("New item's custom formats [{0}] ({1}) do not improve on [{2}] ({3}), skipping", - newCustomFormats.ConcatToString(), - newFormatScore, - currentCustomFormats.ConcatToString(), - currentFormatScore); - return false; + return UpgradeableRejectReason.CustomFormatCutoff; } _logger.Debug("New item's custom formats [{0}] ({1}) improve on [{2}] ({3}), accepting", @@ -92,8 +100,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications newFormatScore, currentCustomFormats.ConcatToString(), currentFormatScore); - - return true; + return UpgradeableRejectReason.None; } public bool QualityCutoffNotMet(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null) @@ -132,7 +139,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications return true; } - _logger.Debug("Existing item meets cut-off, skipping. Existing: {0}", currentQuality); + _logger.Debug("Existing item meets cut-off, skipping. Existing: {0} [{1}] ({2})", + currentQuality, + currentFormats.ConcatToString(), + profile.CalculateCustomFormatScore(currentFormats)); return false; } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs index f569956d1..e6a159dbc 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeDiskSpecification.cs @@ -8,15 +8,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications { public class UpgradeDiskSpecification : IDecisionEngineSpecification { - private readonly UpgradableSpecification _qualityUpgradableSpecification; + private readonly UpgradableSpecification _upgradableSpecification; private readonly ICustomFormatCalculationService _formatService; private readonly Logger _logger; - public UpgradeDiskSpecification(UpgradableSpecification qualityUpgradableSpecification, + public UpgradeDiskSpecification(UpgradableSpecification upgradableSpecification, ICustomFormatCalculationService formatService, Logger logger) { - _qualityUpgradableSpecification = qualityUpgradableSpecification; + _upgradableSpecification = upgradableSpecification; _formatService = formatService; _logger = logger; } @@ -26,24 +26,58 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - if (subject.Movie.MovieFile == null) + var qualityProfile = subject.Movie.QualityProfile; + + var file = subject.Movie.MovieFile; + + if (file == null) { + _logger.Debug("File is no longer available, skipping this file."); return Decision.Accept(); } - var profile = subject.Movie.QualityProfile; - var file = subject.Movie.MovieFile; file.Movie = subject.Movie; var customFormats = _formatService.ParseCustomFormat(file); - _logger.Debug("Comparing file quality with report. Existing file is {0} [{1}]", file.Quality, customFormats.ConcatToString()); - if (!_qualityUpgradableSpecification.IsUpgradable(profile, - file.Quality, - customFormats, - subject.ParsedMovieInfo.Quality, - subject.CustomFormats)) + _logger.Debug("Comparing file quality with report. Existing file is {0} [{1}].", file.Quality, customFormats.ConcatToString()); + + if (!_upgradableSpecification.CutoffNotMet(qualityProfile, + file.Quality, + _formatService.ParseCustomFormat(file), + subject.ParsedMovieInfo.Quality)) { - return Decision.Reject("Quality for existing file on disk is of equal or higher preference: {0} [{1}]", file.Quality, customFormats.ConcatToString()); + _logger.Debug("Cutoff already met, rejecting."); + + var qualityCutoffIndex = qualityProfile.GetIndex(qualityProfile.Cutoff); + var qualityCutoff = qualityProfile.Items[qualityCutoffIndex.Index]; + + return Decision.Reject("Existing file meets cutoff: {0} [{1}]", qualityCutoff, customFormats.ConcatToString()); + } + + var upgradeableRejectReason = _upgradableSpecification.IsUpgradable(qualityProfile, + file.Quality, + customFormats, + subject.ParsedMovieInfo.Quality, + subject.CustomFormats); + + switch (upgradeableRejectReason) + { + case UpgradeableRejectReason.None: + return Decision.Accept(); + case UpgradeableRejectReason.BetterQuality: + return Decision.Reject("Existing file on disk is of equal or higher preference: {0}", file.Quality); + + case UpgradeableRejectReason.BetterRevision: + return Decision.Reject("Existing file on disk is of equal or higher revision: {0}", file.Quality.Revision); + + case UpgradeableRejectReason.QualityCutoff: + return Decision.Reject("Existing file on disk meets quality cutoff: {0}", qualityProfile.Items[qualityProfile.GetIndex(qualityProfile.Cutoff).Index]); + + case UpgradeableRejectReason.CustomFormatCutoff: + return Decision.Reject("Existing file on disk meets Custom Format cutoff: {0}", qualityProfile.CutoffFormatScore); + + case UpgradeableRejectReason.CustomFormatScore: + return Decision.Reject("Existing file on disk has a equal or higher custom format score: {0}", qualityProfile.CalculateCustomFormatScore(customFormats)); } return Decision.Accept(); diff --git a/src/NzbDrone.Core/DecisionEngine/UpgradeableRejectReason.cs b/src/NzbDrone.Core/DecisionEngine/UpgradeableRejectReason.cs new file mode 100644 index 000000000..7ed6d6a0f --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/UpgradeableRejectReason.cs @@ -0,0 +1,12 @@ +namespace NzbDrone.Core.DecisionEngine +{ + public enum UpgradeableRejectReason + { + None, + BetterQuality, + BetterRevision, + QualityCutoff, + CustomFormatScore, + CustomFormatCutoff + } +}