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.
///