Merge pull request #69 from Taloth/xemseasonsearch
Season Search now correctly uses scene numbering
This commit is contained in:
commit
206b69524d
|
@ -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,49 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
|
|||
|
||||
result.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_only_include_reports_for_requested_episodes()
|
||||
{
|
||||
var series = Builder<Series>.CreateNew().Build();
|
||||
|
||||
var episodes = Builder<Episode>.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<IParsingService>()
|
||||
.Setup(v => v.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<int>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Returns<ParsedEpisodeInfo, int, SearchCriteriaBase>((p,id,c) =>
|
||||
new RemoteEpisode
|
||||
{
|
||||
DownloadAllowed = true,
|
||||
ParsedEpisodeInfo = p,
|
||||
Series = series,
|
||||
Episodes = episodes.Where(v => v.SceneEpisodeNumber == p.EpisodeNumbers.First()).ToList()
|
||||
});
|
||||
|
||||
Mocker.SetConstant<IEnumerable<IDecisionEngineSpecification>>(new List<IDecisionEngineSpecification>
|
||||
{
|
||||
Mocker.Resolve<NzbDrone.Core.DecisionEngine.Specifications.Search.EpisodeRequestedSpecification>()
|
||||
});
|
||||
|
||||
var decisions = Subject.GetSearchDecision(reports, criteria);
|
||||
|
||||
var approvedDecisions = decisions.Where(v => v.Approved).ToList();
|
||||
|
||||
approvedDecisions.Count.Should().Be(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
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 FluentAssertions;
|
||||
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<NzbSearchService>
|
||||
{
|
||||
private Series _xemSeries;
|
||||
private List<Episode> _xemEpisodes;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
var indexer = Mocker.GetMock<IIndexer>();
|
||||
indexer.SetupGet(s => s.SupportsSearching).Returns(true);
|
||||
|
||||
Mocker.GetMock<IIndexerFactory>()
|
||||
.Setup(s => s.GetAvailableProviders())
|
||||
.Returns(new List<IIndexer> { indexer.Object });
|
||||
|
||||
Mocker.GetMock<NzbDrone.Core.DecisionEngine.IMakeDownloadDecision>()
|
||||
.Setup(s => s.GetSearchDecision(It.IsAny<List<Parser.Model.ReleaseInfo>>(), It.IsAny<SearchCriteriaBase>()))
|
||||
.Returns(new List<NzbDrone.Core.DecisionEngine.Specifications.DownloadDecision>());
|
||||
|
||||
_xemSeries = Builder<Series>.CreateNew()
|
||||
.With(v => v.UseSceneNumbering = true)
|
||||
.Build();
|
||||
|
||||
_xemEpisodes = new List<Episode>();
|
||||
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Setup(v => v.GetSeries(_xemSeries.Id))
|
||||
.Returns(_xemSeries);
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Setup(v => v.GetEpisodesBySeason(_xemSeries.Id, It.IsAny<int>()))
|
||||
.Returns<int, int>((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList());
|
||||
}
|
||||
|
||||
private void WithEpisode(int seasonNumber, int episodeNumber, int sceneSeasonNumber, int sceneEpisodeNumber)
|
||||
{
|
||||
var episode = Builder<Episode>.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);
|
||||
|
||||
// Season 7+ maps normally, so no mapping specified.
|
||||
WithEpisode(7, 1, 0, 0);
|
||||
WithEpisode(7, 2, 0, 0);
|
||||
}
|
||||
|
||||
private List<SearchCriteriaBase> WatchForSearchCriteria()
|
||||
{
|
||||
List<SearchCriteriaBase> result = new List<SearchCriteriaBase>();
|
||||
|
||||
Mocker.GetMock<IFetchFeedFromIndexers>()
|
||||
.Setup(v => v.Fetch(It.IsAny<IIndexer>(), It.IsAny<SingleEpisodeSearchCriteria>()))
|
||||
.Callback<IIndexer, SingleEpisodeSearchCriteria>((i, s) => result.Add(s))
|
||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
||||
|
||||
Mocker.GetMock<IFetchFeedFromIndexers>()
|
||||
.Setup(v => v.Fetch(It.IsAny<IIndexer>(), It.IsAny<SeasonSearchCriteria>()))
|
||||
.Callback<IIndexer, SeasonSearchCriteria>((i, s) => result.Add(s))
|
||||
.Returns(new List<Parser.Model.ReleaseInfo>());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_episodesearch()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.EpisodeSearch(_xemEpisodes.First());
|
||||
|
||||
var criteria = allCriteria.OfType<SingleEpisodeSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].SeasonNumber.Should().Be(2);
|
||||
criteria[0].EpisodeNumber.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 1);
|
||||
|
||||
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].SeasonNumber.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch_should_search_multiple_seasons()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 2);
|
||||
|
||||
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(2);
|
||||
criteria[0].SeasonNumber.Should().Be(3);
|
||||
criteria[1].SeasonNumber.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch_should_search_single_episode_if_possible()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 4);
|
||||
|
||||
var criteria1 = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
var criteria2 = allCriteria.OfType<SingleEpisodeSearchCriteria>().ToList();
|
||||
|
||||
criteria1.Count.Should().Be(1);
|
||||
criteria1[0].SeasonNumber.Should().Be(5);
|
||||
|
||||
criteria2.Count.Should().Be(1);
|
||||
criteria2[0].SeasonNumber.Should().Be(6);
|
||||
criteria2[0].EpisodeNumber.Should().Be(11);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void scene_seasonsearch_should_use_seasonnumber_if_no_scene_number_is_available()
|
||||
{
|
||||
WithEpisodes();
|
||||
|
||||
var allCriteria = WatchForSearchCriteria();
|
||||
|
||||
Subject.SeasonSearch(_xemSeries.Id, 7);
|
||||
|
||||
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
|
||||
|
||||
criteria.Count.Should().Be(1);
|
||||
criteria[0].SeasonNumber.Should().Be(7);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,6 +138,7 @@
|
|||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\CleanupDuplicateMetadataFilesFixture.cs" />
|
||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasksFixture.cs" />
|
||||
<Compile Include="IndexerSearchTests\NzbSearchServiceFixture.cs" />
|
||||
<Compile Include="IndexerSearchTests\SearchDefinitionFixture.cs" />
|
||||
<Compile Include="IndexerTests\BasicRssParserFixture.cs" />
|
||||
<Compile Include="IndexerTests\IndexerServiceFixture.cs" />
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class EpisodeRequestedSpecification : IDecisionEngineSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EpisodeRequestedSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode wasn't requested";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
if (searchCriteria == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var criteriaEpisodes = searchCriteria.Episodes.Select(v => v.Id).ToList();
|
||||
var remoteEpisodes = remoteEpisode.Episodes.Select(v => v.Id).ToList();
|
||||
if (!criteriaEpisodes.Intersect(remoteEpisodes).Any())
|
||||
{
|
||||
_logger.Debug("Release rejected since the episode wasn't requested: {0}", remoteEpisode.ParsedEpisodeInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,10 +138,53 @@ namespace NzbDrone.Core.IndexerSearch
|
|||
return SearchSpecial(series, episodes);
|
||||
}
|
||||
|
||||
List<DownloadDecision> downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
if (series.UseSceneNumbering)
|
||||
{
|
||||
var sceneSeasonGroups = episodes.GroupBy(v =>
|
||||
{
|
||||
if (v.SceneSeasonNumber == 0 && v.SceneEpisodeNumber == 0)
|
||||
return v.SeasonNumber;
|
||||
else
|
||||
return v.SceneSeasonNumber;
|
||||
}).Distinct();
|
||||
|
||||
foreach (var sceneSeasonEpisodes in sceneSeasonGroups)
|
||||
{
|
||||
if (sceneSeasonEpisodes.Count() == 1)
|
||||
{
|
||||
var episode = sceneSeasonEpisodes.First();
|
||||
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, sceneSeasonEpisodes.ToList());
|
||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
||||
if (episode.SceneSeasonNumber == 0 && episode.SceneEpisodeNumber == 0)
|
||||
searchSpec.EpisodeNumber = episode.EpisodeNumber;
|
||||
else
|
||||
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber;
|
||||
|
||||
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchCriteria>(series, sceneSeasonEpisodes.ToList());
|
||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
||||
|
||||
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchCriteria>(series, episodes);
|
||||
searchSpec.SeasonNumber = seasonNumber;
|
||||
|
||||
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
var decisions = Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
|
||||
return downloadDecisions;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(Series series, List<Episode> episodes) where TSpec : SearchCriteriaBase, new()
|
||||
|
|
|
@ -217,6 +217,7 @@
|
|||
<Compile Include="DecisionEngine\Specifications\NotRestrictedReleaseSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\NotSampleSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\RssSync\ProperSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\EpisodeRequestedSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\SeriesSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\SeasonMatchSpecification.cs" />
|
||||
<Compile Include="DecisionEngine\Specifications\Search\DailyEpisodeMatchSpecification.cs" />
|
||||
|
|
Loading…
Reference in New Issue