From 732107563155f9badad23004e1da82f97fea786e Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 4 May 2019 00:33:13 -0700 Subject: [PATCH] New: Option to not prefer repacks/propers (for use with Preferred Words) Closes #3084 --- .../MediaManagement/MediaManagement.js | 25 ++++-- .../Config/MediaManagementConfigResource.cs | 5 +- .../PrioritizeDownloadDecisionFixture.cs | 85 ++++++++++++++++++- .../RssSync/ProperSpecificationFixture.cs | 29 +++++-- .../UpgradeSpecificationFixture.cs | 47 ++++++++-- .../Configuration/ConfigService.cs | 7 +- .../Configuration/IConfigService.cs | 3 +- .../Migration/131_download_propers_config.cs | 43 ++++++++++ .../DownloadDecisionComparer.cs | 13 ++- .../DownloadDecisionPriorizationService.cs | 8 +- .../RssSync/ProperSpecification.cs | 21 +++-- .../Specifications/UpgradableSpecification.cs | 54 +++++------- src/NzbDrone.Core/NzbDrone.Core.csproj | 2 + .../Qualities/ProperDownloadTypes.cs | 9 ++ .../Config/MediaManagementConfigResource.cs | 5 +- 15 files changed, 281 insertions(+), 75 deletions(-) create mode 100644 src/NzbDrone.Core/Datastore/Migration/131_download_propers_config.cs create mode 100644 src/NzbDrone.Core/Qualities/ProperDownloadTypes.cs diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js index e33991ddf..77e956458 100644 --- a/frontend/src/Settings/MediaManagement/MediaManagement.js +++ b/frontend/src/Settings/MediaManagement/MediaManagement.js @@ -25,6 +25,12 @@ const rescanAfterRefreshOptions = [ { key: 'never', value: 'Never' } ]; +const downloadPropersAndRepacksOptions = [ + { key: 'preferAndUpgrade', value: 'Prefer and Upgrade' }, + { key: 'doNotUpgrade', value: 'Do not Upgrade Automatically' }, + { key: 'doNotPrefer', value: 'Do not Prefer' } +]; + const fileDateOptions = [ { key: 'none', value: 'None' }, { key: 'localAirDate', value: 'Local Air Date' }, @@ -227,14 +233,23 @@ class MediaManagement extends Component { isAdvanced={true} size={sizes.MEDIUM} > - Download Propers + Propers and Repacks diff --git a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs index 0593f33b3..1992f8d5e 100644 --- a/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs +++ b/src/NzbDrone.Api/Config/MediaManagementConfigResource.cs @@ -1,6 +1,7 @@ using Sonarr.Http.REST; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Qualities; namespace NzbDrone.Api.Config { @@ -8,7 +9,7 @@ namespace NzbDrone.Api.Config { public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } public string RecycleBin { get; set; } - public bool AutoDownloadPropers { get; set; } + public ProperDownloadTypes DownloadPropersAndRepacks { get; set; } public bool CreateEmptySeriesFolders { get; set; } public bool DeleteEmptyFolders { get; set; } public FileDateType FileDate { get; set; } @@ -34,7 +35,7 @@ namespace NzbDrone.Api.Config { AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes, RecycleBin = model.RecycleBin, - AutoDownloadPropers = model.AutoDownloadPropers, + DownloadPropersAndRepacks = model.DownloadPropersAndRepacks, CreateEmptySeriesFolders = model.CreateEmptySeriesFolders, DeleteEmptyFolders = model.DeleteEmptyFolders, FileDate = model.FileDate, diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs index 493b12af3..5de93f33f 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/PrioritizeDownloadDecisionFixture.cs @@ -13,6 +13,7 @@ using NUnit.Framework; using FluentAssertions; using FizzWare.NBuilder; using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Languages; using NzbDrone.Core.Profiles.Languages; @@ -440,7 +441,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests } [Test] - public void should_put_higher_quality_before_lower_allways() + public void should_put_higher_quality_before_lower_always() { var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.SDTV), Language.French); var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.HDTV720p), Language.German); @@ -452,5 +453,87 @@ namespace NzbDrone.Core.Test.DecisionEngineTests var qualifiedReports = Subject.PrioritizeDecisions(decisions); qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.HDTV720p); } + + [Test] + public void should_prefer_higher_score_over_lower_score() + { + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p), Language.English); + + remoteEpisode1.PreferredWordScore = 10; + remoteEpisode2.PreferredWordScore = 0; + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.PrioritizeDecisions(decisions); + qualifiedReports.First().RemoteEpisode.PreferredWordScore.Should().Be(10); + } + + [Test] + public void should_prefer_proper_over_score_when_download_propers_is_prefer_and_upgrade() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.PreferAndUpgrade); + + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(1)), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(2)), Language.English); + + remoteEpisode1.PreferredWordScore = 10; + remoteEpisode2.PreferredWordScore = 0; + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.PrioritizeDecisions(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version.Should().Be(2); + } + + [Test] + public void should_prefer_proper_over_score_when_download_propers_is_do_not_upgrade() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotUpgrade); + + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(1)), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(2)), Language.English); + + remoteEpisode1.PreferredWordScore = 10; + remoteEpisode2.PreferredWordScore = 0; + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.PrioritizeDecisions(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version.Should().Be(2); + } + + [Test] + public void should_prefer_score_over_proper_when_download_propers_is_do_not_prefer() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotPrefer); + + var remoteEpisode1 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(1)), Language.English); + var remoteEpisode2 = GivenRemoteEpisode(new List { GivenEpisode(1) }, new QualityModel(Quality.WEBDL1080p, new Revision(2)), Language.English); + + remoteEpisode1.PreferredWordScore = 10; + remoteEpisode2.PreferredWordScore = 0; + + var decisions = new List(); + decisions.Add(new DownloadDecision(remoteEpisode1)); + decisions.Add(new DownloadDecision(remoteEpisode2)); + + var qualifiedReports = Subject.PrioritizeDecisions(decisions); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Quality.Should().Be(Quality.WEBDL1080p); + qualifiedReports.First().RemoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version.Should().Be(1); + qualifiedReports.First().RemoteEpisode.PreferredWordScore.Should().Be(10); + } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs index cb8f3f663..5204cf715 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/ProperSpecificationFixture.cs @@ -11,7 +11,6 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Qualities; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; -using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Test.Framework; @@ -61,13 +60,6 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync _firstFile.Quality = new QualityModel(Quality.SDTV); } - private void GivenAutoDownloadPropers() - { - Mocker.GetMock() - .Setup(s => s.AutoDownloadPropers) - .Returns(true); - } - [Test] public void should_return_false_when_episodeFile_was_added_more_than_7_days_ago() { @@ -118,6 +110,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync [Test] public void should_return_false_when_proper_but_auto_download_propers_is_false() { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotUpgrade); + _firstFile.Quality.Quality = Quality.DVD; _firstFile.DateAdded = DateTime.Today; @@ -127,7 +123,22 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync [Test] public void should_return_true_when_episodeFile_was_added_today() { - GivenAutoDownloadPropers(); + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.PreferAndUpgrade); + + _firstFile.Quality.Quality = Quality.DVD; + + _firstFile.DateAdded = DateTime.Today; + Subject.IsSatisfiedBy(_parseResultSingle, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_when_propers_are_not_preferred() + { + Mocker.GetMock() + .Setup(s => s.DownloadPropersAndRepacks) + .Returns(ProperDownloadTypes.DoNotPrefer); _firstFile.Quality.Quality = Quality.DVD; diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs index 786df4a4f..e8383a0db 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/UpgradeSpecificationFixture.cs @@ -38,17 +38,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests private static readonly int NoPreferredWordScore = 0; - private void GivenAutoDownloadPropers(bool autoDownloadPropers) + private void GivenAutoDownloadPropers(ProperDownloadTypes type) { Mocker.GetMock() - .SetupGet(s => s.AutoDownloadPropers) - .Returns(autoDownloadPropers); + .SetupGet(s => s.DownloadPropersAndRepacks) + .Returns(type); } [Test, TestCaseSource(nameof(IsUpgradeTestCases))] public void IsUpgradeTest(Quality current, int currentVersion, Quality newQuality, int newVersion, Quality cutoff, bool expected) { - GivenAutoDownloadPropers(true); + GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade); var profile = new QualityProfile @@ -79,7 +79,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test, TestCaseSource("IsUpgradeTestCasesLanguages")] public void IsUpgradeTestLanguage(Quality current, int currentVersion, Language currentLanguage, Quality newQuality, int newVersion, Language newLanguage, Quality cutoff, Language languageCutoff, bool expected) { - GivenAutoDownloadPropers(true); + GivenAutoDownloadPropers(ProperDownloadTypes.PreferAndUpgrade); var profile = new QualityProfile { @@ -108,9 +108,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests } [Test] - public void should_return_false_if_proper_and_autoDownloadPropers_is_false() + public void should_return_true_if_proper_and_download_propers_is_do_not_download() { - GivenAutoDownloadPropers(false); + GivenAutoDownloadPropers(ProperDownloadTypes.DoNotUpgrade); var profile = new QualityProfile { @@ -127,13 +127,42 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Subject.IsUpgradable( profile, langProfile, - new QualityModel(Quality.DVD, new Revision(version: 2)), + new QualityModel(Quality.DVD, new Revision(version: 1)), Language.English, NoPreferredWordScore, + new QualityModel(Quality.DVD, new Revision(version: 2)), + Language.English, + NoPreferredWordScore) + .Should().BeTrue(); + } + + [Test] + public void should_return_false_if_proper_and_autoDownloadPropers_is_do_not_prefer() + { + GivenAutoDownloadPropers(ProperDownloadTypes.DoNotPrefer); + + var profile = new QualityProfile + { + Items = Qualities.QualityFixture.GetDefaultQualities(), + }; + + var langProfile = new LanguageProfile + { + Languages = LanguageFixture.GetDefaultLanguages(), + Cutoff = Language.English + }; + + + Subject.IsUpgradable( + profile, + langProfile, new QualityModel(Quality.DVD, new Revision(version: 1)), Language.English, + NoPreferredWordScore, + new QualityModel(Quality.DVD, new Revision(version: 2)), + Language.English, NoPreferredWordScore) - .Should().BeFalse(); + .Should().BeFalse(); } } } diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index beabaa284..bf6c1ed1d 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -9,6 +9,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; using NzbDrone.Common.Http.Proxy; using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Security; namespace NzbDrone.Core.Configuration @@ -113,11 +114,11 @@ namespace NzbDrone.Core.Configuration set { SetValue("MinimumAge", value); } } - public bool AutoDownloadPropers + public ProperDownloadTypes DownloadPropersAndRepacks { - get { return GetValueBoolean("AutoDownloadPropers", true); } + get { return GetValueEnum("DownloadPropersAndRepacks", ProperDownloadTypes.PreferAndUpgrade); } - set { SetValue("AutoDownloadPropers", value); } + set { SetValue("DownloadPropersAndRepacks", value); } } public bool EnableCompletedDownloadHandling diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 80d18b2f8..a16b64f76 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NzbDrone.Core.MediaFiles; using NzbDrone.Common.Http.Proxy; using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Security; namespace NzbDrone.Core.Configuration @@ -26,7 +27,7 @@ namespace NzbDrone.Core.Configuration //Media Management bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } string RecycleBin { get; set; } - bool AutoDownloadPropers { get; set; } + ProperDownloadTypes DownloadPropersAndRepacks { get; set; } bool CreateEmptySeriesFolders { get; set; } bool DeleteEmptyFolders { get; set; } FileDateType FileDate { get; set; } diff --git a/src/NzbDrone.Core/Datastore/Migration/131_download_propers_config.cs b/src/NzbDrone.Core/Datastore/Migration/131_download_propers_config.cs new file mode 100644 index 000000000..fdf1a363b --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/131_download_propers_config.cs @@ -0,0 +1,43 @@ +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(131)] + public class download_propers_config : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.WithConnection(SetMetadataFileExtension); + Execute.Sql("DELETE FROM Config WHERE Key = 'autodownloadpropers'"); + } + + private void SetMetadataFileExtension(IDbConnection conn, IDbTransaction tran) + { + using (var cmd = conn.CreateCommand()) + { + cmd.Transaction = tran; + cmd.CommandText = "SELECT Value FROM Config WHERE Key = 'autodownloadpropers'"; + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var value = reader.GetString(0); + var newValue = bool.Parse(value) ? "PreferAndUpgrade" : "DoNotUpgrade"; + + using (var updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = "INSERT INTO Config (key, value) VALUES ('downloadpropersandrepacks', ?)"; + updateCmd.AddParameter(newValue); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index 5f57800c0..b1dc83189 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -1,21 +1,26 @@ using System; using System.Collections.Generic; using System.Linq; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles.Delay; +using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; namespace NzbDrone.Core.DecisionEngine { public class DownloadDecisionComparer : IComparer { + private readonly IConfigService _configService; private readonly IDelayProfileService _delayProfileService; + public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y); public delegate int CompareDelegate(DownloadDecision x, DownloadDecision y); - public DownloadDecisionComparer(IDelayProfileService delayProfileService) + public DownloadDecisionComparer(IConfigService configService, IDelayProfileService delayProfileService) { + _configService = configService; _delayProfileService = delayProfileService; } @@ -59,6 +64,12 @@ namespace NzbDrone.Core.DecisionEngine private int CompareQuality(DownloadDecision x, DownloadDecision y) { + if (_configService.DownloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer) + { + return CompareAll(CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.QualityProfile.Value.GetIndex(remoteEpisode.ParsedEpisodeInfo.Quality.Quality)), + CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Real)); + } + return CompareAll(CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.Series.QualityProfile.Value.GetIndex(remoteEpisode.ParsedEpisodeInfo.Quality.Quality)), CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Real), CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version)); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs index 344c7544c..ce3365471 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionPriorizationService.cs @@ -1,7 +1,7 @@ using System.Linq; using System.Collections.Generic; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Profiles.Delay; -using NzbDrone.Core.Languages; namespace NzbDrone.Core.DecisionEngine { @@ -12,10 +12,12 @@ namespace NzbDrone.Core.DecisionEngine public class DownloadDecisionPriorizationService : IPrioritizeDownloadDecision { + private readonly IConfigService _configService; private readonly IDelayProfileService _delayProfileService; - public DownloadDecisionPriorizationService(IDelayProfileService delayProfileService) + public DownloadDecisionPriorizationService(IConfigService configService, IDelayProfileService delayProfileService) { + _configService = configService; _delayProfileService = delayProfileService; } @@ -24,7 +26,7 @@ namespace NzbDrone.Core.DecisionEngine return decisions.Where(c => c.RemoteEpisode.Series != null) .GroupBy(c => c.RemoteEpisode.Series.Id, (seriesId, downloadDecisions) => { - return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_delayProfileService)); + return downloadDecisions.OrderByDescending(decision => decision, new DownloadDecisionComparer(_configService, _delayProfileService)); }) .SelectMany(c => c) .Union(decisions.Where(c => c.RemoteEpisode.Series == null)) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs index 163fbea84..d2e843c90 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/ProperSpecification.cs @@ -4,6 +4,7 @@ using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync { @@ -30,20 +31,28 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.RssSync return Decision.Accept(); } + var downloadPropersAndRepacks = _configService.DownloadPropersAndRepacks; + + if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotPrefer) + { + _logger.Debug("Propers are not preferred, skipping check"); + return Decision.Accept(); + } + foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) { if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedEpisodeInfo.Quality)) { - if (file.DateAdded < DateTime.Today.AddDays(-7)) + if (downloadPropersAndRepacks == ProperDownloadTypes.DoNotUpgrade) { - _logger.Debug("Proper for old file, rejecting: {0}", subject); - return Decision.Reject("Proper for old file"); + _logger.Debug("Auto downloading of propers is disabled"); + return Decision.Reject("Proper downloading is disabled"); } - if (!_configService.AutoDownloadPropers) + if (file.DateAdded < DateTime.Today.AddDays(-7)) { - _logger.Debug("Auto downloading of propers is disabled"); - return Decision.Reject("Proper downloading is disabled"); + _logger.Debug("Proper for old file, rejecting: {0}", subject); + return Decision.Reject("Proper for old file"); } } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index 82656a2cc..a278d0b09 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -1,4 +1,5 @@ using NLog; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Languages; using NzbDrone.Core.Profiles.Languages; using NzbDrone.Core.Profiles.Qualities; @@ -18,41 +19,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications public class UpgradableSpecification : IUpgradableSpecification { + private readonly IConfigService _configService; private readonly Logger _logger; - public UpgradableSpecification(Logger logger) + public UpgradableSpecification(IConfigService configService, Logger logger) { + _configService = configService; _logger = logger; } - private bool IsLanguageUpgradable(LanguageProfile profile, Language currentLanguage, Language newLanguage = null) - { - if (newLanguage != null) - { - var compare = new LanguageComparer(profile).Compare(newLanguage, currentLanguage); - if (compare <= 0) - { - return false; - } - } - return true; - } - - private bool IsQualityUpgradable(QualityProfile profile, QualityModel currentQuality, QualityModel newQuality = null) - { - if (newQuality != null) - { - var compare = new QualityModelComparer(profile).Compare(newQuality, currentQuality); - - if (compare <= 0) - { - _logger.Debug("Existing item has better quality, skipping"); - return false; - } - } - return true; - } - private bool IsPreferredWordUpgradable(int currentScore, int newScore) { return newScore > currentScore; @@ -60,23 +35,36 @@ 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)) + var qualityComparer = new QualityModelComparer(qualityProfile); + var qualityCompare = qualityComparer.Compare(newQuality?.Quality, currentQuality.Quality); + + if (qualityCompare > 0) { return true; } - if (new QualityModelComparer(qualityProfile).Compare(newQuality, currentQuality) < 0) + if (qualityCompare < 0) { _logger.Debug("Existing item has better quality, skipping"); return false; } - if (IsLanguageUpgradable(languageProfile, currentLanguage, newLanguage)) + // Accept unless the user doesn't want to prefer propers, optionally they can + // use preferred words to prefer propers/repacks over non-propers/repacks. + if (_configService.DownloadPropersAndRepacks != ProperDownloadTypes.DoNotPrefer && + newQuality?.Revision.CompareTo(currentQuality.Revision) > 0) + { + return true; + } + + var languageCompare = new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage); + + if (languageCompare > 0) { return true; } - if (new LanguageComparer(languageProfile).Compare(newLanguage, currentLanguage) < 0) + if (languageCompare < 0) { _logger.Debug("Existing item has better language, skipping"); return false; diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 5c3ea3dc0..dce02434c 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -136,6 +136,7 @@ + @@ -1034,6 +1035,7 @@ + diff --git a/src/NzbDrone.Core/Qualities/ProperDownloadTypes.cs b/src/NzbDrone.Core/Qualities/ProperDownloadTypes.cs new file mode 100644 index 000000000..1620d75ff --- /dev/null +++ b/src/NzbDrone.Core/Qualities/ProperDownloadTypes.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Qualities +{ + public enum ProperDownloadTypes + { + PreferAndUpgrade, + DoNotUpgrade, + DoNotPrefer + } +} diff --git a/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs b/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs index e47578501..338c9e282 100644 --- a/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs +++ b/src/Sonarr.Api.V3/Config/MediaManagementConfigResource.cs @@ -1,6 +1,7 @@ using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Qualities; using Sonarr.Http.REST; namespace Sonarr.Api.V3.Config @@ -9,7 +10,7 @@ namespace Sonarr.Api.V3.Config { public bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } public string RecycleBin { get; set; } - public bool AutoDownloadPropers { get; set; } + public ProperDownloadTypes DownloadPropersAndRepacks { get; set; } public bool CreateEmptySeriesFolders { get; set; } public bool DeleteEmptyFolders { get; set; } public FileDateType FileDate { get; set; } @@ -37,7 +38,7 @@ namespace Sonarr.Api.V3.Config { AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes, RecycleBin = model.RecycleBin, - AutoDownloadPropers = model.AutoDownloadPropers, + DownloadPropersAndRepacks = model.DownloadPropersAndRepacks, CreateEmptySeriesFolders = model.CreateEmptySeriesFolders, DeleteEmptyFolders = model.DeleteEmptyFolders, FileDate = model.FileDate,