diff --git a/src/NzbDrone.Common/Http/HttpResponse.cs b/src/NzbDrone.Common/Http/HttpResponse.cs index 43340c40e..2d3b169f5 100644 --- a/src/NzbDrone.Common/Http/HttpResponse.cs +++ b/src/NzbDrone.Common/Http/HttpResponse.cs @@ -50,6 +50,8 @@ namespace NzbDrone.Common.Http public bool HasHttpError => (int)StatusCode >= 400; + public bool HasHttpServerError => (int)StatusCode >= 500; + public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved || StatusCode == HttpStatusCode.MovedPermanently || StatusCode == HttpStatusCode.Found || diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs index 608a5273f..c473b95b8 100644 --- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; @@ -94,6 +96,7 @@ namespace NzbDrone.Core.Indexers { var releases = new List(); var url = string.Empty; + var minimumBackoff = TimeSpan.FromHours(1); try { @@ -190,8 +193,7 @@ namespace NzbDrone.Core.Indexers } catch (WebException webException) { - if (webException.Status == WebExceptionStatus.NameResolutionFailure || - webException.Status == WebExceptionStatus.ConnectFailure) + if (webException.Status is WebExceptionStatus.NameResolutionFailure or WebExceptionStatus.ConnectFailure) { _indexerStatusService.RecordConnectionFailure(Definition.Id); } @@ -201,7 +203,7 @@ namespace NzbDrone.Core.Indexers } if (webException.Message.Contains("502") || webException.Message.Contains("503") || - webException.Message.Contains("timed out")) + webException.Message.Contains("504") || webException.Message.Contains("timed out")) { _logger.Warn("{0} server is currently unavailable. {1} {2}", this, url, webException.Message); } @@ -212,34 +214,29 @@ namespace NzbDrone.Core.Indexers } catch (TooManyRequestsException ex) { - if (ex.RetryAfter != TimeSpan.Zero) - { - _indexerStatusService.RecordFailure(Definition.Id, ex.RetryAfter); - } - else - { - _indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1)); - } + var retryTime = ex.RetryAfter != TimeSpan.Zero ? ex.RetryAfter : minimumBackoff; + _indexerStatusService.RecordFailure(Definition.Id, retryTime); - _logger.Warn("API Request Limit reached for {0}", this); + _logger.Warn("API Request Limit reached for {0}. Disabled for {1}", this, retryTime); } catch (HttpException ex) { _indexerStatusService.RecordFailure(Definition.Id); - _logger.Warn("{0} {1}", this, ex.Message); - } - catch (RequestLimitReachedException ex) - { - if (ex.RetryAfter != TimeSpan.Zero) + if (ex.Response.HasHttpServerError) { - _indexerStatusService.RecordFailure(Definition.Id, ex.RetryAfter); + _logger.Warn("Unable to connect to {0} at [{1}]. Indexer's server is unavailable. Try again later. {2}", this, url, ex.Message); } else { - _indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1)); + _logger.Warn("{0} {1}", this, ex.Message); } + } + catch (RequestLimitReachedException ex) + { + var retryTime = ex.RetryAfter != TimeSpan.Zero ? ex.RetryAfter : minimumBackoff; + _indexerStatusService.RecordFailure(Definition.Id, retryTime); - _logger.Warn("API Request Limit reached for {0}", this); + _logger.Warn("API Request Limit reached for {0}. Disabled for {1}", this, retryTime); } catch (ApiKeyException) { @@ -259,6 +256,11 @@ namespace NzbDrone.Core.Indexers _logger.Error(ex, "CAPTCHA token required for {0}, check indexer settings.", this); } } + catch (TaskCanceledException ex) + { + _indexerStatusService.RecordFailure(Definition.Id); + _logger.Warn(ex, "Unable to connect to indexer, possibly due to a timeout. {0}", url); + } catch (IndexerException ex) { _indexerStatusService.RecordFailure(Definition.Id); @@ -360,6 +362,8 @@ namespace NzbDrone.Core.Indexers catch (RequestLimitReachedException ex) { _logger.Warn("Request limit reached: " + ex.Message); + + return new ValidationFailure(string.Empty, "Request limit reached: " + ex.Message); } catch (CloudFlareCaptchaException ex) { @@ -392,11 +396,45 @@ namespace NzbDrone.Core.Indexers _logger.Warn(ex, "Indexer does not support the query"); return new ValidationFailure(string.Empty, "Indexer does not support the current query. Check if the categories and or searching for movies are supported. Check the log for more details."); } - else + + _logger.Warn(ex, "Unable to connect to indexer"); + if (ex.Response.HasHttpServerError) { - _logger.Warn(ex, "Unable to connect to indexer"); + return new ValidationFailure(string.Empty, "Unable to connect to indexer, indexer's server is unavailable. Try again later. " + ex.Message); + } - return new ValidationFailure(string.Empty, "Unable to connect to indexer. " + ex.Message); + if (ex.Response.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.Unauthorized) + { + return new ValidationFailure(string.Empty, "Unable to connect to indexer, invalid credentials. " + ex.Message); + } + + return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log above the ValidationFailure for more details. " + ex.Message); + } + catch (HttpRequestException ex) + { + _logger.Warn(ex, "Unable to connect to indexer"); + + return new ValidationFailure(string.Empty, "Unable to connect to indexer, please check your DNS settings and ensure IPv6 is working or disabled. " + ex.Message); + } + catch (TaskCanceledException ex) + { + _logger.Warn(ex, "Unable to connect to indexer"); + + return new ValidationFailure(string.Empty, "Unable to connect to indexer, possibly due to a timeout. Try again or check your network settings. " + ex.Message); + } + catch (WebException webException) + { + _logger.Warn("Unable to connect to indexer."); + + if (webException.Status is WebExceptionStatus.NameResolutionFailure or WebExceptionStatus.ConnectFailure) + { + return new ValidationFailure(string.Empty, "Unable to connect to indexer connection failure. Check your connection to the indexer's server and DNS." + webException.Message); + } + + if (webException.Message.Contains("502") || webException.Message.Contains("503") || + webException.Message.Contains("504") || webException.Message.Contains("timed out")) + { + return new ValidationFailure(string.Empty, "Unable to connect to indexer, indexer's server is unavailable. Try again later. " + webException.Message); } } catch (Exception ex) diff --git a/src/NzbDrone.Core/Indexers/RssParser.cs b/src/NzbDrone.Core/Indexers/RssParser.cs index c370de316..090982be9 100644 --- a/src/NzbDrone.Core/Indexers/RssParser.cs +++ b/src/NzbDrone.Core/Indexers/RssParser.cs @@ -123,7 +123,8 @@ namespace NzbDrone.Core.Indexers protected virtual bool PreProcess(IndexerResponse indexerResponse) { - if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK) + // Server Down HTTP Errors are handled in HTTPIndexerBase so ignore them here + if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK && !indexerResponse.HttpResponse.HasHttpServerError) { throw new IndexerException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode); } @@ -268,11 +269,11 @@ namespace NzbDrone.Core.Indexers try { return new RssEnclosure - { - Url = v.Attribute("url")?.Value, - Type = v.Attribute("type")?.Value, - Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0 - }; + { + Url = v.Attribute("url")?.Value, + Type = v.Attribute("type")?.Value, + Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0 + }; } catch (Exception e) {