diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 6af68d08a..bb8f27d12 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -128,6 +128,7 @@ + diff --git a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/EpisodeAiredAfterCutoffSpecificationFixture.cs b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/EpisodeAiredAfterCutoffSpecificationFixture.cs new file mode 100644 index 000000000..2041fba02 --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/EpisodeAiredAfterCutoffSpecificationFixture.cs @@ -0,0 +1,145 @@ +// ReSharper disable RedundantUsingDirective + +using System.Linq; +using System; +using System.Collections.Generic; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.DecisionEngine; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.ProviderTests.DecisionEngineTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class EpisodeAiredAfterCutoffSpecificationFixture : CoreTest + { + private EpisodeAiredAfterCutoffSpecification episodeAiredAfterCutoffSpecification; + + private EpisodeParseResult parseResultMulti; + private EpisodeParseResult parseResultSingle; + private Series fakeSeries; + private Episode firstEpisode; + private Episode secondEpisode; + + [SetUp] + public void Setup() + { + episodeAiredAfterCutoffSpecification = Mocker.Resolve(); + + fakeSeries = Builder.CreateNew() + .With(c => c.Monitored = true) + .With(c => c.DownloadEpisodesAiredAfter = null) + .Build(); + + parseResultMulti = new EpisodeParseResult + { + SeriesTitle = "Title", + Series = fakeSeries, + EpisodeNumbers = new List { 3, 4 }, + SeasonNumber = 12, + }; + + parseResultSingle = new EpisodeParseResult + { + SeriesTitle = "Title", + Series = fakeSeries, + EpisodeNumbers = new List { 3 }, + SeasonNumber = 12, + }; + + firstEpisode = new Episode { AirDate = DateTime.Today }; + secondEpisode = new Episode { AirDate = DateTime.Today }; + + var singleEpisodeList = new List { firstEpisode }; + var doubleEpisodeList = new List { firstEpisode, secondEpisode }; + + Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultSingle)).Returns(singleEpisodeList); + Mocker.GetMock().Setup(c => c.GetEpisodesByParseResult(parseResultMulti)).Returns(doubleEpisodeList); + } + + private void WithFirstEpisodeLastYear() + { + firstEpisode.AirDate = DateTime.Today.AddYears(-1); + } + + private void WithSecondEpisodeYear() + { + secondEpisode.AirDate = DateTime.Today.AddYears(-1); + } + + private void WithAiredAfterYesterday() + { + fakeSeries.DownloadEpisodesAiredAfter = DateTime.Today.AddDays(-1); + } + + private void WithAiredAfterLastWeek() + { + fakeSeries.DownloadEpisodesAiredAfter = DateTime.Today.AddDays(-7); + } + + [Test] + public void should_return_true_when_downloadEpisodesAiredAfter_is_null_for_single_episode() + { + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultSingle).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_downloadEpisodesAiredAfter_is_null_for_multiple_episodes() + { + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultMulti).Should().BeTrue(); + } + + [Test] + public void should_return_true_if_both_episodes_air_after_cutoff() + { + WithAiredAfterLastWeek(); + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultMulti).Should().BeTrue(); + } + + [Test] + public void should_return_true_if_episode_airs_after_cutoff() + { + WithAiredAfterLastWeek(); + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultSingle).Should().BeTrue(); + } + + [Test] + public void should_return_true_if_first_episode_aired_after_cutoff() + { + WithAiredAfterLastWeek(); + WithSecondEpisodeYear(); + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultMulti).Should().BeTrue(); + } + + [Test] + public void should_return_true_if_second_episode_aired_after_cutoff() + { + WithAiredAfterLastWeek(); + WithFirstEpisodeLastYear(); + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultMulti).Should().BeTrue(); + } + + [Test] + public void should_return_false_if_both_episodes_aired_before_cutoff() + { + WithAiredAfterLastWeek(); + WithFirstEpisodeLastYear(); + WithSecondEpisodeYear(); + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultMulti).Should().BeFalse(); + } + + [Test] + public void should_return_false_if_episode_aired_before_cutoff() + { + WithAiredAfterLastWeek(); + WithFirstEpisodeLastYear(); + episodeAiredAfterCutoffSpecification.IsSatisfiedBy(parseResultSingle).Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20120918.cs b/NzbDrone.Core/Datastore/Migrations/Migration20120918.cs new file mode 100644 index 000000000..f5cf8a93e --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20120918.cs @@ -0,0 +1,17 @@ +using System; +using System.Data; +using Migrator.Framework; +using NzbDrone.Common; + +namespace NzbDrone.Core.Datastore.Migrations +{ + + [Migration(20120918)] + public class Migration20120918 : NzbDroneMigration + { + protected override void MainDbUpgrade() + { + Database.AddColumn("Series", new Column("DownloadEpisodesAiredAfter", DbType.DateTime, ColumnProperty.Null)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/ReportRejectionType.cs b/NzbDrone.Core/Model/ReportRejectionType.cs index b4cf7336f..ad695a577 100644 --- a/NzbDrone.Core/Model/ReportRejectionType.cs +++ b/NzbDrone.Core/Model/ReportRejectionType.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.Model DownloadClientFailure = 10, Skipped = 11, Failure = 12, - ReleaseGroupNotWanted = 13 + ReleaseGroupNotWanted = 13, + EpisodeAiredBeforeCutoff = 14 } } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 69532ee79..4b6c2cdf7 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -227,6 +227,7 @@ + @@ -291,6 +292,7 @@ + diff --git a/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs index df58a271f..3ef8408aa 100644 --- a/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs +++ b/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs @@ -14,13 +14,14 @@ namespace NzbDrone.Core.Providers.DecisionEngine private readonly AlreadyInQueueSpecification _alreadyInQueueSpecification; private readonly RetentionSpecification _retentionSpecification; private readonly AllowedReleaseGroupSpecification _allowedReleaseGroupSpecification; + private readonly EpisodeAiredAfterCutoffSpecification _episodeAiredAfterCutoffSpecification; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); [Inject] public AllowedDownloadSpecification(QualityAllowedByProfileSpecification qualityAllowedByProfileSpecification, UpgradeDiskSpecification upgradeDiskSpecification, AcceptableSizeSpecification acceptableSizeSpecification, AlreadyInQueueSpecification alreadyInQueueSpecification, RetentionSpecification retentionSpecification, - AllowedReleaseGroupSpecification allowedReleaseGroupSpecification) + AllowedReleaseGroupSpecification allowedReleaseGroupSpecification, EpisodeAiredAfterCutoffSpecification episodeAiredAfterCutoffSpecification) { _qualityAllowedByProfileSpecification = qualityAllowedByProfileSpecification; _upgradeDiskSpecification = upgradeDiskSpecification; @@ -28,6 +29,7 @@ namespace NzbDrone.Core.Providers.DecisionEngine _alreadyInQueueSpecification = alreadyInQueueSpecification; _retentionSpecification = retentionSpecification; _allowedReleaseGroupSpecification = allowedReleaseGroupSpecification; + _episodeAiredAfterCutoffSpecification = episodeAiredAfterCutoffSpecification; } public AllowedDownloadSpecification() @@ -37,6 +39,7 @@ namespace NzbDrone.Core.Providers.DecisionEngine public virtual ReportRejectionType IsSatisfiedBy(EpisodeParseResult subject) { if (!_qualityAllowedByProfileSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.QualityNotWanted; + if (!_episodeAiredAfterCutoffSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.EpisodeAiredBeforeCutoff; if (!_upgradeDiskSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.ExistingQualityIsEqualOrBetter; if (!_retentionSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.Retention; if (!_acceptableSizeSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.Size; diff --git a/NzbDrone.Core/Providers/DecisionEngine/EpisodeAiredAfterCutoffSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/EpisodeAiredAfterCutoffSpecification.cs new file mode 100644 index 000000000..f6778816f --- /dev/null +++ b/NzbDrone.Core/Providers/DecisionEngine/EpisodeAiredAfterCutoffSpecification.cs @@ -0,0 +1,44 @@ +using System.Linq; +using NLog; +using Ninject; +using NzbDrone.Core.Model; + +namespace NzbDrone.Core.Providers.DecisionEngine +{ + public class EpisodeAiredAfterCutoffSpecification + { + private readonly EpisodeProvider _episodeProvider; + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public EpisodeAiredAfterCutoffSpecification(EpisodeProvider episodeProvider) + { + _episodeProvider = episodeProvider; + } + + public EpisodeAiredAfterCutoffSpecification() + { + + } + + public virtual bool IsSatisfiedBy(EpisodeParseResult subject) + { + if (!subject.Series.DownloadEpisodesAiredAfter.HasValue) + { + logger.Debug("{0} does not restrict downloads before date.", subject.Series.Title); + return true; + } + + var episodes = _episodeProvider.GetEpisodesByParseResult(subject); + + if (episodes.Any(episode => episode.AirDate > subject.Series.DownloadEpisodesAiredAfter.Value)) + { + logger.Debug("One or more episodes aired after cutoff, downloading."); + return true; + } + + logger.Debug("Episodes aired before cutoff date: {0}", subject.Series.DownloadEpisodesAiredAfter); + return false; + } + } +} diff --git a/NzbDrone.Core/Repository/Series.cs b/NzbDrone.Core/Repository/Series.cs index 13fce0064..acb0c25b8 100644 --- a/NzbDrone.Core/Repository/Series.cs +++ b/NzbDrone.Core/Repository/Series.cs @@ -48,6 +48,8 @@ namespace NzbDrone.Core.Repository public string Network { get; set; } + public DateTime? DownloadEpisodesAiredAfter { get; set; } + /// /// Gets or sets a value indicating whether this is hidden. ///