diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs index ccc13ed59..111081e4e 100644 --- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests { private const string SCENE_NAME = "Scene Name"; private const int SEASON_NUMBER = 5; + private const int EPISODE_NUMBER = 1; private const int PARSE_RESULT_COUNT = 10; private Mock _episodeIndexer1; @@ -170,26 +171,13 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests public void PerformSearch_should_search_all_enabled_providers() { //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformEpisodeSearch(_series, SEASON_NUMBER, _episodes.First().EpisodeNumber); //Assert VerifyFetchEpisode(Times.Once()); result.Should().HaveCount(PARSE_RESULT_COUNT * 2); } - [Test] - public void PerformSearch_should_look_for_scene_name_to_search() - { - WithSceneName(); - - //Act - Mocker.Resolve().PerformSearch(MockNotification, _series, 1, _episodes); - - //Assert - Mocker.GetMock().Verify(c => c.GetSceneName(_series.SeriesId), - Times.Once()); - } - [Test] public void broken_indexer_should_not_block_other_indexers() { @@ -197,7 +185,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests WithTwoGoodOneBrokenIndexer(); //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER); //Assert result.Should().HaveCount(PARSE_RESULT_COUNT * 2); @@ -215,7 +203,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests public void PerformSearch_for_episode_should_call_FetchEpisode() { //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER); //Assert result.Should().HaveCount(PARSE_RESULT_COUNT * 2); @@ -230,7 +218,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests _series.IsDaily = true; //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformDailyEpisodeSearch(_series, _episodes.First()); //Assert result.Should().HaveCount(PARSE_RESULT_COUNT * 2); @@ -244,7 +232,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests With30Episodes(); //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformPartialSeasonSearch(_series, SEASON_NUMBER, new List{0, 1, 2, 3}); //Assert result.Should().HaveCount(80); @@ -257,7 +245,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests WithNullEpisodes(); //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformSeasonSearch(_series, SEASON_NUMBER); //Assert result.Should().HaveCount(20); @@ -271,7 +259,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests WithNullIndexers(); //Act - var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + var result = Mocker.Resolve().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER); //Assert result.Should().HaveCount(0); @@ -283,10 +271,9 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests { WithSceneName(); - Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + Mocker.Resolve().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER); VerifyFetchEpisodeWithSceneName(Times.Once()); } - } } diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index ac6856dd3..478f0caa2 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -62,35 +62,60 @@ namespace NzbDrone.Core.Providers { _logger.Error("Unable to find an series {0} in database", seriesId); return new List(); - } + } - //Return false if the series is a daily series (we only support individual episode searching if (series.IsDaily) + { + _logger.Trace("Daily series detected, skipping season search: {0}", series.Title); + return new List(); + } + + _logger.Debug("Getting episodes from database for series: {0} and season: {1}", seriesId, seasonNumber); + var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); + + if (episodes == null || episodes.Count == 0) + { + _logger.Warn("No episodes in database found for series: {0} and season: {1}.", seriesId, seasonNumber); return new List(); + } notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber); - var reports = PerformSearch(notification, series, seasonNumber); + List reports; - _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + if (series.UseSceneNumbering) + { + var sceneSeasonNumbers = episodes.Select(e => e.SceneSeasonNumber).ToList(); + var sceneEpisodeNumbers = episodes.Select(e => e.SceneEpisodeNumber).ToList(); - if (reports.Count == 0) - return new List(); + if (sceneSeasonNumbers.Distinct().Count() > 1) + { + _logger.Trace("Uses scene numbering, but multiple seasons found, skipping."); + return new List(); + } - _logger.Debug("Getting episodes from database for series: {0} and season: {1}", seriesId, seasonNumber); - var episodeNumbers = _episodeProvider.GetEpisodeNumbersBySeason(seriesId, seasonNumber); + reports = PerformSeasonSearch(series, sceneSeasonNumbers.First()); - if (episodeNumbers == null || episodeNumbers.Count == 0) - { - _logger.Warn("No episodes in database found for series: {0} and season: {1}.", seriesId, seasonNumber); - return new List(); + reports.Where(p => p.FullSeason && p.SeasonNumber == sceneSeasonNumbers.First()).ToList().ForEach( + e => e.EpisodeNumbers = sceneEpisodeNumbers.ToList() + ); } - notification.CurrentMessage = "Processing search results"; + else + { + reports = PerformSeasonSearch(series, seasonNumber); - reports.Where(p => p.FullSeason && p.SeasonNumber == seasonNumber).ToList().ForEach( - e => e.EpisodeNumbers = episodeNumbers.ToList() + reports.Where(p => p.FullSeason && p.SeasonNumber == seasonNumber).ToList().ForEach( + e => e.EpisodeNumbers = episodes.Select(ep => ep.EpisodeNumber).ToList() ); + } + + _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + + if (reports.Count == 0) + return new List(); + + notification.CurrentMessage = "Processing search results"; searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, series, seasonNumber); _searchHistoryProvider.Add(searchResult); @@ -115,13 +140,36 @@ 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) + { + _logger.Trace("Daily series detected, skipping season search: {0}", series.Title); return new List(); + } notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber); var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); - var reports = PerformSearch(notification, series, seasonNumber, episodes); + + List reports; + + if (series.UseSceneNumbering) + { + var sceneSeasonNumbers = episodes.Select(e => e.SceneSeasonNumber).ToList(); + var sceneEpisodeNumbers = episodes.Select(e => e.SceneEpisodeNumber).ToList(); + + if (sceneSeasonNumbers.Distinct().Count() > 1) + { + _logger.Trace("Uses scene numbering, but multiple seasons found, skipping."); + return new List(); + } + + reports = PerformPartialSeasonSearch(series, sceneSeasonNumbers.First(), GetEpisodeNumberPrefixes(sceneEpisodeNumbers)); + } + + else + { + reports = PerformPartialSeasonSearch(series, seasonNumber, GetEpisodeNumberPrefixes(episodes.Select(e => e.EpisodeNumber))); + } + _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); if (reports.Count == 0) @@ -153,27 +201,29 @@ namespace NzbDrone.Core.Providers } notification.CurrentMessage = "Looking for " + episode; - - if (episode.Series.IsDaily && !episode.AirDate.HasValue) - { - _logger.Warn("AirDate is not Valid for: {0}", episode); - notification.CurrentMessage = String.Format("Search for {0} Failed, AirDate is invalid", episode); - return false; - } + List reports; var searchResult = new SearchHistory { SearchTime = DateTime.Now, - SeriesId = episode.Series.SeriesId + SeriesId = episode.Series.SeriesId, + EpisodeId = episodeId }; - var reports = PerformSearch(notification, episode.Series, episode.SeasonNumber, new List { episode }); - - _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); - notification.CurrentMessage = "Processing search results"; - if (episode.Series.IsDaily) { + if (!episode.AirDate.HasValue) + { + _logger.Warn("AirDate is not Valid for: {0}", episode); + notification.CurrentMessage = String.Format("Search for {0} Failed, AirDate is invalid", episode); + return false; + } + + reports = PerformDailyEpisodeSearch(episode.Series, episode); + + _logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + notification.CurrentMessage = "Processing search results"; + searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, episode.Series, episode.AirDate.Value); _searchHistoryProvider.Add(searchResult); @@ -183,17 +233,17 @@ namespace NzbDrone.Core.Providers else if (episode.Series.UseSceneNumbering) { - searchResult.EpisodeId = episodeId; - var seasonNumber = episode.SceneSeasonNumber; var episodeNumber = episode.SceneEpisodeNumber; - if (seasonNumber == 0 || episodeNumber == 0) + if (seasonNumber == 0 && episodeNumber == 0) { seasonNumber = episode.SeasonNumber; episodeNumber = episode.EpisodeNumber; } + reports = PerformEpisodeSearch(episode.Series, seasonNumber, episodeNumber); + searchResult.SearchHistoryItems = ProcessSearchResults( notification, reports, @@ -211,7 +261,8 @@ namespace NzbDrone.Core.Providers else { - searchResult.EpisodeId = episodeId; + reports = PerformEpisodeSearch(episode.Series, episode.SeasonNumber, episode.EpisodeNumber); + searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, episode.Series, episode.SeasonNumber, episode.EpisodeNumber); _searchHistoryProvider.Add(searchResult); @@ -221,75 +272,12 @@ namespace NzbDrone.Core.Providers _logger.Warn("Unable to find {0} in any of indexers.", episode); - if (reports.Any()) - { - notification.CurrentMessage = String.Format("Sorry, couldn't find {0}, that matches your preferences.", episode); - } - else - { - notification.CurrentMessage = String.Format("Sorry, couldn't find {0} in any of indexers.", episode); - } + notification.CurrentMessage = reports.Any() ? String.Format("Sorry, couldn't find {0}, that matches your preferences.", episode) + : String.Format("Sorry, couldn't find {0} in any of indexers.", episode); return false; } - public List PerformSearch(ProgressNotification notification, Series series, int seasonNumber, IList episodes = null) - { - //If single episode, do a single episode search, if full season then do a full season search, otherwise, do a partial search - - var reports = new List(); - - var title = _sceneMappingProvider.GetSceneName(series.SeriesId); - - if (string.IsNullOrWhiteSpace(title)) - { - title = series.Title; - } - - Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer => - { - try - { - if (episodes == null) - reports.AddRange(indexer.FetchSeason(title, seasonNumber)); - - //Treat as single episode - else if (episodes.Count == 1) - { - //Use SceneNumbering - Only if SceneSN and SceneEN are greater than zero - if (series.UseSceneNumbering && episodes.First().SceneSeasonNumber > 0 && episodes.First().SceneEpisodeNumber > 0) - reports.AddRange(indexer.FetchEpisode(title, episodes.First().SceneSeasonNumber, episodes.First().SceneEpisodeNumber)); - - //Standard - else 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) - { - reports.AddRange(indexer.FetchPartialSeason(title, seasonNumber, episodePrefix)); - } - } - } - - catch (Exception e) - { - _logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); - } - }); - - return reports; - } - public List ProcessSearchResults(ProgressNotification notification, IEnumerable reports, SearchHistory searchResult, Series series, int seasonNumber, int? episodeNumber = null) { var items = new List(); @@ -488,5 +476,106 @@ namespace NzbDrone.Core.Providers return results.Distinct().ToList(); } + + public List PerformEpisodeSearch(Series series, int seasonNumber, int episodeNumber) + { + var reports = new List(); + var title = GetSeriesTitle(series); + + Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer => + { + try + { + reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodeNumber)); + } + + catch (Exception e) + { + _logger.ErrorException(String.Format("An error has occurred while searching for {0}-S{1:00}E{2:00} from: {3}", + series.Title, seasonNumber, episodeNumber, indexer.Name), e); + } + }); + + return reports; + } + + public List PerformDailyEpisodeSearch(Series series, Episode episode) + { + var reports = new List(); + var title = GetSeriesTitle(series); + + Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer => + { + try + { + _logger.Trace("Episode {0} is a daily episode, searching as daily", episode); + reports.AddRange(indexer.FetchDailyEpisode(title, episode.AirDate.Value)); + } + + catch (Exception e) + { + _logger.ErrorException(String.Format("An error has occurred while searching for {0}-{1} from: {2}", + series.Title, episode.AirDate, indexer.Name), e); + } + }); + + return reports; + } + + public List PerformPartialSeasonSearch(Series series, int seasonNumber, List prefixes) + { + var reports = new List(); + var title = GetSeriesTitle(series); + + Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer => + { + try + { + foreach (var episodePrefix in prefixes) + { + reports.AddRange(indexer.FetchPartialSeason(title, seasonNumber, episodePrefix)); + } + } + + catch (Exception e) + { + _logger.ErrorException(String.Format("An error has occurred while searching for {0}-S{1:00} from: {2}", + series.Title, seasonNumber, indexer.Name), e); + } + }); + + return reports; + } + + public List PerformSeasonSearch(Series series, int seasonNumber) + { + var reports = new List(); + var title = GetSeriesTitle(series); + + Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer => + { + try + { + reports.AddRange(indexer.FetchSeason(title, seasonNumber)); + } + + catch (Exception e) + { + _logger.ErrorException("An error has occurred while searching for items from: " + indexer.Name, e); + } + }); + + return reports; + } + + public string GetSeriesTitle(Series series) + { + var title = _sceneMappingProvider.GetSceneName(series.SeriesId); + + if (String.IsNullOrWhiteSpace(title)) + title = series.Title; + + return title; + } } }