diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs index 340ad3ff7..80fe1e3ac 100644 --- a/NzbDrone.Common/DiskProvider.cs +++ b/NzbDrone.Common/DiskProvider.cs @@ -106,7 +106,16 @@ public virtual IEnumerable GetFileInfos(string path, string pattern, S public virtual void MoveDirectory(string source, string destination) { - Directory.Move(source, destination); + try + { + Directory.Move(source, destination); + } + catch (Exception e) + { + e.Data.Add("Source", source); + e.Data.Add("Destination", destination); + throw; + } } public virtual void InheritFolderPermissions(string filename) diff --git a/NzbDrone.Core.Test/Framework/CoreTest.cs b/NzbDrone.Core.Test/Framework/CoreTest.cs index 877096554..b70ecf38e 100644 --- a/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using Ninject; using NzbDrone.Common; +using NzbDrone.Core.Model.Notification; using NzbDrone.Test.Common; using PetaPoco; @@ -39,7 +40,7 @@ static CoreTest() [SetUp] - public virtual void SetupBase() + public virtual void CoreTestSetup() { LiveKernel = new StandardKernel(); } @@ -59,5 +60,14 @@ protected void WithRealDb() Db = MockLib.GetEmptyDatabase(); Mocker.SetConstant(Db); } + + + protected ProgressNotification MockNotification + { + get + { + return new ProgressNotification("Mock notification"); + } + } } } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 54c46d44f..ffefc8161 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -84,6 +84,8 @@ + + @@ -101,9 +103,7 @@ - - @@ -118,7 +118,6 @@ - diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_Episode.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_Episode.cs deleted file mode 100644 index 12d640eeb..000000000 --- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_Episode.cs +++ /dev/null @@ -1,437 +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.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 -{ - [TestFixture] - // ReSharper disable InconsistentNaming - public class SearchProviderTest_Episode : CoreTest - { - [Test] - public void processResults_ParseResult_should_return_after_match() - { - var parseResults = Builder.CreateListOfSize(5) - .Build(); - - var episode = Builder.CreateNew().Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(true); - - - mocker.GetMock() - .Setup(c => c.DownloadReport(It.IsAny())).Returns(true); - - - //Act - mocker.Resolve().ProcessEpisodeSearchResults(new ProgressNotification("Test"), episode, parseResults); - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Once()); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), - Times.Once()); - } - - - [Test] - public void processResults_higher_quality_should_be_called_first() - { - var parseResults = Builder.CreateListOfSize(10) - .All().With(c => c.Quality = new Quality(QualityTypes.DVD, true)) - .Random(1).With(c => c.Quality = new Quality(QualityTypes.Bluray1080p, true)) - .Build(); - - var episode = Builder.CreateNew().Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup( - c => - c.IsQualityNeeded(It.Is(d => d.Quality.QualityType == QualityTypes.Bluray1080p))) - .Returns(true); - - mocker.GetMock() - .Setup( - c => - c.DownloadReport(It.Is(d => d.Quality.QualityType == QualityTypes.Bluray1080p))) - .Returns(true); - - //Act - mocker.Resolve().ProcessEpisodeSearchResults(new ProgressNotification("Test"), episode, parseResults); - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Once()); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), - Times.Once()); - } - - - [Test] - public void processResults_when_same_quality_proper_should_be_called_first() - { - var parseResults = Builder.CreateListOfSize(20) - .All().With(c => c.Quality = new Quality(QualityTypes.DVD, false)) - .Random(1).With(c => c.Quality = new Quality(QualityTypes.DVD, true)) - .Build(); - - parseResults.Where(c => c.Quality.Proper).Should().HaveCount(1); - - var episode = Builder.CreateNew().Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.Is(p => p.Quality.Proper))).Returns(true); - - mocker.GetMock() - .Setup(c => c.DownloadReport(It.Is(p => p.Quality.Proper))).Returns(true); - - - //Act - mocker.Resolve().ProcessEpisodeSearchResults(new ProgressNotification("Test"), episode, parseResults); - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Once()); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), - Times.Once()); - } - - - [Test] - public void processResults_when_not_needed_should_check_the_rest() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew().Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(false); - - //Act - mocker.Resolve().ProcessEpisodeSearchResults(new ProgressNotification("Test"), episode, parseResults); - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Exactly(4)); - ExceptionVerification.ExcpectedWarns(1); - } - - - [Test] - public void processResults_failed_IsNeeded_should_check_the_rest() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew().Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Throws(new Exception()); - - //Act - mocker.Resolve().ProcessEpisodeSearchResults(new ProgressNotification("Test"), episode, parseResults); - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Exactly(4)); - ExceptionVerification.ExcpectedErrors(4); - ExceptionVerification.ExcpectedWarns(1); - } - - [Test] - public void processResults_failed_download_should_not_check_the_rest() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew().Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(true); - - mocker.GetMock() - .Setup(c => c.DownloadReport(It.IsAny())).Throws(new Exception()); - - //Act - mocker.Resolve().ProcessEpisodeSearchResults(new ProgressNotification("Test"), episode, parseResults); - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Exactly(1)); - - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), - Times.Exactly(1)); - - ExceptionVerification.ExcpectedErrors(1); - } - - [Test] - public void start_should_search_all_providers() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew() - .With(c => c.Series = Builder.CreateNew().Build()) - .With(c => c.SeasonNumber = 12) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.GetEpisode(episode.EpisodeId)) - .Returns(episode); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchEpisode(episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchEpisode(episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - var indexers = new List { indexer1.Object, indexer2.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(It.IsAny())) - .Returns(episode.Series); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(false); - - mocker.GetMock() - .Setup(s => s.GetSceneName(It.IsAny())).Returns(""); - - //Act - mocker.Resolve().EpisodeSearch(new ProgressNotification("Test"), episode.EpisodeId); - - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Exactly(8)); - ExceptionVerification.ExcpectedWarns(1); - indexer1.VerifyAll(); - indexer2.VerifyAll(); - } - - [Test] - public void start_should_use_scene_name_to_search() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew() - .With(c => c.Series = Builder.CreateNew().With(s => s.SeriesId = 71256).Build()) - .With(c => c.SeasonNumber = 12) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.GetEpisode(episode.EpisodeId)) - .Returns(episode); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchEpisode("The Daily Show", episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchEpisode("The Daily Show", episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - var indexers = new List { indexer1.Object, indexer2.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(It.IsAny())) - .Returns(episode.Series); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(false); - - mocker.GetMock() - .Setup(s => s.GetSceneName(71256)).Returns("The Daily Show"); - - //Act - mocker.Resolve().EpisodeSearch(new ProgressNotification("Test"), episode.EpisodeId); - - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Exactly(8)); - ExceptionVerification.ExcpectedWarns(1); - indexer1.VerifyAll(); - indexer2.VerifyAll(); - } - - [Test] - public void start_failed_indexer_should_not_break_job() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew() - .With(c => c.Series = Builder.CreateNew().Build()) - .With(c => c.SeasonNumber = 12) - .Build(); - - var mocker = new AutoMoqer(); - - mocker.GetMock() - .Setup(c => c.GetEpisode(episode.EpisodeId)) - .Returns(episode); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchEpisode(episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchEpisode(episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber)) - .Throws(new Exception()).Verifiable(); - - var indexer3 = new Mock(); - indexer3.Setup(c => c.FetchEpisode(episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - - var indexers = new List { indexer1.Object, indexer2.Object, indexer3.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(It.IsAny())) - .Returns(episode.Series); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(false); - - mocker.GetMock() - .Setup(s => s.GetSceneName(It.IsAny())).Returns(""); - - //Act - mocker.Resolve().EpisodeSearch(new ProgressNotification("Test"), episode.EpisodeId); - - - //Assert - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), - Times.Exactly(8)); - - ExceptionVerification.ExcpectedWarns(1); - ExceptionVerification.ExcpectedErrors(1); - indexer1.VerifyAll(); - indexer2.VerifyAll(); - indexer3.VerifyAll(); - } - - [Test] - public void start_no_episode_found_should_return_with_error_logged() - { - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.GetEpisode(It.IsAny())) - .Returns(null); - - //Act - mocker.Resolve().EpisodeSearch(new ProgressNotification("Test"), 12); - - - //Assert - mocker.VerifyAllMocks(); - ExceptionVerification.ExcpectedErrors(1); - } - - [Test] - public void episode_search_should_call_get_series() - { - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var episode = Builder.CreateNew() - .With(c => c.Series = Builder.CreateNew().Build()) - .With(c => c.SeasonNumber = 12) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - mocker.GetMock() - .Setup(c => c.GetEpisode(episode.EpisodeId)) - .Returns(episode); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchEpisode(episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber)) - .Returns(parseResults).Verifiable(); - - var indexers = new List { indexer1.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.IsQualityNeeded(It.IsAny())).Returns(false); - - mocker.GetMock() - .Setup(s => s.GetSceneName(It.IsAny())).Returns(""); - - mocker.GetMock() - .Setup(s => s.GetSeries(It.IsAny())).Returns(episode.Series); - - //Act - mocker.Resolve().EpisodeSearch(new ProgressNotification("Test"), episode.EpisodeId); - - - //Assert - mocker.VerifyAllMocks(); - ExceptionVerification.ExcpectedWarns(1); - indexer1.VerifyAll(); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_PartialSeason.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_PartialSeason.cs deleted file mode 100644 index cfb761ca0..000000000 --- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_PartialSeason.cs +++ /dev/null @@ -1,209 +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.Indexer; -using NzbDrone.Core.Repository; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common.AutoMoq; - -namespace NzbDrone.Core.Test.ProviderTests -{ - [TestFixture] - // ReSharper disable InconsistentNaming - public class SearchProviderTest_PartialSeason : CoreTest - { - [Test] - public void SeasonPartialSearch_season_success() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var episodes = Builder.CreateListOfSize(5) - .All() - .With(e => e.Series = series) - .With(e => e.SeriesId = 1) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .All() - .With(e => e.EpisodeNumbers = Builder.CreateListOfSize(2).Build().ToList()) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) - .Returns(parseResults).Verifiable(); - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) - .Returns(parseResults).Verifiable(); - - var indexers = new List { indexer1.Object, indexer2.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(1)).Returns(series); - - mocker.GetMock() - .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); - - mocker.GetMock() - .Setup(s => s.GetSceneName(1)).Returns(String.Empty); - - mocker.GetMock() - .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(true); - - mocker.GetMock() - .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); - - //Act - var result = mocker.Resolve().PartialSeasonSearch(notification, 1, 1); - - //Assert - result.Should().HaveCount(16); - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Exactly(8)); - } - - [Test] - public void SeasonPartialSearch_season_no_results() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var episodes = Builder.CreateListOfSize(5) - .All() - .With(e => e.Series = series) - .With(e => e.SeriesId = 1) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .All() - .With(e => e.EpisodeNumbers = Builder.CreateListOfSize(2).Build().ToList()) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) - .Returns(new List()).Verifiable(); - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0)) - .Returns(new List()).Verifiable(); - - var indexers = new List { indexer1.Object, indexer2.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(1)).Returns(series); - - mocker.GetMock() - .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); - - mocker.GetMock() - .Setup(s => s.GetSceneName(1)).Returns(String.Empty); - - //Act - var result = mocker.Resolve().PartialSeasonSearch(notification, 1, 1); - - //Assert - result.Should().HaveCount(0); - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Never()); - } - - [Test] - public void ProcessPartialSeasonSearchResults_success() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .All() - .With(e => e.EpisodeNumbers = Builder.CreateListOfSize(2).Build().ToList()) - .With(e => e.Series = series) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - mocker.GetMock() - .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(true); - - mocker.GetMock() - .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); - - //Act - var result = mocker.Resolve().ProcessPartialSeasonSearchResults(notification, parseResults); - - //Assert - result.Should().HaveCount(8); - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Exactly(4)); - - } - - [Test] - public void ProcessPartialSeasonSearchResults_failure() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .TheFirst(1) - .With(p => p.CleanTitle = "title") - .With(p => p.SeasonNumber = 1) - .With(p => p.FullSeason = true) - .With(p => p.EpisodeNumbers = null) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - mocker.GetMock() - .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(false); - - //Act - var result = mocker.Resolve().ProcessPartialSeasonSearchResults(notification, parseResults); - - //Assert - result.Should().HaveCount(0); - mocker.VerifyAllMocks(); - mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), Times.Never()); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_Season.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_Season.cs deleted file mode 100644 index f71bc1b99..000000000 --- a/NzbDrone.Core.Test/ProviderTests/SearchProviderTest_Season.cs +++ /dev/null @@ -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.Model.Notification; -using NzbDrone.Core.Providers; -using NzbDrone.Core.Providers.Indexer; -using NzbDrone.Core.Repository; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common; -using NzbDrone.Test.Common.AutoMoq; - -namespace NzbDrone.Core.Test.ProviderTests -{ - [TestFixture] - // ReSharper disable InconsistentNaming - public class SearchProviderTest_Season : CoreTest - { - [Test] - public void SeasonSearch_season_success() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var episodes = Builder.CreateListOfSize(5) - .All() - .With(e => e.Series = series) - .With(e => e.SeriesId = 1) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .TheFirst(1) - .With(p => p.CleanTitle = "title") - .With(p => p.SeasonNumber = 1) - .With(p => p.FullSeason = true) - .With(p => p.EpisodeNumbers = null) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchSeason(episodes[0].Series.Title, episodes[0].SeasonNumber)) - .Returns(parseResults).Verifiable(); - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchSeason(episodes[0].Series.Title, episodes[0].SeasonNumber)) - .Returns(parseResults).Verifiable(); - - var indexers = new List { indexer1.Object, indexer2.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(1)).Returns(series); - - mocker.GetMock() - .Setup(c => c.GetEpisodeNumbersBySeason(1, 1)).Returns(episodes.Select(e => e.EpisodeNumber).ToList()); - - mocker.GetMock() - .Setup(s => s.GetSceneName(1)).Returns(String.Empty); - - mocker.GetMock() - .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(true); - - mocker.GetMock() - .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); - - //Act - var result = mocker.Resolve().SeasonSearch(notification, 1, 1); - - //Assert - result.Should().BeTrue(); - mocker.VerifyAllMocks(); - } - - [Test] - public void SeasonSearch_season_failure() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var episodes = Builder.CreateListOfSize(5) - .All() - .With(e => e.Series = series) - .With(e => e.SeriesId = 1) - .With(e => e.SeasonNumber = 1) - .With(e => e.Ignored = false) - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - var indexer1 = new Mock(); - indexer1.Setup(c => c.FetchSeason(episodes[0].Series.Title, episodes[0].SeasonNumber)) - .Returns(parseResults).Verifiable(); - - var indexer2 = new Mock(); - indexer2.Setup(c => c.FetchSeason(episodes[0].Series.Title, episodes[0].SeasonNumber)) - .Returns(parseResults).Verifiable(); - - var indexers = new List { indexer1.Object, indexer2.Object }; - - mocker.GetMock() - .Setup(c => c.GetEnabledIndexers()) - .Returns(indexers); - - mocker.GetMock() - .Setup(c => c.GetSeries(1)).Returns(series); - - mocker.GetMock() - .Setup(c => c.GetEpisodeNumbersBySeason(1, 1)).Returns(episodes.Select(e => e.EpisodeNumber).ToList()); - - mocker.GetMock() - .Setup(s => s.GetSceneName(1)).Returns(String.Empty); - - //Act - var result = mocker.Resolve().SeasonSearch(notification, 1, 1); - - //Assert - ExceptionVerification.ExcpectedWarns(1); - result.Should().BeFalse(); - mocker.VerifyAllMocks(); - } - - [Test] - public void ProcessSeasonSearchResults_success() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .TheFirst(1) - .With(p => p.CleanTitle = "title") - .With(p => p.SeasonNumber = 1) - .With(p => p.FullSeason = true) - .With(p => p.EpisodeNumbers = null) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - mocker.GetMock() - .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(true); - - mocker.GetMock() - .Setup(s => s.DownloadReport(It.IsAny())).Returns(true); - - //Act - var result = mocker.Resolve().ProcessSeasonSearchResults(notification, series, 1, parseResults); - - //Assert - result.Should().BeTrue(); - mocker.VerifyAllMocks(); - } - - [Test] - public void ProcessSeasonSearchResults_failure() - { - var series = Builder.CreateNew() - .With(s => s.SeriesId = 1) - .With(s => s.Title = "Title1") - .Build(); - - var parseResults = Builder.CreateListOfSize(4) - .TheFirst(1) - .With(p => p.CleanTitle = "title") - .With(p => p.SeasonNumber = 1) - .With(p => p.FullSeason = true) - .With(p => p.EpisodeNumbers = null) - .Build(); - - var mocker = new AutoMoqer(MockBehavior.Strict); - - var notification = new ProgressNotification("Season Search"); - - mocker.GetMock() - .Setup(s => s.IsQualityNeeded(It.IsAny())).Returns(false); - - //Act - var result = mocker.Resolve().ProcessSeasonSearchResults(notification, series, 1, parseResults); - - //Assert - result.Should().BeFalse(); - ExceptionVerification.ExcpectedWarns(1); - mocker.VerifyAllMocks(); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs new file mode 100644 index 000000000..0a132b11e --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/PerformSearchFixture.cs @@ -0,0 +1,264 @@ +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 PARSE_RESULT_COUNT = 10; + + private Mock _episodeIndexer1; + private Mock _episodeIndexer2; + private Mock _brokenIndexer; + private Mock _nullIndexer; + + private List _indexers; + + private Series _series; + private IList _episodes; + + [SetUp] + public void Setup() + { + var parseResults = Builder.CreateListOfSize(PARSE_RESULT_COUNT) + .Build(); + + _series = Builder.CreateNew() + .Build(); + + _episodes = Builder.CreateListOfSize(1) + .Build(); + + BuildIndexers(parseResults); + + _indexers = new List { _episodeIndexer1.Object, _episodeIndexer2.Object }; + + Mocker.GetMock() + .Setup(c => c.GetEnabledIndexers()) + .Returns(_indexers); + } + + private void BuildIndexers(IList parseResults) + { + _episodeIndexer1 = new Mock(); + _episodeIndexer1.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(parseResults); + _episodeIndexer1.Setup(c => c.FetchSeason(It.IsAny(), It.IsAny())) + .Returns(parseResults); + _episodeIndexer1.Setup(c => c.FetchPartialSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(parseResults); + + + _episodeIndexer2 = new Mock(); + _episodeIndexer2.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(parseResults); + _episodeIndexer2.Setup(c => c.FetchSeason(It.IsAny(), It.IsAny())) + .Returns(parseResults); + _episodeIndexer2.Setup(c => c.FetchPartialSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(parseResults); + + _brokenIndexer = new Mock(); + _brokenIndexer.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(new Exception()); + + _nullIndexer = new Mock(); + _nullIndexer.Setup(c => c.FetchEpisode(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns>(null); + } + + private void WithTwoGoodOneBrokenIndexer() + { + _indexers = new List { _episodeIndexer1.Object, _brokenIndexer.Object, _episodeIndexer2.Object }; + + Mocker.GetMock() + .Setup(c => c.GetEnabledIndexers()) + .Returns(_indexers); + } + + private void WithNullIndexers() + { + _indexers = new List { _nullIndexer.Object, _nullIndexer.Object }; + + Mocker.GetMock() + .Setup(c => c.GetEnabledIndexers()) + .Returns(_indexers); + } + + private void WithSceneName() + { + Mocker.GetMock() + .Setup(s => s.GetSceneName(_series.SeriesId)).Returns(SCENE_NAME); + } + + private void With30Episodes() + { + _episodes = Builder.CreateListOfSize(30) + .Build(); + } + + private void WithNullEpisodes() + { + _episodes = null; + } + + private void VerifyFetchEpisode(Times times) + { + _episodeIndexer1.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny()) + , times); + + _episodeIndexer2.Verify(v => v.FetchEpisode(_series.Title, SEASON_NUMBER, It.IsAny()) + , times); + } + + private void VerifyFetchEpisodeWithSceneName(Times times) + { + _episodeIndexer1.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny()) + , times); + + _episodeIndexer2.Verify(v => v.FetchEpisode(SCENE_NAME, SEASON_NUMBER, It.IsAny()) + , times); + } + + private void VerifyFetchEpisodeBrokenIndexer(Times times) + { + _brokenIndexer.Verify(v => v.FetchEpisode(It.IsAny(), SEASON_NUMBER, It.IsAny()) + , times); + } + + private void VerifyFetchPartialSeason(Times times) + { + _episodeIndexer1.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny()) + , times); + + _episodeIndexer2.Verify(v => v.FetchPartialSeason(_series.Title, SEASON_NUMBER, It.IsAny()) + , 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().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + //Assert + VerifyFetchEpisode(Times.Once()); + result.Should().HaveCount(PARSE_RESULT_COUNT * 2); + } + + [Test] + public void PerformSearch_should_look_for_scene_name_to_search() + { + WithSceneName(); + + //Act + Mocker.Resolve().PerformSearch(MockNotification, _series, 1, _episodes); + + //Assert + Mocker.GetMock().Verify(c => c.GetSceneName(_series.SeriesId), + Times.Once()); + } + + [Test] + public void broken_indexer_should_not_block_other_indexers() + { + //Setup + WithTwoGoodOneBrokenIndexer(); + + //Act + var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + //Assert + result.Should().HaveCount(PARSE_RESULT_COUNT * 2); + + VerifyFetchEpisode(Times.Once()); + VerifyFetchEpisodeBrokenIndexer(Times.Once()); + + Mocker.GetMock().Verify(c => c.GetSceneName(_series.SeriesId), + Times.Once()); + + ExceptionVerification.ExcpectedErrors(1); + } + + [Test] + public void PerformSearch_for_episode_should_call_FetchEpisode() + { + //Act + var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + //Assert + result.Should().HaveCount(PARSE_RESULT_COUNT * 2); + + VerifyFetchEpisode(Times.Once()); + } + + [Test] + public void PerformSearch_for_partial_season_should_call_FetchPartialSeason() + { + With30Episodes(); + + //Act + var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + //Assert + result.Should().HaveCount(80); + VerifyFetchPartialSeason(Times.Exactly(4)); + } + + [Test] + public void PerformSearch_for_season_should_call_FetchSeason() + { + WithNullEpisodes(); + + //Act + var result = Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + //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().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + //Assert + result.Should().HaveCount(0); + ExceptionVerification.ExcpectedErrors(2); + } + + [Test] + public void should_use_scene_name_to_search_for_episode_when_avilable() + { + WithSceneName(); + + Mocker.Resolve().PerformSearch(MockNotification, _series, SEASON_NUMBER, _episodes); + + VerifyFetchEpisodeWithSceneName(Times.Once()); + } + + } +} diff --git a/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/ProcessSearchResultsFixture.cs b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/ProcessSearchResultsFixture.cs new file mode 100644 index 000000000..ec9a87602 --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/SearchProviderTests/ProcessSearchResultsFixture.cs @@ -0,0 +1,289 @@ +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.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 ProcessSearchResultsFixture : CoreTest + { + private Series _matchingSeries = null; + private Series _mismatchedSeries = null; + private Series _nullSeries = null; + + [SetUp] + public void setup() + { + _matchingSeries = Builder.CreateNew() + .With(s => s.SeriesId = 79488) + .With(s => s.Title = "30 Rock") + .Build(); + + _mismatchedSeries = Builder.CreateNew() + .With(s => s.SeriesId = 12345) + .With(s => s.Title = "Not 30 Rock") + .Build(); + } + + private void WithMatchingSeries() + { + Mocker.GetMock() + .Setup(s => s.FindSeries(It.IsAny())).Returns(_matchingSeries); + } + + private void WithMisMatchedSeries() + { + Mocker.GetMock() + .Setup(s => s.FindSeries(It.IsAny())).Returns(_mismatchedSeries); + } + + private void WithNullSeries() + { + Mocker.GetMock() + .Setup(s => s.FindSeries(It.IsAny())).Returns(_nullSeries); + } + + private void WithSuccessfulDownload() + { + Mocker.GetMock() + .Setup(s => s.DownloadReport(It.IsAny())) + .Returns(true); + } + + private void WithFailingDownload() + { + Mocker.GetMock() + .Setup(s => s.DownloadReport(It.IsAny())) + .Returns(false); + } + + private void WithQualityNeeded() + { + Mocker.GetMock() + .Setup(s => s.IsQualityNeeded(It.IsAny())) + .Returns(true); + } + + private void WithQualityNotNeeded() + { + Mocker.GetMock() + .Setup(s => s.IsQualityNeeded(It.IsAny())) + .Returns(false); + } + + [Test] + public void processSearchResults_higher_quality_should_be_called_first() + { + var parseResults = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 1 }) + .With(c => c.Quality = new Quality(QualityTypes.DVD, true)) + .Random(1) + .With(c => c.Quality = new Quality(QualityTypes.Bluray1080p, true)) + .Build(); + + WithMatchingSeries(); + WithSuccessfulDownload(); + + Mocker.GetMock() + .Setup(s => s.IsQualityNeeded(It.Is(d => d.Quality.QualityType == QualityTypes.Bluray1080p))) + .Returns(true); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1); + + //Assert + result.Should().HaveCount(1); + result.First().Should().Be(1); + + Mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), + Times.Once()); + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Once()); + } + + [Test] + public void processSearchResults_when_quality_is_not_needed_should_check_the_rest() + { + var parseResults = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 1 }) + .With(c => c.Quality = new Quality(QualityTypes.DVD, true)) + .Build(); + + WithMatchingSeries(); + WithQualityNotNeeded(); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1); + + //Assert + result.Should().HaveCount(0); + + Mocker.GetMock().Verify(c => c.IsQualityNeeded(It.IsAny()), + Times.Exactly(5)); + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Never()); + } + + [Test] + public void processSearchResults_should_skip_if_series_is_null() + { + var parseResults = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 1 }) + .Build(); + + WithNullSeries(); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1); + + //Assert + result.Should().HaveCount(0); + + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Never()); + } + + [Test] + public void processSearchResults_should_skip_if_series_is_mismatched() + { + var parseResults = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 1 }) + .Build(); + + WithMisMatchedSeries(); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1); + + //Assert + result.Should().HaveCount(0); + + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Never()); + } + + [Test] + public void processSearchResults_should_skip_if_season_doesnt_match() + { + var parseResults = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 2) + .With(e => e.EpisodeNumbers = new List { 1 }) + .Build(); + + WithMatchingSeries(); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1); + + //Assert + result.Should().HaveCount(0); + + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Never()); + } + + [Test] + public void processSearchResults_should_skip_if_episodeNumber_doesnt_match() + { + var parseResults = Builder.CreateListOfSize(5) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 2 }) + .Build(); + + WithMatchingSeries(); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1); + + //Assert + result.Should().HaveCount(0); + + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Never()); + } + + [Test] + public void processSearchResults_should_skip_if_any_episodeNumber_was_already_added_to_download_queue() + { + var parseResults = Builder.CreateListOfSize(2) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 5 }) + .With(c => c.Quality = new Quality(QualityTypes.DVD, true)) + .TheLast(1) + .With(e => e.EpisodeNumbers = new List { 1,2,3,4,5 }) + .Build(); + + WithMatchingSeries(); + WithQualityNeeded(); + WithSuccessfulDownload(); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1); + + //Assert + result.Should().HaveCount(1); + + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Once()); + } + + [Test] + public void processSearchResults_should_try_next_if_download_fails() + { + var parseResults = Builder.CreateListOfSize(2) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumbers = new List { 1 }) + .With(c => c.Quality = new Quality(QualityTypes.DVD, true)) + .TheLast(1) + .With(c => c.Quality = new Quality(QualityTypes.SDTV, true)) + .Build(); + + WithMatchingSeries(); + WithQualityNeeded(); + + Mocker.GetMock() + .Setup(s => s.DownloadReport(It.Is(d => d.Quality.QualityType == QualityTypes.DVD))) + .Returns(false); + + Mocker.GetMock() + .Setup(s => s.DownloadReport(It.Is(d => d.Quality.QualityType == QualityTypes.SDTV))) + .Returns(true); + + //Act + var result = Mocker.Resolve().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1); + + //Assert + result.Should().HaveCount(1); + + Mocker.GetMock().Verify(c => c.DownloadReport(It.IsAny()), + Times.Exactly(2)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Indexer/Newznab.cs b/NzbDrone.Core/Providers/Indexer/Newznab.cs index 9f12c1f6a..8c2ac0784 100644 --- a/NzbDrone.Core/Providers/Indexer/Newznab.cs +++ b/NzbDrone.Core/Providers/Indexer/Newznab.cs @@ -49,7 +49,9 @@ protected override IList GetSearchUrls(SearchModel searchModel) if (searchModel.SearchType == SearchType.SeasonSearch) { - searchUrls.Add(String.Format("{0}&limit=100&q={1}&season{2}", url, searchModel.SeriesTitle, searchModel.SeasonNumber)); + //Todo: Allow full season searching to process individual episodes + //searchUrls.Add(String.Format("{0}&limit=100&q={1}&season{2}", url, searchModel.SeriesTitle, searchModel.SeasonNumber)); + searchUrls.Add(String.Format("{0}&limit=100&q={1}+Season", url, searchModel.SeriesTitle)); } } diff --git a/NzbDrone.Core/Providers/SearchProvider.cs b/NzbDrone.Core/Providers/SearchProvider.cs index 353a05ef1..9b5f43a21 100644 --- a/NzbDrone.Core/Providers/SearchProvider.cs +++ b/NzbDrone.Core/Providers/SearchProvider.cs @@ -52,29 +52,7 @@ public virtual bool SeasonSearch(ProgressNotification notification, int seriesId notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber); - var indexers = _indexerProvider.GetEnabledIndexers(); - var reports = new List(); - - var title = _sceneMappingProvider.GetSceneName(series.SeriesId); - - if (string.IsNullOrWhiteSpace(title)) - { - title = series.Title; - } - - foreach (var indexer in indexers) - { - try - { - var indexerResults = indexer.FetchSeason(title, seasonNumber); - - reports.AddRange(indexerResults); - } - catch (Exception e) - { - Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); - } - } + var reports = PerformSearch(notification, series, seasonNumber); Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); @@ -92,51 +70,46 @@ public virtual bool SeasonSearch(ProgressNotification notification, int seriesId notification.CurrentMessage = "Processing search results"; - var reportsToProcess = reports.Where(p => p.FullSeason && p.SeasonNumber == seasonNumber).ToList(); + reports.Where(p => p.FullSeason && p.SeasonNumber == seasonNumber).ToList().ForEach( + e => e.EpisodeNumbers = episodeNumbers.ToList() + ); - reportsToProcess.ForEach(c => - { - c.Series = series; - c.EpisodeNumbers = episodeNumbers.ToList(); - }); + var downloadedEpisodes = ProcessSearchResults(notification, reports, series, seasonNumber); - return ProcessSeasonSearchResults(notification, series, seasonNumber, reportsToProcess); + downloadedEpisodes.Sort(); + episodeNumbers.ToList().Sort(); + + //Returns true if the list of downloaded episodes matches the list of episode numbers + //(either a full season release was grabbed or all individual episodes) + return (downloadedEpisodes.SequenceEqual(episodeNumbers)); } - public bool ProcessSeasonSearchResults(ProgressNotification notification, Series series, int seasonNumber, IEnumerable reports) + public virtual List PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber) { - foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality)) - { - try - { - Logger.Trace("Analysing report " + episodeParseResult); - if (_inventoryProvider.IsQualityNeeded(episodeParseResult)) - { - Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); - try - { - _downloadProvider.DownloadReport(episodeParseResult); - notification.CurrentMessage = String.Format("{0} Season {1} {2} Added to download queue", series.Title, seasonNumber, episodeParseResult.Quality); - } - 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); - } + //This method will search for episodes in a season in groups of 10 episodes S01E0, S01E1, S01E2, etc - return true; - } - } - catch (Exception e) - { - Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); - } + var series = _seriesProvider.GetSeries(seriesId); + + if (series == null) + { + Logger.Error("Unable to find an series {0} in database", seriesId); + return new List(); } - Logger.Warn("Unable to find {0} Season {1} in any of indexers.", series.Title, seasonNumber); - notification.CurrentMessage = String.Format("Unable to find {0} Season {1} in any of indexers.", series.Title, seasonNumber); + notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber); - return false; + var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); + + var reports = PerformSearch(notification, series, seasonNumber, episodes); + + Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); + + if (reports.Count == 0) + return new List(); + + notification.CurrentMessage = "Processing search results"; + + return ProcessSearchResults(notification, reports, series, seasonNumber); } public virtual bool EpisodeSearch(ProgressNotification notification, int episodeId) @@ -190,58 +163,12 @@ public virtual bool EpisodeSearch(ProgressNotification notification, int episode c.Series = series; }); - return ProcessEpisodeSearchResults(notification, episode, reports); + return (ProcessSearchResults(notification, reports, series, episode.SeasonNumber, episode.EpisodeNumber).Count == 1); } - public bool ProcessEpisodeSearchResults(ProgressNotification notification, Episode episode, IEnumerable reports) + public List PerformSearch(ProgressNotification notification, Series series, int seasonNumber, IList episodes = null) { - foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality)) - { - try - { - Logger.Trace("Analysing report " + episodeParseResult); - if (_inventoryProvider.IsQualityNeeded(episodeParseResult)) - { - Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); - try - { - _downloadProvider.DownloadReport(episodeParseResult); - notification.CurrentMessage = String.Format("{0} {1} Added to download queue", episode, episodeParseResult.Quality); - } - 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); - } - - return true; - } - } - catch (Exception e) - { - Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e); - } - } - - Logger.Warn("Unable to find {0} in any of indexers.", episode); - notification.CurrentMessage = String.Format("Unable to find {0} in any of indexers.", episode); - - return false; - } - - public virtual List PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber) - { - //This method will search for episodes in a season in groups of 10 episodes S01E0, S01E1, S01E2, etc - - var series = _seriesProvider.GetSeries(seriesId); - - if (series == null) - { - Logger.Error("Unable to find an series {0} in database", seriesId); - return new List(); - } - - notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber); + //If single episode, do a single episode search, if full season then do a full season search, otherwise, do a partial search var indexers = _indexerProvider.GetEnabledIndexers(); var reports = new List(); @@ -253,47 +180,38 @@ public virtual List PartialSeasonSearch(ProgressNotification notification, title = series.Title; } - var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber); - var episodeCount = episodes.Count; - var episodePrefix = 0; - - while(episodeCount >= 0) + foreach(var indexer in indexers) { - //Do the actual search for each indexer - foreach (var indexer in indexers) + try { - try - { - var indexerResults = indexer.FetchPartialSeason(title, seasonNumber, episodePrefix); + if (episodes == null) + reports.AddRange(indexer.FetchSeason(title, seasonNumber)); - reports.AddRange(indexerResults); - } - catch (Exception e) + else if(episodes.Count == 1) + reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodes.First().EpisodeNumber)); + + //Treat as Partial Season + else { - Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); + var prefixes = GetEpisodeNumberPrefixes(episodes.Select(s => s.EpisodeNumber)); + + foreach(var episodePrefix in prefixes) + { + reports.AddRange(indexer.FetchPartialSeason(title, seasonNumber, episodePrefix)); + } } } - episodePrefix++; - episodeCount -= 10; + catch (Exception e) + { + Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e); + } } - Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); - - if (reports.Count == 0) - return new List(); - - notification.CurrentMessage = "Processing search results"; - - reports.ForEach(c => - { - c.Series = series; - }); - - return ProcessPartialSeasonSearchResults(notification, reports); + return reports; } - public List ProcessPartialSeasonSearchResults(ProgressNotification notification, IEnumerable reports) + public List ProcessSearchResults(ProgressNotification notification, IEnumerable reports, Series series, int seasonNumber, int? episodeNumber = null) { var successes = new List(); @@ -302,17 +220,41 @@ public List ProcessPartialSeasonSearchResults(ProgressNotification notifica try { 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) + 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) + 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)) + continue; + + //Make sure we haven't already downloaded a report with this episodenumber, if we have, skip the report. + if (successes.Intersect(episodeParseResult.EpisodeNumbers).Count() > 0) + continue; + if (_inventoryProvider.IsQualityNeeded(episodeParseResult)) { Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult); try { - _downloadProvider.DownloadReport(episodeParseResult); - notification.CurrentMessage = String.Format("{0} - S{1:00}E{2:00} {3}Added to download queue", - episodeParseResult.Series.Title, episodeParseResult.SeasonNumber, episodeParseResult.EpisodeNumbers[0], episodeParseResult.Quality); + if (_downloadProvider.DownloadReport(episodeParseResult)) + { + notification.CurrentMessage = + String.Format("{0} - S{1:00}E{2:00} {3}Added to download queue", + episodeParseResult.Series.Title, episodeParseResult.SeasonNumber, + episodeParseResult.EpisodeNumbers[0], episodeParseResult.Quality); - //Add the list of episode numbers from this release - successes.AddRange(episodeParseResult.EpisodeNumbers); + //Add the list of episode numbers from this release + successes.AddRange(episodeParseResult.EpisodeNumbers); + } } catch (Exception e) { @@ -329,5 +271,17 @@ public List ProcessPartialSeasonSearchResults(ProgressNotification notifica return successes; } + + private List GetEpisodeNumberPrefixes(IEnumerable episodeNumbers) + { + var results = new List(); + + foreach (var i in episodeNumbers) + { + results.Add(i / 10); + } + + return results.Distinct().ToList(); + } } }