diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index c86256099..689d36ab3 100644 --- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions } } - private static string GetQueryTitle(string title) + public static string GetQueryTitle(string title) { Ensure.That(title,() => title).IsNotNullOrWhiteSpace(); diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SpecialEpisodeSearchCriteria.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SpecialEpisodeSearchCriteria.cs new file mode 100644 index 000000000..d4f034e44 --- /dev/null +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SpecialEpisodeSearchCriteria.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.IndexerSearch.Definitions +{ + public class SpecialEpisodeSearchCriteria : SearchCriteriaBase + { + public string[] EpisodeQueryTitles { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + bool delimiter = false; + foreach (var title in EpisodeQueryTitles) + { + if (delimiter) + { + sb.Append(','); + } + sb.Append(title); + delimiter = true; + } + return string.Format("[{0} : {1}]", SceneTitle, sb.ToString()); + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 0981c5eb9..cafc2cd32 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -64,6 +64,12 @@ namespace NzbDrone.Core.IndexerSearch return SearchDaily(series, episode); } + if (episode.SeasonNumber == 0) + { + // search for special episodes in season 0 + return SearchSpecial(series, new List{episode}); + } + return SearchSingle(series, episode); } @@ -103,11 +109,28 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); } + private List SearchSpecial(Series series, List episodes) + { + var searchSpec = Get(series, episodes); + // build list of queries for each episode in the form: " " + searchSpec.EpisodeQueryTitles = episodes.Where(e => !String.IsNullOrWhiteSpace(e.Title)) + .Select(e => searchSpec.QueryTitle + "+" + SearchCriteriaBase.GetQueryTitle(e.Title)) + .ToArray(); + + return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec); + } + public List SeasonSearch(int seriesId, int seasonNumber) { var series = _seriesService.GetSeries(seriesId); var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); + if (seasonNumber == 0) + { + // search for special episodes in season 0 + return SearchSpecial(series, episodes); + } + var searchSpec = Get(series, episodes); searchSpec.SeasonNumber = seasonNumber; diff --git a/src/NzbDrone.Core/Indexers/Eztv/Eztv.cs b/src/NzbDrone.Core/Indexers/Eztv/Eztv.cs index 52c55df60..315178c54 100644 --- a/src/NzbDrone.Core/Indexers/Eztv/Eztv.cs +++ b/src/NzbDrone.Core/Indexers/Eztv/Eztv.cs @@ -46,5 +46,10 @@ namespace NzbDrone.Core.Indexers.Eztv //EZTV doesn't support searching based on actual episode airdate. they only support release date. return new string[0]; } + + public override IEnumerable GetSearchUrls(string query, int offset) + { + return new List(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/IIndexer.cs b/src/NzbDrone.Core/Indexers/IIndexer.cs index 34daa6a26..4ec97efb2 100644 --- a/src/NzbDrone.Core/Indexers/IIndexer.cs +++ b/src/NzbDrone.Core/Indexers/IIndexer.cs @@ -13,5 +13,6 @@ namespace NzbDrone.Core.Indexers IEnumerable GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber); IEnumerable GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); IEnumerable GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset); + IEnumerable GetSearchUrls(string query, int offset = 0); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index 7d42847cd..b89b0538d 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -48,6 +48,7 @@ namespace NzbDrone.Core.Indexers public abstract IEnumerable GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber); public abstract IEnumerable GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date); public abstract IEnumerable GetSeasonSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int offset); + public abstract IEnumerable GetSearchUrls(string query, int offset); public override string ToString() { diff --git a/src/NzbDrone.Core/Indexers/IndexerFetchService.cs b/src/NzbDrone.Core/Indexers/IndexerFetchService.cs index 2de0c51b0..924b6689a 100644 --- a/src/NzbDrone.Core/Indexers/IndexerFetchService.cs +++ b/src/NzbDrone.Core/Indexers/IndexerFetchService.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.Indexers IList Fetch(IIndexer indexer, SeasonSearchCriteria searchCriteria); IList Fetch(IIndexer indexer, SingleEpisodeSearchCriteria searchCriteria); IList Fetch(IIndexer indexer, DailyEpisodeSearchCriteria searchCriteria); + IList Fetch(IIndexer indexer, SpecialEpisodeSearchCriteria searchCriteria); } public class FetchFeedService : IFetchFeedFromIndexers @@ -77,9 +78,8 @@ namespace NzbDrone.Core.Indexers var searchUrls = indexer.GetEpisodeSearchUrls(searchCriteria.QueryTitle, searchCriteria.Series.TvRageId, searchCriteria.SeasonNumber, searchCriteria.EpisodeNumber); var result = Fetch(indexer, searchUrls); - - _logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count); + return result; } @@ -94,6 +94,20 @@ namespace NzbDrone.Core.Indexers return result; } + public IList Fetch(IIndexer indexer, SpecialEpisodeSearchCriteria searchCriteria) + { + var queryUrls = new List(); + foreach (var episodeQueryTitle in searchCriteria.EpisodeQueryTitles) + { + _logger.Debug("Performing query of {0} for {1}", indexer, episodeQueryTitle); + queryUrls.AddRange(indexer.GetSearchUrls(episodeQueryTitle)); + } + + var result = Fetch(indexer, queryUrls); + _logger.Info("Finished searching {0} for {1}. Found {2}", indexer, searchCriteria, result.Count); + return result; + } + private List Fetch(IIndexer indexer, IEnumerable urls) { var result = new List(); diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index 8a1be6f66..d14df1540 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -104,6 +104,15 @@ namespace NzbDrone.Core.Indexers.Newznab return RecentFeed.Select(url => String.Format("{0}&limit=100&q={1}&season={2}&ep={3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, episodeNumber)); } + public override IEnumerable GetSearchUrls(string query, int offset) + { + // encode query (replace the + with spaces first) + query = query.Replace("+", " "); + query = System.Web.HttpUtility.UrlEncode(query); + return RecentFeed.Select(url => String.Format("{0}&offset={1}&limit=100&q={2}", url.Replace("t=tvsearch", "t=search"), offset, query)); + } + + public override IEnumerable GetDailyEpisodeSearchUrls(string seriesTitle, int tvRageId, DateTime date) { if (tvRageId > 0) diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs index c4daeab66..20f0953af 100644 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs @@ -66,5 +66,11 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs return searchUrls; } + + public override IEnumerable GetSearchUrls(string query, int offset) + { + return new List(); + } + } } diff --git a/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs b/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs index 5355d853d..f111db688 100644 --- a/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs +++ b/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs @@ -41,5 +41,10 @@ namespace NzbDrone.Core.Indexers.Wombles { return new List(); } + + public override IEnumerable GetSearchUrls(string query, int offset) + { + return new List(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 599e2cdb1..2e2f74d42 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -259,6 +259,7 @@ +