diff --git a/src/NzbDrone.Common/Extensions/DateTimeExtensions.cs b/src/NzbDrone.Common/Extensions/DateTimeExtensions.cs
new file mode 100644
index 000000000..75be57cb6
--- /dev/null
+++ b/src/NzbDrone.Common/Extensions/DateTimeExtensions.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace NzbDrone.Common.Extensions
+{
+ public static class DateTimeExtensions
+ {
+ public static bool InNextDays(this DateTime dateTime, int days)
+ {
+ return InNext(dateTime, new TimeSpan(days, 0, 0, 0));
+ }
+
+ public static bool InLastDays(this DateTime dateTime, int days)
+ {
+ return InLast(dateTime, new TimeSpan(days, 0, 0, 0));
+ }
+
+ public static bool InNext(this DateTime dateTime, TimeSpan timeSpan)
+ {
+ return dateTime >= DateTime.UtcNow && dateTime <= DateTime.UtcNow.Add(timeSpan);
+ }
+
+ public static bool InLast(this DateTime dateTime, TimeSpan timeSpan)
+ {
+ return dateTime >= DateTime.UtcNow.Add(-timeSpan) && dateTime <= DateTime.UtcNow;
+ }
+
+ public static bool Before(this DateTime dateTime, DateTime beforeDateTime)
+ {
+ return dateTime <= beforeDateTime;
+ }
+
+ public static bool After(this DateTime dateTime, DateTime afterDateTime)
+ {
+ return dateTime >= afterDateTime;
+ }
+
+ public static bool Between(this DateTime dateTime, DateTime afterDateTime, DateTime beforeDateTime)
+ {
+ return dateTime >= afterDateTime && dateTime <= beforeDateTime;
+ }
+ }
+}
diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj
index 92491e068..6f1131a79 100644
--- a/src/NzbDrone.Common/NzbDrone.Common.csproj
+++ b/src/NzbDrone.Common/NzbDrone.Common.csproj
@@ -128,6 +128,7 @@
+
diff --git a/src/NzbDrone.Common/StringExtensions.cs b/src/NzbDrone.Common/StringExtensions.cs
index d09c7c0d9..26cf8f8f2 100644
--- a/src/NzbDrone.Common/StringExtensions.cs
+++ b/src/NzbDrone.Common/StringExtensions.cs
@@ -3,7 +3,6 @@ using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
-using ICSharpCode.SharpZipLib.Zip;
namespace NzbDrone.Common
{
diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs
new file mode 100644
index 000000000..5a57a6f34
--- /dev/null
+++ b/src/NzbDrone.Core.Test/IndexerSearchTests/EpisodeInfoRefreshedSearchFixture.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using FizzWare.NBuilder;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.DecisionEngine;
+using NzbDrone.Core.Download;
+using NzbDrone.Core.IndexerSearch;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Core.Tv;
+using NzbDrone.Core.Tv.Events;
+
+namespace NzbDrone.Core.Test.IndexerSearchTests
+{
+ [TestFixture]
+ public class EpisodeInfoRefreshedSearchFixture : CoreTest
+ {
+ private Series _series;
+ private IList _added;
+ private IList _updated;
+
+ [SetUp]
+ public void Setup()
+ {
+ _series = Builder.CreateNew()
+ .With(s => s.Added = DateTime.UtcNow.AddDays(-7))
+ .Build();
+
+ _added = new List();
+ _updated = new List();
+ }
+
+ private void GivenUpdated()
+ {
+ _updated.Add(Builder.CreateNew().Build());
+ }
+
+ [Test]
+ public void should_not_search_if_no_episodes_were_upgraded()
+ {
+ _added.Add(new Episode());
+
+ Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
+
+ VerifyNoSearch();
+ }
+
+ [Test]
+ public void should_not_search_if_series_was_added_within_the_last_day()
+ {
+ GivenUpdated();
+
+ _series.Added = DateTime.UtcNow;
+ _added.Add(new Episode());
+
+
+
+ Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
+
+ VerifyNoSearch();
+ }
+
+ [Test]
+ public void should_not_search_if_no_episodes_were_added()
+ {
+ GivenUpdated();
+
+ _updated.Add(new Episode());
+
+ Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
+
+ VerifyNoSearch();
+ }
+
+ [Test]
+ public void should_not_search_if_air_date_doesnt_have_a_value()
+ {
+ GivenUpdated();
+
+ _added.Add(new Episode());
+
+ Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
+
+ VerifyNoSearch();
+ }
+
+ [Test]
+ public void should_not_search_if_episodes_air_in_the_future()
+ {
+ GivenUpdated();
+
+ _added.Add(new Episode { AirDateUtc = DateTime.UtcNow.AddDays(7) });
+
+ Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
+
+ VerifyNoSearch();
+ }
+
+ [Test]
+ public void should_search_for_a_newly_added_episode()
+ {
+ GivenUpdated();
+
+ _added.Add(new Episode { AirDateUtc = DateTime.UtcNow });
+
+ Mocker.GetMock()
+ .Setup(s => s.ProcessDecisions(It.IsAny>()))
+ .Returns(new ProcessedDecisions(new List(), new List()));
+
+ Subject.Handle(new EpisodeInfoRefreshedEvent(_series, _added, _updated));
+
+ Mocker.GetMock()
+ .Verify(v => v.EpisodeSearch(It.IsAny()), Times.Once());
+ }
+
+ private void VerifyNoSearch()
+ {
+ Mocker.GetMock()
+ .Verify(v => v.EpisodeSearch(It.IsAny()), Times.Never());
+ }
+ }
+}
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 98b20705d..e5f36913e 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -179,6 +179,7 @@
+
diff --git a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs
index 8d87e8f75..84e65e813 100644
--- a/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs
+++ b/src/NzbDrone.Core/IndexerSearch/EpisodeSearchService.cs
@@ -3,22 +3,27 @@ using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common;
+using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download;
using NzbDrone.Core.Messaging.Commands;
+using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Queue;
using NzbDrone.Core.Tv;
+using NzbDrone.Core.Tv.Events;
namespace NzbDrone.Core.IndexerSearch
{
public interface IEpisodeSearchService
{
- void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable grabbed
- );
+ void MissingEpisodesAiredAfter(DateTime dateTime, IEnumerable grabbed);
}
- public class MissingEpisodeSearchService : IEpisodeSearchService, IExecute, IExecute
+ public class EpisodeSearchService : IEpisodeSearchService,
+ IExecute,
+ IExecute,
+ IHandle
{
private readonly ISearchForNzb _nzbSearchService;
private readonly IProcessDownloadDecisions _processDownloadDecisions;
@@ -26,7 +31,7 @@ namespace NzbDrone.Core.IndexerSearch
private readonly IQueueService _queueService;
private readonly Logger _logger;
- public MissingEpisodeSearchService(ISearchForNzb nzbSearchService,
+ public EpisodeSearchService(ISearchForNzb nzbSearchService,
IProcessDownloadDecisions processDownloadDecisions,
IEpisodeService episodeService,
IQueueService queueService,
@@ -105,5 +110,42 @@ namespace NzbDrone.Core.IndexerSearch
_logger.ProgressInfo("Completed missing search for {0} episodes. {1} reports downloaded.", missing.Count, downloadedCount);
}
+
+ public void Handle(EpisodeInfoRefreshedEvent message)
+ {
+ if (message.Updated.Empty() || message.Series.Added.InLastDays(1))
+ {
+ _logger.Debug("Appears to be a new series, skipping search.");
+ return;
+ }
+
+ if (message.Added.Empty())
+ {
+ _logger.Debug("No new episodes, skipping search");
+ return;
+ }
+
+ if (message.Added.None(a => a.AirDateUtc.HasValue))
+ {
+ _logger.Debug("No new episodes have an air date");
+ return;
+ }
+
+ var previouslyAired = message.Added.Where(a => a.AirDateUtc.HasValue && a.AirDateUtc.Value.Before(DateTime.UtcNow.AddDays(1))).ToList();
+
+ if (previouslyAired.Empty())
+ {
+ _logger.Debug("Newly added episodes all air in the future");
+ return;
+ }
+
+ foreach (var episode in previouslyAired)
+ {
+ var decisions = _nzbSearchService.EpisodeSearch(episode);
+ var processed = _processDownloadDecisions.ProcessDecisions(decisions);
+
+ _logger.ProgressInfo("Episode search completed. {0} reports downloaded.", processed.Grabbed.Count);
+ }
+ }
}
}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index dedf7fbff..b070d0b70 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -796,9 +796,7 @@
Code
-
-
-
+
diff --git a/src/NzbDrone.Core/Tv/Events/EpisodeInfoAddedEvent.cs b/src/NzbDrone.Core/Tv/Events/EpisodeInfoAddedEvent.cs
deleted file mode 100644
index ff6353b9c..000000000
--- a/src/NzbDrone.Core/Tv/Events/EpisodeInfoAddedEvent.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using NzbDrone.Common.Messaging;
-
-namespace NzbDrone.Core.Tv.Events
-{
- public class EpisodeInfoAddedEvent : IEvent
- {
- public Series Series { get; private set; }
- public ReadOnlyCollection Episodes { get; private set; }
-
- public EpisodeInfoAddedEvent(IList episodes, Series series)
- {
- Series = series;
- Episodes = new ReadOnlyCollection(episodes);
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Tv/Events/EpisodeInfoDeletedEvent.cs b/src/NzbDrone.Core/Tv/Events/EpisodeInfoDeletedEvent.cs
deleted file mode 100644
index 864445e58..000000000
--- a/src/NzbDrone.Core/Tv/Events/EpisodeInfoDeletedEvent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using NzbDrone.Common.Messaging;
-
-namespace NzbDrone.Core.Tv.Events
-{
- public class EpisodeInfoDeletedEvent : IEvent
- {
- public ReadOnlyCollection Episodes { get; private set; }
-
- public EpisodeInfoDeletedEvent(IList episodes)
- {
- Episodes = new ReadOnlyCollection(episodes);
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs b/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs
new file mode 100644
index 000000000..4eded3b79
--- /dev/null
+++ b/src/NzbDrone.Core/Tv/Events/EpisodeInfoRefreshedEvent.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using NzbDrone.Common.Messaging;
+
+namespace NzbDrone.Core.Tv.Events
+{
+ public class EpisodeInfoRefreshedEvent : IEvent
+ {
+ public Series Series { get; set; }
+ public ReadOnlyCollection Added { get; private set; }
+ public ReadOnlyCollection Updated { get; private set; }
+
+ public EpisodeInfoRefreshedEvent(Series series, IList added, IList updated)
+ {
+ Series = series;
+ Added = new ReadOnlyCollection(added);
+ Updated = new ReadOnlyCollection(updated);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Tv/Events/EpisodeInfoUpdatedEvent.cs b/src/NzbDrone.Core/Tv/Events/EpisodeInfoUpdatedEvent.cs
deleted file mode 100644
index 310e0d85d..000000000
--- a/src/NzbDrone.Core/Tv/Events/EpisodeInfoUpdatedEvent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using NzbDrone.Common.Messaging;
-
-namespace NzbDrone.Core.Tv.Events
-{
- public class EpisodeInfoUpdatedEvent : IEvent
- {
- public ReadOnlyCollection Episodes { get; private set; }
-
- public EpisodeInfoUpdatedEvent(IList episodes)
- {
- Episodes = new ReadOnlyCollection(episodes);
- }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs
index 8808d02c5..4a829d083 100644
--- a/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs
+++ b/src/NzbDrone.Core/Tv/RefreshEpisodeService.cs
@@ -96,20 +96,7 @@ namespace NzbDrone.Core.Tv
_episodeService.UpdateMany(updateList);
_episodeService.InsertMany(newList);
- if (newList.Any())
- {
- _eventAggregator.PublishEvent(new EpisodeInfoAddedEvent(newList, series));
- }
-
- if (updateList.Any())
- {
- _eventAggregator.PublishEvent(new EpisodeInfoUpdatedEvent(updateList));
- }
-
- if (existingEpisodes.Any())
- {
- _eventAggregator.PublishEvent(new EpisodeInfoDeletedEvent(updateList));
- }
+ _eventAggregator.PublishEvent(new EpisodeInfoRefreshedEvent(series, newList, updateList));
if (failCount != 0)
{