Episode searching now stores the results of the tests.

pull/4/head
Mark McDowall 12 years ago
parent b9e3d1a921
commit c7f8f57f77

@ -6,9 +6,11 @@ using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Model;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Repository;
using NzbDrone.Core.Repository.Search;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common.AutoMoq;
@ -39,10 +41,16 @@ namespace NzbDrone.Core.Test.JobTests
[Test]
public void SeasonSearch_partial_season_success()
{
var resultItems = Builder<SearchResultItem>.CreateListOfSize(5)
.All()
.With(e => e.SearchError = ReportRejectionType.None)
.With(e => e.Success = true)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.SeriesId = 1)
.With(e => e.SeasonNumber = 1)
.With(e => e.SeriesId = 5)
.Build();
var notification = new ProgressNotification("Season Search");
@ -55,7 +63,7 @@ namespace NzbDrone.Core.Test.JobTests
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
.Returns(episodes.Select(e => e.EpisodeNumber).ToList());
.Returns(resultItems.ToList());
//Act
Mocker.Resolve<SeasonSearchJob>().Start(notification, 1, 1);
@ -88,7 +96,7 @@ namespace NzbDrone.Core.Test.JobTests
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
.Returns(new List<int>{1});
.Returns(new List<SearchResultItem>{ new SearchResultItem{ Success = true }});
//Act
Mocker.Resolve<SeasonSearchJob>().Start(notification, 1, 1);
@ -122,7 +130,7 @@ namespace NzbDrone.Core.Test.JobTests
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
.Returns(new List<int>());
.Returns(new List<SearchResultItem> { new SearchResultItem { Success = false, SearchError = ReportRejectionType.Size} });
//Act

@ -88,42 +88,42 @@ namespace NzbDrone.Core.Test.ProviderTests.DecisionEngineTests
[Test]
public void should_be_allowed_if_all_conditions_are_met()
{
spec.IsSatisfiedBy(parseResult).Should().BeTrue();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.None);
}
[Test]
public void should_not_be_allowed_if_profile_is_not_allowed()
{
WithProfileNotAllowed();
spec.IsSatisfiedBy(parseResult).Should().BeFalse();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.QualityNotWanted);
}
[Test]
public void should_not_be_allowed_if_size_is_not_allowed()
{
WithNotAcceptableSize();
spec.IsSatisfiedBy(parseResult).Should().BeFalse();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.Size);
}
[Test]
public void should_not_be_allowed_if_disk_is_not_upgrade()
{
WithNoDiskUpgrade();
spec.IsSatisfiedBy(parseResult).Should().BeFalse();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.ExistingQualityIsEqualOrBetter);
}
[Test]
public void should_not_be_allowed_if_episode_is_already_in_queue()
{
WithEpisodeAlreadyInQueue();
spec.IsSatisfiedBy(parseResult).Should().BeFalse();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.AlreadyInQueue);
}
[Test]
public void should_not_be_allowed_if_report_is_over_retention()
{
WithOverRetention();
spec.IsSatisfiedBy(parseResult).Should().BeFalse();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.Retention);
}
[Test]
@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DecisionEngineTests
WithProfileNotAllowed();
WithOverRetention();
spec.IsSatisfiedBy(parseResult).Should().BeFalse();
spec.IsSatisfiedBy(parseResult).Should().Be(ReportRejectionType.QualityNotWanted);
}
}
}

@ -77,14 +77,14 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
{
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()))
.Returns(true);
.Returns(ReportRejectionType.None);
}
private void WithQualityNotNeeded()
{
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()))
.Returns(false);
.Returns(ReportRejectionType.ExistingQualityIsEqualOrBetter);
}
[Test]
@ -103,13 +103,13 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.Is<EpisodeParseResult>(d => d.Quality.QualityType == QualityTypes.Bluray1080p)))
.Returns(true);
.Returns(ReportRejectionType.None);
//Act
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeTrue();
result.Should().Contain(n => n.Success);
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
Times.Once());
@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeFalse();
result.Should().NotContain(n => n.Success);
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
Times.Exactly(5));
@ -155,7 +155,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeFalse();
result.Should().NotContain(n => n.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeFalse();
result.Should().NotContain(n => n.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -198,7 +198,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeTrue();
result.Should().Contain(n => n.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Once());
@ -230,7 +230,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeTrue();
result.Should().Contain(n => n.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Exactly(2));
@ -250,7 +250,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeFalse();
result.Should().NotContain(n => n.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -270,7 +270,7 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, DateTime.Today);
//Assert
result.Should().BeFalse();
result.Should().NotContain(n => n.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());

@ -73,14 +73,14 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
{
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()))
.Returns(true);
.Returns(ReportRejectionType.None);
}
private void WithQualityNotNeeded()
{
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()))
.Returns(false);
.Returns(ReportRejectionType.ExistingQualityIsEqualOrBetter);
}
[Test]
@ -102,14 +102,14 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.Is<EpisodeParseResult>(d => d.Quality.QualityType == QualityTypes.Bluray1080p)))
.Returns(true);
.Returns(ReportRejectionType.None);
//Act
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(1);
result.First().Should().Be(1);
result.Should().HaveCount(parseResults.Count);
result.Should().Contain(s => s.Success);
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
Times.Once());
@ -135,13 +135,14 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
WithSuccessfulDownload();
Mocker.GetMock<AllowedDownloadSpecification>()
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(true);
.Setup(s => s.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(ReportRejectionType.None);
//Act
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(MockNotification, parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(1);
result.Should().HaveCount(parseResults.Count);
result.Should().Contain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.Is<EpisodeParseResult>(d => d.Age != 100)), Times.Never());
@ -165,7 +166,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(0);
result.Should().HaveCount(parseResults.Count);
result.Should().NotContain(s => s.Success);
Mocker.GetMock<AllowedDownloadSpecification>().Verify(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>()),
Times.Exactly(5));
@ -188,7 +190,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(0);
result.Should().HaveCount(parseResults.Count);
result.Should().NotContain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -209,7 +212,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(0);
result.Should().HaveCount(parseResults.Count);
result.Should().NotContain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -230,7 +234,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(0);
result.Should().HaveCount(parseResults.Count);
result.Should().NotContain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -251,7 +256,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1, 1);
//Assert
result.Should().HaveCount(0);
result.Should().HaveCount(parseResults.Count);
result.Should().NotContain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Never());
@ -277,7 +283,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1);
//Assert
result.Should().HaveCount(1);
result.Should().HaveCount(parseResults.Count);
result.Should().Contain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Once());
@ -310,7 +317,8 @@ namespace NzbDrone.Core.Test.ProviderTests.SearchProviderTests
var result = Mocker.Resolve<SearchProvider>().ProcessSearchResults(new ProgressNotification("Test"), parseResults, _matchingSeries, 1);
//Assert
result.Should().HaveCount(1);
result.Should().HaveCount(parseResults.Count);
result.Should().Contain(s => s.Success);
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
Times.Exactly(2));

@ -70,7 +70,7 @@ namespace NzbDrone.Core.Jobs
try
{
if (_isMonitoredEpisodeSpecification.IsSatisfiedBy(episodeParseResult) &&
_allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult) &&
_allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult) == ReportRejectionType.None &&
_upgradeHistorySpecification.IsSatisfiedBy(episodeParseResult))
{
_downloadProvider.DownloadReport(episodeParseResult);

@ -60,15 +60,15 @@ namespace NzbDrone.Core.Jobs
//Perform a Partial Season Search
var addedSeries = _searchProvider.PartialSeasonSearch(notification, targetId, secondaryTargetId);
addedSeries.Distinct().ToList().Sort();
var episodeNumbers = episodes.Where(w => w.AirDate <= DateTime.Today.AddDays(1)).Select(s => s.EpisodeNumber).ToList();
episodeNumbers.Sort();
//addedSeries.Distinct().ToList().Sort();
//var episodeNumbers = episodes.Where(w => w.AirDate <= DateTime.Today.AddDays(1)).Select(s => s.EpisodeNumber).ToList();
//episodeNumbers.Sort();
if (addedSeries.SequenceEqual(episodeNumbers))
return;
//if (addedSeries.SequenceEqual(episodeNumbers))
// return;
//Get the list of episodes that weren't downloaded
var missingEpisodes = episodeNumbers.Except(addedSeries).ToList();
////Get the list of episodes that weren't downloaded
//var missingEpisodes = episodeNumbers.Except(addedSeries).ToList();
//TODO: do one by one check only when max number of feeds have been returned by the indexer
//Only process episodes that is in missing episodes (To ensure we double check if the episode is available)

@ -0,0 +1,21 @@
using System.Linq;
namespace NzbDrone.Core.Model
{
public enum ReportRejectionType
{
None = 0,
WrongSeries = 1,
QualityNotWanted = 2,
WrongSeason = 3,
WrongEpisode = 3,
Size = 3,
Retention = 3,
ExistingQualityIsEqualOrBetter = 4,
Cutoff = 5,
AlreadyInQueue = 6,
DownloadClientFailure = 7,
Skipped = 8,
Failure,
}
}

@ -277,6 +277,7 @@
<Compile Include="Providers\Indexer\NzbIndex.cs" />
<Compile Include="Providers\Indexer\FileSharingTalk.cs" />
<Compile Include="Providers\Indexer\Wombles.cs" />
<Compile Include="Providers\SearchResultProvider.cs" />
<Compile Include="Providers\SeasonProvider.cs" />
<Compile Include="Jobs\RecentBacklogSearchJob.cs" />
<Compile Include="Jobs\TrimLogsJob.cs" />
@ -304,6 +305,9 @@
<Compile Include="Providers\AnalyticsProvider.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Repository\Search\SearchResultItem.cs" />
<Compile Include="Repository\Search\SearchResult.cs" />
<Compile Include="Model\ReportRejectionType.cs" />
<Compile Include="Repository\Season.cs" />
<Compile Include="Providers\AutoConfigureProvider.cs">
<SubType>Code</SubType>

@ -2,6 +2,7 @@
using NLog;
using Ninject;
using NzbDrone.Core.Model;
using NzbDrone.Core.Repository.Search;
namespace NzbDrone.Core.Providers.DecisionEngine
{
@ -30,16 +31,16 @@ namespace NzbDrone.Core.Providers.DecisionEngine
{
}
public virtual bool IsSatisfiedBy(EpisodeParseResult subject)
public virtual ReportRejectionType IsSatisfiedBy(EpisodeParseResult subject)
{
if (!_qualityAllowedByProfileSpecification.IsSatisfiedBy(subject)) return false;
if (!_upgradeDiskSpecification.IsSatisfiedBy(subject)) return false;
if (!_retentionSpecification.IsSatisfiedBy(subject)) return false;
if (!_acceptableSizeSpecification.IsSatisfiedBy(subject)) return false;
if (_alreadyInQueueSpecification.IsSatisfiedBy(subject)) return false;
if (!_qualityAllowedByProfileSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.QualityNotWanted;
if (!_upgradeDiskSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.ExistingQualityIsEqualOrBetter;
if (!_retentionSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.Retention;
if (!_acceptableSizeSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.Size;
if (_alreadyInQueueSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.AlreadyInQueue;
logger.Debug("Episode {0} is needed", subject);
return true;
return ReportRejectionType.None;
}
}
}

@ -8,6 +8,7 @@ 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
{
@ -21,13 +22,15 @@ namespace NzbDrone.Core.Providers
private readonly SceneMappingProvider _sceneMappingProvider;
private readonly UpgradePossibleSpecification _upgradePossibleSpecification;
private readonly AllowedDownloadSpecification _allowedDownloadSpecification;
private readonly SearchResultProvider _searchResultProvider;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
[Inject]
public SearchProvider(EpisodeProvider episodeProvider, DownloadProvider downloadProvider, SeriesProvider seriesProvider,
IndexerProvider indexerProvider, SceneMappingProvider sceneMappingProvider,
UpgradePossibleSpecification upgradePossibleSpecification, AllowedDownloadSpecification allowedDownloadSpecification)
UpgradePossibleSpecification upgradePossibleSpecification, AllowedDownloadSpecification allowedDownloadSpecification,
SearchResultProvider searchResultProvider)
{
_episodeProvider = episodeProvider;
_downloadProvider = downloadProvider;
@ -36,6 +39,7 @@ namespace NzbDrone.Core.Providers
_sceneMappingProvider = sceneMappingProvider;
_upgradePossibleSpecification = upgradePossibleSpecification;
_allowedDownloadSpecification = allowedDownloadSpecification;
_searchResultProvider = searchResultProvider;
}
public SearchProvider()
@ -44,13 +48,20 @@ namespace NzbDrone.Core.Providers
public virtual bool SeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
{
var searchResult = new SearchResult
{
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 false;
}
}
//Return false if the series is a daily series (we only support individual episode searching
if (series.IsDaily)
@ -80,46 +91,45 @@ namespace NzbDrone.Core.Providers
e => e.EpisodeNumbers = episodeNumbers.ToList()
);
var downloadedEpisodes = ProcessSearchResults(notification, reports, series, seasonNumber);
searchResult.SearchResultItems = ProcessSearchResults(notification, reports, series, seasonNumber);
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));
return (searchResult.SearchResultItems.Select(s => s.Success).Count() == episodeNumbers.Count);
}
public virtual List<int> PartialSeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber)
public virtual List<SearchResultItem> 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 searchResult = new SearchResult
{
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>();
return new List<SearchResultItem>();
}
//Return empty list if the series is a daily series (we only support individual episode searching
if (series.IsDaily)
return new List<int>();
return new List<SearchResultItem>();
notification.CurrentMessage = String.Format("Searching for {0} Season {1}", series.Title, seasonNumber);
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<int>();
return new List<SearchResultItem>();
notification.CurrentMessage = "Processing search results";
searchResult.SearchResultItems = ProcessSearchResults(notification, reports, series, seasonNumber);
return ProcessSearchResults(notification, reports, series, seasonNumber);
_searchResultProvider.Add(searchResult);
return searchResult.SearchResultItems;
}
public virtual bool EpisodeSearch(ProgressNotification notification, int episodeId)
@ -136,7 +146,7 @@ namespace NzbDrone.Core.Providers
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}, file you have is already at cutoff", episode);
notification.CurrentMessage = String.Format("Skipping search for {0}, the file you have is already at cutoff", episode);
return false;
}
@ -145,19 +155,41 @@ namespace NzbDrone.Core.Providers
if (episode.Series.IsDaily && !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;
}
var searchResult = new SearchResult
{
SearchTime = DateTime.Now,
SeriesId = episode.Series.SeriesId
};
var reports = PerformSearch(notification, episode.Series, episode.SeasonNumber, new List<Episode> { episode });
Logger.Debug("Finished searching all indexers. Total {0}", reports.Count);
notification.CurrentMessage = "Processing search results";
if (!episode.Series.IsDaily && ProcessSearchResults(notification, reports, episode.Series, episode.SeasonNumber, episode.EpisodeNumber).Count == 1)
return true;
if (episode.Series.IsDaily)
{
searchResult.AirDate = episode.AirDate.Value;
searchResult.SearchResultItems = ProcessSearchResults(notification, reports, episode.Series, episode.AirDate.Value);
if (episode.Series.IsDaily && ProcessSearchResults(notification, reports, episode.Series, episode.AirDate.Value))
_searchResultProvider.Add(searchResult);
if (searchResult.SearchResultItems.Any(r => r.Success))
return true;
return false;
}
if (!episode.Series.IsDaily)
{
searchResult.SeasonNumber = episode.SeasonNumber;
searchResult.EpisodeId = episodeId;
ProcessSearchResults(notification, reports, episode.Series, episode.SeasonNumber, episode.EpisodeNumber);
return true;
}
Logger.Warn("Unable to find {0} in any of indexers.", episode);
@ -170,7 +202,6 @@ namespace NzbDrone.Core.Providers
notification.CurrentMessage = String.Format("Sorry, couldn't find you {0} in any of indexers.", episode);
}
return false;
}
@ -227,9 +258,10 @@ namespace NzbDrone.Core.Providers
return reports;
}
public List<int> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, Series series, int seasonNumber, int? episodeNumber = null)
public List<SearchResultItem> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, Series series, int seasonNumber, int? episodeNumber = null)
{
var successes = new List<int>();
var items = new List<SearchResultItem>();
foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality).ThenBy(c => c.Age))
{
@ -237,6 +269,14 @@ namespace NzbDrone.Core.Providers
{
Logger.Trace("Analysing report " + episodeParseResult);
var item = new SearchResultItem
{
ReportTitle = episodeParseResult.OriginalString,
NzbUrl = episodeParseResult.NzbUrl
};
items.Add(item);
//Get the matching series
episodeParseResult.Series = _seriesProvider.FindSeries(episodeParseResult.CleanTitle);
@ -244,6 +284,7 @@ namespace NzbDrone.Core.Providers
if (episodeParseResult.Series == null || episodeParseResult.Series.SeriesId != series.SeriesId)
{
Logger.Trace("Unexpected series for search: {0}. Skipping.", episodeParseResult.CleanTitle);
item.SearchError = ReportRejectionType.WrongSeries;
continue;
}
@ -251,6 +292,7 @@ namespace NzbDrone.Core.Providers
if (episodeParseResult.SeasonNumber != seasonNumber)
{
Logger.Trace("Season number does not match searched season number, skipping.");
item.SearchError = ReportRejectionType.WrongSeason;
continue;
}
@ -258,6 +300,7 @@ namespace NzbDrone.Core.Providers
if (episodeNumber.HasValue && !episodeParseResult.EpisodeNumbers.Contains(episodeNumber.Value))
{
Logger.Trace("Searched episode number is not contained in post, skipping.");
item.SearchError = ReportRejectionType.WrongEpisode;
continue;
}
@ -265,10 +308,12 @@ namespace NzbDrone.Core.Providers
if (successes.Intersect(episodeParseResult.EpisodeNumbers).Any())
{
Logger.Trace("Episode has already been downloaded in this search, skipping.");
item.SearchError = ReportRejectionType.Skipped;
continue;
}
if (_allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult))
var rejectionType = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
if (rejectionType == ReportRejectionType.None)
{
Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
try
@ -279,12 +324,18 @@ namespace NzbDrone.Core.Providers
//Add the list of episode numbers from this release
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;
}
}
}
@ -294,15 +345,32 @@ namespace NzbDrone.Core.Providers
}
}
return successes;
return items;
}
public bool ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, Series series, DateTime airDate)
public List<SearchResultItem> ProcessSearchResults(ProgressNotification notification, IEnumerable<EpisodeParseResult> reports, Series series, DateTime airDate)
{
var items = new List<SearchResultItem>();
var skip = false;
foreach (var episodeParseResult in reports.OrderByDescending(c => c.Quality))
{
try
{
var item = new SearchResultItem
{
ReportTitle = episodeParseResult.OriginalString,
NzbUrl = episodeParseResult.NzbUrl
};
items.Add(item);
if (skip)
{
item.SearchError = ReportRejectionType.Skipped;
continue;
}
Logger.Trace("Analysing report " + episodeParseResult);
//Get the matching series
@ -310,13 +378,20 @@ namespace NzbDrone.Core.Providers
//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;
}
if (_allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult))
var allowedDownload = _allowedDownloadSpecification.IsSatisfiedBy(episodeParseResult);
if (allowedDownload == ReportRejectionType.None)
{
Logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
try
@ -327,7 +402,12 @@ namespace NzbDrone.Core.Providers
String.Format("{0} - {1} {2} Added to download queue",
episodeParseResult.Series.Title, episodeParseResult.AirDate.Value.ToShortDateString(), episodeParseResult.Quality);
return true;
item.Success = true;
skip = true;
}
else
{
item.SearchError = ReportRejectionType.DownloadClientFailure;
}
}
catch (Exception e)
@ -336,13 +416,18 @@ namespace NzbDrone.Core.Providers
notification.CurrentMessage = String.Format("Unable to add report to download queue. {0}", episodeParseResult);
}
}
else
{
item.SearchError = allowedDownload;
}
}
catch (Exception e)
{
Logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
}
}
return false;
return items;
}
private List<int> GetEpisodeNumberPrefixes(IEnumerable<int> episodeNumbers)

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using Ninject;
using NzbDrone.Core.Repository.Search;
using PetaPoco;
namespace NzbDrone.Core.Providers
{
public class SearchResultProvider
{
private readonly IDatabase _database;
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
[Inject]
public SearchResultProvider(IDatabase database)
{
_database = database;
}
public SearchResultProvider()
{
}
public virtual void Add(SearchResult searchResult)
{
logger.Trace("Adding new search result");
var id = Convert.ToInt32(_database.Insert(searchResult));
searchResult.SearchResultItems.ForEach(s => s.Id = id);
logger.Trace("Adding search result items");
_database.InsertMany(searchResult.SearchResultItems);
}
public virtual void Delete(int id)
{
logger.Trace("Deleting search result items attached to: {0}", id);
_database.Execute("DELETE FROM SearchResultItems WHERE SearchResultId = @0", id);
logger.Trace("Deleting search result: {0}", id);
_database.Delete<SearchResult>(id);
}
public virtual List<SearchResult> AllSearchResults()
{
return _database.Fetch<SearchResult>();
}
public virtual SearchResult GetSearchResult(int id)
{
var result = _database.Single<SearchResult>(id);
result.SearchResultItems = _database.Fetch<SearchResultItem>("WHERE SearchResultId = @0", id);
return result;
}
}
}

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using NzbDrone.Core.Model;
using NzbDrone.Core.Repository.Quality;
using PetaPoco;
namespace NzbDrone.Core.Repository.Search
{
[PrimaryKey("Id", autoIncrement = true)]
[TableName("SearchResults")]
public class SearchResult
{
public int Id { get; set; }
public int SeriesId { get; set; }
public int? SeasonNumber { get; set; }
public int? EpisodeId { get; set; }
public DateTime? AirDate { get; set; }
public DateTime SearchTime { get; set; }
[ResultColumn]
public List<SearchResultItem> SearchResultItems { get; set; }
}
}

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using NzbDrone.Core.Model;
using NzbDrone.Core.Repository.Quality;
using PetaPoco;
namespace NzbDrone.Core.Repository.Search
{
[PrimaryKey("Id", autoIncrement = true)]
[TableName("SearchResultItems")]
public class SearchResultItem
{
public int Id { get; set; }
public int SearchResultId { get; set; }
public string ReportTitle { get; set; }
public string NzbUrl { get; set; }
public string NzbInfoUrl { get; set; }
public bool Success { get; set; }
public ReportRejectionType SearchError { get; set; }
}
}
Loading…
Cancel
Save