using System; using System.Collections.Generic; using System.Linq; using System.Text; using NLog; using Ninject; using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Providers.Jobs; using NzbDrone.Core.Repository; namespace NzbDrone.Core.Providers { public class SearchProvider { //Season and Episode Searching private readonly EpisodeProvider _episodeProvider; private readonly InventoryProvider _inventoryProvider; private readonly DownloadProvider _downloadProvider; private readonly SeriesProvider _seriesProvider; private readonly IndexerProvider _indexerProvider; private readonly SceneMappingProvider _sceneMappingProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); [Inject] public SearchProvider(EpisodeProvider episodeProvider, InventoryProvider inventoryProvider, DownloadProvider downloadProvider, SeriesProvider seriesProvider, IndexerProvider indexerProvider, SceneMappingProvider sceneMappingProvider) { _episodeProvider = episodeProvider; _inventoryProvider = inventoryProvider; _downloadProvider = downloadProvider; _seriesProvider = seriesProvider; _indexerProvider = indexerProvider; _sceneMappingProvider = sceneMappingProvider; } public SearchProvider() { } public virtual bool SeasonSearch(ProgressNotification notification, int seriesId, int seasonNumber) { var series = _seriesProvider.GetSeries(seriesId); if (series == null) { Logger.Error("Unable to find an series {0} in database", seriesId); return false; } 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); } } Logger.Debug("Finished searching all indexers. Total {0}", reports.Count); if (reports.Count == 0) return false; Logger.Debug("Getting episodes from database for series: {0} and season: {1}", seriesId, seasonNumber); var episodeNumbers = _episodeProvider.GetEpisodeNumbersBySeason(seriesId, seasonNumber); if (episodeNumbers == null || episodeNumbers.Count == 0) { Logger.Warn("No episodes in database found for series: {0} and season: {1}.", seriesId, seasonNumber); return false; } notification.CurrentMessage = "Processing search results"; var reportsToProcess = reports.Where(p => p.FullSeason && p.SeasonNumber == seasonNumber).ToList(); reportsToProcess.ForEach(c => { c.Series = series; c.EpisodeNumbers = episodeNumbers.ToList(); }); return ProcessSeasonSearchResults(notification, series, seasonNumber, reportsToProcess); } public bool ProcessSeasonSearchResults(ProgressNotification notification, Series series, int seasonNumber, IEnumerable reports) { 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); } 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} 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); return false; } public virtual bool EpisodeSearch(ProgressNotification notification, int episodeId) { var episode = _episodeProvider.GetEpisode(episodeId); if (episode == null) { Logger.Error("Unable to find an episode {0} in database", episodeId); return false; } notification.CurrentMessage = "Searching for " + episode; var series = _seriesProvider.GetSeries(episode.SeriesId); 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 { //notification.CurrentMessage = String.Format("Searching for {0} in {1}", episode, indexer.Name); //TODO:Add support for daily episodes, maybe search using both date and season/episode? var indexerResults = indexer.FetchEpisode(title, episode.SeasonNumber, episode.EpisodeNumber); reports.AddRange(indexerResults); } 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); notification.CurrentMessage = "Processing search results"; //TODO:fix this so when search returns more than one episode //TODO:-its populated with more than the original episode. reports.ForEach(c => { c.Series = series; }); return ProcessEpisodeSearchResults(notification, episode, reports); } public bool ProcessEpisodeSearchResults(ProgressNotification notification, Episode episode, IEnumerable reports) { 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); var indexers = _indexerProvider.GetEnabledIndexers(); var reports = new List(); 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(); notification.CurrentMessage = "Processing search results"; reports.ForEach(c => { c.Series = series; }); return ProcessPartialSeasonSearchResults(notification, reports); } public List ProcessPartialSeasonSearchResults(ProgressNotification notification, IEnumerable reports) { var successes = new List(); 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; } } }