diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs index fb27d7437..5ca2b02e5 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; +using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Core.DataAugmentation.Scene; @@ -117,6 +118,10 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests { GivenAbsoluteNumberingSeries(); + Mocker.GetMock() + .Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny(), It.IsAny())) + .Returns(new List()); + Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria); Mocker.GetMock() @@ -253,7 +258,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests [TestCase(0)] [TestCase(1)] [TestCase(2)] - public void should_find_episode_by_season_and_absolute_episode_number_when_scene_absolute_episode_number_returns_multiple_results(int seasonNumber) + public void should_return_episodes_when_scene_absolute_episode_number_returns_multiple_results(int seasonNumber) { GivenAbsoluteNumberingSeries(); @@ -265,6 +270,32 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests .Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny(), seasonNumber, It.IsAny())) .Returns(Builder.CreateListOfSize(5).Build().ToList()); + var result = Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); + + result.Should().HaveCount(5); + + Mocker.GetMock() + .Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny(), seasonNumber, It.IsAny()), Times.Once()); + + Mocker.GetMock() + .Verify(v => v.FindEpisode(It.IsAny(), seasonNumber, It.IsAny()), Times.Never()); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void should_find_episode_by_season_and_absolute_episode_number_when_scene_absolute_episode_number_returns_no_results(int seasonNumber) + { + GivenAbsoluteNumberingSeries(); + + Mocker.GetMock() + .Setup(s => s.GetSceneSeasonNumber(_parsedEpisodeInfo.SeriesTitle, It.IsAny())) + .Returns(seasonNumber); + + Mocker.GetMock() + .Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny(), seasonNumber, It.IsAny())) + .Returns(Builder.CreateListOfSize(0).Build().ToList()); + Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); Mocker.GetMock() diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs index e4bd7b2c6..18c59a061 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/EpisodeRequestedSpecification.cs @@ -31,7 +31,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search if (!criteriaEpisodes.Intersect(remoteEpisodes).Any()) { _logger.Debug("Release rejected since the episode wasn't requested: {0}", remoteEpisode.ParsedEpisodeInfo); - return Decision.Reject("Episode wasn't requested"); + + if (remoteEpisodes.Any()) + { + var episodes = remoteEpisode.Episodes.OrderBy(v => v.SeasonNumber).ThenBy(v => v.EpisodeNumber).ToList(); + + if (episodes.Count > 1) + { + return Decision.Reject($"Episode wasn't requested: {episodes.First().SeasonNumber}x{episodes.First().EpisodeNumber}-{episodes.Last().EpisodeNumber}"); + } + else + { + return Decision.Reject($"Episode wasn't requested: {episodes.First().SeasonNumber}x{episodes.First().EpisodeNumber}"); + } + } + else + { + return Decision.Reject("Episode wasn't requested"); + } + } return Decision.Accept(); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs index 4d4f76d05..2f1c341e4 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs @@ -25,8 +25,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search } var singleEpisodeSpec = searchCriteria as SingleEpisodeSearchCriteria; - if (singleEpisodeSpec == null) return Decision.Accept(); + if (singleEpisodeSpec != null) return IsSatisfiedBy(remoteEpisode, singleEpisodeSpec); + var animeEpisodeSpec = searchCriteria as AnimeEpisodeSearchCriteria; + if (animeEpisodeSpec != null) return IsSatisfiedBy(remoteEpisode, animeEpisodeSpec); + + return Decision.Accept(); + } + + private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SingleEpisodeSearchCriteria singleEpisodeSpec) + { if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber) { _logger.Debug("Season number does not match searched season number, skipping."); @@ -47,5 +55,16 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search return Decision.Accept(); } + + private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, AnimeEpisodeSearchCriteria singleEpisodeSpec) + { + if (remoteEpisode.ParsedEpisodeInfo.FullSeason) + { + _logger.Debug("Full season result during single episode search, skipping."); + return Decision.Reject("Full season pack"); + } + + return Decision.Accept(); + } } } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index dd5e1d313..cbd38784b 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -363,13 +363,13 @@ namespace NzbDrone.Core.Parser foreach (var absoluteEpisodeNumber in parsedEpisodeInfo.AbsoluteEpisodeNumbers) { - Episode episode = null; + var episodes = new List(); if (parsedEpisodeInfo.Special) { - episode = _episodeService.FindEpisode(series.Id, 0, absoluteEpisodeNumber); + var episode = _episodeService.FindEpisode(series.Id, 0, absoluteEpisodeNumber); + episodes.AddIfNotNull(episode); } - else if (sceneSource) { // Is there a reason why we excluded season 1 from this handling before? @@ -377,31 +377,33 @@ namespace NzbDrone.Core.Parser // If this needs to be reverted tests will need to be added if (sceneSeasonNumber.HasValue) { - var episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); + episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); - if (episodes.Count == 1) + if (episodes.Empty()) { - episode = episodes.First(); - } - - if (episode == null) - { - episode = _episodeService.FindEpisode(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); + var episode = _episodeService.FindEpisode(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber); + episodes.AddIfNotNull(episode); } } - else { - episode = _episodeService.FindEpisodeBySceneNumbering(series.Id, absoluteEpisodeNumber); + episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, absoluteEpisodeNumber); + + // Don't allow multiple results without a scene name mapping. + if (episodes.Count > 1) + { + episodes.Clear(); + } } } - if (episode == null) + if (episodes.Empty()) { - episode = _episodeService.FindEpisode(series.Id, absoluteEpisodeNumber); + var episode = _episodeService.FindEpisode(series.Id, absoluteEpisodeNumber); + episodes.AddIfNotNull(episode); } - if (episode != null) + foreach (var episode in episodes) { _logger.Debug("Using absolute episode number {0} for: {1} - TVDB: {2}x{3:00}", absoluteEpisodeNumber, diff --git a/src/NzbDrone.Core/Tv/EpisodeRepository.cs b/src/NzbDrone.Core/Tv/EpisodeRepository.cs index 5a1f413ad..a0602e307 100644 --- a/src/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/src/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Tv PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec, bool includeSpecials); PagingSpec EpisodesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff, bool includeSpecials); List FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber); - Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); + List FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); List EpisodesBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored); void SetMonitoredFlat(Episode episode, bool monitored); void SetMonitoredBySeason(int seriesId, int seasonNumber, bool monitored); @@ -134,21 +134,15 @@ namespace NzbDrone.Core.Tv { return Query.Where(s => s.SeriesId == seriesId) .AndWhere(s => s.SceneSeasonNumber == seasonNumber) - .AndWhere(s => s.SceneEpisodeNumber == episodeNumber); + .AndWhere(s => s.SceneEpisodeNumber == episodeNumber) + .ToList(); } - public Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber) + public List FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber) { - var episodes = Query.Where(s => s.SeriesId == seriesId) + return Query.Where(s => s.SeriesId == seriesId) .AndWhere(s => s.SceneAbsoluteEpisodeNumber == sceneAbsoluteEpisodeNumber) .ToList(); - - if (episodes.Empty() || episodes.Count > 1) - { - return null; - } - - return episodes.Single(); } public List EpisodesBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored) diff --git a/src/NzbDrone.Core/Tv/EpisodeService.cs b/src/NzbDrone.Core/Tv/EpisodeService.cs index 8f6dd9a67..acb756bd8 100644 --- a/src/NzbDrone.Core/Tv/EpisodeService.cs +++ b/src/NzbDrone.Core/Tv/EpisodeService.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Tv Episode FindEpisode(int seriesId, int absoluteEpisodeNumber); Episode FindEpisodeByTitle(int seriesId, int seasonNumber, string releaseTitle); List FindEpisodesBySceneNumbering(int seriesId, int seasonNumber, int episodeNumber); - Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); + List FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber); Episode GetEpisode(int seriesId, string date); Episode FindEpisode(int seriesId, string date); List GetEpisodeBySeries(int seriesId); @@ -78,9 +78,9 @@ namespace NzbDrone.Core.Tv return _episodeRepository.FindEpisodesBySceneNumbering(seriesId, seasonNumber, episodeNumber); } - public Episode FindEpisodeBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber) + public List FindEpisodesBySceneNumbering(int seriesId, int sceneAbsoluteEpisodeNumber) { - return _episodeRepository.FindEpisodeBySceneNumbering(seriesId, sceneAbsoluteEpisodeNumber); + return _episodeRepository.FindEpisodesBySceneNumbering(seriesId, sceneAbsoluteEpisodeNumber); } public Episode GetEpisode(int seriesId, string date)