From 35cad3d27e054733164eb4c052589cf2a97f6e03 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 31 Aug 2011 23:58:54 -0700 Subject: [PATCH] Added partial season searching when a full season NZB is not available. --- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 2 + .../SearchProviderTest_PartialSeason.cs | 210 ++++++++++++++++++ .../SearchProviderTest_Season.cs | 23 +- NzbDrone.Core.Test/SeasonSearchJobTest.cs | 114 ++++++++++ NzbDrone.Core/Model/Search/SearchModel.cs | 1 + NzbDrone.Core/Model/Search/SearchType.cs | 3 +- .../Providers/Indexer/IndexerBase.cs | 27 +++ NzbDrone.Core/Providers/Indexer/Newzbin.cs | 17 +- NzbDrone.Core/Providers/Indexer/NzbMatrix.cs | 8 +- NzbDrone.Core/Providers/Indexer/NzbsOrg.cs | 8 +- .../Providers/Jobs/SeasonSearchJob.cs | 16 +- NzbDrone.Core/Providers/SearchProvider.cs | 101 +++++++++ 12 files changed, 510 insertions(+), 20 deletions(-) create mode 100644 NzbDrone.Core.Test/SearchProviderTest_PartialSeason.cs create mode 100644 NzbDrone.Core.Test/SeasonSearchJobTest.cs diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 97f14ff4e..678f753ca 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -89,6 +89,8 @@ + + diff --git a/NzbDrone.Core.Test/SearchProviderTest_PartialSeason.cs b/NzbDrone.Core.Test/SearchProviderTest_PartialSeason.cs new file mode 100644 index 000000000..dc57802ec --- /dev/null +++ b/NzbDrone.Core.Test/SearchProviderTest_PartialSeason.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMoq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Indexer; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class SearchProviderTest_PartialSeason : TestBase + { + [Test] + public void SeasonPartialSearch_season_success() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 1) + .With(s => s.Title = "Title1") + .Build(); + + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.Series = series) + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Have(e => e.Ignored = false) + .Build(); + + var parseResults = Builder.CreateListOfSize(4) + .WhereAll() + .Have(e => e.EpisodeNumbers = Builder.CreateListOfSize(2).Build().ToList()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + var indexer1 = new Mock(); + indexer1.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) + .Returns(parseResults).Verifiable(); + + var indexer2 = new Mock(); + indexer2.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) + .Returns(parseResults).Verifiable(); + + var indexers = new List { indexer1.Object, indexer2.Object }; + + mocker.GetMock() + .Setup(c => c.GetEnabledIndexers()) + .Returns(indexers); + + mocker.GetMock() + .Setup(c => c.GetSeries(1)).Returns(series); + + mocker.GetMock() + .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.GetSceneName(1)).Returns(String.Empty); + + mocker.GetMock() + .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(true); + + mocker.GetMock() + .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); + + //Act + var result = mocker.Resolve().PartialSeasonSearch(notification, 1, 1); + + //Assert + result.Should().HaveCount(16); + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Exactly(8)); + } + + [Test] + public void SeasonPartialSearch_season_no_results() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 1) + .With(s => s.Title = "Title1") + .Build(); + + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.Series = series) + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Have(e => e.Ignored = false) + .Build(); + + var parseResults = Builder.CreateListOfSize(4) + .WhereAll() + .Have(e => e.EpisodeNumbers = Builder.CreateListOfSize(2).Build().ToList()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + var indexer1 = new Mock(); + indexer1.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) + .Returns(new List()).Verifiable(); + + var indexer2 = new Mock(); + indexer2.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) + .Returns(new List()).Verifiable(); + + var indexers = new List { indexer1.Object, indexer2.Object }; + + mocker.GetMock() + .Setup(c => c.GetEnabledIndexers()) + .Returns(indexers); + + mocker.GetMock() + .Setup(c => c.GetSeries(1)).Returns(series); + + mocker.GetMock() + .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); + + mocker.GetMock() + .Setup(s => s.GetSceneName(1)).Returns(String.Empty); + + //Act + var result = mocker.Resolve().PartialSeasonSearch(notification, 1, 1); + + //Assert + result.Should().HaveCount(0); + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Never()); + } + + [Test] + public void ProcessPartialSeasonSearchResults_success() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 1) + .With(s => s.Title = "Title1") + .Build(); + + var parseResults = Builder.CreateListOfSize(4) + .WhereAll() + .Have(e => e.EpisodeNumbers = Builder.CreateListOfSize(2).Build().ToList()) + .Have(e => e.Series = series) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + mocker.GetMock() + .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(true); + + mocker.GetMock() + .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); + + //Act + var result = mocker.Resolve().ProcessPartialSeasonSearchResults(notification, parseResults); + + //Assert + result.Should().HaveCount(8); + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Exactly(4)); + + } + + [Test] + public void ProcessPartialSeasonSearchResults_failure() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 1) + .With(s => s.Title = "Title1") + .Build(); + + var parseResults = Builder.CreateListOfSize(4) + .WhereTheFirst(1) + .Has(p => p.CleanTitle = "title") + .Has(p => p.SeasonNumber = 1) + .Has(p => p.FullSeason = true) + .Has(p => p.EpisodeNumbers = null) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + mocker.GetMock() + .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(false); + + //Act + var result = mocker.Resolve().ProcessPartialSeasonSearchResults(notification, parseResults); + + //Assert + result.Should().HaveCount(0); + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Never()); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/SearchProviderTest_Season.cs b/NzbDrone.Core.Test/SearchProviderTest_Season.cs index 87fc5bff8..5f0f62658 100644 --- a/NzbDrone.Core.Test/SearchProviderTest_Season.cs +++ b/NzbDrone.Core.Test/SearchProviderTest_Season.cs @@ -79,12 +79,11 @@ namespace NzbDrone.Core.Test .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); //Act - mocker.Resolve().SeasonSearch(notification, 1, 1); + var result = mocker.Resolve().SeasonSearch(notification, 1, 1); //Assert + result.Should().BeTrue(); mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), - Times.Never()); } [Test] @@ -134,14 +133,12 @@ namespace NzbDrone.Core.Test .Setup(s => s.GetSceneName(1)).Returns(String.Empty); //Act - mocker.Resolve().SeasonSearch(notification, 1, 1); + var result = mocker.Resolve().SeasonSearch(notification, 1, 1); //Assert ExceptionVerification.ExcpectedWarns(1); + result.Should().BeFalse(); mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), - Times.Never()); - } [Test] @@ -171,13 +168,11 @@ namespace NzbDrone.Core.Test .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); //Act - mocker.Resolve().ProcessSeasonSearchResults(notification, series, 1, parseResults); + var result = mocker.Resolve().ProcessSeasonSearchResults(notification, series, 1, parseResults); //Assert + result.Should().BeTrue(); mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), - Times.Never()); - } [Test] @@ -204,14 +199,12 @@ namespace NzbDrone.Core.Test .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(false); //Act - mocker.Resolve().ProcessSeasonSearchResults(notification, series, 1, parseResults); + var result = mocker.Resolve().ProcessSeasonSearchResults(notification, series, 1, parseResults); //Assert + result.Should().BeFalse(); ExceptionVerification.ExcpectedWarns(1); mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), - Times.Never()); - } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/SeasonSearchJobTest.cs b/NzbDrone.Core.Test/SeasonSearchJobTest.cs new file mode 100644 index 000000000..3dab3660e --- /dev/null +++ b/NzbDrone.Core.Test/SeasonSearchJobTest.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMoq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Indexer; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class SeasonSearchJobTest : TestBase + { + [Test] + public void SeasonSearch_full_season_success() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + mocker.GetMock() + .Setup(c => c.SeasonSearch(notification, 1, 1)).Returns(true); + + //Act + mocker.Resolve().Start(notification, 1, 1); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.SeasonSearch(notification, 1, 1), Times.Once()); + mocker.GetMock().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Never()); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), Times.Never()); + } + + [Test] + public void SeasonSearch_partial_season_success() + { + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + mocker.GetMock() + .Setup(c => c.SeasonSearch(notification, 1, 1)).Returns(false); + + mocker.GetMock() + .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); + + mocker.GetMock() + .Setup(c => c.PartialSeasonSearch(notification, 1, 1)) + .Returns(episodes.Select(e => e.EpisodeNumber).ToList()); + + //Act + mocker.Resolve().Start(notification, 1, 1); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.SeasonSearch(notification, 1, 1), Times.Once()); + mocker.GetMock().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once()); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), Times.Never()); + } + + [Test] + public void SeasonSearch_partial_season_failure() + { + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .Have(e => e.Ignored = false) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + mocker.GetMock() + .Setup(c => c.SeasonSearch(notification, 1, 1)).Returns(false); + + mocker.GetMock() + .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); + + mocker.GetMock() + .Setup(c => c.PartialSeasonSearch(notification, 1, 1)) + .Returns(new List{1}); + + mocker.GetMock() + .Setup(c => c.Start(notification, It.IsAny(), 0)).Verifiable(); + + //Act + mocker.Resolve().Start(notification, 1, 1); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.SeasonSearch(notification, 1, 1), Times.Once()); + mocker.GetMock().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once()); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), Times.Exactly(4)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/Search/SearchModel.cs b/NzbDrone.Core/Model/Search/SearchModel.cs index 70ef93960..85520b106 100644 --- a/NzbDrone.Core/Model/Search/SearchModel.cs +++ b/NzbDrone.Core/Model/Search/SearchModel.cs @@ -10,6 +10,7 @@ namespace NzbDrone.Core.Model.Search public string SeriesTitle { get; set; } public int EpisodeNumber { get; set; } public int SeasonNumber { get; set; } + public int EpisodePrefix { get; set; } public DateTime AirDate { get; set; } public SearchType SearchType { get; set; } } diff --git a/NzbDrone.Core/Model/Search/SearchType.cs b/NzbDrone.Core/Model/Search/SearchType.cs index deac8a229..6a9666da7 100644 --- a/NzbDrone.Core/Model/Search/SearchType.cs +++ b/NzbDrone.Core/Model/Search/SearchType.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Model.Search { EpisodeSearch = 0, DailySearch = 1, - SeasonSearch = 2 + PartialSeasonSearch = 2, + SeasonSearch = 3 } } diff --git a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs index 333ba0ac3..39c3bd041 100644 --- a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs +++ b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs @@ -124,6 +124,33 @@ namespace NzbDrone.Core.Providers.Indexer return result; } + public virtual IList FetchPartialSeason(string seriesTitle, int seasonNumber, int episodePrefix) + { + _logger.Debug("Searching {0} for {1}-Season {2}, Prefix: {3}", Name, seriesTitle, seasonNumber, episodePrefix); + + var result = new List(); + + var searchModel = new SearchModel + { + SeriesTitle = GetQueryTitle(seriesTitle), + SeasonNumber = seasonNumber, + EpisodePrefix = episodePrefix, + SearchType = SearchType.PartialSeasonSearch + }; + + 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}-S{2}, Found {3}", Name, seriesTitle, seasonNumber, result.Count); + return result; + } + public virtual IList FetchEpisode(string seriesTitle, int seasonNumber, int episodeNumber) { _logger.Debug("Searching {0} for {1}-S{2:00}E{3:00}", Name, seriesTitle, seasonNumber, episodeNumber); diff --git a/NzbDrone.Core/Providers/Indexer/Newzbin.cs b/NzbDrone.Core/Providers/Indexer/Newzbin.cs index 3edb4e6b6..ae99761d4 100644 --- a/NzbDrone.Core/Providers/Indexer/Newzbin.cs +++ b/NzbDrone.Core/Providers/Indexer/Newzbin.cs @@ -52,12 +52,27 @@ namespace NzbDrone.Core.Providers.Indexer }; } - return new List + if (searchModel.SearchType == SearchType.SeasonSearch) + { + return new List { String.Format( @"http://www.newzbin.com/search/query/?q={0}+Season+{1}&fpn=p&searchaction=Go&category=8&{2}", searchModel.SeriesTitle, searchModel.SeasonNumber, UrlParams) }; + } + + if (searchModel.SearchType == SearchType.PartialSeasonSearch) + { + return new List + { + String.Format( + @"http://www.newzbin.com/search/query/?q={0}+{1}x{2}&fpn=p&searchaction=Go&category=8&{3}", + searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodePrefix, UrlParams) + }; + } + + return new List(); } public override string Name diff --git a/NzbDrone.Core/Providers/Indexer/NzbMatrix.cs b/NzbDrone.Core/Providers/Indexer/NzbMatrix.cs index 4366490cb..e9799e6c8 100644 --- a/NzbDrone.Core/Providers/Indexer/NzbMatrix.cs +++ b/NzbDrone.Core/Providers/Indexer/NzbMatrix.cs @@ -51,7 +51,13 @@ namespace NzbDrone.Core.Providers.Indexer searchModel.SeasonNumber, searchModel.EpisodeNumber)); } - else + if (searchModel.SearchType == SearchType.PartialSeasonSearch) + { + searchUrls.Add(String.Format("{0}&term={1}+S{2:00}E{3}", + url, searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodePrefix)); + } + + if (searchModel.SearchType == SearchType.SeasonSearch) { searchUrls.Add(String.Format("{0}&term={1}+Season", url, searchModel.SeriesTitle)); searchUrls.Add(String.Format("{0}&term={1}+S{2:00}", url, searchModel.SeriesTitle, searchModel.SeasonNumber)); diff --git a/NzbDrone.Core/Providers/Indexer/NzbsOrg.cs b/NzbDrone.Core/Providers/Indexer/NzbsOrg.cs index 7d3d60576..cb6acef3c 100644 --- a/NzbDrone.Core/Providers/Indexer/NzbsOrg.cs +++ b/NzbDrone.Core/Providers/Indexer/NzbsOrg.cs @@ -49,7 +49,13 @@ namespace NzbDrone.Core.Providers.Indexer searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodeNumber)); } - else + if (searchModel.SearchType == SearchType.PartialSeasonSearch) + { + searchUrls.Add(String.Format("{0}&action=search&q={1}+S{2:00}E{3}", + url, searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodePrefix)); + } + + if (searchModel.SearchType == SearchType.SeasonSearch) { searchUrls.Add(String.Format("{0}&action=search&q={1}+Season", url, searchModel.SeriesTitle)); searchUrls.Add(String.Format("{0}&action=search&q={1}+S{2:00}", url, searchModel.SeriesTitle, searchModel.SeasonNumber)); diff --git a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs index 9f90c6e58..cfc224537 100644 --- a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs +++ b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs @@ -59,7 +59,21 @@ namespace NzbDrone.Core.Providers.Jobs return; } - foreach (var episode in episodes.Where(e => !e.Ignored)) + //Perform a Partial Season Search + var addedSeries = _searchProvider.PartialSeasonSearch(notification, targetId, secondaryTargetId); + + addedSeries.Distinct().ToList().Sort(); + var episodeNumbers = episodes.Select(s => s.EpisodeNumber).ToList(); + episodeNumbers.Sort(); + + if (addedSeries.SequenceEqual(episodeNumbers)) + return; + + //Get the list of episodes that weren't downloaded + var missingEpisodes = episodeNumbers.Except(addedSeries).ToList(); + + //Only process episodes that is in missing episodes (To ensure we double check if the episode is available) + foreach (var episode in episodes.Where(e => !e.Ignored && missingEpisodes.Contains(e.EpisodeNumber))) { _episodeSearchJob.Start(notification, episode.EpisodeId, 0); } diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 01afb165f..5e7f713ac 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -229,5 +229,106 @@ namespace NzbDrone.Core.Providers return false; } + + public virtual List PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber) + { + //This method will search for episodes in a season in groups of 10 episodes S01E0, S01E1, S01E2, etc + + var series = _seriesProvider.GetSeries(seriesId); + + if (series == null) + { + Logger.Error("Unable to find an series {0} in database", seriesId); + return new List(); + } + + notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series, seasonNumber); + + var indexers = _indexerProvider.GetEnabledIndexers(); + var reports = new List(); + + var title = _sceneMappingProvider.GetSceneName(series.SeriesId); + + if (string.IsNullOrWhiteSpace(title)) + { + title = series.Title; + } + + var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); + var episodeCount = episodes.Count; + var episodePrefix = 0; + + while(episodeCount >= 0) + { + //Do the actual search for each indexer + foreach (var indexer in indexers) + { + try + { + var indexerResults = indexer.FetchPartialSeason(title, seasonNumber, episodePrefix); + + reports.AddRange(indexerResults); + } + catch (Exception e) + { + Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); + } + } + + episodePrefix++; + episodeCount -= 10; + } + + Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + + if (reports.Count == 0) + return new List(); + + notification.CurrentMessage = "Processing search results"; + + reports.ForEach(c => + { + c.Series = series; + }); + + return ProcessPartialSeasonSearchResults(notification, reports); + } + + public List ProcessPartialSeasonSearchResults(ProgressNotification notification, IEnumerable reports) + { + var successes = new List(); + + foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality)) + { + try + { + Logger.Trace("Analysing report " + episodeParseResult); + if (_inventoryProvider.IsQualityNeeded(episodeParseResult)) + { + Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); + try + { + _downloadProvider.DownloadReport(episodeParseResult); + notification.CurrentMessage = String.Format("{0} - S{1:00}E{2:00} {3}Added to download queue", + episodeParseResult.Series.Title, episodeParseResult.SeasonNumber, episodeParseResult.EpisodeNumbers[0], episodeParseResult.Quality); + + //Add the list of episode numbers from this release + successes.AddRange(episodeParseResult.EpisodeNumbers); + } + catch (Exception e) + { + Logger.ErrorException("Unable to add report to download queue." + episodeParseResult, e); + notification.CurrentMessage = String.Format("Unable to add report to download queue. {0}", episodeParseResult); + } + } + } + catch (Exception e) + { + Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); + } + } + + return successes; + } } }