diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 7c45300f8..9d18b6fda 100644
--- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -99,6 +99,7 @@
+
diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs
index 0a132b11e..3d87877f4 100644
--- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs
+++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs
@@ -56,6 +56,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
_episodeIndexer1 = new Mock();
_episodeIndexer1.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(parseResults);
+ _episodeIndexer1.Setup(c => c.FetchDailyEpisode(It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
_episodeIndexer1.Setup(c => c.FetchSeason(It.IsAny(), It.IsAny()))
.Returns(parseResults);
_episodeIndexer1.Setup(c => c.FetchPartialSeason(It.IsAny(), It.IsAny(), It.IsAny()))
@@ -65,6 +67,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
_episodeIndexer2 = new Mock();
_episodeIndexer2.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(parseResults);
+ _episodeIndexer2.Setup(c => c.FetchDailyEpisode(It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
_episodeIndexer2.Setup(c => c.FetchSeason(It.IsAny(), It.IsAny()))
.Returns(parseResults);
_episodeIndexer2.Setup(c => c.FetchPartialSeason(It.IsAny(), It.IsAny(), It.IsAny()))
@@ -123,6 +127,15 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
, times);
}
+ private void VerifyFetchDailyEpisode(Times times)
+ {
+ _episodeIndexer1.Verify(v => v.FetchDailyEpisode(_series.Title, It.IsAny())
+ , times);
+
+ _episodeIndexer2.Verify(v => v.FetchDailyEpisode(_series.Title, It.IsAny())
+ , times);
+ }
+
private void VerifyFetchEpisodeWithSceneName(Times times)
{
_episodeIndexer1.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny())
@@ -210,6 +223,21 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
VerifyFetchEpisode(Times.Once());
}
+ [Test]
+ public void PerformSearch_for_daily_episode_should_call_FetchEpisode()
+ {
+ //Setup
+ _series.IsDaily = true;
+
+ //Act
+ var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes);
+
+ //Assert
+ result.Should().HaveCount(PARSE_RESULT_COUNT * 2);
+
+ VerifyFetchDailyEpisode(Times.Once());
+ }
+
[Test]
public void PerformSearch_for_partial_season_should_call_FetchPartialSeason()
{
diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs
new file mode 100644
index 000000000..13670e5a5
--- /dev/null
+++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/SearchFixture.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FizzWare.NBuilder;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.Model;
+using NzbDrone.Core.Providers;
+using NzbDrone.Core.Providers.Indexer;
+using NzbDrone.Core.Repository;
+using NzbDrone.Core.Test.Framework;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
+{
+ public class SearchFixture : CoreTest
+ {
+ private const string SCENE_NAME = "Scene Name";
+ private const int SEASON_NUMBER = 5;
+ private const int PARSE_RESULT_COUNT = 10;
+
+ private Mock _episodeIndexer1;
+ private Mock _episodeIndexer2;
+ private Mock _brokenIndexer;
+ private Mock _nullIndexer;
+
+ private List _indexers;
+
+ private Series _series;
+ private IList _episodes;
+
+ [SetUp]
+ public void Setup()
+ {
+ var parseResults = Builder.CreateListOfSize(PARSE_RESULT_COUNT)
+ .Build();
+
+ _series = Builder.CreateNew()
+ .Build();
+
+ _episodes = Builder.CreateListOfSize(1)
+ .Build();
+
+ BuildIndexers(parseResults);
+
+ _indexers = new List { _episodeIndexer1.Object, _episodeIndexer2.Object };
+
+ Mocker.GetMock()
+ .Setup(c => c.GetEnabledIndexers())
+ .Returns(_indexers);
+ }
+
+ private void BuildIndexers(IList parseResults)
+ {
+ _episodeIndexer1 = new Mock();
+ _episodeIndexer1.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
+ _episodeIndexer1.Setup(c => c.FetchSeason(It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
+ _episodeIndexer1.Setup(c => c.FetchPartialSeason(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
+
+
+ _episodeIndexer2 = new Mock();
+ _episodeIndexer2.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
+ _episodeIndexer2.Setup(c => c.FetchSeason(It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
+ _episodeIndexer2.Setup(c => c.FetchPartialSeason(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(parseResults);
+
+ _brokenIndexer = new Mock();
+ _brokenIndexer.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Throws(new Exception());
+
+ _nullIndexer = new Mock();
+ _nullIndexer.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns>(null);
+ }
+
+ private void WithTwoGoodOneBrokenIndexer()
+ {
+ _indexers = new List { _episodeIndexer1.Object, _brokenIndexer.Object, _episodeIndexer2.Object };
+
+ Mocker.GetMock()
+ .Setup(c => c.GetEnabledIndexers())
+ .Returns(_indexers);
+ }
+
+ private void WithNullIndexers()
+ {
+ _indexers = new List { _nullIndexer.Object, _nullIndexer.Object };
+
+ Mocker.GetMock()
+ .Setup(c => c.GetEnabledIndexers())
+ .Returns(_indexers);
+ }
+
+ private void WithSceneName()
+ {
+ Mocker.GetMock()
+ .Setup(s => s.GetSceneName(_series.SeriesId)).Returns(SCENE_NAME);
+ }
+
+ private void With30Episodes()
+ {
+ _episodes = Builder.CreateListOfSize(30)
+ .Build();
+ }
+
+ private void WithNullEpisodes()
+ {
+ _episodes = null;
+ }
+
+ private void VerifyFetchEpisode(Times times)
+ {
+ _episodeIndexer1.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny())
+ , times);
+
+ _episodeIndexer2.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny())
+ , times);
+ }
+
+ private void VerifyFetchEpisodeWithSceneName(Times times)
+ {
+ _episodeIndexer1.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny())
+ , times);
+
+ _episodeIndexer2.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny())
+ , times);
+ }
+
+ private void VerifyFetchEpisodeBrokenIndexer(Times times)
+ {
+ _brokenIndexer.Verify(v => v.FetchEpisode(It.IsAny(), SEASON_NUMBER, It.IsAny())
+ , times);
+ }
+
+ private void VerifyFetchPartialSeason(Times times)
+ {
+ _episodeIndexer1.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny())
+ , times);
+
+ _episodeIndexer2.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny())
+ , times);
+ }
+
+ private void VerifyFetchSeason(Times times)
+ {
+ _episodeIndexer1.Verify(v => v.FetchSeason(_series.Title, SEASON_NUMBER), times);
+ _episodeIndexer1.Verify(v => v.FetchSeason(_series.Title, SEASON_NUMBER), times);
+ }
+
+ [Test]
+ public void SeasonSearch_should_skip_daily_series()
+ {
+ //Setup
+ _series.IsDaily = true;
+
+ Mocker.GetMock().Setup(s => s.GetSeries(1)).Returns(_series);
+
+ //Act
+ var result = Mocker.Resolve().SeasonSearch(MockNotification, _series.SeriesId, 1);
+
+ //Assert
+ result.Should().BeFalse();
+ }
+
+ [Test]
+ public void PartialSeasonSearch_should_skip_daily_series()
+ {
+ //Setup
+ _series.IsDaily = true;
+
+ Mocker.GetMock().Setup(s => s.GetSeries(1)).Returns(_series);
+
+ //Act
+ var result = Mocker.Resolve().PartialSeasonSearch(MockNotification, _series.SeriesId, 1);
+
+ //Assert
+ result.Should().BeEmpty();
+ }
+
+ [Test]
+ public void EpisodeSearch_should_skip_if_air_date_is_null()
+ {
+ //Setup
+ _series.IsDaily = true;
+ var episode = _episodes.First();
+ episode.AirDate = null;
+ episode.Series = _series;
+
+ Mocker.GetMock().Setup(s => s.GetEpisode(episode.EpisodeId))
+ .Returns(episode);
+
+ //Act
+ var result = Mocker.Resolve().EpisodeSearch(MockNotification, episode.EpisodeId);
+
+ //Assert
+ result.Should().BeFalse();
+ ExceptionVerification.ExcpectedWarns(1);
+ }
+ }
+}
diff --git a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs
index cdedde253..90d656bd5 100644
--- a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs
+++ b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs
@@ -179,6 +179,33 @@ namespace NzbDrone.Core.Providers.Indexer
}
+ public virtual IList FetchDailyEpisode(string seriesTitle, DateTime airDate)
+ {
+ _logger.Debug("Searching {0} for {1}-{2}", Name, seriesTitle, airDate.ToShortDateString());
+
+ var result = new List();
+
+ var searchModel = new SearchModel
+ {
+ SeriesTitle = GetQueryTitle(seriesTitle),
+ AirDate = airDate,
+ SearchType = SearchType.DailySearch
+ };
+
+ var searchUrls = GetSearchUrls(searchModel);
+
+ foreach (var url in searchUrls)
+ {
+ result.AddRange(Fetch(url));
+ }
+
+ result = result.Where(e => e.CleanTitle == Parser.NormalizeTitle(seriesTitle)).ToList();
+
+ _logger.Info("Finished searching {0} for {1}-{2}, Found {3}", Name, seriesTitle, airDate.ToShortDateString(), result.Count);
+ return result;
+
+ }
+
private IEnumerable Fetch(string url)
{
var result = new List();
diff --git a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs
index e0307692a..a15761e89 100644
--- a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs
+++ b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs
@@ -11,12 +11,15 @@ namespace NzbDrone.Core.Providers.Jobs
{
private readonly SeriesProvider _seriesProvider;
private readonly EpisodeProvider _episodeProvider;
+ private readonly ReferenceDataProvider _referenceDataProvider;
[Inject]
- public UpdateInfoJob(SeriesProvider seriesProvider, EpisodeProvider episodeProvider)
+ public UpdateInfoJob(SeriesProvider seriesProvider, EpisodeProvider episodeProvider,
+ ReferenceDataProvider referenceDataProvider)
{
_seriesProvider = seriesProvider;
_episodeProvider = episodeProvider;
+ _referenceDataProvider = referenceDataProvider;
}
public UpdateInfoJob()
@@ -46,6 +49,9 @@ namespace NzbDrone.Core.Providers.Jobs
seriesToUpdate = new List() { _seriesProvider.GetSeries(targetId) };
}
+ //Update any Daily Series in the DB with the IsDaily flag
+ _referenceDataProvider.UpdateDailySeries();
+
foreach (var series in seriesToUpdate)
{
notification.CurrentMessage = "Updating " + series.Title;
diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs
index 3e07d6d1e..5d8105968 100644
--- a/NzbDrone.Core/Providers/SearchProvider.cs
+++ b/NzbDrone.Core/Providers/SearchProvider.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -50,6 +51,10 @@ namespace NzbDrone.Core.Providers
return false;
}
+ //Return false if the series is a daily series (we only support individual episode searching
+ if (series.IsDaily)
+ return false;
+
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
var reports = PerformSearch(notification, series, seasonNumber);
@@ -96,6 +101,10 @@ namespace NzbDrone.Core.Providers
return new List();
}
+ //Return empty list if the series is a daily series (we only support individual episode searching
+ if (series.IsDaily)
+ return new List();
+
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber);
@@ -121,36 +130,18 @@ namespace NzbDrone.Core.Providers
Logger.Error("Unable to find an episode {0} in database", episodeId);
return false;
}
+
notification.CurrentMessage = "Searching for " + episode;
var series = _seriesProvider.GetSeries(episode.SeriesId);
- var indexers = _indexerProvider.GetEnabledIndexers();
- var reports = new List();
-
- var title = _sceneMappingProvider.GetSceneName(series.SeriesId);
-
- if (string.IsNullOrWhiteSpace(title))
+ if (episode.Series.IsDaily && !episode.AirDate.HasValue)
{
- title = series.Title;
+ Logger.Warn("AirDate is not Valid for: {0}", episode);
+ return false;
}
- foreach (var indexer in indexers)
- {
- try
- {
- //notification.CurrentMessage = String.Format("Searching for {0} in {1}", episode, indexer.Name);
-
- //TODO:Add support for daily episodes, maybe search using both date and season/episode?
- var indexerResults = indexer.FetchEpisode(title, episode.SeasonNumber, episode.EpisodeNumber);
-
- reports.AddRange(indexerResults);
- }
- catch (Exception e)
- {
- Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e);
- }
- }
+ var reports = PerformSearch(notification, series, episode.SeasonNumber, new List { episode });
Logger.Debug("Finished searching all indexers. Total {0}", reports.Count);
notification.CurrentMessage = "Processing search results";
@@ -184,15 +175,23 @@ namespace NzbDrone.Core.Providers
if (episodes == null)
reports.AddRange(indexer.FetchSeason(title, seasonNumber));
- else if(episodes.Count == 1)
- reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodes.First().EpisodeNumber));
+ //Treat as single episode
+ else if (episodes.Count == 1)
+ {
+ if (!series.IsDaily)
+ reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodes.First().EpisodeNumber));
+
+ //Daily Episode
+ else
+ reports.AddRange(indexer.FetchDailyEpisode(title, episodes.First().AirDate.Value));
+ }
//Treat as Partial Season
else
{
var prefixes = GetEpisodeNumberPrefixes(episodes.Select(s => s.EpisodeNumber));
- foreach(var episodePrefix in prefixes)
+ foreach (var episodePrefix in prefixes)
{
reports.AddRange(indexer.FetchPartialSeason(title, seasonNumber, episodePrefix));
}