Merge branch 'searching'
This commit is contained in:
commit
52e49ce879
|
@ -10,7 +10,7 @@ using NzbDrone.Test.Common.AutoMoq;
|
||||||
namespace NzbDrone.Core.Test.JobTests
|
namespace NzbDrone.Core.Test.JobTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class SearchJobTest:CoreTest
|
public class EpisodeSearchJobTest:CoreTest
|
||||||
{
|
{
|
||||||
[TestCase(0)]
|
[TestCase(0)]
|
||||||
[TestCase(-1)]
|
[TestCase(-1)]
|
|
@ -142,7 +142,16 @@
|
||||||
<Compile Include="IndexerTests\NzbxFixture.cs" />
|
<Compile Include="IndexerTests\NzbxFixture.cs" />
|
||||||
<Compile Include="JobTests\RenameSeasonJobFixture.cs" />
|
<Compile Include="JobTests\RenameSeasonJobFixture.cs" />
|
||||||
<Compile Include="ProviderTests\RootDirProviderTests\FreeSpaceOnDrivesFixture.cs" />
|
<Compile Include="ProviderTests\RootDirProviderTests\FreeSpaceOnDrivesFixture.cs" />
|
||||||
<Compile Include="ProviderTests\SearchProviderTests\GetSeriesTitleFixture.cs" />
|
<Compile Include="ProviderTests\SearchTests\ProcessResultsFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\DailyEpisodeSearchTests\CheckReportFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\EpisodeSearchTests\CheckReportFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\EpisodeSearchTests\PerformSearchFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\GetSearchTitleFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\DailyEpisodeSearchTests\PerformSearchFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\PartialSeasonSearchTests\CheckReportFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\PartialSeasonSearchTests\PerformSearchFixture.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\PerformSearchTestBase.cs" />
|
||||||
|
<Compile Include="ProviderTests\SearchTests\TestSearch.cs" />
|
||||||
<Compile Include="ProviderTests\TvRageMappingProviderTests\FindMatchingTvRageSeriesFixture.cs" />
|
<Compile Include="ProviderTests\TvRageMappingProviderTests\FindMatchingTvRageSeriesFixture.cs" />
|
||||||
<Compile Include="ProviderTests\TvRageMappingProviderTests\ProcessResultsFixture.cs" />
|
<Compile Include="ProviderTests\TvRageMappingProviderTests\ProcessResultsFixture.cs" />
|
||||||
<Compile Include="ProviderTests\TvRageProviderTests\GetSeriesFixture.cs" />
|
<Compile Include="ProviderTests\TvRageProviderTests\GetSeriesFixture.cs" />
|
||||||
|
@ -190,17 +199,13 @@
|
||||||
<Compile Include="ProviderTests\ReferenceDataProviderTest.cs" />
|
<Compile Include="ProviderTests\ReferenceDataProviderTest.cs" />
|
||||||
<Compile Include="ProviderTests\NotificationProviderTests\NotificationProviderFixture.cs" />
|
<Compile Include="ProviderTests\NotificationProviderTests\NotificationProviderFixture.cs" />
|
||||||
<Compile Include="ProviderTests\DownloadClientTests\SabProviderTests\QueueFixture.cs" />
|
<Compile Include="ProviderTests\DownloadClientTests\SabProviderTests\QueueFixture.cs" />
|
||||||
<Compile Include="ProviderTests\SearchProviderTests\ProcessDailySearchResultsFixture.cs" />
|
|
||||||
<Compile Include="ProviderTests\SearchProviderTests\SearchFixture.cs" />
|
|
||||||
<Compile Include="ProviderTests\SearchProviderTests\PerformSearchFixture.cs" />
|
|
||||||
<Compile Include="ProviderTests\SearchProviderTests\ProcessSearchResultsFixture.cs" />
|
|
||||||
<Compile Include="ProviderTests\NewznabProviderTest.cs" />
|
<Compile Include="ProviderTests\NewznabProviderTest.cs" />
|
||||||
<Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceTest.cs" />
|
<Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceTest.cs" />
|
||||||
<Compile Include="ProviderTests\ProwlProviderTest.cs" />
|
<Compile Include="ProviderTests\ProwlProviderTest.cs" />
|
||||||
<Compile Include="ProviderTests\GrowlProviderTest.cs" />
|
<Compile Include="ProviderTests\GrowlProviderTest.cs" />
|
||||||
<Compile Include="ProviderTests\DiskProviderTests\ExtractArchiveFixture.cs" />
|
<Compile Include="ProviderTests\DiskProviderTests\ExtractArchiveFixture.cs" />
|
||||||
<Compile Include="ProviderTests\PostDownloadProviderTests\GetFolderNameWithStatusFixture.cs" />
|
<Compile Include="ProviderTests\PostDownloadProviderTests\GetFolderNameWithStatusFixture.cs" />
|
||||||
<Compile Include="JobTests\SearchJobTest.cs" />
|
<Compile Include="JobTests\EpisodeSearchJobTest.cs" />
|
||||||
<Compile Include="ProviderTests\PostDownloadProviderTests\ProcessDownloadFixture.cs" />
|
<Compile Include="ProviderTests\PostDownloadProviderTests\ProcessDownloadFixture.cs" />
|
||||||
<Compile Include="ProviderTests\JobProviderTests\TestJobs.cs" />
|
<Compile Include="ProviderTests\JobProviderTests\TestJobs.cs" />
|
||||||
<Compile Include="JobTests\AppUpdateJobFixture.cs" />
|
<Compile Include="JobTests\AppUpdateJobFixture.cs" />
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.Providers;
|
|
||||||
using NzbDrone.Core.Repository;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
|
||||||
{
|
|
||||||
public class GetSeriesTitleFixture : TestBase
|
|
||||||
{
|
|
||||||
private Series _series;
|
|
||||||
private const string SCENE_NAME = "Scandal";
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_series = Builder<Series>
|
|
||||||
.CreateNew()
|
|
||||||
.With(s => s.Title = "Scandal (2012)")
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithSceneName()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<SceneMappingProvider>()
|
|
||||||
.Setup(s => s.GetSceneName(_series.SeriesId))
|
|
||||||
.Returns("Scandal");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_scene_name_when_sceneName_is_available()
|
|
||||||
{
|
|
||||||
WithSceneName();
|
|
||||||
|
|
||||||
Mocker.Resolve<SearchProvider>().GetSeriesTitle(_series).Should().Be(SCENE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_return_seriesTitle_when_sceneName_is_not_available()
|
|
||||||
{
|
|
||||||
Mocker.Resolve<SearchProvider>().GetSeriesTitle(_series).Should().Be(_series.Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase("Mike & Molly", "Mike and Molly")]
|
|
||||||
[TestCase("Franklin & Bash", "Franklin and Bash")]
|
|
||||||
[TestCase("Law & Order", "Law and Order")]
|
|
||||||
public void should_replace_ampersand_with_and_when_returning_title(string seriesTitle, string expectedTitle)
|
|
||||||
{
|
|
||||||
_series.Title = seriesTitle;
|
|
||||||
|
|
||||||
Mocker.Resolve<SearchProvider>().GetSeriesTitle(_series).Should().Be(expectedTitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,282 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.Model;
|
|
||||||
using NzbDrone.Core.Providers;
|
|
||||||
using NzbDrone.Core.Providers.Indexer;
|
|
||||||
using NzbDrone.Core.Repository;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
|
||||||
{
|
|
||||||
public class PerformSearchFixture : CoreTest
|
|
||||||
{
|
|
||||||
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<IndexerBase> _episodeIndexer1;
|
|
||||||
private Mock<IndexerBase> _episodeIndexer2;
|
|
||||||
private Mock<IndexerBase> _brokenIndexer;
|
|
||||||
private Mock<IndexerBase> _nullIndexer;
|
|
||||||
|
|
||||||
private List<IndexerBase> _indexers;
|
|
||||||
|
|
||||||
private Series _series;
|
|
||||||
private IList<Episode> _episodes;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(PARSE_RESULT_COUNT)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_series = Builder<Series>.CreateNew()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_episodes = Builder<Episode>.CreateListOfSize(1)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
BuildIndexers(parseResults);
|
|
||||||
|
|
||||||
_indexers = new List<IndexerBase> { _episodeIndexer1.Object, _episodeIndexer2.Object };
|
|
||||||
|
|
||||||
Mocker.GetMock<IndexerProvider>()
|
|
||||||
.Setup(c => c.GetEnabledIndexers())
|
|
||||||
.Returns(_indexers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildIndexers(IList<EpisodeParseResult> parseResults)
|
|
||||||
{
|
|
||||||
_episodeIndexer1 = new Mock<IndexerBase>();
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchSeason(It.IsAny<string>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer1.Setup(s => s.Name).Returns("Episode Indexer 1");
|
|
||||||
|
|
||||||
_episodeIndexer2 = new Mock<IndexerBase>();
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchSeason(It.IsAny<string>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer2.Setup(s => s.Name).Returns("Episode Indexer 2");
|
|
||||||
|
|
||||||
_brokenIndexer = new Mock<IndexerBase>();
|
|
||||||
_brokenIndexer.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Throws(new Exception());
|
|
||||||
_brokenIndexer.Setup(s => s.Name).Returns("Broken Indexer");
|
|
||||||
|
|
||||||
_nullIndexer = new Mock<IndexerBase>();
|
|
||||||
_nullIndexer.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns<List<EpisodeParseResult>>(null);
|
|
||||||
_nullIndexer.Setup(s => s.Name).Returns("Null Indexer");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithTwoGoodOneBrokenIndexer()
|
|
||||||
{
|
|
||||||
_indexers = new List<IndexerBase> { _episodeIndexer1.Object, _brokenIndexer.Object, _episodeIndexer2.Object };
|
|
||||||
|
|
||||||
Mocker.GetMock<IndexerProvider>()
|
|
||||||
.Setup(c => c.GetEnabledIndexers())
|
|
||||||
.Returns(_indexers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithNullIndexers()
|
|
||||||
{
|
|
||||||
_indexers = new List<IndexerBase> { _nullIndexer.Object, _nullIndexer.Object };
|
|
||||||
|
|
||||||
Mocker.GetMock<IndexerProvider>()
|
|
||||||
.Setup(c => c.GetEnabledIndexers())
|
|
||||||
.Returns(_indexers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithSceneName()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<SceneMappingProvider>()
|
|
||||||
.Setup(s => s.GetSceneName(_series.SeriesId)).Returns(SCENE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void With30Episodes()
|
|
||||||
{
|
|
||||||
_episodes = Builder<Episode>.CreateListOfSize(30)
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithNullEpisodes()
|
|
||||||
{
|
|
||||||
_episodes = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchEpisode(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchDailyEpisode(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchDailyEpisode(_series.Title, It.IsAny<DateTime>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchDailyEpisode(_series.Title, It.IsAny<DateTime>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchEpisodeWithSceneName(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchEpisodeBrokenIndexer(Times times)
|
|
||||||
{
|
|
||||||
_brokenIndexer.Verify(v => v.FetchEpisode(It.IsAny<string>(), SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchPartialSeason(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchSeason(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchSeason(_series.Title, SEASON_NUMBER), times);
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchSeason(_series.Title, SEASON_NUMBER), times);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PerformSearch_should_search_all_enabled_providers()
|
|
||||||
{
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformEpisodeSearch(_series, SEASON_NUMBER, _episodes.First().EpisodeNumber);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
VerifyFetchEpisode(Times.Once());
|
|
||||||
result.Should().HaveCount(PARSE_RESULT_COUNT * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void broken_indexer_should_not_block_other_indexers()
|
|
||||||
{
|
|
||||||
//Setup
|
|
||||||
WithTwoGoodOneBrokenIndexer();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(PARSE_RESULT_COUNT * 2);
|
|
||||||
|
|
||||||
VerifyFetchEpisode(Times.Once());
|
|
||||||
VerifyFetchEpisodeBrokenIndexer(Times.Once());
|
|
||||||
|
|
||||||
Mocker.GetMock<SceneMappingProvider>().Verify(c => c.GetSceneName(_series.SeriesId),
|
|
||||||
Times.Once());
|
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PerformSearch_for_episode_should_call_FetchEpisode()
|
|
||||||
{
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(PARSE_RESULT_COUNT * 2);
|
|
||||||
|
|
||||||
VerifyFetchEpisode(Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PerformSearch_for_daily_episode_should_call_FetchEpisode()
|
|
||||||
{
|
|
||||||
//Setup
|
|
||||||
_series.IsDaily = true;
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformDailyEpisodeSearch(_series, _episodes.First());
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(PARSE_RESULT_COUNT * 2);
|
|
||||||
|
|
||||||
VerifyFetchDailyEpisode(Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PerformSearch_for_partial_season_should_call_FetchPartialSeason()
|
|
||||||
{
|
|
||||||
With30Episodes();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformPartialSeasonSearch(_series, SEASON_NUMBER, new List<int>{0, 1, 2, 3});
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(80);
|
|
||||||
VerifyFetchPartialSeason(Times.Exactly(4));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PerformSearch_for_season_should_call_FetchSeason()
|
|
||||||
{
|
|
||||||
WithNullEpisodes();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformSeasonSearch(_series, SEASON_NUMBER);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(20);
|
|
||||||
VerifyFetchSeason(Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PerformSearch_should_return_empty_list_when_results_from_indexers_are_null()
|
|
||||||
{
|
|
||||||
//Setup
|
|
||||||
WithNullIndexers();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(0);
|
|
||||||
ExceptionVerification.ExpectedErrors(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void should_use_scene_name_to_search_for_episode_when_avilable()
|
|
||||||
{
|
|
||||||
WithSceneName();
|
|
||||||
|
|
||||||
Mocker.Resolve<SearchProvider>().PerformEpisodeSearch(_series, SEASON_NUMBER, EPISODE_NUMBER);
|
|
||||||
|
|
||||||
VerifyFetchEpisodeWithSceneName(Times.Once());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,283 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
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.DecisionEngine;
|
|
||||||
using NzbDrone.Core.Providers.Indexer;
|
|
||||||
using NzbDrone.Core.Repository;
|
|
||||||
using NzbDrone.Core.Repository.Quality;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
using NzbDrone.Test.Common.AutoMoq;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
public class ProcessDailySearchResultsFixture : CoreTest
|
|
||||||
{
|
|
||||||
private Series _matchingSeries = null;
|
|
||||||
private Series _mismatchedSeries = null;
|
|
||||||
private Series _nullSeries = null;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void setup()
|
|
||||||
{
|
|
||||||
_matchingSeries = Builder<Series>.CreateNew()
|
|
||||||
.With(s => s.SeriesId = 79488)
|
|
||||||
.With(s => s.Title = "30 Rock")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_mismatchedSeries = Builder<Series>.CreateNew()
|
|
||||||
.With(s => s.SeriesId = 12345)
|
|
||||||
.With(s => s.Title = "Not 30 Rock")
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithMatchingSeries()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<SeriesProvider>()
|
|
||||||
.Setup(s => s.FindSeries(It.IsAny<string>())).Returns(_matchingSeries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithMisMatchedSeries()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<SeriesProvider>()
|
|
||||||
.Setup(s => s.FindSeries(It.IsAny<string>())).Returns(_mismatchedSeries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithNullSeries()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<SeriesProvider>()
|
|
||||||
.Setup(s => s.FindSeries(It.IsAny<string>())).Returns(_nullSeries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithSuccessfulDownload()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<DownloadProvider>()
|
|
||||||
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>()))
|
|
||||||
.Returns(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithFailingDownload()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<DownloadProvider>()
|
|
||||||
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>()))
|
|
||||||
.Returns(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithQualityNeeded()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>()
|
|
||||||
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()))
|
|
||||||
.Returns(ReportRejectionType.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithQualityNotNeeded()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>()
|
|
||||||
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()))
|
|
||||||
.Returns(ReportRejectionType.ExistingQualityIsEqualOrBetter);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_higher_quality_should_be_called_first()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(c => c.AirDate = DateTime.Today)
|
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
|
||||||
.Random(1)
|
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
WithSuccessfulDownload();
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>()
|
|
||||||
.Setup(s => s.IsSatisfiedBy(It.Is<EpisodeParseResult>(d => d.Quality.Quality == QualityTypes.Bluray1080p)))
|
|
||||||
.Returns(ReportRejectionType.None);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().Contain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Once());
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_when_quality_is_not_needed_should_check_the_rest()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(c => c.AirDate = DateTime.Today)
|
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
WithQualityNotNeeded();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().NotContain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Exactly(5));
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_skip_if_series_is_null()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(e => e.AirDate = DateTime.Today)
|
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithNullSeries();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().NotContain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_skip_if_series_is_mismatched()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(e => e.AirDate = DateTime.Today)
|
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMisMatchedSeries();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().NotContain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_return_after_successful_download()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
|
||||||
.All()
|
|
||||||
.With(e => e.AirDate = DateTime.Today)
|
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
WithQualityNeeded();
|
|
||||||
WithSuccessfulDownload();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().Contain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Once());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_try_next_if_download_fails()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
|
||||||
.All()
|
|
||||||
.With(e => e.AirDate = DateTime.Today)
|
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
|
||||||
.TheLast(1)
|
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.SDTV, true))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
WithQualityNeeded();
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>()
|
|
||||||
.Setup(s => s.DownloadReport(It.Is<EpisodeParseResult>(d => d.Quality.Quality == QualityTypes.DVD)))
|
|
||||||
.Returns(false);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>()
|
|
||||||
.Setup(s => s.DownloadReport(It.Is<EpisodeParseResult>(d => d.Quality.Quality == QualityTypes.SDTV)))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().Contain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Exactly(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_skip_if_parseResult_does_not_have_airdate()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(e => e.AirDate = null)
|
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().NotContain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_skip_if_parseResult_airdate_does_not_match()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(e => e.AirDate = DateTime.Today.AddDays(10))
|
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().NotContain(n => n.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,210 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using FizzWare.NBuilder;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Moq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.Model;
|
|
||||||
using NzbDrone.Core.Providers;
|
|
||||||
using NzbDrone.Core.Providers.DecisionEngine;
|
|
||||||
using NzbDrone.Core.Providers.Indexer;
|
|
||||||
using NzbDrone.Core.Repository;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
|
||||||
using NzbDrone.Test.Common;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
|
||||||
{
|
|
||||||
public class SearchFixture : CoreTest
|
|
||||||
{
|
|
||||||
private const string SCENE_NAME = "Scene Name";
|
|
||||||
private const int SEASON_NUMBER = 5;
|
|
||||||
private const int PARSE_RESULT_COUNT = 10;
|
|
||||||
|
|
||||||
private Mock<IndexerBase> _episodeIndexer1;
|
|
||||||
private Mock<IndexerBase> _episodeIndexer2;
|
|
||||||
private Mock<IndexerBase> _brokenIndexer;
|
|
||||||
private Mock<IndexerBase> _nullIndexer;
|
|
||||||
|
|
||||||
private List<IndexerBase> _indexers;
|
|
||||||
|
|
||||||
private Series _series;
|
|
||||||
private IList<Episode> _episodes;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(PARSE_RESULT_COUNT)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_series = Builder<Series>.CreateNew()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_episodes = Builder<Episode>.CreateListOfSize(1)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
BuildIndexers(parseResults);
|
|
||||||
|
|
||||||
_indexers = new List<IndexerBase> { _episodeIndexer1.Object, _episodeIndexer2.Object };
|
|
||||||
|
|
||||||
Mocker.GetMock<IndexerProvider>()
|
|
||||||
.Setup(c => c.GetEnabledIndexers())
|
|
||||||
.Returns(_indexers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildIndexers(IList<EpisodeParseResult> parseResults)
|
|
||||||
{
|
|
||||||
_episodeIndexer1 = new Mock<IndexerBase>();
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchSeason(It.IsAny<string>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer1.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
|
|
||||||
|
|
||||||
_episodeIndexer2 = new Mock<IndexerBase>();
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchSeason(It.IsAny<string>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
_episodeIndexer2.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns(parseResults);
|
|
||||||
|
|
||||||
_brokenIndexer = new Mock<IndexerBase>();
|
|
||||||
_brokenIndexer.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Throws(new Exception());
|
|
||||||
|
|
||||||
_nullIndexer = new Mock<IndexerBase>();
|
|
||||||
_nullIndexer.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
|
||||||
.Returns<List<EpisodeParseResult>>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithTwoGoodOneBrokenIndexer()
|
|
||||||
{
|
|
||||||
_indexers = new List<IndexerBase> { _episodeIndexer1.Object, _brokenIndexer.Object, _episodeIndexer2.Object };
|
|
||||||
|
|
||||||
Mocker.GetMock<IndexerProvider>()
|
|
||||||
.Setup(c => c.GetEnabledIndexers())
|
|
||||||
.Returns(_indexers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithNullIndexers()
|
|
||||||
{
|
|
||||||
_indexers = new List<IndexerBase> { _nullIndexer.Object, _nullIndexer.Object };
|
|
||||||
|
|
||||||
Mocker.GetMock<IndexerProvider>()
|
|
||||||
.Setup(c => c.GetEnabledIndexers())
|
|
||||||
.Returns(_indexers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithSceneName()
|
|
||||||
{
|
|
||||||
Mocker.GetMock<SceneMappingProvider>()
|
|
||||||
.Setup(s => s.GetSceneName(_series.SeriesId)).Returns(SCENE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void With30Episodes()
|
|
||||||
{
|
|
||||||
_episodes = Builder<Episode>.CreateListOfSize(30)
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WithNullEpisodes()
|
|
||||||
{
|
|
||||||
_episodes = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchEpisode(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchEpisodeWithSceneName(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchEpisodeBrokenIndexer(Times times)
|
|
||||||
{
|
|
||||||
_brokenIndexer.Verify(v => v.FetchEpisode(It.IsAny<string>(), SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchPartialSeason(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
|
|
||||||
_episodeIndexer2.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny<int>())
|
|
||||||
, times);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyFetchSeason(Times times)
|
|
||||||
{
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchSeason(_series.Title, SEASON_NUMBER), times);
|
|
||||||
_episodeIndexer1.Verify(v => v.FetchSeason(_series.Title, SEASON_NUMBER), times);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void SeasonSearch_should_skip_daily_series()
|
|
||||||
{
|
|
||||||
//Setup
|
|
||||||
_series.IsDaily = true;
|
|
||||||
|
|
||||||
Mocker.GetMock<SeriesProvider>().Setup(s => s.GetSeries(1)).Returns(_series);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().SeasonSearch(MockNotification, _series.SeriesId, 1);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().BeEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void PartialSeasonSearch_should_skip_daily_series()
|
|
||||||
{
|
|
||||||
//Setup
|
|
||||||
_series.IsDaily = true;
|
|
||||||
|
|
||||||
Mocker.GetMock<SeriesProvider>().Setup(s => s.GetSeries(1)).Returns(_series);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().PartialSeasonSearch(MockNotification, _series.SeriesId, 1);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().BeEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void EpisodeSearch_should_skip_if_air_date_is_null_and_is_a_daily_series()
|
|
||||||
{
|
|
||||||
//Setup
|
|
||||||
_series.IsDaily = true;
|
|
||||||
var episode = _episodes.First();
|
|
||||||
episode.AirDate = null;
|
|
||||||
episode.Series = _series;
|
|
||||||
|
|
||||||
Mocker.GetMock<UpgradePossibleSpecification>().Setup(s => s.IsSatisfiedBy(It.IsAny<Episode>()))
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
Mocker.GetMock<EpisodeProvider>().Setup(s => s.GetEpisode(episode.EpisodeId))
|
|
||||||
.Returns(episode);
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().EpisodeSearch(MockNotification, episode.EpisodeId);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().BeFalse();
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests.DailyEpisodeSearchTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckReportFixture : TestBase
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
private Episode _episode;
|
||||||
|
private EpisodeParseResult _episodeParseResult;
|
||||||
|
private SearchHistoryItem _searchHistoryItem;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>
|
||||||
|
.CreateNew()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episode = Builder<Episode>
|
||||||
|
.CreateNew()
|
||||||
|
.With(e => e.SeriesId = _series.SeriesId)
|
||||||
|
.With(e => e.Series = _series)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episodeParseResult = Builder<EpisodeParseResult>
|
||||||
|
.CreateNew()
|
||||||
|
.With(p => p.AirDate = _episode.AirDate)
|
||||||
|
.With(p => p.Episodes = new List<Episode> { _episode })
|
||||||
|
.With(p => p.Series = _series)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_searchHistoryItem = new SearchHistoryItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_WrongEpisode_is_parseResult_doesnt_have_airdate()
|
||||||
|
{
|
||||||
|
_episodeParseResult.AirDate = null;
|
||||||
|
|
||||||
|
Mocker.Resolve<DailyEpisodeSearch>()
|
||||||
|
.CheckReport(_series, new {Episode = _episode}, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.WrongEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_WrongEpisode_is_parseResult_airdate_doesnt_match_episode()
|
||||||
|
{
|
||||||
|
_episodeParseResult.AirDate = _episode.AirDate.Value.AddDays(-10);
|
||||||
|
|
||||||
|
Mocker.Resolve<DailyEpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.WrongEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_return_error_when_airDates_match()
|
||||||
|
{
|
||||||
|
Mocker.Resolve<DailyEpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests.DailyEpisodeSearchTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class PerformSearchFixture : PerformSearchTestBase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_throw_if_episode_is_null()
|
||||||
|
{
|
||||||
|
Episode nullEpisode = null;
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
Mocker.Resolve<DailyEpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = nullEpisode }, notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_fetch_results_from_indexers()
|
||||||
|
{
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<DailyEpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new {Episode = _episode}, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_log_error_when_fetching_from_indexer_fails()
|
||||||
|
{
|
||||||
|
WithInvalidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<DailyEpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = _episode }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(0);
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests.EpisodeSearchTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckReportFixture : TestBase
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
private Episode _episode;
|
||||||
|
private EpisodeParseResult _episodeParseResult;
|
||||||
|
private SearchHistoryItem _searchHistoryItem;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>
|
||||||
|
.CreateNew()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episode = Builder<Episode>
|
||||||
|
.CreateNew()
|
||||||
|
.With(e => e.SeriesId = _series.SeriesId)
|
||||||
|
.With(e => e.Series = _series)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episodeParseResult = Builder<EpisodeParseResult>
|
||||||
|
.CreateNew()
|
||||||
|
.With(p => p.SeasonNumber = 1)
|
||||||
|
.With(p => p.EpisodeNumbers = new List<int>{ _episode.EpisodeNumber })
|
||||||
|
.With(p => p.Episodes = new List<Episode> { _episode })
|
||||||
|
.With(p => p.Series = _series)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_searchHistoryItem = new SearchHistoryItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_WrongSeason_when_season_doesnt_match()
|
||||||
|
{
|
||||||
|
_episode.SeasonNumber = 10;
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.CheckReport(_series, new {Episode = _episode}, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.WrongSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_WrongEpisode_when_episode_doesnt_match()
|
||||||
|
{
|
||||||
|
_episode.EpisodeNumber = 10;
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.WrongEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_return_error_when_season_and_episode_match()
|
||||||
|
{
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_WrongSeason_when_season_doesnt_match_for_scene_series()
|
||||||
|
{
|
||||||
|
_series.UseSceneNumbering = true;
|
||||||
|
_episode.SceneSeasonNumber = 10;
|
||||||
|
_episode.SeasonNumber = 10;
|
||||||
|
_episode.EpisodeNumber = 10;
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.WrongSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_WrongEpisode_when_episode_doesnt_match_for_scene_series()
|
||||||
|
{
|
||||||
|
_series.UseSceneNumbering = true;
|
||||||
|
_episode.SceneEpisodeNumber = 10;
|
||||||
|
_episode.SeasonNumber = 10;
|
||||||
|
_episode.EpisodeNumber = 10;
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.WrongEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_return_error_when_season_and_episode_match_for_scene_series()
|
||||||
|
{
|
||||||
|
_series.UseSceneNumbering = true;
|
||||||
|
_episode.SceneSeasonNumber = _episode.SeasonNumber;
|
||||||
|
_episode.SceneEpisodeNumber = _episode.EpisodeNumber;
|
||||||
|
_episode.SeasonNumber = 10;
|
||||||
|
_episode.EpisodeNumber = 10;
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.CheckReport(_series, new { Episode = _episode }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError
|
||||||
|
.Should()
|
||||||
|
.Be(ReportRejectionType.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests.EpisodeSearchTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class PerformSearchFixture : PerformSearchTestBase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_throw_if_episode_is_null()
|
||||||
|
{
|
||||||
|
Episode nullEpisode = null;
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = nullEpisode }, notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_fetch_results_from_indexers()
|
||||||
|
{
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new {Episode = _episode}, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_log_error_when_fetching_from_indexer_fails()
|
||||||
|
{
|
||||||
|
WithInvalidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = _episode }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(0);
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_use_scene_numbering_when_available()
|
||||||
|
{
|
||||||
|
_series.UseSceneNumbering = true;
|
||||||
|
_episode.SceneEpisodeNumber = 5;
|
||||||
|
_episode.SceneSeasonNumber = 10;
|
||||||
|
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = _episode }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(20);
|
||||||
|
|
||||||
|
_indexer1.Verify(v => v.FetchEpisode(_series.Title, 10, 5), Times.Once());
|
||||||
|
_indexer2.Verify(v => v.FetchEpisode(_series.Title, 10, 5), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_use_standard_numbering_when_scene_series_set_but_info_is_not_available()
|
||||||
|
{
|
||||||
|
_series.UseSceneNumbering = true;
|
||||||
|
_episode.SceneEpisodeNumber = 0;
|
||||||
|
_episode.SceneSeasonNumber = 0;
|
||||||
|
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = _episode }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(20);
|
||||||
|
|
||||||
|
_indexer1.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||||
|
_indexer2.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_use_standard_numbering_when_not_scene_series()
|
||||||
|
{
|
||||||
|
_series.UseSceneNumbering = false;
|
||||||
|
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<EpisodeSearch>()
|
||||||
|
.PerformSearch(_series, new { Episode = _episode }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(20);
|
||||||
|
|
||||||
|
_indexer1.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||||
|
_indexer2.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Providers.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests
|
||||||
|
{
|
||||||
|
public class GetSearchTitleFixture : TestBase
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>
|
||||||
|
.CreateNew()
|
||||||
|
.With(s => s.Title = "Hawaii Five-0")
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WithSceneMapping()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<SceneMappingProvider>()
|
||||||
|
.Setup(s => s.GetSceneName(_series.SeriesId))
|
||||||
|
.Returns("Hawaii Five 0 2010");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_series_title_when_there_is_no_scene_mapping()
|
||||||
|
{
|
||||||
|
Mocker.Resolve<TestSearch>().GetSearchTitle(_series, 5)
|
||||||
|
.Should().Be(_series.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_scene_mapping_when_one_exists()
|
||||||
|
{
|
||||||
|
WithSceneMapping();
|
||||||
|
|
||||||
|
Mocker.Resolve<TestSearch>().GetSearchTitle(_series, 5)
|
||||||
|
.Should().Be("Hawaii Five 0 2010");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_replace_ampersand_with_and()
|
||||||
|
{
|
||||||
|
_series.Title = "Franklin & Bash";
|
||||||
|
|
||||||
|
Mocker.Resolve<TestSearch>().GetSearchTitle(_series, 5)
|
||||||
|
.Should().Be("Franklin and Bash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests.PartialSeasonSearchTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckReportFixture : TestBase
|
||||||
|
{
|
||||||
|
private Series _series;
|
||||||
|
private List<Episode> _episodes;
|
||||||
|
private EpisodeParseResult _episodeParseResult;
|
||||||
|
private SearchHistoryItem _searchHistoryItem;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>
|
||||||
|
.CreateNew()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episodes = Builder<Episode>
|
||||||
|
.CreateListOfSize(10)
|
||||||
|
.All()
|
||||||
|
.With(e => e.SeriesId = _series.SeriesId)
|
||||||
|
.With(e => e.Series = _series)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_episodeParseResult = Builder<EpisodeParseResult>
|
||||||
|
.CreateNew()
|
||||||
|
.With(p => p.SeasonNumber = 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_searchHistoryItem = new SearchHistoryItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_wrongSeason_when_season_does_not_match()
|
||||||
|
{
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.CheckReport(_series, new { SeasonNumber = 2, Episodes = _episodes }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError.Should().Be(ReportRejectionType.WrongSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_return_error_when_season_matches()
|
||||||
|
{
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.CheckReport(_series, new { SeasonNumber = 1, Episodes = _episodes }, _episodeParseResult, _searchHistoryItem)
|
||||||
|
.SearchError.Should().Be(ReportRejectionType.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
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.Search;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests.PartialSeasonSearchTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class PerformSearchFixture : PerformSearchTestBase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_throw_if_season_number_is_less_than_zero()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.PerformSearch(_series, new
|
||||||
|
{
|
||||||
|
SeasonNumber = -1,
|
||||||
|
Episodes = new List<Episode>{ new Episode() }
|
||||||
|
}, notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_throw_if_episodes_is_empty()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentException>(() =>
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.PerformSearch(_series, new { SeasonNumber = 10, Episodes = new List<Episode>() }, notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_fetch_results_from_indexers()
|
||||||
|
{
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.PerformSearch(_series, new { SeasonNumber = 1, Episodes = _episodes }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(40);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_log_error_when_fetching_from_indexer_fails()
|
||||||
|
{
|
||||||
|
WithInvalidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.PerformSearch(_series, new { SeasonNumber = 1, Episodes = _episodes }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(0);
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_hit_each_indexer_once_for_each_prefix()
|
||||||
|
{
|
||||||
|
WithValidIndexers();
|
||||||
|
|
||||||
|
Mocker.Resolve<PartialSeasonSearch>()
|
||||||
|
.PerformSearch(_series, new { SeasonNumber = 1, Episodes = _episodes }, notification)
|
||||||
|
.Should()
|
||||||
|
.HaveCount(40);
|
||||||
|
|
||||||
|
_indexer1.Verify(v => v.FetchPartialSeason(_series.Title, 1, 0), Times.Once());
|
||||||
|
_indexer1.Verify(v => v.FetchPartialSeason(_series.Title, 1, 1), Times.Once());
|
||||||
|
_indexer2.Verify(v => v.FetchPartialSeason(_series.Title, 1, 0), Times.Once());
|
||||||
|
_indexer2.Verify(v => v.FetchPartialSeason(_series.Title, 1, 1), Times.Once());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
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.Repository;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests
|
||||||
|
{
|
||||||
|
public class PerformSearchTestBase : TestBase
|
||||||
|
{
|
||||||
|
protected Series _series;
|
||||||
|
protected Episode _episode;
|
||||||
|
protected List<Episode> _episodes;
|
||||||
|
protected ProgressNotification notification = new ProgressNotification("Testing");
|
||||||
|
|
||||||
|
protected Mock<IndexerBase> _indexer1;
|
||||||
|
protected Mock<IndexerBase> _indexer2;
|
||||||
|
protected List<IndexerBase> _indexers;
|
||||||
|
protected IList<EpisodeParseResult> _parseResults;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_series = Builder<Series>
|
||||||
|
.CreateNew()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episode = Builder<Episode>
|
||||||
|
.CreateNew()
|
||||||
|
.With(e => e.SeriesId = _series.SeriesId)
|
||||||
|
.With(e => e.Series = _series)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_episodes = Builder<Episode>
|
||||||
|
.CreateListOfSize(10)
|
||||||
|
.All()
|
||||||
|
.With(e => e.SeriesId = _series.SeriesId)
|
||||||
|
.With(e => e.Series = _series)
|
||||||
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_parseResults = Builder<EpisodeParseResult>
|
||||||
|
.CreateListOfSize(10)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_indexer1 = new Mock<IndexerBase>();
|
||||||
|
_indexer2 = new Mock<IndexerBase>();
|
||||||
|
_indexers = new List<IndexerBase> { _indexer1.Object, _indexer2.Object };
|
||||||
|
|
||||||
|
Mocker.GetMock<IndexerProvider>()
|
||||||
|
.Setup(c => c.GetEnabledIndexers())
|
||||||
|
.Returns(_indexers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WithValidIndexers()
|
||||||
|
{
|
||||||
|
_indexer1.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Returns(_parseResults);
|
||||||
|
_indexer1.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||||
|
.Returns(_parseResults);
|
||||||
|
_indexer1.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Returns(_parseResults);
|
||||||
|
|
||||||
|
_indexer2.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Returns(_parseResults);
|
||||||
|
_indexer2.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||||
|
.Returns(_parseResults);
|
||||||
|
_indexer2.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Returns(_parseResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void WithInvalidIndexers()
|
||||||
|
{
|
||||||
|
_indexer1.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Throws(new Exception());
|
||||||
|
_indexer1.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||||
|
.Throws(new Exception());
|
||||||
|
_indexer1.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Throws(new Exception());
|
||||||
|
|
||||||
|
_indexer2.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Throws(new Exception());
|
||||||
|
_indexer2.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||||
|
.Throws(new Exception());
|
||||||
|
_indexer2.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Throws(new Exception());
|
||||||
|
|
||||||
|
_indexer1.SetupGet(c => c.Name).Returns("Indexer1");
|
||||||
|
_indexer1.SetupGet(c => c.Name).Returns("Indexer2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
@ -9,23 +10,28 @@ using NzbDrone.Core.Model;
|
||||||
using NzbDrone.Core.Model.Notification;
|
using NzbDrone.Core.Model.Notification;
|
||||||
using NzbDrone.Core.Providers;
|
using NzbDrone.Core.Providers;
|
||||||
using NzbDrone.Core.Providers.DecisionEngine;
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Providers.Search;
|
||||||
using NzbDrone.Core.Repository;
|
using NzbDrone.Core.Repository;
|
||||||
using NzbDrone.Core.Repository.Quality;
|
using NzbDrone.Core.Repository.Quality;
|
||||||
using NzbDrone.Core.Repository.Search;
|
using NzbDrone.Core.Repository.Search;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
// ReSharper disable InconsistentNaming
|
public class ProcessResultsFixture : TestBase
|
||||||
public class ProcessSearchResultsFixture : CoreTest
|
|
||||||
{
|
{
|
||||||
private Series _matchingSeries = null;
|
private Series _matchingSeries;
|
||||||
private Series _mismatchedSeries = null;
|
private Series _mismatchedSeries;
|
||||||
private Series _nullSeries = null;
|
private Series _nullSeries = null;
|
||||||
|
|
||||||
|
private SearchHistory _searchHistory;
|
||||||
|
private ProgressNotification _notification;
|
||||||
|
|
||||||
|
private IList<Episode> _episodes;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_matchingSeries = Builder<Series>.CreateNew()
|
_matchingSeries = Builder<Series>.CreateNew()
|
||||||
.With(s => s.SeriesId = 79488)
|
.With(s => s.SeriesId = 79488)
|
||||||
|
@ -36,6 +42,17 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.With(s => s.SeriesId = 12345)
|
.With(s => s.SeriesId = 12345)
|
||||||
.With(s => s.Title = "Not 30 Rock")
|
.With(s => s.Title = "Not 30 Rock")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
_searchHistory = new SearchHistory();
|
||||||
|
_notification = new ProgressNotification("Test");
|
||||||
|
|
||||||
|
_episodes = Builder<Episode>
|
||||||
|
.CreateListOfSize(1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<EpisodeProvider>()
|
||||||
|
.Setup(s => s.GetEpisodesByParseResult(It.IsAny<EpisodeParseResult>()))
|
||||||
|
.Returns(_episodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithMatchingSeries()
|
private void WithMatchingSeries()
|
||||||
|
@ -85,8 +102,11 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_higher_quality_should_be_called_first()
|
public void should_process_higher_quality_results_first()
|
||||||
{
|
{
|
||||||
|
WithMatchingSeries();
|
||||||
|
WithSuccessfulDownload();
|
||||||
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||||
.All()
|
.All()
|
||||||
.With(e => e.SeasonNumber = 1)
|
.With(e => e.SeasonNumber = 1)
|
||||||
|
@ -96,21 +116,18 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.Random(1)
|
.Random(1)
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
||||||
.With(c => c.Age = 100)
|
.With(c => c.Age = 100)
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
WithSuccessfulDownload();
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>()
|
Mocker.GetMock<AllowedDownloadSpecification>()
|
||||||
.Setup(s => s.IsSatisfiedBy(It.Is<EpisodeParseResult>(d => d.Quality.Quality == QualityTypes.Bluray1080p)))
|
.Setup(s => s.IsSatisfiedBy(It.Is<EpisodeParseResult>(d => d.Quality.Quality == QualityTypes.Bluray1080p)))
|
||||||
.Returns(ReportRejectionType.None);
|
.Returns(ReportRejectionType.None);
|
||||||
|
|
||||||
//Act
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
|
||||||
|
|
||||||
//Assert
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
result.Should().HaveCount(parseResults.Count);
|
result.SearchHistoryItems.Should().Contain(s => s.Success);
|
||||||
result.Should().Contain(s => s.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Once());
|
Times.Once());
|
||||||
|
@ -119,31 +136,29 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_newer_report_should_be_called_first()
|
public void should_process_newer_reports_first()
|
||||||
{
|
{
|
||||||
|
WithMatchingSeries();
|
||||||
|
WithSuccessfulDownload();
|
||||||
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||||
.All()
|
.All()
|
||||||
.With(e => e.SeasonNumber = 1)
|
.With(e => e.SeasonNumber = 1)
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
||||||
.With(c => c.Age = 300)
|
.With(c => c.Age = 300)
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
parseResults[2].Age = 100;
|
parseResults[2].Age = 100;
|
||||||
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
WithSuccessfulDownload();
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>()
|
Mocker.GetMock<AllowedDownloadSpecification>()
|
||||||
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(ReportRejectionType.None);
|
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(ReportRejectionType.None);
|
||||||
|
|
||||||
//Act
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
|
||||||
|
|
||||||
//Assert
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
result.Should().HaveCount(parseResults.Count);
|
result.SearchHistoryItems.Should().Contain(s => s.Success);
|
||||||
result.Should().Contain(s => s.Success);
|
|
||||||
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.Is<EpisodeParseResult>(d => d.Age != 100)), Times.Never());
|
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.Is<EpisodeParseResult>(d => d.Age != 100)), Times.Never());
|
||||||
|
@ -151,24 +166,23 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_when_quality_is_not_needed_should_check_the_rest()
|
public void should_check_other_reports_when_quality_is_not_wanted()
|
||||||
{
|
{
|
||||||
|
WithMatchingSeries();
|
||||||
|
WithQualityNotNeeded();
|
||||||
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||||
.All()
|
.All()
|
||||||
.With(e => e.SeasonNumber = 1)
|
.With(e => e.SeasonNumber = 1)
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
WithMatchingSeries();
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
WithQualityNotNeeded();
|
|
||||||
|
|
||||||
//Act
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
result.SearchHistoryItems.Should().NotContain(s => s.Success);
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(parseResults.Count);
|
|
||||||
result.Should().NotContain(s => s.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Exactly(5));
|
Times.Exactly(5));
|
||||||
|
@ -177,99 +191,55 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_should_skip_if_series_is_null()
|
public void should_should_skip_if_series_is_not_watched()
|
||||||
{
|
{
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||||
.All()
|
.All()
|
||||||
.With(e => e.SeasonNumber = 1)
|
.With(e => e.SeasonNumber = 1)
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
WithNullSeries();
|
WithNullSeries();
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
result.Should().HaveCount(parseResults.Count);
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
result.Should().NotContain(s => s.Success);
|
result.SearchHistoryItems.Should().NotContain(s => s.Success);
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Never());
|
Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_should_skip_if_series_is_mismatched()
|
public void should_skip_if_series_does_not_match_searched_series()
|
||||||
{
|
{
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||||
.All()
|
.All()
|
||||||
.With(e => e.SeasonNumber = 1)
|
.With(e => e.SeasonNumber = 1)
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
WithMisMatchedSeries();
|
WithMisMatchedSeries();
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
result.Should().HaveCount(parseResults.Count);
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
result.Should().NotContain(s => s.Success);
|
result.SearchHistoryItems.Should().NotContain(s => s.Success);
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Never());
|
Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_should_skip_if_season_doesnt_match()
|
public void should_skip_if_episode_was_already_downloaded()
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(e => e.SeasonNumber = 2)
|
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(parseResults.Count);
|
|
||||||
result.Should().NotContain(s => s.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_skip_if_episodeNumber_doesnt_match()
|
|
||||||
{
|
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
|
||||||
.All()
|
|
||||||
.With(e => e.SeasonNumber = 1)
|
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 2 })
|
|
||||||
.With(e => e.Quality = new QualityModel(QualityTypes.HDTV720p, false))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
|
||||||
|
|
||||||
//Act
|
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1, 1);
|
|
||||||
|
|
||||||
//Assert
|
|
||||||
result.Should().HaveCount(parseResults.Count);
|
|
||||||
result.Should().NotContain(s => s.Success);
|
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
|
||||||
Times.Never());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void processSearchResults_should_skip_if_any_episodeNumber_was_already_added_to_download_queue()
|
|
||||||
{
|
{
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
||||||
.All()
|
.All()
|
||||||
|
@ -278,25 +248,26 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
||||||
.TheLast(1)
|
.TheLast(1)
|
||||||
.With(e => e.EpisodeNumbers = new List<int> { 1, 2, 3, 4, 5 })
|
.With(e => e.EpisodeNumbers = new List<int> { 1, 2, 3, 4, 5 })
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
WithMatchingSeries();
|
WithMatchingSeries();
|
||||||
WithQualityNeeded();
|
WithQualityNeeded();
|
||||||
WithSuccessfulDownload();
|
WithSuccessfulDownload();
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1);
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
result.Should().HaveCount(parseResults.Count);
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
result.Should().Contain(s => s.Success);
|
result.SearchHistoryItems.Should().Contain(s => s.Success);
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Once());
|
Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_should_try_next_if_download_fails()
|
public void should_try_next_report_if_download_fails()
|
||||||
{
|
{
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
||||||
.All()
|
.All()
|
||||||
|
@ -305,7 +276,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.DVD, true))
|
||||||
.TheLast(1)
|
.TheLast(1)
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.SDTV, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.SDTV, true))
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
WithMatchingSeries();
|
WithMatchingSeries();
|
||||||
WithQualityNeeded();
|
WithQualityNeeded();
|
||||||
|
@ -319,18 +291,18 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, new SearchHistory(), _matchingSeries, 1);
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
result.Should().HaveCount(parseResults.Count);
|
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||||
result.Should().Contain(s => s.Success);
|
result.SearchHistoryItems.Should().Contain(s => s.Success);
|
||||||
|
|
||||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Exactly(2));
|
Times.Exactly(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void processSearchResults_Successes_should_not_be_null_or_empty()
|
public void should_return_valid_successes_when_one_or_more_downloaded()
|
||||||
{
|
{
|
||||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||||
.All()
|
.All()
|
||||||
|
@ -341,9 +313,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.Random(1)
|
.Random(1)
|
||||||
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
.With(c => c.Quality = new QualityModel(QualityTypes.Bluray1080p, true))
|
||||||
.With(c => c.Age = 100)
|
.With(c => c.Age = 100)
|
||||||
.Build();
|
.Build()
|
||||||
|
.ToList();
|
||||||
var searchHistory = new SearchHistory();
|
|
||||||
|
|
||||||
WithMatchingSeries();
|
WithMatchingSeries();
|
||||||
WithSuccessfulDownload();
|
WithSuccessfulDownload();
|
||||||
|
@ -353,11 +324,11 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
|
||||||
.Returns(ReportRejectionType.None);
|
.Returns(ReportRejectionType.None);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, searchHistory, _matchingSeries, 1, 1);
|
var result = Mocker.Resolve<TestSearch>().ProcessReports(_matchingSeries, new { }, parseResults, _searchHistory, _notification);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
searchHistory.Successes.Should().NotBeNull();
|
result.Successes.Should().NotBeNull();
|
||||||
searchHistory.Successes.Should().NotBeEmpty();
|
result.Successes.Should().NotBeEmpty();
|
||||||
|
|
||||||
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
|
||||||
Times.Once());
|
Times.Once());
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Model;
|
||||||
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Providers.Search;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.ProviderTests.SearchTests
|
||||||
|
{
|
||||||
|
public class TestSearch : SearchBase
|
||||||
|
{
|
||||||
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public TestSearch(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, DownloadProvider downloadProvider,
|
||||||
|
IndexerProvider indexerProvider, SceneMappingProvider sceneMappingProvider,
|
||||||
|
AllowedDownloadSpecification allowedDownloadSpecification, SearchHistoryProvider searchHistoryProvider)
|
||||||
|
: base(seriesProvider, episodeProvider, downloadProvider, indexerProvider, sceneMappingProvider,
|
||||||
|
allowedDownloadSpecification, searchHistoryProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<EpisodeParseResult> PerformSearch(Repository.Series series, dynamic options, Model.Notification.ProgressNotification notification)
|
||||||
|
{
|
||||||
|
if (options.Episode == null)
|
||||||
|
throw new ArgumentException("Episode is invalid");
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Looking for " + options.Episode;
|
||||||
|
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
var title = GetSearchTitle(series);
|
||||||
|
|
||||||
|
var seasonNumber = options.Episode.SeasonNumber;
|
||||||
|
var episodeNumber = options.Episode.EpisodeNumber;
|
||||||
|
|
||||||
|
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, options.SeasonNumber, options.EpisodeNumber, indexer.Name), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SearchHistoryItem CheckReport(Repository.Series series, dynamic options, EpisodeParseResult episodeParseResult, Repository.Search.SearchHistoryItem item)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FinalizeSearch(Repository.Series series, dynamic options, bool reportsFound, Model.Notification.ProgressNotification notification)
|
||||||
|
{
|
||||||
|
logger.Warn("Unable to find {0} in any of indexers.", series.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,30 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System;
|
using System;
|
||||||
|
using NLog;
|
||||||
using NzbDrone.Core.Model.Notification;
|
using NzbDrone.Core.Model.Notification;
|
||||||
using NzbDrone.Core.Providers;
|
using NzbDrone.Core.Providers;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Providers.Search;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Jobs
|
namespace NzbDrone.Core.Jobs
|
||||||
{
|
{
|
||||||
public class EpisodeSearchJob : IJob
|
public class EpisodeSearchJob : IJob
|
||||||
{
|
{
|
||||||
private readonly SearchProvider _searchProvider;
|
private readonly EpisodeProvider _episodeProvider;
|
||||||
|
private readonly UpgradePossibleSpecification _upgradePossibleSpecification;
|
||||||
|
private readonly EpisodeSearch _episodeSearch;
|
||||||
|
private readonly DailyEpisodeSearch _dailyEpisodeSearch;
|
||||||
|
|
||||||
public EpisodeSearchJob(SearchProvider searchProvider)
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public EpisodeSearchJob(EpisodeProvider episodeProvider, UpgradePossibleSpecification upgradePossibleSpecification,
|
||||||
|
EpisodeSearch episodeSearch, DailyEpisodeSearch dailyEpisodeSearch)
|
||||||
{
|
{
|
||||||
_searchProvider = searchProvider;
|
if(dailyEpisodeSearch == null) throw new ArgumentNullException("dailyEpisodeSearch");
|
||||||
|
_episodeProvider = episodeProvider;
|
||||||
|
_upgradePossibleSpecification = upgradePossibleSpecification;
|
||||||
|
_episodeSearch = episodeSearch;
|
||||||
|
_dailyEpisodeSearch = dailyEpisodeSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeSearchJob()
|
public EpisodeSearchJob()
|
||||||
|
@ -34,7 +47,37 @@ namespace NzbDrone.Core.Jobs
|
||||||
if (options == null || options.EpisodeId <= 0)
|
if (options == null || options.EpisodeId <= 0)
|
||||||
throw new ArgumentException("options");
|
throw new ArgumentException("options");
|
||||||
|
|
||||||
_searchProvider.EpisodeSearch(notification, options.EpisodeId);
|
var episode = _episodeProvider.GetEpisode(options.EpisodeId);
|
||||||
|
|
||||||
|
if (episode == null)
|
||||||
|
{
|
||||||
|
logger.Error("Unable to find an episode {0} in database", options.EpisodeId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_upgradePossibleSpecification.IsSatisfiedBy(episode))
|
||||||
|
{
|
||||||
|
logger.Info("Search for {0} was aborted, file in disk meets or exceeds Profile's Cutoff", episode);
|
||||||
|
notification.CurrentMessage = String.Format("Skipping search for {0}, the file you have is already at cutoff", episode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dailyEpisodeSearch.Search(episode.Series, new { Episode = episode }, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_episodeSearch.Search(episode.Series, new { Episode = episode }, notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -67,9 +67,9 @@ namespace NzbDrone.Core.Jobs
|
||||||
if (episodes.Count == successes.Count)
|
if (episodes.Count == successes.Count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var missingEpisodes = episodes.Select(e => e.EpisodeNumber).Except(successes).ToList();
|
var missingEpisodes = episodes.Select(e => e.EpisodeId).Except(successes).ToList();
|
||||||
|
|
||||||
foreach (var episode in episodes.Where(e => !e.Ignored && missingEpisodes.Contains(e.EpisodeNumber)).OrderBy(o => o.EpisodeNumber))
|
foreach (var episode in episodes.Where(e => !e.Ignored && missingEpisodes.Contains(e.EpisodeId)).OrderBy(o => o.EpisodeNumber))
|
||||||
{
|
{
|
||||||
_episodeSearchJob.Start(notification, new { EpisodeId = episode.EpisodeId });
|
_episodeSearchJob.Start(notification, new { EpisodeId = episode.EpisodeId });
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,6 +313,7 @@
|
||||||
<Compile Include="Model\Xem\XemValues.cs" />
|
<Compile Include="Model\Xem\XemValues.cs" />
|
||||||
<Compile Include="AutofacSignalrDependencyResolver.cs" />
|
<Compile Include="AutofacSignalrDependencyResolver.cs" />
|
||||||
<Compile Include="Providers\BannerProvider.cs" />
|
<Compile Include="Providers\BannerProvider.cs" />
|
||||||
|
<Compile Include="Providers\SearchProvider2.cs" />
|
||||||
<Compile Include="Providers\DecisionEngine\AllowedReleaseGroupSpecification.cs" />
|
<Compile Include="Providers\DecisionEngine\AllowedReleaseGroupSpecification.cs" />
|
||||||
<Compile Include="Providers\DecisionEngine\CustomStartDateSpecification.cs" />
|
<Compile Include="Providers\DecisionEngine\CustomStartDateSpecification.cs" />
|
||||||
<Compile Include="Providers\DownloadClients\PneumaticProvider.cs" />
|
<Compile Include="Providers\DownloadClients\PneumaticProvider.cs" />
|
||||||
|
@ -328,6 +329,10 @@
|
||||||
<Compile Include="Providers\Metadata\Xbmc.cs" />
|
<Compile Include="Providers\Metadata\Xbmc.cs" />
|
||||||
<Compile Include="Providers\RecycleBinProvider.cs" />
|
<Compile Include="Providers\RecycleBinProvider.cs" />
|
||||||
<Compile Include="Providers\SearchHistoryProvider.cs" />
|
<Compile Include="Providers\SearchHistoryProvider.cs" />
|
||||||
|
<Compile Include="Providers\Search\DailyEpisodeSearch.cs" />
|
||||||
|
<Compile Include="Providers\Search\PartialSeasonSearch.cs" />
|
||||||
|
<Compile Include="Providers\Search\EpisodeSearch.cs" />
|
||||||
|
<Compile Include="Providers\Search\SearchBase.cs" />
|
||||||
<Compile Include="Providers\SeasonProvider.cs" />
|
<Compile Include="Providers\SeasonProvider.cs" />
|
||||||
<Compile Include="Jobs\RecentBacklogSearchJob.cs" />
|
<Compile Include="Jobs\RecentBacklogSearchJob.cs" />
|
||||||
<Compile Include="Jobs\TrimLogsJob.cs" />
|
<Compile Include="Jobs\TrimLogsJob.cs" />
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Model;
|
||||||
|
using NzbDrone.Core.Model.Notification;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers.Search
|
||||||
|
{
|
||||||
|
public class DailyEpisodeSearch : SearchBase
|
||||||
|
{
|
||||||
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public DailyEpisodeSearch(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, DownloadProvider downloadProvider, IndexerProvider indexerProvider,
|
||||||
|
SceneMappingProvider sceneMappingProvider, AllowedDownloadSpecification allowedDownloadSpecification,
|
||||||
|
SearchHistoryProvider searchHistoryProvider)
|
||||||
|
: base(seriesProvider, episodeProvider, downloadProvider, indexerProvider, sceneMappingProvider,
|
||||||
|
allowedDownloadSpecification, searchHistoryProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DailyEpisodeSearch()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<EpisodeParseResult> PerformSearch(Series series, dynamic options, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
if (options.Episode == null)
|
||||||
|
throw new ArgumentException("Episode is invalid");
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Looking for " + options.Episode;
|
||||||
|
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
var title = GetSearchTitle(series);
|
||||||
|
|
||||||
|
Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reports.AddRange(indexer.FetchDailyEpisode(title, options.Episode.AirDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.ErrorException(String.Format("An error has occurred while searching for {0} - {1:yyyy-MM-dd} from: {2}",
|
||||||
|
series.Title, options.Episode.AirDate, indexer.Name), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SearchHistoryItem CheckReport(Series series, dynamic options, EpisodeParseResult episodeParseResult,
|
||||||
|
SearchHistoryItem item)
|
||||||
|
{
|
||||||
|
Episode episode = options.Episode;
|
||||||
|
|
||||||
|
if (!episodeParseResult.AirDate.HasValue || episodeParseResult.AirDate.Value != episode.AirDate.Value)
|
||||||
|
{
|
||||||
|
logger.Trace("Episode AirDate does not match searched episode number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongEpisode;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FinalizeSearch(Series series, dynamic options, Boolean reportsFound, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
logger.Warn("Unable to find {0} in any of indexers.", options.Episode);
|
||||||
|
|
||||||
|
notification.CurrentMessage = reportsFound ? String.Format("Sorry, couldn't find {0}, that matches your preferences.", options.Episode)
|
||||||
|
: String.Format("Sorry, couldn't find {0} in any of indexers.", options.Episode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Model;
|
||||||
|
using NzbDrone.Core.Model.Notification;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers.Search
|
||||||
|
{
|
||||||
|
public class EpisodeSearch : SearchBase
|
||||||
|
{
|
||||||
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public EpisodeSearch(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, DownloadProvider downloadProvider, IndexerProvider indexerProvider,
|
||||||
|
SceneMappingProvider sceneMappingProvider, AllowedDownloadSpecification allowedDownloadSpecification,
|
||||||
|
SearchHistoryProvider searchHistoryProvider)
|
||||||
|
: base(seriesProvider, episodeProvider, downloadProvider, indexerProvider, sceneMappingProvider,
|
||||||
|
allowedDownloadSpecification, searchHistoryProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public EpisodeSearch()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<EpisodeParseResult> PerformSearch(Series series, dynamic options, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
//Todo: Daily and Anime or separate them out?
|
||||||
|
//Todo: Epsiodes that use scene numbering
|
||||||
|
|
||||||
|
if (options.Episode == null)
|
||||||
|
throw new ArgumentException("Episode is invalid");
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Looking for " + options.Episode;
|
||||||
|
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
var title = GetSearchTitle(series);
|
||||||
|
|
||||||
|
var seasonNumber = options.Episode.SeasonNumber;
|
||||||
|
var episodeNumber = options.Episode.EpisodeNumber;
|
||||||
|
|
||||||
|
if (series.UseSceneNumbering)
|
||||||
|
{
|
||||||
|
if(options.Episode.SceneSeasonNumber > 0 && options.Episode.SceneEpisodeNumber > 0)
|
||||||
|
{
|
||||||
|
logger.Trace("Using Scene Numbering for: {0}", options.Episode);
|
||||||
|
seasonNumber = options.Episode.SceneSeasonNumber;
|
||||||
|
episodeNumber = options.Episode.SceneEpisodeNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, options.Episode.SeasonNumber, options.Episode.EpisodeNumber, indexer.Name), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SearchHistoryItem CheckReport(Series series, dynamic options, EpisodeParseResult episodeParseResult,
|
||||||
|
SearchHistoryItem item)
|
||||||
|
{
|
||||||
|
if(series.UseSceneNumbering && options.Episode.SeasonNumber > 0 && options.Episode.EpisodeNumber > 0)
|
||||||
|
{
|
||||||
|
if (options.Episode.SceneSeasonNumber != episodeParseResult.SeasonNumber)
|
||||||
|
{
|
||||||
|
logger.Trace("Season number does not match searched season number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeason;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!episodeParseResult.EpisodeNumbers.Contains(options.Episode.SceneEpisodeNumber))
|
||||||
|
{
|
||||||
|
logger.Trace("Episode number does not match searched episode number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongEpisode;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.Episode.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||||
|
{
|
||||||
|
logger.Trace("Season number does not match searched season number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeason;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!episodeParseResult.EpisodeNumbers.Contains(options.Episode.EpisodeNumber))
|
||||||
|
{
|
||||||
|
logger.Trace("Episode number does not match searched episode number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongEpisode;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FinalizeSearch(Series series, dynamic options, Boolean reportsFound, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
logger.Warn("Unable to find {0} in any of indexers.", options.Episode);
|
||||||
|
|
||||||
|
notification.CurrentMessage = reportsFound ? String.Format("Sorry, couldn't find {0}, that matches your preferences.", options.Episode)
|
||||||
|
: String.Format("Sorry, couldn't find {0} in any of indexers.", options.Episode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Model;
|
||||||
|
using NzbDrone.Core.Model.Notification;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers.Search
|
||||||
|
{
|
||||||
|
public class PartialSeasonSearch : SearchBase
|
||||||
|
{
|
||||||
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public PartialSeasonSearch(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, DownloadProvider downloadProvider, IndexerProvider indexerProvider,
|
||||||
|
SceneMappingProvider sceneMappingProvider, AllowedDownloadSpecification allowedDownloadSpecification,
|
||||||
|
SearchHistoryProvider searchHistoryProvider)
|
||||||
|
: base(seriesProvider, episodeProvider, downloadProvider, indexerProvider, sceneMappingProvider,
|
||||||
|
allowedDownloadSpecification, searchHistoryProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartialSeasonSearch()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override List<EpisodeParseResult> PerformSearch(Series series, dynamic options, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
if (options.SeasonNumber == null || options.SeasonNumber < 0)
|
||||||
|
throw new ArgumentException("SeasonNumber is invalid");
|
||||||
|
|
||||||
|
if (options.Episodes == null)
|
||||||
|
throw new ArgumentException("Episodes were not provided");
|
||||||
|
|
||||||
|
List<Episode> episodes = options.Episodes;
|
||||||
|
|
||||||
|
if (!episodes.Any())
|
||||||
|
throw new ArgumentException("Episodes were not provided");
|
||||||
|
|
||||||
|
notification.CurrentMessage = String.Format("Looking for {0} - Season {1}", series.Title, options.SeasonNumber);
|
||||||
|
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
object reportsLock = new object();
|
||||||
|
|
||||||
|
var title = GetSearchTitle(series);
|
||||||
|
var prefixes = GetEpisodeNumberPrefixes(episodes.Select(e => e.EpisodeNumber));
|
||||||
|
|
||||||
|
foreach(var p in prefixes)
|
||||||
|
{
|
||||||
|
var prefix = p;
|
||||||
|
|
||||||
|
Parallel.ForEach(_indexerProvider.GetEnabledIndexers(), indexer =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock(reportsLock)
|
||||||
|
{
|
||||||
|
reports.AddRange(indexer.FetchPartialSeason(title, options.SeasonNumber, prefix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
logger.ErrorException(
|
||||||
|
String.Format(
|
||||||
|
"An error has occurred while searching for {0} Season {1:00} Prefix: {2} from: {3}",
|
||||||
|
series.Title, options.SeasonNumber, prefix, indexer.Name),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SearchHistoryItem CheckReport(Series series, dynamic options, EpisodeParseResult episodeParseResult,
|
||||||
|
SearchHistoryItem item)
|
||||||
|
{
|
||||||
|
if(options.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||||
|
{
|
||||||
|
logger.Trace("Season number does not match searched season number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeason;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FinalizeSearch(Series series, dynamic options, Boolean reportsFound, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
logger.Warn("Unable to find {0} - Season {1} in any of indexers.", series.Title, options.SeasonNumber);
|
||||||
|
|
||||||
|
notification.CurrentMessage = reportsFound ? String.Format("Sorry, couldn't find {0} Season {1:00}, that matches your preferences.", series.Title, options.SeasonNumber)
|
||||||
|
: String.Format("Sorry, couldn't find {0} Season {1:00} in any of indexers.", series.Title, options.SeasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<int> GetEpisodeNumberPrefixes(IEnumerable<int> episodeNumbers)
|
||||||
|
{
|
||||||
|
var results = new List<int>();
|
||||||
|
|
||||||
|
foreach (var i in episodeNumbers)
|
||||||
|
{
|
||||||
|
results.Add(i / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.Distinct().ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Model;
|
||||||
|
using NzbDrone.Core.Model.Notification;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers.Search
|
||||||
|
{
|
||||||
|
public abstract class SearchBase
|
||||||
|
{
|
||||||
|
protected readonly SeriesProvider _seriesProvider;
|
||||||
|
protected readonly EpisodeProvider _episodeProvider;
|
||||||
|
protected readonly DownloadProvider _downloadProvider;
|
||||||
|
protected readonly IndexerProvider _indexerProvider;
|
||||||
|
protected readonly SceneMappingProvider _sceneMappingProvider;
|
||||||
|
protected readonly AllowedDownloadSpecification _allowedDownloadSpecification;
|
||||||
|
protected readonly SearchHistoryProvider _searchHistoryProvider;
|
||||||
|
|
||||||
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
protected SearchBase(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, DownloadProvider downloadProvider,
|
||||||
|
IndexerProvider indexerProvider, SceneMappingProvider sceneMappingProvider,
|
||||||
|
AllowedDownloadSpecification allowedDownloadSpecification,
|
||||||
|
SearchHistoryProvider searchHistoryProvider)
|
||||||
|
{
|
||||||
|
_seriesProvider = seriesProvider;
|
||||||
|
_episodeProvider = episodeProvider;
|
||||||
|
_downloadProvider = downloadProvider;
|
||||||
|
_indexerProvider = indexerProvider;
|
||||||
|
_sceneMappingProvider = sceneMappingProvider;
|
||||||
|
_allowedDownloadSpecification = allowedDownloadSpecification;
|
||||||
|
_searchHistoryProvider = searchHistoryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SearchBase()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract List<EpisodeParseResult> PerformSearch(Series series, dynamic options, ProgressNotification notification);
|
||||||
|
public abstract SearchHistoryItem CheckReport(Series series, dynamic options, EpisodeParseResult episodeParseResult,
|
||||||
|
SearchHistoryItem item);
|
||||||
|
|
||||||
|
protected abstract void FinalizeSearch(Series series, dynamic options, Boolean reportsFound, ProgressNotification notification);
|
||||||
|
|
||||||
|
public virtual List<Int32> Search(Series series, dynamic options, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
throw new ArgumentNullException(options);
|
||||||
|
|
||||||
|
var searchResult = new SearchHistory
|
||||||
|
{
|
||||||
|
SearchTime = DateTime.Now,
|
||||||
|
SeriesId = series.SeriesId,
|
||||||
|
EpisodeId = options.GetType().GetProperty("Episode") != null ? options.Episode.EpisodeId : null
|
||||||
|
};
|
||||||
|
|
||||||
|
List<EpisodeParseResult> reports = PerformSearch(series, options, notification);
|
||||||
|
|
||||||
|
logger.Debug("Finished searching all indexers. Total {0}", reports.Count);
|
||||||
|
notification.CurrentMessage = "Processing search results";
|
||||||
|
|
||||||
|
ProcessReports(series, options, reports, searchResult, notification);
|
||||||
|
_searchHistoryProvider.Add(searchResult);
|
||||||
|
|
||||||
|
if(searchResult.Successes.Any())
|
||||||
|
return searchResult.Successes;
|
||||||
|
|
||||||
|
FinalizeSearch(series, options, reports.Any(), notification);
|
||||||
|
return new List<Int32>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual SearchHistory ProcessReports(Series series, dynamic options, List<EpisodeParseResult> episodeParseResults,
|
||||||
|
SearchHistory searchResult, ProgressNotification notification)
|
||||||
|
{
|
||||||
|
var items = new List<SearchHistoryItem>();
|
||||||
|
searchResult.Successes = new List<Int32>();
|
||||||
|
|
||||||
|
foreach(var episodeParseResult in episodeParseResults
|
||||||
|
.OrderByDescending(c => c.Quality)
|
||||||
|
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
|
||||||
|
.ThenBy(c => c.Age))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var item = new SearchHistoryItem
|
||||||
|
{
|
||||||
|
ReportTitle = episodeParseResult.OriginalString,
|
||||||
|
NzbUrl = episodeParseResult.NzbUrl,
|
||||||
|
Indexer = episodeParseResult.Indexer,
|
||||||
|
Quality = episodeParseResult.Quality.Quality,
|
||||||
|
Proper = episodeParseResult.Quality.Proper,
|
||||||
|
Size = episodeParseResult.Size,
|
||||||
|
Age = episodeParseResult.Age,
|
||||||
|
Language = episodeParseResult.Language
|
||||||
|
};
|
||||||
|
|
||||||
|
items.Add(item);
|
||||||
|
|
||||||
|
logger.Trace("Analysing report " + episodeParseResult);
|
||||||
|
episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle);
|
||||||
|
|
||||||
|
if(episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId)
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeries;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult);
|
||||||
|
|
||||||
|
if (searchResult.Successes.Intersect(episodeParseResult.Episodes.Select(e => e.EpisodeId)).Any())
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.Skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckReport(series, options, episodeParseResult, item);
|
||||||
|
if (item.SearchError != ReportRejectionType.None)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
|
||||||
|
|
||||||
|
if(item.SearchError == ReportRejectionType.None)
|
||||||
|
{
|
||||||
|
if(DownloadReport(notification, episodeParseResult, item))
|
||||||
|
searchResult.Successes.AddRange(episodeParseResult.Episodes.Select(e => e.EpisodeId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResult.SearchHistoryItems = items;
|
||||||
|
return searchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Boolean DownloadReport(ProgressNotification notification, EpisodeParseResult episodeParseResult, SearchHistoryItem item)
|
||||||
|
{
|
||||||
|
logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_downloadProvider.DownloadReport(episodeParseResult))
|
||||||
|
{
|
||||||
|
notification.CurrentMessage = String.Format("{0} Added to download queue", episodeParseResult);
|
||||||
|
item.Success = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetSearchTitle(Series series, int seasonNumber = -1)
|
||||||
|
{
|
||||||
|
//Todo: Add support for per season lookup (used for anime)
|
||||||
|
//Todo: Add support for multiple names
|
||||||
|
var title = _sceneMappingProvider.GetSceneName(series.SeriesId);
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(title))
|
||||||
|
{
|
||||||
|
title = series.Title;
|
||||||
|
title = title.Replace("&", "and");
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ using NLog;
|
||||||
using NzbDrone.Core.Model;
|
using NzbDrone.Core.Model;
|
||||||
using NzbDrone.Core.Model.Notification;
|
using NzbDrone.Core.Model.Notification;
|
||||||
using NzbDrone.Core.Providers.DecisionEngine;
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Providers.Search;
|
||||||
using NzbDrone.Core.Repository;
|
using NzbDrone.Core.Repository;
|
||||||
using NzbDrone.Core.Repository.Search;
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
@ -14,30 +15,18 @@ namespace NzbDrone.Core.Providers
|
||||||
{
|
{
|
||||||
public class SearchProvider
|
public class SearchProvider
|
||||||
{
|
{
|
||||||
private readonly EpisodeProvider _episodeProvider;
|
|
||||||
private readonly DownloadProvider _downloadProvider;
|
|
||||||
private readonly SeriesProvider _seriesProvider;
|
private readonly SeriesProvider _seriesProvider;
|
||||||
private readonly IndexerProvider _indexerProvider;
|
private readonly EpisodeProvider _episodeProvider;
|
||||||
private readonly SceneMappingProvider _sceneMappingProvider;
|
private readonly PartialSeasonSearch _partialSeasonSearch;
|
||||||
private readonly UpgradePossibleSpecification _upgradePossibleSpecification;
|
|
||||||
private readonly AllowedDownloadSpecification _allowedDownloadSpecification;
|
|
||||||
private readonly SearchHistoryProvider _searchHistoryProvider;
|
|
||||||
|
|
||||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public SearchProvider(EpisodeProvider episodeProvider, DownloadProvider downloadProvider, SeriesProvider seriesProvider,
|
public SearchProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider,
|
||||||
IndexerProvider indexerProvider, SceneMappingProvider sceneMappingProvider,
|
PartialSeasonSearch partialSeasonSearch)
|
||||||
UpgradePossibleSpecification upgradePossibleSpecification, AllowedDownloadSpecification allowedDownloadSpecification,
|
|
||||||
SearchHistoryProvider searchHistoryProvider)
|
|
||||||
{
|
{
|
||||||
_episodeProvider = episodeProvider;
|
|
||||||
_downloadProvider = downloadProvider;
|
|
||||||
_seriesProvider = seriesProvider;
|
_seriesProvider = seriesProvider;
|
||||||
_indexerProvider = indexerProvider;
|
_episodeProvider = episodeProvider;
|
||||||
_sceneMappingProvider = sceneMappingProvider;
|
_partialSeasonSearch = partialSeasonSearch;
|
||||||
_upgradePossibleSpecification = upgradePossibleSpecification;
|
|
||||||
_allowedDownloadSpecification = allowedDownloadSpecification;
|
|
||||||
_searchHistoryProvider = searchHistoryProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchProvider()
|
public SearchProvider()
|
||||||
|
@ -46,13 +35,6 @@ namespace NzbDrone.Core.Providers
|
||||||
|
|
||||||
public virtual List<int> SeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
|
public virtual List<int> SeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
|
||||||
{
|
{
|
||||||
var searchResult = new SearchHistory
|
|
||||||
{
|
|
||||||
SearchTime = DateTime.Now,
|
|
||||||
SeriesId = seriesId,
|
|
||||||
SeasonNumber = seasonNumber
|
|
||||||
};
|
|
||||||
|
|
||||||
var series = _seriesProvider.GetSeries(seriesId);
|
var series = _seriesProvider.GetSeries(seriesId);
|
||||||
|
|
||||||
if (series == null)
|
if (series == null)
|
||||||
|
@ -76,59 +58,12 @@ namespace NzbDrone.Core.Providers
|
||||||
return new List<int>();
|
return new List<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
|
//Todo: Support full season searching
|
||||||
|
|
||||||
List<EpisodeParseResult> 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<int>();
|
return new List<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
reports = PerformSeasonSearch(series, sceneSeasonNumbers.First());
|
|
||||||
|
|
||||||
reports.Where(p => p.FullSeason && p.SeasonNumber == sceneSeasonNumbers.First()).ToList().ForEach(
|
|
||||||
e => e.EpisodeNumbers = sceneEpisodeNumbers.ToList()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reports = PerformSeasonSearch(series, seasonNumber);
|
|
||||||
|
|
||||||
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<int>();
|
|
||||||
|
|
||||||
notification.CurrentMessage = "Processing search results";
|
|
||||||
|
|
||||||
searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, series, seasonNumber);
|
|
||||||
_searchHistoryProvider.Add(searchResult);
|
|
||||||
|
|
||||||
return searchResult.Successes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual List<int> PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
|
public virtual List<int> PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
|
||||||
{
|
{
|
||||||
var searchResult = new SearchHistory
|
|
||||||
{
|
|
||||||
SearchTime = DateTime.Now,
|
|
||||||
SeriesId = seriesId,
|
|
||||||
SeasonNumber = seasonNumber
|
|
||||||
};
|
|
||||||
|
|
||||||
var series = _seriesProvider.GetSeries(seriesId);
|
var series = _seriesProvider.GetSeries(seriesId);
|
||||||
|
|
||||||
if (series == null)
|
if (series == null)
|
||||||
|
@ -143,438 +78,15 @@ namespace NzbDrone.Core.Providers
|
||||||
return new List<int>();
|
return new List<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
|
|
||||||
var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber);
|
var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber);
|
||||||
|
|
||||||
List<EpisodeParseResult> reports;
|
if (episodes == null || episodes.Count == 0)
|
||||||
|
|
||||||
if (series.UseSceneNumbering)
|
|
||||||
{
|
{
|
||||||
var sceneSeasonNumbers = episodes.Select(e => e.SceneSeasonNumber).ToList();
|
logger.Warn("No episodes in database found for series: {0} Season: {1}.", seriesId, seasonNumber);
|
||||||
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<int>();
|
return new List<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
reports = PerformPartialSeasonSearch(series, sceneSeasonNumbers.First(), GetEpisodeNumberPrefixes(sceneEpisodeNumbers));
|
return _partialSeasonSearch.Search(series, new {SeasonNumber = seasonNumber, Episodes = episodes}, notification);
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
return new List<int>();
|
|
||||||
|
|
||||||
notification.CurrentMessage = "Processing search results";
|
|
||||||
searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, series, seasonNumber);
|
|
||||||
|
|
||||||
_searchHistoryProvider.Add(searchResult);
|
|
||||||
return searchResult.Successes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool EpisodeSearch(ProgressNotification notification, int episodeId)
|
|
||||||
{
|
|
||||||
var episode = _episodeProvider.GetEpisode(episodeId);
|
|
||||||
|
|
||||||
if (episode == null)
|
|
||||||
{
|
|
||||||
logger.Error("Unable to find an episode {0} in database", episodeId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_upgradePossibleSpecification.IsSatisfiedBy(episode))
|
|
||||||
{
|
|
||||||
logger.Info("Search for {0} was aborted, file in disk meets or exceeds Profile's Cutoff", episode);
|
|
||||||
notification.CurrentMessage = String.Format("Skipping search for {0}, the file you have is already at cutoff", episode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.CurrentMessage = "Looking for " + episode;
|
|
||||||
List<EpisodeParseResult> reports;
|
|
||||||
|
|
||||||
var searchResult = new SearchHistory
|
|
||||||
{
|
|
||||||
SearchTime = DateTime.Now,
|
|
||||||
SeriesId = episode.Series.SeriesId,
|
|
||||||
EpisodeId = episodeId
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (searchResult.SearchHistoryItems.Any(r => r.Success))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (episode.Series.UseSceneNumbering)
|
|
||||||
{
|
|
||||||
var seasonNumber = episode.SceneSeasonNumber;
|
|
||||||
var episodeNumber = episode.SceneEpisodeNumber;
|
|
||||||
|
|
||||||
if (seasonNumber == 0 && episodeNumber == 0)
|
|
||||||
{
|
|
||||||
seasonNumber = episode.SeasonNumber;
|
|
||||||
episodeNumber = episode.EpisodeNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
reports = PerformEpisodeSearch(episode.Series, seasonNumber, episodeNumber);
|
|
||||||
|
|
||||||
searchResult.SearchHistoryItems = ProcessSearchResults(
|
|
||||||
notification,
|
|
||||||
reports,
|
|
||||||
searchResult,
|
|
||||||
episode.Series,
|
|
||||||
seasonNumber,
|
|
||||||
episodeNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
_searchHistoryProvider.Add(searchResult);
|
|
||||||
|
|
||||||
if (searchResult.SearchHistoryItems.Any(r => r.Success))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reports = PerformEpisodeSearch(episode.Series, episode.SeasonNumber, episode.EpisodeNumber);
|
|
||||||
|
|
||||||
searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, episode.Series, episode.SeasonNumber, episode.EpisodeNumber);
|
|
||||||
_searchHistoryProvider.Add(searchResult);
|
|
||||||
|
|
||||||
if (searchResult.SearchHistoryItems.Any(r => r.Success))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Warn("Unable to 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<SearchHistoryItem> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, SearchHistory searchResult, Series series, int seasonNumber, int? episodeNumber = null)
|
|
||||||
{
|
|
||||||
var items = new List<SearchHistoryItem>();
|
|
||||||
searchResult.Successes = new List<int>();
|
|
||||||
|
|
||||||
foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality)
|
|
||||||
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
|
|
||||||
.ThenBy(c => c.Age))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
logger.Trace("Analysing report " + episodeParseResult);
|
|
||||||
|
|
||||||
var item = new SearchHistoryItem
|
|
||||||
{
|
|
||||||
ReportTitle = episodeParseResult.OriginalString,
|
|
||||||
NzbUrl = episodeParseResult.NzbUrl,
|
|
||||||
Indexer = episodeParseResult.Indexer,
|
|
||||||
Quality = episodeParseResult.Quality.Quality,
|
|
||||||
Proper = episodeParseResult.Quality.Proper,
|
|
||||||
Size = episodeParseResult.Size,
|
|
||||||
Age = episodeParseResult.Age,
|
|
||||||
Language = episodeParseResult.Language
|
|
||||||
};
|
|
||||||
|
|
||||||
items.Add(item);
|
|
||||||
|
|
||||||
//Get the matching series
|
|
||||||
episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle);
|
|
||||||
|
|
||||||
//If series is null or doesn't match the series we're looking for return
|
|
||||||
if (episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId)
|
|
||||||
{
|
|
||||||
logger.Trace("Unexpected series for search: {0}. Skipping.", episodeParseResult.CleanTitle);
|
|
||||||
item.SearchError = ReportRejectionType.WrongSeries;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If SeasonNumber doesn't match or episode is not in the in the list in the parse result, skip the report.
|
|
||||||
if (episodeParseResult.SeasonNumber != seasonNumber)
|
|
||||||
{
|
|
||||||
logger.Trace("Season number does not match searched season number, skipping.");
|
|
||||||
item.SearchError = ReportRejectionType.WrongSeason;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If the EpisodeNumber was passed in and it is not contained in the parseResult, skip the report.
|
|
||||||
if (episodeNumber.HasValue && !episodeParseResult.EpisodeNumbers.Contains(episodeNumber.Value))
|
|
||||||
{
|
|
||||||
logger.Trace("Searched episode number is not contained in post, skipping.");
|
|
||||||
item.SearchError = ReportRejectionType.WrongEpisode;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Make sure we haven't already downloaded a report with this episodenumber, if we have, skip the report.
|
|
||||||
if (searchResult.Successes.Intersect(episodeParseResult.EpisodeNumbers).Any())
|
|
||||||
{
|
|
||||||
logger.Trace("Episode has already been downloaded in this search, skipping.");
|
|
||||||
item.SearchError = ReportRejectionType.Skipped;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult);
|
|
||||||
|
|
||||||
item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
|
|
||||||
if (item.SearchError == ReportRejectionType.None)
|
|
||||||
{
|
|
||||||
logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_downloadProvider.DownloadReport(episodeParseResult))
|
|
||||||
{
|
|
||||||
notification.CurrentMessage = String.Format("{0} Added to download queue", episodeParseResult);
|
|
||||||
|
|
||||||
//Add the list of episode numbers from this release
|
|
||||||
searchResult.Successes.AddRange(episodeParseResult.EpisodeNumbers);
|
|
||||||
item.Success = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SearchHistoryItem> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, Series series, DateTime airDate)
|
|
||||||
{
|
|
||||||
var items = new List<SearchHistoryItem>();
|
|
||||||
var skip = false;
|
|
||||||
|
|
||||||
foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var item = new SearchHistoryItem
|
|
||||||
{
|
|
||||||
ReportTitle = episodeParseResult.OriginalString,
|
|
||||||
NzbUrl = episodeParseResult.NzbUrl,
|
|
||||||
Indexer = episodeParseResult.Indexer,
|
|
||||||
Quality = episodeParseResult.Quality.Quality,
|
|
||||||
Proper = episodeParseResult.Quality.Proper,
|
|
||||||
Size = episodeParseResult.Size,
|
|
||||||
Age = episodeParseResult.Age,
|
|
||||||
Language = episodeParseResult.Language
|
|
||||||
};
|
|
||||||
|
|
||||||
items.Add(item);
|
|
||||||
|
|
||||||
if (skip)
|
|
||||||
{
|
|
||||||
item.SearchError = ReportRejectionType.Skipped;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Trace("Analysing report " + episodeParseResult);
|
|
||||||
|
|
||||||
//Get the matching series
|
|
||||||
episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle);
|
|
||||||
|
|
||||||
//If series is null or doesn't match the series we're looking for return
|
|
||||||
if (episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId)
|
|
||||||
{
|
|
||||||
item.SearchError = ReportRejectionType.WrongSeries;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If parse result doesn't have an air date or it doesn't match passed in airdate, skip the report.
|
|
||||||
if (!episodeParseResult.AirDate.HasValue || episodeParseResult.AirDate.Value.Date != airDate.Date)
|
|
||||||
{
|
|
||||||
item.SearchError = ReportRejectionType.WrongEpisode;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult);
|
|
||||||
|
|
||||||
item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
|
|
||||||
if (item.SearchError == ReportRejectionType.None)
|
|
||||||
{
|
|
||||||
logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_downloadProvider.DownloadReport(episodeParseResult))
|
|
||||||
{
|
|
||||||
notification.CurrentMessage =
|
|
||||||
String.Format("{0} - {1} {2} Added to download queue",
|
|
||||||
episodeParseResult.Series.Title, episodeParseResult.AirDate.Value.ToShortDateString(), episodeParseResult.Quality);
|
|
||||||
|
|
||||||
item.Success = true;
|
|
||||||
skip = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<int> GetEpisodeNumberPrefixes(IEnumerable<int> episodeNumbers)
|
|
||||||
{
|
|
||||||
var results = new List<int>();
|
|
||||||
|
|
||||||
foreach (var i in episodeNumbers)
|
|
||||||
{
|
|
||||||
results.Add(i / 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.Distinct().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<EpisodeParseResult> PerformEpisodeSearch(Series series, int seasonNumber, int episodeNumber)
|
|
||||||
{
|
|
||||||
var reports = new List<EpisodeParseResult>();
|
|
||||||
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<EpisodeParseResult> PerformDailyEpisodeSearch(Series series, Episode episode)
|
|
||||||
{
|
|
||||||
var reports = new List<EpisodeParseResult>();
|
|
||||||
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<EpisodeParseResult> PerformPartialSeasonSearch(Series series, int seasonNumber, List<int> prefixes)
|
|
||||||
{
|
|
||||||
var reports = new List<EpisodeParseResult>();
|
|
||||||
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<EpisodeParseResult> PerformSeasonSearch(Series series, int seasonNumber)
|
|
||||||
{
|
|
||||||
var reports = new List<EpisodeParseResult>();
|
|
||||||
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;
|
|
||||||
title = title.Replace("&", "and");
|
|
||||||
}
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,580 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Model;
|
||||||
|
using NzbDrone.Core.Model.Notification;
|
||||||
|
using NzbDrone.Core.Providers.DecisionEngine;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Search;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Providers
|
||||||
|
{
|
||||||
|
public class SearchProvider2
|
||||||
|
{
|
||||||
|
private readonly EpisodeProvider _episodeProvider;
|
||||||
|
private readonly DownloadProvider _downloadProvider;
|
||||||
|
private readonly SeriesProvider _seriesProvider;
|
||||||
|
private readonly IndexerProvider _indexerProvider;
|
||||||
|
private readonly SceneMappingProvider _sceneMappingProvider;
|
||||||
|
private readonly UpgradePossibleSpecification _upgradePossibleSpecification;
|
||||||
|
private readonly AllowedDownloadSpecification _allowedDownloadSpecification;
|
||||||
|
private readonly SearchHistoryProvider _searchHistoryProvider;
|
||||||
|
|
||||||
|
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public SearchProvider2(EpisodeProvider episodeProvider, DownloadProvider downloadProvider, SeriesProvider seriesProvider,
|
||||||
|
IndexerProvider indexerProvider, SceneMappingProvider sceneMappingProvider,
|
||||||
|
UpgradePossibleSpecification upgradePossibleSpecification, AllowedDownloadSpecification allowedDownloadSpecification,
|
||||||
|
SearchHistoryProvider searchHistoryProvider)
|
||||||
|
{
|
||||||
|
_episodeProvider = episodeProvider;
|
||||||
|
_downloadProvider = downloadProvider;
|
||||||
|
_seriesProvider = seriesProvider;
|
||||||
|
_indexerProvider = indexerProvider;
|
||||||
|
_sceneMappingProvider = sceneMappingProvider;
|
||||||
|
_upgradePossibleSpecification = upgradePossibleSpecification;
|
||||||
|
_allowedDownloadSpecification = allowedDownloadSpecification;
|
||||||
|
_searchHistoryProvider = searchHistoryProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchProvider2()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual List<int> SeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
|
||||||
|
{
|
||||||
|
var searchResult = new SearchHistory
|
||||||
|
{
|
||||||
|
SearchTime = DateTime.Now,
|
||||||
|
SeriesId = seriesId,
|
||||||
|
SeasonNumber = seasonNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
var series = _seriesProvider.GetSeries(seriesId);
|
||||||
|
|
||||||
|
if (series == null)
|
||||||
|
{
|
||||||
|
logger.Error("Unable to find an series {0} in database", seriesId);
|
||||||
|
return new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.IsDaily)
|
||||||
|
{
|
||||||
|
logger.Trace("Daily series detected, skipping season search: {0}", series.Title);
|
||||||
|
return new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
|
||||||
|
|
||||||
|
List<EpisodeParseResult> 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<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
reports = PerformSeasonSearch(series, sceneSeasonNumbers.First());
|
||||||
|
|
||||||
|
reports.Where(p => p.FullSeason && p.SeasonNumber == sceneSeasonNumbers.First()).ToList().ForEach(
|
||||||
|
e => e.EpisodeNumbers = sceneEpisodeNumbers.ToList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reports = PerformSeasonSearch(series, seasonNumber);
|
||||||
|
|
||||||
|
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<int>();
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Processing search results";
|
||||||
|
|
||||||
|
searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, series, seasonNumber);
|
||||||
|
_searchHistoryProvider.Add(searchResult);
|
||||||
|
|
||||||
|
return searchResult.Successes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual List<int> PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
|
||||||
|
{
|
||||||
|
var searchResult = new SearchHistory
|
||||||
|
{
|
||||||
|
SearchTime = DateTime.Now,
|
||||||
|
SeriesId = seriesId,
|
||||||
|
SeasonNumber = seasonNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
var series = _seriesProvider.GetSeries(seriesId);
|
||||||
|
|
||||||
|
if (series == null)
|
||||||
|
{
|
||||||
|
logger.Error("Unable to find an series {0} in database", seriesId);
|
||||||
|
return new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.IsDaily)
|
||||||
|
{
|
||||||
|
logger.Trace("Daily series detected, skipping season search: {0}", series.Title);
|
||||||
|
return new List<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
|
||||||
|
var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber);
|
||||||
|
|
||||||
|
List<EpisodeParseResult> 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<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
return new List<int>();
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Processing search results";
|
||||||
|
searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, series, seasonNumber);
|
||||||
|
|
||||||
|
_searchHistoryProvider.Add(searchResult);
|
||||||
|
return searchResult.Successes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool EpisodeSearch(ProgressNotification notification, int episodeId)
|
||||||
|
{
|
||||||
|
var episode = _episodeProvider.GetEpisode(episodeId);
|
||||||
|
|
||||||
|
if (episode == null)
|
||||||
|
{
|
||||||
|
logger.Error("Unable to find an episode {0} in database", episodeId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_upgradePossibleSpecification.IsSatisfiedBy(episode))
|
||||||
|
{
|
||||||
|
logger.Info("Search for {0} was aborted, file in disk meets or exceeds Profile's Cutoff", episode);
|
||||||
|
notification.CurrentMessage = String.Format("Skipping search for {0}, the file you have is already at cutoff", episode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Looking for " + episode;
|
||||||
|
List<EpisodeParseResult> reports;
|
||||||
|
|
||||||
|
var searchResult = new SearchHistory
|
||||||
|
{
|
||||||
|
SearchTime = DateTime.Now,
|
||||||
|
SeriesId = episode.Series.SeriesId,
|
||||||
|
EpisodeId = episodeId
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (searchResult.SearchHistoryItems.Any(r => r.Success))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (episode.Series.UseSceneNumbering)
|
||||||
|
{
|
||||||
|
var seasonNumber = episode.SceneSeasonNumber;
|
||||||
|
var episodeNumber = episode.SceneEpisodeNumber;
|
||||||
|
|
||||||
|
if (seasonNumber == 0 && episodeNumber == 0)
|
||||||
|
{
|
||||||
|
seasonNumber = episode.SeasonNumber;
|
||||||
|
episodeNumber = episode.EpisodeNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
reports = PerformEpisodeSearch(episode.Series, seasonNumber, episodeNumber);
|
||||||
|
|
||||||
|
searchResult.SearchHistoryItems = ProcessSearchResults(
|
||||||
|
notification,
|
||||||
|
reports,
|
||||||
|
searchResult,
|
||||||
|
episode.Series,
|
||||||
|
seasonNumber,
|
||||||
|
episodeNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
_searchHistoryProvider.Add(searchResult);
|
||||||
|
|
||||||
|
if (searchResult.SearchHistoryItems.Any(r => r.Success))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reports = PerformEpisodeSearch(episode.Series, episode.SeasonNumber, episode.EpisodeNumber);
|
||||||
|
|
||||||
|
searchResult.SearchHistoryItems = ProcessSearchResults(notification, reports, searchResult, episode.Series, episode.SeasonNumber, episode.EpisodeNumber);
|
||||||
|
_searchHistoryProvider.Add(searchResult);
|
||||||
|
|
||||||
|
if (searchResult.SearchHistoryItems.Any(r => r.Success))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Warn("Unable to 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<SearchHistoryItem> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, SearchHistory searchResult, Series series, int seasonNumber, int? episodeNumber = null)
|
||||||
|
{
|
||||||
|
var items = new List<SearchHistoryItem>();
|
||||||
|
searchResult.Successes = new List<int>();
|
||||||
|
|
||||||
|
foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality)
|
||||||
|
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
|
||||||
|
.ThenBy(c => c.Age))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.Trace("Analysing report " + episodeParseResult);
|
||||||
|
|
||||||
|
var item = new SearchHistoryItem
|
||||||
|
{
|
||||||
|
ReportTitle = episodeParseResult.OriginalString,
|
||||||
|
NzbUrl = episodeParseResult.NzbUrl,
|
||||||
|
Indexer = episodeParseResult.Indexer,
|
||||||
|
Quality = episodeParseResult.Quality.Quality,
|
||||||
|
Proper = episodeParseResult.Quality.Proper,
|
||||||
|
Size = episodeParseResult.Size,
|
||||||
|
Age = episodeParseResult.Age,
|
||||||
|
Language = episodeParseResult.Language
|
||||||
|
};
|
||||||
|
|
||||||
|
items.Add(item);
|
||||||
|
|
||||||
|
//Get the matching series
|
||||||
|
episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle);
|
||||||
|
|
||||||
|
//If series is null or doesn't match the series we're looking for return
|
||||||
|
if (episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId)
|
||||||
|
{
|
||||||
|
logger.Trace("Unexpected series for search: {0}. Skipping.", episodeParseResult.CleanTitle);
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeries;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If SeasonNumber doesn't match or episode is not in the in the list in the parse result, skip the report.
|
||||||
|
if (episodeParseResult.SeasonNumber != seasonNumber)
|
||||||
|
{
|
||||||
|
logger.Trace("Season number does not match searched season number, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeason;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If the EpisodeNumber was passed in and it is not contained in the parseResult, skip the report.
|
||||||
|
if (episodeNumber.HasValue && !episodeParseResult.EpisodeNumbers.Contains(episodeNumber.Value))
|
||||||
|
{
|
||||||
|
logger.Trace("Searched episode number is not contained in post, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.WrongEpisode;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make sure we haven't already downloaded a report with this episodenumber, if we have, skip the report.
|
||||||
|
if (searchResult.Successes.Intersect(episodeParseResult.EpisodeNumbers).Any())
|
||||||
|
{
|
||||||
|
logger.Trace("Episode has already been downloaded in this search, skipping.");
|
||||||
|
item.SearchError = ReportRejectionType.Skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult);
|
||||||
|
|
||||||
|
item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
|
||||||
|
if (item.SearchError == ReportRejectionType.None)
|
||||||
|
{
|
||||||
|
logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_downloadProvider.DownloadReport(episodeParseResult))
|
||||||
|
{
|
||||||
|
notification.CurrentMessage = String.Format("{0} Added to download queue", episodeParseResult);
|
||||||
|
|
||||||
|
//Add the list of episode numbers from this release
|
||||||
|
searchResult.Successes.AddRange(episodeParseResult.EpisodeNumbers);
|
||||||
|
item.Success = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SearchHistoryItem> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, Series series, DateTime airDate)
|
||||||
|
{
|
||||||
|
var items = new List<SearchHistoryItem>();
|
||||||
|
var skip = false;
|
||||||
|
|
||||||
|
foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var item = new SearchHistoryItem
|
||||||
|
{
|
||||||
|
ReportTitle = episodeParseResult.OriginalString,
|
||||||
|
NzbUrl = episodeParseResult.NzbUrl,
|
||||||
|
Indexer = episodeParseResult.Indexer,
|
||||||
|
Quality = episodeParseResult.Quality.Quality,
|
||||||
|
Proper = episodeParseResult.Quality.Proper,
|
||||||
|
Size = episodeParseResult.Size,
|
||||||
|
Age = episodeParseResult.Age,
|
||||||
|
Language = episodeParseResult.Language
|
||||||
|
};
|
||||||
|
|
||||||
|
items.Add(item);
|
||||||
|
|
||||||
|
if (skip)
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.Skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Trace("Analysing report " + episodeParseResult);
|
||||||
|
|
||||||
|
//Get the matching series
|
||||||
|
episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle);
|
||||||
|
|
||||||
|
//If series is null or doesn't match the series we're looking for return
|
||||||
|
if (episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId)
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.WrongSeries;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If parse result doesn't have an air date or it doesn't match passed in airdate, skip the report.
|
||||||
|
if (!episodeParseResult.AirDate.HasValue || episodeParseResult.AirDate.Value.Date != airDate.Date)
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.WrongEpisode;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeParseResult.Episodes = _episodeProvider.GetEpisodesByParseResult(episodeParseResult);
|
||||||
|
|
||||||
|
item.SearchError = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
|
||||||
|
if (item.SearchError == ReportRejectionType.None)
|
||||||
|
{
|
||||||
|
logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_downloadProvider.DownloadReport(episodeParseResult))
|
||||||
|
{
|
||||||
|
notification.CurrentMessage =
|
||||||
|
String.Format("{0} - {1} {2} Added to download queue",
|
||||||
|
episodeParseResult.Series.Title, episodeParseResult.AirDate.Value.ToShortDateString(), episodeParseResult.Quality);
|
||||||
|
|
||||||
|
item.Success = true;
|
||||||
|
skip = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
item.SearchError = ReportRejectionType.DownloadClientFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<int> GetEpisodeNumberPrefixes(IEnumerable<int> episodeNumbers)
|
||||||
|
{
|
||||||
|
var results = new List<int>();
|
||||||
|
|
||||||
|
foreach (var i in episodeNumbers)
|
||||||
|
{
|
||||||
|
results.Add(i / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.Distinct().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EpisodeParseResult> PerformEpisodeSearch(Series series, int seasonNumber, int episodeNumber)
|
||||||
|
{
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
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<EpisodeParseResult> PerformDailyEpisodeSearch(Series series, Episode episode)
|
||||||
|
{
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
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<EpisodeParseResult> PerformPartialSeasonSearch(Series series, int seasonNumber, List<int> prefixes)
|
||||||
|
{
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
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<EpisodeParseResult> PerformSeasonSearch(Series series, int seasonNumber)
|
||||||
|
{
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
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;
|
||||||
|
title = title.Replace("&", "and");
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue