@ -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 SearchHistoryProvider _searchHistoryProvider ;
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 ,
SearchHistoryProvider searchHistoryProvider )
{
_episodeProvider = episodeProvider ;
_downloadProvider = downloadProvider ;
@ -36,6 +39,7 @@ namespace NzbDrone.Core.Providers
_sceneMappingProvider = sceneMappingProvider ;
_upgradePossibleSpecification = upgradePossibleSpecification ;
_allowedDownloadSpecification = allowedDownloadSpecification ;
_searchHistoryProvider = searchHistoryProvider ;
}
public SearchProvider ( )
@ -44,13 +48,20 @@ namespace NzbDrone.Core.Providers
public virtual bool SeasonSearch ( ProgressNotification notification , int seriesId , int seasonNumber )
{
var searchResult = new SearchHistory
{
SearchTime = DateTime . Now ,
SeriesId = seriesId ,
SeasonNumber = seasonNumber
} ;
var series = _seriesProvider . GetSeries ( seriesId ) ;
if ( series = = null )
{
Logger . Error ( "Unable to find an series {0} in database" , seriesId ) ;
return false ;
}
}
//Return false if the series is a daily series (we only support individual episode searching
if ( series . IsDaily )
@ -80,19 +91,20 @@ namespace NzbDrone.Core.Providers
e = > e . EpisodeNumbers = episodeNumbers . ToList ( )
) ;
var downloadedEpisodes = ProcessSearchResults ( notification , reports , series , seasonNumber ) ;
searchResult . SearchHistoryItems = ProcessSearchResults ( notification , reports , searchResult , series , seasonNumber ) ;
_searchHistoryProvider . Add ( searchResult ) ;
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 . Successes . Count = = episodeNumbers . Count ) ;
}
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 searchResult = new SearchHistory
{
SearchTime = DateTime . Now ,
SeriesId = seriesId ,
SeasonNumber = seasonNumber
} ;
var series = _seriesProvider . GetSeries ( seriesId ) ;
@ -107,19 +119,18 @@ namespace NzbDrone.Core.Providers
return new List < int > ( ) ;
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 > ( ) ;
notification . CurrentMessage = "Processing search results" ;
searchResult . SearchHistoryItems = ProcessSearchResults ( notification , reports , searchResult , series , seasonNumber ) ;
return ProcessSearchResults ( notification , reports , series , seasonNumber ) ;
_searchHistoryProvider . Add ( searchResult ) ;
return searchResult . Successes ;
}
public virtual bool EpisodeSearch ( ProgressNotification notification , int episodeId )
@ -136,7 +147,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 +156,39 @@ 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 SearchHistory
{
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 . SearchHistoryItems = ProcessSearchResults ( notification , reports , episode . Series , episode . AirDate . Value ) ;
_searchHistoryProvider . Add ( searchResult ) ;
if ( episode . Series . IsDaily & & ProcessSearchResults ( notification , reports , episode . Series , episode . AirDate . Value ) )
if ( searchResult . SearchHistoryItems . Any ( r = > r . Success ) )
return true ;
}
else
{
searchResult . EpisodeId = episodeId ;
searchResult . SearchHistoryItems = ProcessSearchResults ( notification , reports , searchResult , episode . Series , episode . SeasonNumber , episode . EpisodeNumber ) ;
_searchHistoryProvider . Add ( searchResult ) ;
if ( searchResult . SearchHistoryItems . Any ( r = > r . Success ) )
return true ;
}
Logger . Warn ( "Unable to find {0} in any of indexers." , episode ) ;
@ -170,7 +201,6 @@ namespace NzbDrone.Core.Providers
notification . CurrentMessage = String . Format ( "Sorry, couldn't find you {0} in any of indexers." , episode ) ;
}
return false ;
}
@ -227,9 +257,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 < SearchHistoryItem > ProcessSearchResults ( ProgressNotification notification , IEnumerable < EpisodeParseResult > reports , SearchHistory searchResult , Series series , int seasonNumber , int? episodeNumber = null )
{
var successes = new List < int > ( ) ;
var items = new List < SearchHistoryItem > ( ) ;
foreach ( var episodeParseResult in reports . OrderByDescending ( c = > c . Quality ) . ThenBy ( c = > c . Age ) )
{
@ -237,6 +268,20 @@ namespace NzbDrone.Core.Providers
{
Logger . Trace ( "Analysing report " + episodeParseResult ) ;
var item = new SearchHistoryItem
{
ReportTitle = episodeParseResult . OriginalString ,
NzbUrl = episodeParseResult . NzbUrl ,
Indexer = episodeParseResult . Indexer ,
Quality = episodeParseResult . Quality . QualityType ,
Proper = episodeParseResult . Quality . Proper ,
Size = episodeParseResult . Size ,
Age = episodeParseResult . Age ,
Language = episodeParseResult . Language
} ;
items . Add ( item ) ;
//Get the matching series
episodeParseResult . Series = _seriesProvider . FindSeries ( episodeParseResult . CleanTitle ) ;
@ -244,6 +289,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 +297,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 +305,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 +313,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 ) )
item . SearchError = _allowedDownloadSpecification . IsSatisfiedBy ( episodeParseResult ) ;
if ( item . SearchError = = ReportRejectionType . None )
{
Logger . Debug ( "Found '{0}'. Adding to download queue." , episodeParseResult ) ;
try
@ -279,12 +329,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 +350,38 @@ namespace NzbDrone.Core.Providers
}
}
return successe s;
return item s;
}
public bool ProcessSearchResults ( ProgressNotification notification , IEnumerable < EpisodeParseResult > reports , Series series , DateTime airDate )
public List < SearchHistoryItem > ProcessSearchResults ( ProgressNotification notification , IEnumerable < EpisodeParseResult > reports , Series series , DateTime airDate )
{
var items = new List < SearchHistoryItem > ( ) ;
var skip = false ;
foreach ( var episodeParseResult in reports . OrderByDescending ( c = > c . Quality ) )
{
try
{
var item = new SearchHistoryItem
{
ReportTitle = episodeParseResult . OriginalString ,
NzbUrl = episodeParseResult . NzbUrl ,
Indexer = episodeParseResult . Indexer ,
Quality = episodeParseResult . Quality . QualityType ,
Proper = episodeParseResult . Quality . Proper ,
Size = episodeParseResult . Size ,
Age = episodeParseResult . Age ,
Language = episodeParseResult . Language
} ;
items . Add ( item ) ;
if ( skip )
{
item . SearchError = ReportRejectionType . Skipped ;
continue ;
}
Logger . Trace ( "Analysing report " + episodeParseResult ) ;
//Get the matching series
@ -310,13 +389,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 ) )
item . SearchError = _allowedDownloadSpecification . IsSatisfiedBy ( episodeParseResult ) ;
if ( item . SearchError = = ReportRejectionType . None )
{
Logger . Debug ( "Found '{0}'. Adding to download queue." , episodeParseResult ) ;
try
@ -327,13 +413,19 @@ 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 )
{
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 ;
}
}
}
@ -342,7 +434,8 @@ namespace NzbDrone.Core.Providers
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 )