From e08f39ebe0d352dc657c9a60ebde282f2abf5e0c Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 20 Sep 2018 22:21:26 -0400 Subject: [PATCH] New: Setting to prevent download of early releases (#485) * New: Setting to prevent download of early releases * Fixup! Test and Wording --- .../EarlyReleaseSpecificationFixture.cs | 116 ++++++++++++++++++ .../IndexerTests/TestIndexerSettings.cs | 1 + .../NzbDrone.Core.Test.csproj | 1 + .../EarlyReleaseSpecification.cs | 68 ++++++++++ .../Indexers/Gazelle/GazelleSettings.cs | 3 + .../Indexers/Headphones/HeadphonesSettings.cs | 3 + .../Indexers/IIndexerSettings.cs | 5 +- .../Indexers/IPTorrents/IPTorrentsSettings.cs | 3 + .../Indexers/Newznab/NewznabSettings.cs | 7 +- .../Indexers/Nyaa/NyaaSettings.cs | 3 + .../Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs | 3 + .../Indexers/Rarbg/RarbgSettings.cs | 3 + .../TorrentRss/TorrentRssIndexerSettings.cs | 3 + .../Torrentleech/TorrentleechSettings.cs | 3 + .../Indexers/Torznab/TorznabSettings.cs | 4 +- .../Indexers/Waffles/WafflesSettings.cs | 3 + src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 17 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/EarlyReleaseSpecificationFixture.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/EarlyReleaseSpecification.cs diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/EarlyReleaseSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/EarlyReleaseSpecificationFixture.cs new file mode 100644 index 000000000..7a49f3cc2 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/EarlyReleaseSpecificationFixture.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.TorrentRss; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Music; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class EarlyReleaseSpecificationFixture : TestBase + { + private Artist _artist; + private Album _album1; + private Album _album2; + private RemoteAlbum _remoteAlbum; + private IndexerDefinition _indexerDefinition; + + [SetUp] + public void Setup() + { + _artist = Builder.CreateNew().With(s => s.Id = 1).Build(); + _album1 = Builder.CreateNew().With(s => s.ReleaseDate = DateTime.Today).Build(); + _album2 = Builder.CreateNew().With(s => s.ReleaseDate = DateTime.Today).Build(); + + _remoteAlbum = new RemoteAlbum + { + Artist = _artist, + Albums = new List{_album1}, + Release = new TorrentInfo + { + IndexerId = 1, + Title = "Artist - Album [FLAC-RlsGrp]", + PublishDate = DateTime.Today + } + }; + + _indexerDefinition = new IndexerDefinition + { + Settings = new TorrentRssIndexerSettings { EarlyReleaseLimit = 5 } + }; + + Mocker.GetMock() + .Setup(v => v.Get(1)) + .Returns(_indexerDefinition); + + } + + private void GivenPublishDateFromToday(int days) + { + (_remoteAlbum.Release).PublishDate = DateTime.Today.AddDays(days); + } + + [Test] + public void should_return_true_if_indexer_not_specified() + { + _remoteAlbum.Release.IndexerId = 0; + + Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_if_release_contains_multiple_albums() + { + _remoteAlbum.Albums.Add(_album2); + + Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_return_true_if_indexer_no_longer_exists() + { + Mocker.GetMock() + .Setup(v => v.Get(It.IsAny())) + .Callback(i => { throw new ModelNotFoundException(typeof(IndexerDefinition), i); }); + + Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); + } + + [TestCase(-2)] + [TestCase(-5)] + public void should_return_true_if_publish_date_above_or_equal_to_limit(int days) + { + GivenPublishDateFromToday(days); + + Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); + } + + [TestCase(-10)] + [TestCase(-20)] + public void should_return_false_if_publish_date_belove_limit(int days) + { + GivenPublishDateFromToday(days); + + Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeFalse(); + } + + [TestCase(-10)] + [TestCase(-100)] + public void should_return_true_if_limit_null(int days) + { + GivenPublishDateFromToday(days); + + _indexerDefinition.Settings = new TorrentRssIndexerSettings{EarlyReleaseLimit = null}; + + Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs index 2e9e1fa82..7fcefb7de 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TestIndexerSettings.cs @@ -13,5 +13,6 @@ namespace NzbDrone.Core.Test.IndexerTests } public string BaseUrl { get; set; } + public int? EarlyReleaseLimit { get; set; } } } diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index d4922bc05..d8dc997ab 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -150,6 +150,7 @@ + diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/EarlyReleaseSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/EarlyReleaseSpecification.cs new file mode 100644 index 000000000..73fb277b2 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/EarlyReleaseSpecification.cs @@ -0,0 +1,68 @@ +using System.Linq; +using NLog; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class EarlyReleaseSpecification : IDecisionEngineSpecification + { + private readonly IIndexerFactory _indexerFactory; + private readonly Logger _logger; + + public EarlyReleaseSpecification(IIndexerFactory indexerFactory, Logger logger) + { + _indexerFactory = indexerFactory; + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Default; + public RejectionType Type => RejectionType.Permanent; + + public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria) + { + var releaseInfo = subject.Release; + + if (releaseInfo == null || releaseInfo.IndexerId == 0) + { + return Decision.Accept(); + } + + IndexerDefinition indexer; + try + { + indexer = _indexerFactory.Get(subject.Release.IndexerId); + } + catch (ModelNotFoundException) + { + _logger.Debug("Indexer with id {0} does not exist, skipping early release check", subject.Release.IndexerId); + return Decision.Accept(); + } + + var indexerSettings = indexer.Settings as IIndexerSettings; + + if (subject.Albums.Count != 1 || indexerSettings?.EarlyReleaseLimit == null) + { + return Decision.Accept(); + } + + var releaseDate = subject.Albums.First().ReleaseDate; + + if (releaseDate == null) return Decision.Accept(); + + var isEarly = (releaseDate.Value > subject.Release.PublishDate.AddDays(indexerSettings.EarlyReleaseLimit.Value)); + + if (isEarly) + { + var message = $"Release published date, {subject.Release.PublishDate.ToShortDateString()}, is outside of {indexerSettings.EarlyReleaseLimit.Value} day early grab limit allowed by user"; + + _logger.Debug(message); + return Decision.Reject(message); + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Gazelle/GazelleSettings.cs b/src/NzbDrone.Core/Indexers/Gazelle/GazelleSettings.cs index 5a6e345a8..8cad83c67 100644 --- a/src/NzbDrone.Core/Indexers/Gazelle/GazelleSettings.cs +++ b/src/NzbDrone.Core/Indexers/Gazelle/GazelleSettings.cs @@ -41,6 +41,9 @@ namespace NzbDrone.Core.Indexers.Gazelle [FieldDefinition(4)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesSettings.cs b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesSettings.cs index d3fc568b8..85bc604ce 100644 --- a/src/NzbDrone.Core/Indexers/Headphones/HeadphonesSettings.cs +++ b/src/NzbDrone.Core/Indexers/Headphones/HeadphonesSettings.cs @@ -53,6 +53,9 @@ namespace NzbDrone.Core.Indexers.Headphones [FieldDefinition(2, Label = "Password", Type = FieldType.Password)] public string Password { get; set; } + [FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public virtual NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs index 46f6bbae8..1fe4369cb 100644 --- a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers @@ -9,5 +5,6 @@ namespace NzbDrone.Core.Indexers public interface IIndexerSettings : IProviderConfig { string BaseUrl { get; set; } + int? EarlyReleaseLimit { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs index 9ea20e1a7..fb300fbb4 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -38,6 +38,9 @@ namespace NzbDrone.Core.Indexers.IPTorrents [FieldDefinition(2)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index 0412d0c64..ce3d3a59c 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -76,10 +76,13 @@ namespace NzbDrone.Core.Indexers.Newznab [FieldDefinition(3, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows", Advanced = true)] public IEnumerable Categories { get; set; } - [FieldDefinition(4, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)] + [FieldDefinition(4, Type = FieldType.Number, Label = "Early Download Limit", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Unit = "days", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + + [FieldDefinition(5, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)] public string AdditionalParameters { get; set; } - // Field 5 is used by TorznabSettings MinimumSeeders + // Field 6 is used by TorznabSettings MinimumSeeders // If you need to add another field here, update TorznabSettings as well and this comment public virtual NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index 1d07141bc..3bb640d4a 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -36,6 +36,9 @@ namespace NzbDrone.Core.Indexers.Nyaa [FieldDefinition(3)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(4, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs index 429d0e390..f3ecaf102 100644 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs @@ -36,6 +36,9 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs [FieldDefinition(2, Label = "Delay", HelpText = "Time in minutes to delay new nzbs before they appear on the RSS feed", Advanced = true)] public int Delay { get; set; } + [FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs b/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs index af99f9c19..003f1e79c 100644 --- a/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs +++ b/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs @@ -40,6 +40,9 @@ namespace NzbDrone.Core.Indexers.Rarbg [FieldDefinition(4)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index 378a30b46..b9a1b514e 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -40,6 +40,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss [FieldDefinition(4)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs index b8bcf6414..a3528a020 100644 --- a/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torrentleech/TorrentleechSettings.cs @@ -37,6 +37,9 @@ namespace NzbDrone.Core.Indexers.Torrentleech [FieldDefinition(3)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(4, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index 19b353c99..d59fd23de 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -59,10 +59,10 @@ namespace NzbDrone.Core.Indexers.Torznab MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS; } - [FieldDefinition(5, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] + [FieldDefinition(6, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)] public int MinimumSeeders { get; set; } - [FieldDefinition(6)] + [FieldDefinition(7)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); public override NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs b/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs index add45c1c5..28d10c59f 100644 --- a/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs +++ b/src/NzbDrone.Core/Indexers/Waffles/WafflesSettings.cs @@ -40,6 +40,9 @@ namespace NzbDrone.Core.Indexers.Waffles [FieldDefinition(4)] public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings(); + [FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)] + public int? EarlyReleaseLimit { get; set; } + public NzbDroneValidationResult Validate() { diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index ebcb20d34..1fa799b4b 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -225,6 +225,7 @@ +