From cef72f11d06535485d1af1fc3155727b0714bcfa Mon Sep 17 00:00:00 2001 From: kaso17 Date: Thu, 24 Aug 2017 12:28:41 +0200 Subject: [PATCH] manual search: improve error handling (#1717) * manual search: improve error handling * add index changes --- src/Jackett/Content/index.html | 1246 +++++++++-------- src/Jackett/Controllers/ResultsController.cs | 98 +- src/Jackett/IndexerException.cs | 30 + src/Jackett/Indexers/BaseIndexer.cs | 41 +- src/Jackett/Indexers/IIndexer.cs | 14 +- src/Jackett/Indexers/Meta/BaseMetaIndexer.cs | 31 +- src/Jackett/Jackett.csproj | 1 + src/Jackett/Models/DTO/ManualSearchResult.cs | 13 +- src/Jackett/Services/IndexerManagerService.cs | 8 +- 9 files changed, 798 insertions(+), 684 deletions(-) create mode 100644 src/Jackett/IndexerException.cs diff --git a/src/Jackett/Content/index.html b/src/Jackett/Content/index.html index d9d5ad268..e38eff851 100644 --- a/src/Jackett/Content/index.html +++ b/src/Jackett/Content/index.html @@ -1,619 +1,627 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Jackett - - -
- - Jackett - -
- API Key: - -
-
-
- - - - -
-

Configured Indexers

-
-
- -
-

Adding a Jackett indexer in Sonarr or Radarr

-
    -
  1. Go to Settings > Indexers > Add > Torznab > Custom.
  2. -
  3. Click on the indexers corresponding button and paste it into the Sonarr/Radarr URL field.
  4. -
  5. For the API key use .
  6. -
-

Adding a Jackett indexer in CouchPotato

-
    -
  1. Go to Settings > Searchers.
  2. -
  3. Enable TorrentPotato. -
  4. Click on the indexers corresponding button and past it into the CouchPotato host field.
  5. -
  6. For the Passkey use . Leave the username field blank.
  7. -
- -
-
-

Jackett Configuration

-
-
- - - -
-
-
-
- Admin password: - - - -
-
- Base Path Override: - -
-
- Server port: - -
-
- Manual download blackhole directory: - -
-
- External access: - -
-
- Disable auto update: - - -
-
- Update to pre-release: - - -
-
- Enhanced logging: - -
-
- OMDB API key: - -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jackett + + +
+ + Jackett + +
+ API Key: + +
+
+
+ + + + +
+

Configured Indexers

+
+
+ +
+

Adding a Jackett indexer in Sonarr or Radarr

+
    +
  1. Go to Settings > Indexers > Add > Torznab > Custom.
  2. +
  3. Click on the indexers corresponding button and paste it into the Sonarr/Radarr URL field.
  4. +
  5. For the API key use .
  6. +
+

Adding a Jackett indexer in CouchPotato

+
    +
  1. Go to Settings > Searchers.
  2. +
  3. Enable TorrentPotato. +
  4. Click on the indexers corresponding button and past it into the CouchPotato host field.
  5. +
  6. For the Passkey use . Leave the username field blank.
  7. +
+ +
+
+

Jackett Configuration

+
+
+ + + +
+
+
+
+ Admin password: + + + +
+
+ Base Path Override: + +
+
+ Server port: + +
+
+ Manual download blackhole directory: + +
+
+ External access: + +
+
+ Disable auto update: + + +
+
+ Update to pre-release: + + +
+
+ Enhanced logging: + +
+
+ OMDB API key: + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Jackett/Controllers/ResultsController.cs b/src/Jackett/Controllers/ResultsController.cs index 75660b911..0782b1256 100644 --- a/src/Jackett/Controllers/ResultsController.cs +++ b/src/Jackett/Controllers/ResultsController.cs @@ -19,6 +19,7 @@ using Jackett.Utils; using Jackett.Utils.Clients; using Newtonsoft.Json; using NLog; +using Jackett.Models.DTO; namespace Jackett.Controllers.V20 { @@ -154,20 +155,67 @@ namespace Jackett.Controllers.V20 [HttpGet] public async Task Results([FromUri]Models.DTO.ApiSearch request) { + var manualResult = new ManualSearchResult(); var trackers = IndexerService.GetAllIndexers().Where(t => t.IsConfigured); if (CurrentIndexer.ID != "all") trackers = trackers.Where(t => t.ID == CurrentIndexer.ID).ToList(); - trackers = trackers.Where(t => t.IsConfigured && t.CanHandleQuery(CurrentQuery)); + trackers = trackers.Where(t => t.CanHandleQuery(CurrentQuery)); var tasks = trackers.ToList().Select(t => t.ResultsForQuery(CurrentQuery)).ToList(); - var aggregateTask = Task.WhenAll(tasks); - - await aggregateTask; - - var results = tasks.Where(t => t.Status == TaskStatus.RanToCompletion).Where(t => t.Result.Count() > 0).SelectMany(t => + try { - var searchResults = t.Result; - var indexer = searchResults.First().Origin; + var aggregateTask = Task.WhenAll(tasks); + await aggregateTask; + } + catch (AggregateException aex) + { + foreach (var ex in aex.InnerExceptions) + { + logger.Error(ex); + } + } + catch (Exception ex) + { + logger.Error(ex); + } + + manualResult.Indexers = tasks.Select(t => + { + var resultIndexer = new ManualSearchResultIndexer(); + IIndexer indexer = null; + if (t.Status == TaskStatus.RanToCompletion) + { + resultIndexer.Status = ManualSearchResultIndexerStatus.OK; + resultIndexer.Results = t.Result.Releases.Count(); + resultIndexer.Error = null; + indexer = t.Result.Indexer; + } + else if (t.Exception.InnerException is IndexerException) + { + resultIndexer.Status = ManualSearchResultIndexerStatus.Error; + resultIndexer.Results = 0; + resultIndexer.Error = ((IndexerException)t.Exception.InnerException).ToString(); + indexer = ((IndexerException)t.Exception.InnerException).Indexer; + } + else + { + resultIndexer.Status = ManualSearchResultIndexerStatus.Unknown; + resultIndexer.Results = 0; + resultIndexer.Error = null; + } + + if (indexer != null) + { + resultIndexer.ID = indexer.ID; + resultIndexer.Name = indexer.DisplayName; + } + return resultIndexer; + }).ToList(); + + manualResult.Results = tasks.Where(t => t.Status == TaskStatus.RanToCompletion).Where(t => t.Result.Releases.Count() > 0).SelectMany(t => + { + var searchResults = t.Result.Releases; + var indexer = t.Result.Indexer; cacheService.CacheRssResults(indexer, searchResults); return searchResults.Select(result => @@ -181,17 +229,7 @@ namespace Jackett.Controllers.V20 }); }).OrderByDescending(d => d.PublishDate).ToList(); - ConfigureCacheResults(results); - - var manualResult = new Models.DTO.ManualSearchResult() - { - Results = results, - Indexers = trackers.Select(t => t.DisplayName).ToList() - }; - - - if (manualResult.Indexers.Count() == 0) - manualResult.Indexers = new List() { "None" }; + ConfigureCacheResults(manualResult.Results); logger.Info(string.Format("Manual search for \"{0}\" on {1} with {2} results.", CurrentQuery.SanitizedSearchTerm, string.Join(", ", manualResult.Indexers), manualResult.Results.Count())); return manualResult; @@ -236,7 +274,7 @@ namespace Jackett.Controllers.V20 } } - var releases = await CurrentIndexer.ResultsForQuery(CurrentQuery); + var result = await CurrentIndexer.ResultsForQuery(CurrentQuery); // Some trackers do not support multiple category filtering so filter the releases that match manually. int? newItemCount = null; @@ -244,19 +282,19 @@ namespace Jackett.Controllers.V20 // Cache non query results if (string.IsNullOrEmpty(CurrentQuery.SanitizedSearchTerm)) { - newItemCount = cacheService.GetNewItemCount(CurrentIndexer, releases); - cacheService.CacheRssResults(CurrentIndexer, releases); + newItemCount = cacheService.GetNewItemCount(CurrentIndexer, result.Releases); + cacheService.CacheRssResults(CurrentIndexer, result.Releases); } // Log info var logBuilder = new StringBuilder(); if (newItemCount != null) { - logBuilder.AppendFormat("Found {0} ({1} new) releases from {2}", releases.Count(), newItemCount, CurrentIndexer.DisplayName); + logBuilder.AppendFormat("Found {0} ({1} new) releases from {2}", result.Releases.Count(), newItemCount, CurrentIndexer.DisplayName); } else { - logBuilder.AppendFormat("Found {0} releases from {1}", releases.Count(), CurrentIndexer.DisplayName); + logBuilder.AppendFormat("Found {0} releases from {1}", result.Releases.Count(), CurrentIndexer.DisplayName); } if (!string.IsNullOrWhiteSpace(CurrentQuery.SanitizedSearchTerm)) @@ -278,7 +316,7 @@ namespace Jackett.Controllers.V20 ImageDescription = CurrentIndexer.DisplayName }); - var proxiedReleases = releases.Select(r => AutoMapper.Mapper.Map(r)).Select(r => + var proxiedReleases = result.Releases.Select(r => AutoMapper.Mapper.Map(r)).Select(r => { r.Link = serverService.ConvertToProxyLink(r.Link, serverUrl, r.Origin.ID, "dl", r.Title + ".torrent"); return r; @@ -316,20 +354,20 @@ namespace Jackett.Controllers.V20 [JsonResponse] public async Task Potato([FromUri]Models.DTO.TorrentPotatoRequest request) { - var releases = await CurrentIndexer.ResultsForQuery(CurrentQuery); + var result = await CurrentIndexer.ResultsForQuery(CurrentQuery); // Cache non query results if (string.IsNullOrEmpty(CurrentQuery.SanitizedSearchTerm)) - cacheService.CacheRssResults(CurrentIndexer, releases); + cacheService.CacheRssResults(CurrentIndexer, result.Releases); // Log info if (string.IsNullOrWhiteSpace(CurrentQuery.SanitizedSearchTerm)) - logger.Info($"Found {releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName}"); + logger.Info($"Found {result.Releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName}"); else - logger.Info($"Found {releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName} for: {CurrentQuery.GetQueryString()}"); + logger.Info($"Found {result.Releases.Count()} torrentpotato releases from {CurrentIndexer.DisplayName} for: {CurrentQuery.GetQueryString()}"); var serverUrl = string.Format("{0}://{1}:{2}{3}", Request.RequestUri.Scheme, Request.RequestUri.Host, Request.RequestUri.Port, serverService.BasePath()); - var potatoReleases = releases.Where(r => r.Link != null || r.MagnetUri != null).Select(r => + var potatoReleases = result.Releases.Where(r => r.Link != null || r.MagnetUri != null).Select(r => { var release = AutoMapper.Mapper.Map(r); release.Link = serverService.ConvertToProxyLink(release.Link, serverUrl, CurrentIndexer.ID, "dl", release.Title + ".torrent"); diff --git a/src/Jackett/IndexerException.cs b/src/Jackett/IndexerException.cs new file mode 100644 index 000000000..fae662010 --- /dev/null +++ b/src/Jackett/IndexerException.cs @@ -0,0 +1,30 @@ +using Jackett.Indexers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Jackett +{ + class IndexerException : Exception + { + public IIndexer Indexer { get; protected set; } + + public IndexerException(IIndexer Indexer, string message, Exception innerException) + : base(message, innerException) + { + this.Indexer = Indexer; + } + + public IndexerException(IIndexer Indexer, string message) + : this(Indexer, message, null) + { + } + + public IndexerException(IIndexer Indexer, Exception innerException) + : this(Indexer, "Exception (" + Indexer.ID + "): " + innerException.Message, innerException) + { + } + } +} diff --git a/src/Jackett/Indexers/BaseIndexer.cs b/src/Jackett/Indexers/BaseIndexer.cs index df2324bd0..3af31ed2e 100644 --- a/src/Jackett/Indexers/BaseIndexer.cs +++ b/src/Jackett/Indexers/BaseIndexer.cs @@ -219,23 +219,30 @@ namespace Jackett.Indexers public abstract Task ApplyConfiguration(JToken configJson); - public virtual async Task> ResultsForQuery(TorznabQuery query) + public virtual async Task ResultsForQuery(TorznabQuery query) { - if (!CanHandleQuery(query)) - return new ReleaseInfo[0]; - var results = await PerformQuery(query); - results = FilterResults(query, results); - results = results.Select(r => + try { - r.Origin = this; + if (!CanHandleQuery(query)) + return new IndexerResult(this, new ReleaseInfo[0]); + var results = await PerformQuery(query); + results = FilterResults(query, results); + results = results.Select(r => + { + r.Origin = this; - // Some trackers do not keep their clocks up to date and can be ~20 minutes out! - if (r.PublishDate > DateTime.Now) - r.PublishDate = DateTime.Now; - return r; - }); + // Some trackers do not keep their clocks up to date and can be ~20 minutes out! + if (r.PublishDate > DateTime.Now) + r.PublishDate = DateTime.Now; + return r; + }); - return results; + return new IndexerResult(this, results); + } + catch (Exception ex) + { + throw new IndexerException(this, ex); + } } protected abstract Task> PerformQuery(TorznabQuery query); } @@ -667,12 +674,12 @@ namespace Jackett.Indexers return releases; } - public override async Task> ResultsForQuery(TorznabQuery query) + public override async Task ResultsForQuery(TorznabQuery query) { - var results = await base.ResultsForQuery(query); - results = CleanLinks(results); + var result = await base.ResultsForQuery(query); + result.Releases = CleanLinks(result.Releases); - return results; + return result; } protected virtual Uri UncleanLink(Uri link) diff --git a/src/Jackett/Indexers/IIndexer.cs b/src/Jackett/Indexers/IIndexer.cs index 5ff4174d6..1a406e611 100644 --- a/src/Jackett/Indexers/IIndexer.cs +++ b/src/Jackett/Indexers/IIndexer.cs @@ -10,6 +10,18 @@ using System.Web.UI.WebControls; namespace Jackett.Indexers { + public class IndexerResult + { + public IIndexer Indexer { get; set; } + public IEnumerable Releases { get; set; } + + public IndexerResult(IIndexer Indexer, IEnumerable Releases) + { + this.Indexer = Indexer; + this.Releases = Releases; + } + } + public interface IIndexer { string SiteLink { get; } @@ -39,7 +51,7 @@ namespace Jackett.Indexers void Unconfigure(); - Task> ResultsForQuery(TorznabQuery query); + Task ResultsForQuery(TorznabQuery query); bool CanHandleQuery(TorznabQuery query); } diff --git a/src/Jackett/Indexers/Meta/BaseMetaIndexer.cs b/src/Jackett/Indexers/Meta/BaseMetaIndexer.cs index 64f9d3cb1..67f06ee4c 100644 --- a/src/Jackett/Indexers/Meta/BaseMetaIndexer.cs +++ b/src/Jackett/Indexers/Meta/BaseMetaIndexer.cs @@ -27,25 +27,32 @@ namespace Jackett.Indexers.Meta return Task.FromResult(IndexerConfigurationStatus.Completed); } - public override async Task> ResultsForQuery(TorznabQuery query) + public override async Task ResultsForQuery(TorznabQuery query) { - if (!CanHandleQuery(query)) - return new ReleaseInfo[0]; - var results = await PerformQuery(query); - var correctedResults = results.Select(r => + try { - if (r.PublishDate > DateTime.Now) - r.PublishDate = DateTime.Now; - return r; - }); + if (!CanHandleQuery(query)) + return new IndexerResult(this, new ReleaseInfo[0]); + var results = await PerformQuery(query); + var correctedResults = results.Select(r => + { + if (r.PublishDate > DateTime.Now) + r.PublishDate = DateTime.Now; + return r; + }); - return correctedResults; + return new IndexerResult(this, correctedResults); + } + catch (Exception ex) + { + throw new IndexerException(this, ex); + } } protected override async Task> PerformQuery(TorznabQuery query) { var indexers = validIndexers; - IEnumerable>> supportedTasks = indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.ResultsForQuery(query)).ToList(); // explicit conversion to List to execute LINQ query + IEnumerable> supportedTasks = indexers.Where(i => i.CanHandleQuery(query)).Select(i => i.ResultsForQuery(query)).ToList(); // explicit conversion to List to execute LINQ query var fallbackStrategies = fallbackStrategyProvider.FallbackStrategiesForQuery(query); var fallbackQueries = fallbackStrategies.Select(async f => await f.FallbackQueries()).SelectMany(t => t.Result); @@ -72,7 +79,7 @@ namespace Jackett.Indexers.Meta logger.Error(aggregateTask.Exception, "Error during request in metaindexer " + ID); } - var unorderedResult = aggregateTask.Result.Flatten(); + var unorderedResult = aggregateTask.Result.Select(r => r.Releases).Flatten(); var resultFilters = resultFilterProvider.FiltersForQuery(query); var filteredResults = resultFilters.Select(async f => await f.FilterResults(unorderedResult)).SelectMany(t => t.Result); var uniqueFilteredResults = filteredResults.Distinct(); diff --git a/src/Jackett/Jackett.csproj b/src/Jackett/Jackett.csproj index df4fc5ad4..4037df5ba 100644 --- a/src/Jackett/Jackett.csproj +++ b/src/Jackett/Jackett.csproj @@ -173,6 +173,7 @@ + diff --git a/src/Jackett/Models/DTO/ManualSearchResult.cs b/src/Jackett/Models/DTO/ManualSearchResult.cs index 263043e27..34ab6a441 100644 --- a/src/Jackett/Models/DTO/ManualSearchResult.cs +++ b/src/Jackett/Models/DTO/ManualSearchResult.cs @@ -7,9 +7,20 @@ using System.Threading.Tasks; namespace Jackett.Models.DTO { + public enum ManualSearchResultIndexerStatus { Unknown = 0, Error = 1, OK = 2 }; + + public class ManualSearchResultIndexer + { + public string ID { get; set; } + public string Name { get; set; } + public ManualSearchResultIndexerStatus Status { get; set; } + public int Results { get; set; } + public string Error { get; set; } + } + public class ManualSearchResult { public IEnumerable Results { get; set; } - public IEnumerable Indexers { get; set; } + public IList Indexers { get; set; } } } diff --git a/src/Jackett/Services/IndexerManagerService.cs b/src/Jackett/Services/IndexerManagerService.cs index b0da4d7d5..e55aa86d3 100644 --- a/src/Jackett/Services/IndexerManagerService.cs +++ b/src/Jackett/Services/IndexerManagerService.cs @@ -207,11 +207,11 @@ namespace Jackett.Services browseQuery.QueryType = "search"; browseQuery.SearchTerm = ""; browseQuery.IsTest = true; - var results = await indexer.ResultsForQuery(browseQuery); - logger.Info(string.Format("Found {0} releases from {1}", results.Count(), indexer.DisplayName)); - if (results.Count() == 0) + var result = await indexer.ResultsForQuery(browseQuery); + logger.Info(string.Format("Found {0} releases from {1}", result.Releases.Count(), indexer.DisplayName)); + if (result.Releases.Count() == 0) throw new Exception("Found no results while trying to browse this tracker"); - cacheService.CacheRssResults(indexer, results); + cacheService.CacheRssResults(indexer, result.Releases); } public void DeleteIndexer(string name)