mirror of https://github.com/Radarr/Radarr
Added partial season searching when a full season NZB is not available.
This commit is contained in:
parent
fbb4ced77c
commit
35cad3d27e
|
@ -89,6 +89,8 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BacklogSearchJobTest.cs" />
|
<Compile Include="BacklogSearchJobTest.cs" />
|
||||||
|
<Compile Include="SeasonSearchJobTest.cs" />
|
||||||
|
<Compile Include="SearchProviderTest_PartialSeason.cs" />
|
||||||
<Compile Include="SearchJobTest.cs" />
|
<Compile Include="SearchJobTest.cs" />
|
||||||
<Compile Include="SeriesSearchJobTest.cs" />
|
<Compile Include="SeriesSearchJobTest.cs" />
|
||||||
<Compile Include="SearchProviderTest_Season.cs" />
|
<Compile Include="SearchProviderTest_Season.cs" />
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AutoMoq;
|
||||||
|
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.Jobs;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Quality;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
public class SearchProviderTest_PartialSeason : TestBase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void SeasonPartialSearch_season_success()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.SeriesId = 1)
|
||||||
|
.With(s => s.Title = "Title1")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var episodes = Builder<Episode>.CreateListOfSize(5)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.Series = series)
|
||||||
|
.Have(e => e.SeriesId = 1)
|
||||||
|
.Have(e => e.SeasonNumber = 1)
|
||||||
|
.Have(e => e.Ignored = false)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(4)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.EpisodeNumbers = Builder<int>.CreateListOfSize(2).Build().ToList())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
var indexer1 = new Mock<IndexerBase>();
|
||||||
|
indexer1.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0))
|
||||||
|
.Returns(parseResults).Verifiable();
|
||||||
|
|
||||||
|
var indexer2 = new Mock<IndexerBase>();
|
||||||
|
indexer2.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0))
|
||||||
|
.Returns(parseResults).Verifiable();
|
||||||
|
|
||||||
|
var indexers = new List<IndexerBase> { indexer1.Object, indexer2.Object };
|
||||||
|
|
||||||
|
mocker.GetMock<IndexerProvider>()
|
||||||
|
.Setup(c => c.GetEnabledIndexers())
|
||||||
|
.Returns(indexers);
|
||||||
|
|
||||||
|
mocker.GetMock<SeriesProvider>()
|
||||||
|
.Setup(c => c.GetSeries(1)).Returns(series);
|
||||||
|
|
||||||
|
mocker.GetMock<EpisodeProvider>()
|
||||||
|
.Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes);
|
||||||
|
|
||||||
|
mocker.GetMock<SceneMappingProvider>()
|
||||||
|
.Setup(s => s.GetSceneName(1)).Returns(String.Empty);
|
||||||
|
|
||||||
|
mocker.GetMock<InventoryProvider>()
|
||||||
|
.Setup(s => s.IsQualityNeeded(It.IsAny<EpisodeParseResult>())).Returns(true);
|
||||||
|
|
||||||
|
mocker.GetMock<DownloadProvider>()
|
||||||
|
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>())).Returns(true);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = mocker.Resolve<SearchProvider>().PartialSeasonSearch(notification, 1, 1);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
result.Should().HaveCount(16);
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()), Times.Exactly(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SeasonPartialSearch_season_no_results()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.SeriesId = 1)
|
||||||
|
.With(s => s.Title = "Title1")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var episodes = Builder<Episode>.CreateListOfSize(5)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.Series = series)
|
||||||
|
.Have(e => e.SeriesId = 1)
|
||||||
|
.Have(e => e.SeasonNumber = 1)
|
||||||
|
.Have(e => e.Ignored = false)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(4)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.EpisodeNumbers = Builder<int>.CreateListOfSize(2).Build().ToList())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
var indexer1 = new Mock<IndexerBase>();
|
||||||
|
indexer1.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0))
|
||||||
|
.Returns(new List<EpisodeParseResult>()).Verifiable();
|
||||||
|
|
||||||
|
var indexer2 = new Mock<IndexerBase>();
|
||||||
|
indexer2.Setup(c => c.FetchPartialSeason(episodes[0].Series.Title, episodes[0].SeasonNumber, 0))
|
||||||
|
.Returns(new List<EpisodeParseResult>()).Verifiable();
|
||||||
|
|
||||||
|
var indexers = new List<IndexerBase> { indexer1.Object, indexer2.Object };
|
||||||
|
|
||||||
|
mocker.GetMock<IndexerProvider>()
|
||||||
|
.Setup(c => c.GetEnabledIndexers())
|
||||||
|
.Returns(indexers);
|
||||||
|
|
||||||
|
mocker.GetMock<SeriesProvider>()
|
||||||
|
.Setup(c => c.GetSeries(1)).Returns(series);
|
||||||
|
|
||||||
|
mocker.GetMock<EpisodeProvider>()
|
||||||
|
.Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes);
|
||||||
|
|
||||||
|
mocker.GetMock<SceneMappingProvider>()
|
||||||
|
.Setup(s => s.GetSceneName(1)).Returns(String.Empty);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = mocker.Resolve<SearchProvider>().PartialSeasonSearch(notification, 1, 1);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
result.Should().HaveCount(0);
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ProcessPartialSeasonSearchResults_success()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.SeriesId = 1)
|
||||||
|
.With(s => s.Title = "Title1")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(4)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.EpisodeNumbers = Builder<int>.CreateListOfSize(2).Build().ToList())
|
||||||
|
.Have(e => e.Series = series)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
mocker.GetMock<InventoryProvider>()
|
||||||
|
.Setup(s => s.IsQualityNeeded(It.IsAny<EpisodeParseResult>())).Returns(true);
|
||||||
|
|
||||||
|
mocker.GetMock<DownloadProvider>()
|
||||||
|
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>())).Returns(true);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = mocker.Resolve<SearchProvider>().ProcessPartialSeasonSearchResults(notification, parseResults);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
result.Should().HaveCount(8);
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()), Times.Exactly(4));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ProcessPartialSeasonSearchResults_failure()
|
||||||
|
{
|
||||||
|
var series = Builder<Series>.CreateNew()
|
||||||
|
.With(s => s.SeriesId = 1)
|
||||||
|
.With(s => s.Title = "Title1")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(4)
|
||||||
|
.WhereTheFirst(1)
|
||||||
|
.Has(p => p.CleanTitle = "title")
|
||||||
|
.Has(p => p.SeasonNumber = 1)
|
||||||
|
.Has(p => p.FullSeason = true)
|
||||||
|
.Has(p => p.EpisodeNumbers = null)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
mocker.GetMock<InventoryProvider>()
|
||||||
|
.Setup(s => s.IsQualityNeeded(It.IsAny<EpisodeParseResult>())).Returns(false);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = mocker.Resolve<SearchProvider>().ProcessPartialSeasonSearchResults(notification, parseResults);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
result.Should().HaveCount(0);
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()), Times.Never());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,12 +79,11 @@ namespace NzbDrone.Core.Test
|
||||||
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>())).Returns(true);
|
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>())).Returns(true);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
mocker.Resolve<SearchProvider>().SeasonSearch(notification, 1, 1);
|
var result = mocker.Resolve<SearchProvider>().SeasonSearch(notification, 1, 1);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
|
result.Should().BeTrue();
|
||||||
mocker.VerifyAllMocks();
|
mocker.VerifyAllMocks();
|
||||||
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0),
|
|
||||||
Times.Never());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -134,14 +133,12 @@ namespace NzbDrone.Core.Test
|
||||||
.Setup(s => s.GetSceneName(1)).Returns(String.Empty);
|
.Setup(s => s.GetSceneName(1)).Returns(String.Empty);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
mocker.Resolve<SearchProvider>().SeasonSearch(notification, 1, 1);
|
var result = mocker.Resolve<SearchProvider>().SeasonSearch(notification, 1, 1);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
ExceptionVerification.ExcpectedWarns(1);
|
ExceptionVerification.ExcpectedWarns(1);
|
||||||
|
result.Should().BeFalse();
|
||||||
mocker.VerifyAllMocks();
|
mocker.VerifyAllMocks();
|
||||||
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0),
|
|
||||||
Times.Never());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -171,13 +168,11 @@ namespace NzbDrone.Core.Test
|
||||||
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>())).Returns(true);
|
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>())).Returns(true);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
mocker.Resolve<SearchProvider>().ProcessSeasonSearchResults(notification, series, 1, parseResults);
|
var result = mocker.Resolve<SearchProvider>().ProcessSeasonSearchResults(notification, series, 1, parseResults);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
|
result.Should().BeTrue();
|
||||||
mocker.VerifyAllMocks();
|
mocker.VerifyAllMocks();
|
||||||
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0),
|
|
||||||
Times.Never());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -204,14 +199,12 @@ namespace NzbDrone.Core.Test
|
||||||
.Setup(s => s.IsQualityNeeded(It.IsAny<EpisodeParseResult>())).Returns(false);
|
.Setup(s => s.IsQualityNeeded(It.IsAny<EpisodeParseResult>())).Returns(false);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
mocker.Resolve<SearchProvider>().ProcessSeasonSearchResults(notification, series, 1, parseResults);
|
var result = mocker.Resolve<SearchProvider>().ProcessSeasonSearchResults(notification, series, 1, parseResults);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
|
result.Should().BeFalse();
|
||||||
ExceptionVerification.ExcpectedWarns(1);
|
ExceptionVerification.ExcpectedWarns(1);
|
||||||
mocker.VerifyAllMocks();
|
mocker.VerifyAllMocks();
|
||||||
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0),
|
|
||||||
Times.Never());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AutoMoq;
|
||||||
|
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.Jobs;
|
||||||
|
using NzbDrone.Core.Repository;
|
||||||
|
using NzbDrone.Core.Repository.Quality;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
public class SeasonSearchJobTest : TestBase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void SeasonSearch_full_season_success()
|
||||||
|
{
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
mocker.GetMock<SearchProvider>()
|
||||||
|
.Setup(c => c.SeasonSearch(notification, 1, 1)).Returns(true);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
mocker.Resolve<SeasonSearchJob>().Start(notification, 1, 1);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<SearchProvider>().Verify(c => c.SeasonSearch(notification, 1, 1), Times.Once());
|
||||||
|
mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Never());
|
||||||
|
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SeasonSearch_partial_season_success()
|
||||||
|
{
|
||||||
|
var episodes = Builder<Episode>.CreateListOfSize(5)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.SeriesId = 1)
|
||||||
|
.Have(e => e.SeasonNumber = 1)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
mocker.GetMock<SearchProvider>()
|
||||||
|
.Setup(c => c.SeasonSearch(notification, 1, 1)).Returns(false);
|
||||||
|
|
||||||
|
mocker.GetMock<EpisodeProvider>()
|
||||||
|
.Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes);
|
||||||
|
|
||||||
|
mocker.GetMock<SearchProvider>()
|
||||||
|
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
|
||||||
|
.Returns(episodes.Select(e => e.EpisodeNumber).ToList());
|
||||||
|
|
||||||
|
//Act
|
||||||
|
mocker.Resolve<SeasonSearchJob>().Start(notification, 1, 1);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<SearchProvider>().Verify(c => c.SeasonSearch(notification, 1, 1), Times.Once());
|
||||||
|
mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once());
|
||||||
|
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SeasonSearch_partial_season_failure()
|
||||||
|
{
|
||||||
|
var episodes = Builder<Episode>.CreateListOfSize(5)
|
||||||
|
.WhereAll()
|
||||||
|
.Have(e => e.SeriesId = 1)
|
||||||
|
.Have(e => e.SeasonNumber = 1)
|
||||||
|
.Have(e => e.Ignored = false)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var mocker = new AutoMoqer(MockBehavior.Strict);
|
||||||
|
|
||||||
|
var notification = new ProgressNotification("Season Search");
|
||||||
|
|
||||||
|
mocker.GetMock<SearchProvider>()
|
||||||
|
.Setup(c => c.SeasonSearch(notification, 1, 1)).Returns(false);
|
||||||
|
|
||||||
|
mocker.GetMock<EpisodeProvider>()
|
||||||
|
.Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes);
|
||||||
|
|
||||||
|
mocker.GetMock<SearchProvider>()
|
||||||
|
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
|
||||||
|
.Returns(new List<int>{1});
|
||||||
|
|
||||||
|
mocker.GetMock<EpisodeSearchJob>()
|
||||||
|
.Setup(c => c.Start(notification, It.IsAny<int>(), 0)).Verifiable();
|
||||||
|
|
||||||
|
//Act
|
||||||
|
mocker.Resolve<SeasonSearchJob>().Start(notification, 1, 1);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
mocker.VerifyAllMocks();
|
||||||
|
mocker.GetMock<SearchProvider>().Verify(c => c.SeasonSearch(notification, 1, 1), Times.Once());
|
||||||
|
mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once());
|
||||||
|
mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.IsAny<int>(), 0), Times.Exactly(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ namespace NzbDrone.Core.Model.Search
|
||||||
public string SeriesTitle { get; set; }
|
public string SeriesTitle { get; set; }
|
||||||
public int EpisodeNumber { get; set; }
|
public int EpisodeNumber { get; set; }
|
||||||
public int SeasonNumber { get; set; }
|
public int SeasonNumber { get; set; }
|
||||||
|
public int EpisodePrefix { get; set; }
|
||||||
public DateTime AirDate { get; set; }
|
public DateTime AirDate { get; set; }
|
||||||
public SearchType SearchType { get; set; }
|
public SearchType SearchType { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.Model.Search
|
||||||
{
|
{
|
||||||
EpisodeSearch = 0,
|
EpisodeSearch = 0,
|
||||||
DailySearch = 1,
|
DailySearch = 1,
|
||||||
SeasonSearch = 2
|
PartialSeasonSearch = 2,
|
||||||
|
SeasonSearch = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,33 @@ namespace NzbDrone.Core.Providers.Indexer
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual IList<EpisodeParseResult> FetchPartialSeason(string seriesTitle, int seasonNumber, int episodePrefix)
|
||||||
|
{
|
||||||
|
_logger.Debug("Searching {0} for {1}-Season {2}, Prefix: {3}", Name, seriesTitle, seasonNumber, episodePrefix);
|
||||||
|
|
||||||
|
var result = new List<EpisodeParseResult>();
|
||||||
|
|
||||||
|
var searchModel = new SearchModel
|
||||||
|
{
|
||||||
|
SeriesTitle = GetQueryTitle(seriesTitle),
|
||||||
|
SeasonNumber = seasonNumber,
|
||||||
|
EpisodePrefix = episodePrefix,
|
||||||
|
SearchType = SearchType.PartialSeasonSearch
|
||||||
|
};
|
||||||
|
|
||||||
|
var searchUrls = GetSearchUrls(searchModel);
|
||||||
|
|
||||||
|
foreach (var url in searchUrls)
|
||||||
|
{
|
||||||
|
result.AddRange(Fetch(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.Where(e => e.CleanTitle == Parser.NormalizeTitle(seriesTitle)).ToList();
|
||||||
|
|
||||||
|
_logger.Info("Finished searching {0} for {1}-S{2}, Found {3}", Name, seriesTitle, seasonNumber, result.Count);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual IList<EpisodeParseResult> FetchEpisode(string seriesTitle, int seasonNumber, int episodeNumber)
|
public virtual IList<EpisodeParseResult> FetchEpisode(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||||
{
|
{
|
||||||
_logger.Debug("Searching {0} for {1}-S{2:00}E{3:00}", Name, seriesTitle, seasonNumber, episodeNumber);
|
_logger.Debug("Searching {0} for {1}-S{2:00}E{3:00}", Name, seriesTitle, seasonNumber, episodeNumber);
|
||||||
|
|
|
@ -52,6 +52,8 @@ namespace NzbDrone.Core.Providers.Indexer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchModel.SearchType == SearchType.SeasonSearch)
|
||||||
|
{
|
||||||
return new List<string>
|
return new List<string>
|
||||||
{
|
{
|
||||||
String.Format(
|
String.Format(
|
||||||
|
@ -60,6 +62,19 @@ namespace NzbDrone.Core.Providers.Indexer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchModel.SearchType == SearchType.PartialSeasonSearch)
|
||||||
|
{
|
||||||
|
return new List<string>
|
||||||
|
{
|
||||||
|
String.Format(
|
||||||
|
@"http://www.newzbin.com/search/query/?q={0}+{1}x{2}&fpn=p&searchaction=Go&category=8&{3}",
|
||||||
|
searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodePrefix, UrlParams)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
public override string Name
|
public override string Name
|
||||||
{
|
{
|
||||||
get { return "Newzbin"; }
|
get { return "Newzbin"; }
|
||||||
|
|
|
@ -51,7 +51,13 @@ namespace NzbDrone.Core.Providers.Indexer
|
||||||
searchModel.SeasonNumber, searchModel.EpisodeNumber));
|
searchModel.SeasonNumber, searchModel.EpisodeNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
if (searchModel.SearchType == SearchType.PartialSeasonSearch)
|
||||||
|
{
|
||||||
|
searchUrls.Add(String.Format("{0}&term={1}+S{2:00}E{3}",
|
||||||
|
url, searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodePrefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchModel.SearchType == SearchType.SeasonSearch)
|
||||||
{
|
{
|
||||||
searchUrls.Add(String.Format("{0}&term={1}+Season", url, searchModel.SeriesTitle));
|
searchUrls.Add(String.Format("{0}&term={1}+Season", url, searchModel.SeriesTitle));
|
||||||
searchUrls.Add(String.Format("{0}&term={1}+S{2:00}", url, searchModel.SeriesTitle, searchModel.SeasonNumber));
|
searchUrls.Add(String.Format("{0}&term={1}+S{2:00}", url, searchModel.SeriesTitle, searchModel.SeasonNumber));
|
||||||
|
|
|
@ -49,7 +49,13 @@ namespace NzbDrone.Core.Providers.Indexer
|
||||||
searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodeNumber));
|
searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodeNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
if (searchModel.SearchType == SearchType.PartialSeasonSearch)
|
||||||
|
{
|
||||||
|
searchUrls.Add(String.Format("{0}&action=search&q={1}+S{2:00}E{3}",
|
||||||
|
url, searchModel.SeriesTitle, searchModel.SeasonNumber, searchModel.EpisodePrefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchModel.SearchType == SearchType.SeasonSearch)
|
||||||
{
|
{
|
||||||
searchUrls.Add(String.Format("{0}&action=search&q={1}+Season", url, searchModel.SeriesTitle));
|
searchUrls.Add(String.Format("{0}&action=search&q={1}+Season", url, searchModel.SeriesTitle));
|
||||||
searchUrls.Add(String.Format("{0}&action=search&q={1}+S{2:00}", url, searchModel.SeriesTitle, searchModel.SeasonNumber));
|
searchUrls.Add(String.Format("{0}&action=search&q={1}+S{2:00}", url, searchModel.SeriesTitle, searchModel.SeasonNumber));
|
||||||
|
|
|
@ -59,7 +59,21 @@ namespace NzbDrone.Core.Providers.Jobs
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var episode in episodes.Where(e => !e.Ignored))
|
//Perform a Partial Season Search
|
||||||
|
var addedSeries = _searchProvider.PartialSeasonSearch(notification, targetId, secondaryTargetId);
|
||||||
|
|
||||||
|
addedSeries.Distinct().ToList().Sort();
|
||||||
|
var episodeNumbers = episodes.Select(s => s.EpisodeNumber).ToList();
|
||||||
|
episodeNumbers.Sort();
|
||||||
|
|
||||||
|
if (addedSeries.SequenceEqual(episodeNumbers))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Get the list of episodes that weren't downloaded
|
||||||
|
var missingEpisodes = episodeNumbers.Except(addedSeries).ToList();
|
||||||
|
|
||||||
|
//Only process episodes that is in missing episodes (To ensure we double check if the episode is available)
|
||||||
|
foreach (var episode in episodes.Where(e => !e.Ignored && missingEpisodes.Contains(e.EpisodeNumber)))
|
||||||
{
|
{
|
||||||
_episodeSearchJob.Start(notification, episode.EpisodeId, 0);
|
_episodeSearchJob.Start(notification, episode.EpisodeId, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,5 +229,106 @@ namespace NzbDrone.Core.Providers
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual List<int> 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<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series, seasonNumber);
|
||||||
|
|
||||||
|
var indexers = _indexerProvider.GetEnabledIndexers();
|
||||||
|
var reports = new List<EpisodeParseResult>();
|
||||||
|
|
||||||
|
var title = _sceneMappingProvider.GetSceneName(series.SeriesId);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(title))
|
||||||
|
{
|
||||||
|
title = series.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
var episodes = _episodeProvider.GetEpisodesBySeason(seriesId, seasonNumber);
|
||||||
|
var episodeCount = episodes.Count;
|
||||||
|
var episodePrefix = 0;
|
||||||
|
|
||||||
|
while(episodeCount >= 0)
|
||||||
|
{
|
||||||
|
//Do the actual search for each indexer
|
||||||
|
foreach (var indexer in indexers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var indexerResults = indexer.FetchPartialSeason(title, seasonNumber, episodePrefix);
|
||||||
|
|
||||||
|
reports.AddRange(indexerResults);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("An error has occurred while fetching items from " + indexer.Name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
episodePrefix++;
|
||||||
|
episodeCount -= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug("Finished searching all indexers. Total {0}", reports.Count);
|
||||||
|
|
||||||
|
if (reports.Count == 0)
|
||||||
|
return new List<int>();
|
||||||
|
|
||||||
|
notification.CurrentMessage = "Processing search results";
|
||||||
|
|
||||||
|
reports.ForEach(c =>
|
||||||
|
{
|
||||||
|
c.Series = series;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ProcessPartialSeasonSearchResults(notification, reports);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<int> ProcessPartialSeasonSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports)
|
||||||
|
{
|
||||||
|
var successes = new List<int>();
|
||||||
|
|
||||||
|
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} - 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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return successes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue