diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs index 23b662982..2d501ca2a 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; +using FizzWare.NBuilder; namespace NzbDrone.Core.Test.DecisionEngineTests { @@ -141,7 +142,8 @@ namespace NzbDrone.Core.Test.DecisionEngineTests results.Should().BeEmpty(); } - [Test] public void should_not_attempt_to_map_episode_series_title_is_blank() + [Test] + public void should_not_attempt_to_map_episode_series_title_is_blank() { GivenSpecifications(_pass1, _pass2, _pass3); _reports[0].Title = "1937 - Snow White and the Seven Dwarves"; @@ -204,5 +206,44 @@ namespace NzbDrone.Core.Test.DecisionEngineTests result.Should().HaveCount(1); } + + [Test] + public void should_only_include_reports_for_requested_episodes() + { + var series = Builder.CreateNew().Build(); + + var episodes = Builder.CreateListOfSize(2) + .All() + .With(v => v.SeriesId, series.Id) + .With(v => v.Series, series) + .With(v => v.SeasonNumber, 1) + .With(v => v.SceneSeasonNumber, 2) + .BuildList(); + + var criteria = new SeasonSearchCriteria { Episodes = episodes.Take(1).ToList(), SeasonNumber = 1 }; + + var reports = episodes.Select(v => + new ReleaseInfo() + { + Title = string.Format("{0}.S{1:00}E{2:00}.720p.WEB-DL-DRONE", series.Title, v.SceneSeasonNumber, v.SceneEpisodeNumber) + }).ToList(); + + Mocker.GetMock() + .Setup(v => v.Map(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((p,id,c) => + new RemoteEpisode + { + DownloadAllowed = true, + ParsedEpisodeInfo = p, + Series = series, + Episodes = episodes.Where(v => v.SceneEpisodeNumber == p.EpisodeNumbers.First()).ToList() + }); + + Mocker.SetConstant>(new List()); + + var decisions = Subject.GetSearchDecision(reports, criteria); + + Assert.AreEqual(1, decisions.Count); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs new file mode 100644 index 000000000..2ccfa51ea --- /dev/null +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs @@ -0,0 +1,170 @@ +using NzbDrone.Core.IndexerSearch; +using NzbDrone.Core.Test.Framework; +using FizzWare.NBuilder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; + +namespace NzbDrone.Core.Test.IndexerSearchTests +{ + public class NzbSearchServiceFixture : CoreTest + { + private Series _xemSeries; + private List _xemEpisodes; + + [SetUp] + public void SetUp() + { + var indexer = Mocker.GetMock(); + indexer.SetupGet(s => s.SupportsSearching).Returns(true); + + Mocker.GetMock() + .Setup(s => s.GetAvailableProviders()) + .Returns(new List{indexer.Object}); + + Mocker.GetMock() + .Setup(s => s.GetSearchDecision(It.IsAny>(), It.IsAny())) + .Returns(new List()); + + _xemSeries = Builder.CreateNew() + .With(v => v.UseSceneNumbering = true) + .Build(); + + _xemEpisodes = new List(); + + Mocker.GetMock() + .Setup(v => v.GetSeries(_xemSeries.Id)) + .Returns(_xemSeries); + + Mocker.GetMock() + .Setup(v => v.GetEpisodesBySeason(_xemSeries.Id, It.IsAny())) + .Returns((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList()); + } + + private void WithEpisode(int seasonNumber, int episodeNumber, int sceneSeasonNumber, int sceneEpisodeNumber) + { + var episode = Builder.CreateNew() + .With(v => v.SeriesId == _xemSeries.Id) + .With(v => v.Series == _xemSeries) + .With(v => v.SeasonNumber, seasonNumber) + .With(v => v.EpisodeNumber, episodeNumber) + .With(v => v.SceneSeasonNumber, sceneSeasonNumber) + .With(v => v.SceneEpisodeNumber, sceneEpisodeNumber) + .Build(); + + _xemEpisodes.Add(episode); + } + + private void WithEpisodes() + { + // Season 1 maps to Scene Season 2 (one-to-one) + WithEpisode(1, 12, 2, 3); + WithEpisode(1, 13, 2, 4); + + // Season 2 maps to Scene Season 3 & 4 (one-to-one) + WithEpisode(2, 1, 3, 11); + WithEpisode(2, 2, 3, 12); + WithEpisode(2, 3, 4, 11); + WithEpisode(2, 4, 4, 12); + + // Season 3 maps to Scene Season 5 (partial) + // Season 4 maps to Scene Season 5 & 6 (partial) + WithEpisode(3, 1, 5, 11); + WithEpisode(3, 2, 5, 12); + WithEpisode(4, 1, 5, 13); + WithEpisode(4, 2, 5, 14); + WithEpisode(4, 3, 6, 11); + WithEpisode(5, 1, 6, 12); + } + + private List WatchForSearchCriteria() + { + List result = new List(); + + Mocker.GetMock() + .Setup(v => v.Fetch(It.IsAny(), It.IsAny())) + .Callback((i, s) => result.Add(s)) + .Returns(new List()); + + Mocker.GetMock() + .Setup(v => v.Fetch(It.IsAny(), It.IsAny())) + .Callback((i,s) => result.Add(s)) + .Returns(new List()); + + return result; + } + + [Test] + public void scene_episodesearch() + { + WithEpisodes(); + + var allCriteria = WatchForSearchCriteria(); + + Subject.EpisodeSearch(_xemEpisodes.First()); + + var criteria = allCriteria.OfType().ToList(); + + Assert.AreEqual(1, criteria.Count); + Assert.AreEqual(2, criteria[0].SeasonNumber); + Assert.AreEqual(3, criteria[0].EpisodeNumber); + } + + [Test] + public void scene_seasonsearch() + { + WithEpisodes(); + + var allCriteria = WatchForSearchCriteria(); + + Subject.SeasonSearch(_xemSeries.Id, 1); + + var criteria = allCriteria.OfType().ToList(); + + Assert.AreEqual(1, criteria.Count); + Assert.AreEqual(2, criteria[0].SeasonNumber); + } + + [Test] + public void scene_seasonsearch_should_search_multiple_seasons() + { + WithEpisodes(); + + var allCriteria = WatchForSearchCriteria(); + + Subject.SeasonSearch(_xemSeries.Id, 2); + + var criteria = allCriteria.OfType().ToList(); + + Assert.AreEqual(2, criteria.Count); + Assert.AreEqual(3, criteria[0].SeasonNumber); + Assert.AreEqual(4, criteria[1].SeasonNumber); + } + + [Test] + public void scene_seasonsearch_should_search_single_episode_if_possible() + { + WithEpisodes(); + + var allCriteria = WatchForSearchCriteria(); + + Subject.SeasonSearch(_xemSeries.Id, 4); + + var criteria1 = allCriteria.OfType().ToList(); + var criteria2 = allCriteria.OfType().ToList(); + + Assert.AreEqual(1, criteria1.Count); + Assert.AreEqual(5, criteria1[0].SeasonNumber); + + Assert.AreEqual(1, criteria2.Count); + Assert.AreEqual(6, criteria2[0].SeasonNumber); + Assert.AreEqual(11, criteria2[0].EpisodeNumber); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index f140c68b8..70086788a 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -138,6 +138,7 @@ + diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index cfed22a5c..b5b13d9a7 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -97,6 +97,17 @@ namespace NzbDrone.Core.DecisionEngine if (decision != null) { + if (searchCriteria != null) + { + var criteriaEpisodes = searchCriteria.Episodes.Select(v => v.Id).ToList(); + var remoteEpisodes = decision.RemoteEpisode.Episodes.Select(v => v.Id).ToList(); + if (!criteriaEpisodes.Intersect(remoteEpisodes).Any()) + { + _logger.Debug("Release rejected since the episode wasn't requested: {0}", decision.RemoteEpisode.ParsedEpisodeInfo); + continue; + } + } + if (decision.Rejections.Any()) { _logger.Debug("Release rejected for the following reasons: {0}", String.Join(", ", decision.Rejections)); diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index b2b3ddb1d..a5b140beb 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -138,10 +138,43 @@ namespace NzbDrone.Core.IndexerSearch return SearchSpecial(series, episodes); } - var searchSpec = Get(series, episodes); - searchSpec.SeasonNumber = seasonNumber; + List downloadDecisions = new List(); - return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + if (series.UseSceneNumbering) + { + var sceneSeasonGroups = episodes.GroupBy(v => v.SceneSeasonNumber).Distinct(); + + foreach (var sceneSeasonEpisodes in sceneSeasonGroups) + { + if (sceneSeasonEpisodes.Count() == 1) + { + var searchSpec = Get(series, sceneSeasonEpisodes.ToList()); + searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; + searchSpec.EpisodeNumber = sceneSeasonEpisodes.First().SceneEpisodeNumber; + + var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + downloadDecisions.AddRange(decisions); + } + else + { + var searchSpec = Get(series, sceneSeasonEpisodes.ToList()); + searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; + + var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + downloadDecisions.AddRange(decisions); + } + } + } + else + { + var searchSpec = Get(series, episodes); + searchSpec.SeasonNumber = seasonNumber; + + var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + downloadDecisions.AddRange(decisions); + } + + return downloadDecisions; } private TSpec Get(Series series, List episodes) where TSpec : SearchCriteriaBase, new()