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) {