From 6880e67507e7ad9b4f557eb90f868c962f11e533 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Mon, 26 Jun 2023 13:19:08 +0300 Subject: [PATCH] Fixed: (Apps) Ensure validation for test connection --- .../LazyLibrarian/LazyLibrarian.cs | 7 +- .../LazyLibrarian/LazyLibrarianSettings.cs | 2 +- .../LazyLibrarian/LazyLibrarianV1Proxy.cs | 21 +++--- .../Applications/Lidarr/Lidarr.cs | 32 ++++++++- .../Applications/Lidarr/LidarrSettings.cs | 2 +- .../Applications/Lidarr/LidarrV1Proxy.cs | 56 +++++---------- src/NzbDrone.Core/Applications/Mylar/Mylar.cs | 7 +- .../Applications/Mylar/MylarSettings.cs | 2 +- .../Applications/Mylar/MylarV3Proxy.cs | 21 +++--- .../Applications/Radarr/Radarr.cs | 32 ++++++++- .../Applications/Radarr/RadarrSettings.cs | 2 +- .../Applications/Radarr/RadarrV3Proxy.cs | 70 ++++++------------- .../Applications/Readarr/Readarr.cs | 35 +++++++++- .../Applications/Readarr/ReadarrSettings.cs | 2 +- .../Applications/Readarr/ReadarrV1Proxy.cs | 46 +++--------- .../Applications/Sonarr/Sonarr.cs | 36 +++++++++- .../Applications/Sonarr/SonarrSettings.cs | 2 +- .../Applications/Sonarr/SonarrV3Proxy.cs | 62 +++++----------- .../Applications/Whisparr/Whisparr.cs | 35 +++++++++- .../Applications/Whisparr/WhisparrSettings.cs | 2 +- .../Applications/Whisparr/WhisparrV3Proxy.cs | 44 +++--------- 21 files changed, 275 insertions(+), 243 deletions(-) diff --git a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs index 56401c46d..7ed59061c 100644 --- a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs +++ b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarian.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; @@ -32,10 +31,10 @@ namespace NzbDrone.Core.Applications.LazyLibrarian { failures.AddIfNotNull(_lazyLibrarianV1Proxy.TestConnection(Settings)); } - catch (WebException ex) + catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); - failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to LazyLibrarian")); + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to LazyLibrarian. {ex.Message}")); } return new ValidationResult(failures); diff --git a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianSettings.cs b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianSettings.cs index ab5590c15..9b2d17221 100644 --- a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianSettings.cs +++ b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianSettings.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Core.Applications.LazyLibrarian [FieldDefinition(1, Label = "LazyLibrarian Server", HelpText = "URL used to connect to LazyLibrarian server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:5299")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by LazyLibrarian in Settings/Web Interface")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by LazyLibrarian in Settings/Web Interface")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianV1Proxy.cs b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianV1Proxy.cs index 43df7764f..563f9ed3b 100644 --- a/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianV1Proxy.cs +++ b/src/NzbDrone.Core/Applications/LazyLibrarian/LazyLibrarianV1Proxy.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using FluentValidation.Results; -using Newtonsoft.Json; using NLog; using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Applications.LazyLibrarian { @@ -139,11 +139,11 @@ namespace NzbDrone.Core.Applications.LazyLibrarian return new ValidationFailure("ApiKey", status.Error.Message); } - var indexers = GetIndexers(settings); + GetIndexers(settings); } catch (HttpException ex) { - _logger.Error(ex, "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); return new ValidationFailure("BaseUrl", "Unable to complete application test"); } catch (LazyLibrarianException ex) @@ -153,8 +153,8 @@ namespace NzbDrone.Core.Applications.LazyLibrarian } catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); + return new ValidationFailure("", $"Unable to send test message. {ex.Message}"); } return null; @@ -164,7 +164,9 @@ namespace NzbDrone.Core.Applications.LazyLibrarian { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var requestBuilder = new HttpRequestBuilder(baseUrl).Resource(resource) + var requestBuilder = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .AddQueryParam("cmd", command) .AddQueryParam("apikey", settings.ApiKey); @@ -191,9 +193,12 @@ namespace NzbDrone.Core.Applications.LazyLibrarian { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } private int CalculatePriority(int indexerPriority) => ProwlarrHighestPriority - indexerPriority + 1; diff --git a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs index f33cb700a..a5f97c0dc 100644 --- a/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs +++ b/src/NzbDrone.Core/Applications/Lidarr/Lidarr.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using FluentValidation.Results; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; @@ -48,9 +51,36 @@ namespace NzbDrone.Core.Applications.Lidarr { failures.AddIfNotNull(_lidarrV1Proxy.TestConnection(BuildLidarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings)); } + catch (HttpException ex) + { + switch (ex.Response.StatusCode) + { + case HttpStatusCode.Unauthorized: + _logger.Error(ex, "API Key is invalid"); + failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid")); + break; + case HttpStatusCode.BadRequest: + _logger.Error(ex, "Prowlarr URL is invalid"); + failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Lidarr cannot connect to Prowlarr")); + break; + case HttpStatusCode.SeeOther: + _logger.Error(ex, "Lidarr returned redirect and is invalid"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Lidarr URL is invalid, Prowlarr cannot connect to Lidarr - are you missing a URL base?")); + break; + default: + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}")); + break; + } + } + catch (JsonReaderException ex) + { + _logger.Error(ex, "Unable to parse JSON response from application"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}")); + } catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Lidarr. {ex.Message}")); } diff --git a/src/NzbDrone.Core/Applications/Lidarr/LidarrSettings.cs b/src/NzbDrone.Core/Applications/Lidarr/LidarrSettings.cs index a39cc7637..f53f3d90d 100644 --- a/src/NzbDrone.Core/Applications/Lidarr/LidarrSettings.cs +++ b/src/NzbDrone.Core/Applications/Lidarr/LidarrSettings.cs @@ -33,7 +33,7 @@ namespace NzbDrone.Core.Applications.Lidarr [FieldDefinition(1, Label = "Lidarr Server", HelpText = "URL used to connect to Lidarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8686")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Lidarr in Settings/General")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs b/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs index 2674fcf59..8518a1df4 100644 --- a/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs +++ b/src/NzbDrone.Core/Applications/Lidarr/LidarrV1Proxy.cs @@ -91,6 +91,8 @@ namespace NzbDrone.Core.Applications.Lidarr } catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest) { + _logger.Debug("Retrying to add indexer forcefully"); + request.Url = request.Url.AddQueryParam("forceSave", "true"); return ExecuteIndexerRequest(request); @@ -113,47 +115,16 @@ namespace NzbDrone.Core.Applications.Lidarr request.SetContent(indexer.ToJson()); - try - { - var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version"); - - if (applicationVersion == null) - { - return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version"); - } + var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version"); - if (new Version(applicationVersion) < MinimumApplicationVersion) - { - return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); - } - } - catch (HttpException ex) + if (applicationVersion == null) { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "API Key is invalid"); - return new ValidationFailure("ApiKey", "API Key is invalid"); - } - - if (ex.Response.StatusCode == HttpStatusCode.BadRequest) - { - _logger.Error(ex, "Prowlarr URL is invalid"); - return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Lidarr cannot connect to Prowlarr"); - } - - if (ex.Response.StatusCode == HttpStatusCode.SeeOther) - { - _logger.Error(ex, "Lidarr returned redirect and is invalid"); - return new ValidationFailure("BaseUrl", "Lidarr url is invalid, Prowlarr cannot connect to Lidarr - are you missing a url base?"); - } - - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("BaseUrl", "Unable to complete application test"); + return new ValidationFailure(string.Empty, "Failed to fetch Lidarr version"); } - catch (Exception ex) + + if (new Version(applicationVersion) < MinimumApplicationVersion) { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); + return new ValidationFailure(string.Empty, $"Lidarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); } return null; @@ -210,7 +181,9 @@ namespace NzbDrone.Core.Applications.Lidarr { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var request = new HttpRequestBuilder(baseUrl).Resource(resource) + var request = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .SetHeader("X-Api-Key", settings.ApiKey) .Build(); @@ -227,9 +200,12 @@ namespace NzbDrone.Core.Applications.Lidarr { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } } } diff --git a/src/NzbDrone.Core/Applications/Mylar/Mylar.cs b/src/NzbDrone.Core/Applications/Mylar/Mylar.cs index 247c752dd..7d84feae1 100644 --- a/src/NzbDrone.Core/Applications/Mylar/Mylar.cs +++ b/src/NzbDrone.Core/Applications/Mylar/Mylar.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; @@ -32,10 +31,10 @@ namespace NzbDrone.Core.Applications.Mylar { failures.AddIfNotNull(_mylarV3Proxy.TestConnection(Settings)); } - catch (WebException ex) + catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); - failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Mylar")); + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Mylar. {ex.Message}")); } return new ValidationResult(failures); diff --git a/src/NzbDrone.Core/Applications/Mylar/MylarSettings.cs b/src/NzbDrone.Core/Applications/Mylar/MylarSettings.cs index 385be12e1..f6f58b9e5 100644 --- a/src/NzbDrone.Core/Applications/Mylar/MylarSettings.cs +++ b/src/NzbDrone.Core/Applications/Mylar/MylarSettings.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Mylar [FieldDefinition(1, Label = "Mylar Server", HelpText = "URL used to connect to Mylar server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8090")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Mylar in Settings/Web Interface")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Mylar in Settings/Web Interface")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/Mylar/MylarV3Proxy.cs b/src/NzbDrone.Core/Applications/Mylar/MylarV3Proxy.cs index 4c72160e9..119175634 100644 --- a/src/NzbDrone.Core/Applications/Mylar/MylarV3Proxy.cs +++ b/src/NzbDrone.Core/Applications/Mylar/MylarV3Proxy.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using FluentValidation.Results; -using Newtonsoft.Json; using NLog; using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Applications.Mylar { @@ -135,11 +135,11 @@ namespace NzbDrone.Core.Applications.Mylar return new ValidationFailure("ApiKey", status.Error.Message); } - var indexers = GetIndexers(settings); + GetIndexers(settings); } catch (HttpException ex) { - _logger.Error(ex, "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); return new ValidationFailure("BaseUrl", "Unable to complete application test"); } catch (MylarException ex) @@ -149,8 +149,8 @@ namespace NzbDrone.Core.Applications.Mylar } catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); + return new ValidationFailure("", $"Unable to send test message. {ex.Message}"); } return null; @@ -160,7 +160,9 @@ namespace NzbDrone.Core.Applications.Mylar { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var requestBuilder = new HttpRequestBuilder(baseUrl).Resource(resource) + var requestBuilder = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .AddQueryParam("cmd", command) .AddQueryParam("apikey", settings.ApiKey); @@ -187,9 +189,12 @@ namespace NzbDrone.Core.Applications.Mylar { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } } } diff --git a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs index 8b10a6db1..a60c229b3 100644 --- a/src/NzbDrone.Core/Applications/Radarr/Radarr.cs +++ b/src/NzbDrone.Core/Applications/Radarr/Radarr.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using FluentValidation.Results; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; @@ -48,9 +51,36 @@ namespace NzbDrone.Core.Applications.Radarr { failures.AddIfNotNull(_radarrV3Proxy.TestConnection(BuildRadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings)); } + catch (HttpException ex) + { + switch (ex.Response.StatusCode) + { + case HttpStatusCode.Unauthorized: + _logger.Error(ex, "API Key is invalid"); + failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid")); + break; + case HttpStatusCode.BadRequest: + _logger.Error(ex, "Prowlarr URL is invalid"); + failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Radarr cannot connect to Prowlarr")); + break; + case HttpStatusCode.SeeOther: + _logger.Error(ex, "Radarr returned redirect and is invalid"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Radarr URL is invalid, Prowlarr cannot connect to Radarr - are you missing a URL base?")); + break; + default: + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}")); + break; + } + } + catch (JsonReaderException ex) + { + _logger.Error(ex, "Unable to parse JSON response from application"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}")); + } catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Radarr. {ex.Message}")); } diff --git a/src/NzbDrone.Core/Applications/Radarr/RadarrSettings.cs b/src/NzbDrone.Core/Applications/Radarr/RadarrSettings.cs index 68a98879c..030c89244 100644 --- a/src/NzbDrone.Core/Applications/Radarr/RadarrSettings.cs +++ b/src/NzbDrone.Core/Applications/Radarr/RadarrSettings.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Radarr [FieldDefinition(1, Label = "Radarr Server", HelpText = "URL used to connect to Radarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:7878")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs b/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs index 2adb8f5c3..4243b5ff5 100644 --- a/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs +++ b/src/NzbDrone.Core/Applications/Radarr/RadarrV3Proxy.cs @@ -92,6 +92,8 @@ namespace NzbDrone.Core.Applications.Radarr } catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest) { + _logger.Debug("Retrying to add indexer forcefully"); + request.Url = request.Url.AddQueryParam("forceSave", "true"); return ExecuteIndexerRequest(request); @@ -114,59 +116,28 @@ namespace NzbDrone.Core.Applications.Radarr request.SetContent(indexer.ToJson()); - try - { - var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version"); + var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version"); - if (applicationVersion == null) - { - return new ValidationFailure(string.Empty, "Failed to fetch Radarr version"); - } + if (applicationVersion == null) + { + return new ValidationFailure(string.Empty, "Failed to fetch Radarr version"); + } - var version = new Version(applicationVersion); + var version = new Version(applicationVersion); - if (version.Major == 3) - { - if (version < MinimumApplicationV3Version) - { - return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); - } - } - else + if (version.Major == 3) + { + if (version < MinimumApplicationV3Version) { - if (version < MinimumApplicationV4Version) - { - return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); - } + return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV3Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); } } - catch (HttpException ex) + else { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + if (version < MinimumApplicationV4Version) { - _logger.Error(ex, "API Key is invalid"); - return new ValidationFailure("ApiKey", "API Key is invalid"); + return new ValidationFailure(string.Empty, $"Radarr version should be at least {MinimumApplicationV4Version.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); } - - if (ex.Response.StatusCode == HttpStatusCode.BadRequest) - { - _logger.Error(ex, "Prowlarr URL is invalid"); - return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Radarr cannot connect to Prowlarr"); - } - - if (ex.Response.StatusCode == HttpStatusCode.SeeOther) - { - _logger.Error(ex, "Radarr returned redirect and is invalid"); - return new ValidationFailure("BaseUrl", "Radarr url is invalid, Prowlarr cannot connect to Radarr - are you missing a url base?"); - } - - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("BaseUrl", "Unable to complete application test"); - } - catch (Exception ex) - { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); } return null; @@ -223,7 +194,9 @@ namespace NzbDrone.Core.Applications.Radarr { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var request = new HttpRequestBuilder(baseUrl).Resource(resource) + var request = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .SetHeader("X-Api-Key", settings.ApiKey) .Build(); @@ -240,9 +213,12 @@ namespace NzbDrone.Core.Applications.Radarr { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } } } diff --git a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs index 81655bf03..6d2b593f3 100644 --- a/src/NzbDrone.Core/Applications/Readarr/Readarr.cs +++ b/src/NzbDrone.Core/Applications/Readarr/Readarr.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Net; using FluentValidation.Results; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; @@ -49,10 +51,37 @@ namespace NzbDrone.Core.Applications.Readarr { failures.AddIfNotNull(_readarrV1Proxy.TestConnection(BuildReadarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings)); } - catch (WebException ex) + catch (HttpException ex) { - _logger.Error(ex, "Unable to send test message"); - failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Readarr")); + switch (ex.Response.StatusCode) + { + case HttpStatusCode.Unauthorized: + _logger.Error(ex, "API Key is invalid"); + failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid")); + break; + case HttpStatusCode.BadRequest: + _logger.Error(ex, "Prowlarr URL is invalid"); + failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Readarr cannot connect to Prowlarr")); + break; + case HttpStatusCode.SeeOther: + _logger.Error(ex, "Readarr returned redirect and is invalid"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Readarr URL is invalid, Prowlarr cannot connect to Readarr - are you missing a URL base?")); + break; + default: + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Readarr. {ex.Message}")); + break; + } + } + catch (JsonReaderException ex) + { + _logger.Error(ex, "Unable to parse JSON response from application"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}")); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Readarr. {ex.Message}")); } return new ValidationResult(failures); diff --git a/src/NzbDrone.Core/Applications/Readarr/ReadarrSettings.cs b/src/NzbDrone.Core/Applications/Readarr/ReadarrSettings.cs index a769d8406..55a7014bd 100644 --- a/src/NzbDrone.Core/Applications/Readarr/ReadarrSettings.cs +++ b/src/NzbDrone.Core/Applications/Readarr/ReadarrSettings.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Readarr [FieldDefinition(1, Label = "Readarr Server", HelpText = "URL used to connect to Readarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8787")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Readarr in Settings/General")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs b/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs index cfcd18264..90f22e207 100644 --- a/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs +++ b/src/NzbDrone.Core/Applications/Readarr/ReadarrV1Proxy.cs @@ -88,6 +88,8 @@ namespace NzbDrone.Core.Applications.Readarr } catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest) { + _logger.Debug("Retrying to add indexer forcefully"); + request.Url = request.Url.AddQueryParam("forceSave", "true"); return ExecuteIndexerRequest(request); @@ -110,38 +112,7 @@ namespace NzbDrone.Core.Applications.Readarr request.SetContent(indexer.ToJson()); - try - { - Execute(request); - } - catch (HttpException ex) - { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "API Key is invalid"); - return new ValidationFailure("ApiKey", "API Key is invalid"); - } - - if (ex.Response.StatusCode == HttpStatusCode.BadRequest) - { - _logger.Error(ex, "Prowlarr URL is invalid"); - return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Readarr cannot connect to Prowlarr"); - } - - if (ex.Response.StatusCode == HttpStatusCode.SeeOther) - { - _logger.Error(ex, "Readarr returned redirect and is invalid"); - return new ValidationFailure("BaseUrl", "Readarr url is invalid, Prowlarr cannot connect to Readarr - are you missing a url base?"); - } - - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("BaseUrl", "Unable to complete application test"); - } - catch (Exception ex) - { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); - } + _httpClient.Post(request); return null; } @@ -197,7 +168,9 @@ namespace NzbDrone.Core.Applications.Readarr { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var request = new HttpRequestBuilder(baseUrl).Resource(resource) + var request = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .SetHeader("X-Api-Key", settings.ApiKey) .Build(); @@ -214,9 +187,12 @@ namespace NzbDrone.Core.Applications.Readarr { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } } } diff --git a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs index 4f43b58c5..acfdbc137 100644 --- a/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs +++ b/src/NzbDrone.Core/Applications/Sonarr/Sonarr.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using FluentValidation.Results; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; @@ -48,9 +51,40 @@ namespace NzbDrone.Core.Applications.Sonarr { failures.AddIfNotNull(_sonarrV3Proxy.TestConnection(BuildSonarrIndexer(testIndexer, DownloadProtocol.Usenet), Settings)); } + catch (HttpException ex) + { + switch (ex.Response.StatusCode) + { + case HttpStatusCode.Unauthorized: + _logger.Error(ex, "API Key is invalid"); + failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid")); + break; + case HttpStatusCode.BadRequest: + _logger.Error(ex, "Prowlarr URL is invalid"); + failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Sonarr cannot connect to Prowlarr")); + break; + case HttpStatusCode.SeeOther: + _logger.Error(ex, "Sonarr returned redirect and is invalid"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Sonarr URL is invalid, Prowlarr cannot connect to Sonarr - are you missing a URL base?")); + break; + case HttpStatusCode.NotFound: + _logger.Error(ex, "Sonarr not found"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Sonarr URL is invalid, Prowlarr cannot connect to Sonarr. Is Sonarr running and accessible? Sonarr v2 is not supported.")); + break; + default: + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}")); + break; + } + } + catch (JsonReaderException ex) + { + _logger.Error(ex, "Unable to parse JSON response from application"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}")); + } catch (Exception ex) { - _logger.Error(ex, "Unable to send test message"); + _logger.Error(ex, "Unable to complete application test"); failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Sonarr. {ex.Message}")); } diff --git a/src/NzbDrone.Core/Applications/Sonarr/SonarrSettings.cs b/src/NzbDrone.Core/Applications/Sonarr/SonarrSettings.cs index 92669b94e..f859fa917 100644 --- a/src/NzbDrone.Core/Applications/Sonarr/SonarrSettings.cs +++ b/src/NzbDrone.Core/Applications/Sonarr/SonarrSettings.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Sonarr [FieldDefinition(1, Label = "Sonarr Server", HelpText = "URL used to connect to Sonarr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:8989")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Sonarr in Settings/General")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Sonarr in Settings/General")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs b/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs index 63bc96a5f..a4b9cf713 100644 --- a/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs +++ b/src/NzbDrone.Core/Applications/Sonarr/SonarrV3Proxy.cs @@ -91,6 +91,8 @@ namespace NzbDrone.Core.Applications.Sonarr } catch (HttpException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest) { + _logger.Debug("Retrying to add indexer forcefully"); + request.Url = request.Url.AddQueryParam("forceSave", "true"); return ExecuteIndexerRequest(request); @@ -113,53 +115,16 @@ namespace NzbDrone.Core.Applications.Sonarr request.SetContent(indexer.ToJson()); - try - { - var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version"); + var applicationVersion = _httpClient.Post(request).Headers.GetSingleValue("X-Application-Version"); - if (applicationVersion == null) - { - return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version"); - } - - if (new Version(applicationVersion) < MinimumApplicationVersion) - { - return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); - } - } - catch (HttpException ex) + if (applicationVersion == null) { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "API Key is invalid"); - return new ValidationFailure("ApiKey", "API Key is invalid"); - } - - if (ex.Response.StatusCode == HttpStatusCode.BadRequest) - { - _logger.Error(ex, "Prowlarr URL is invalid"); - return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Sonarr cannot connect to Prowlarr"); - } - - if (ex.Response.StatusCode == HttpStatusCode.SeeOther) - { - _logger.Error(ex, "Sonarr returned redirect and is invalid"); - return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr - are you missing a url base?"); - } - - if (ex.Response.StatusCode == HttpStatusCode.NotFound) - { - _logger.Error(ex, "Sonarr not found"); - return new ValidationFailure("BaseUrl", "Sonarr url is invalid, Prowlarr cannot connect to Sonarr. Is Sonarr running and accessible? Sonarr v2 is not supported."); - } - - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("BaseUrl", "Unable to complete application test"); + return new ValidationFailure(string.Empty, "Failed to fetch Sonarr version"); } - catch (Exception ex) + + if (new Version(applicationVersion) < MinimumApplicationVersion) { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); + return new ValidationFailure(string.Empty, $"Sonarr version should be at least {MinimumApplicationVersion.ToString(3)}. Version reported is {applicationVersion}", applicationVersion); } return null; @@ -216,7 +181,9 @@ namespace NzbDrone.Core.Applications.Sonarr { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var request = new HttpRequestBuilder(baseUrl).Resource(resource) + var request = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .SetHeader("X-Api-Key", settings.ApiKey) .Build(); @@ -233,9 +200,12 @@ namespace NzbDrone.Core.Applications.Sonarr { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } } } diff --git a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs index cab60d72a..94753ecd9 100644 --- a/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs +++ b/src/NzbDrone.Core/Applications/Whisparr/Whisparr.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Net; using FluentValidation.Results; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; @@ -49,10 +51,37 @@ namespace NzbDrone.Core.Applications.Whisparr { failures.AddIfNotNull(_whisparrV3Proxy.TestConnection(BuildWhisparrIndexer(testIndexer, DownloadProtocol.Usenet), Settings)); } - catch (WebException ex) + catch (HttpException ex) { - _logger.Error(ex, "Unable to send test message"); - failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Unable to complete application test, cannot connect to Whisparr")); + switch (ex.Response.StatusCode) + { + case HttpStatusCode.Unauthorized: + _logger.Error(ex, "API Key is invalid"); + failures.AddIfNotNull(new ValidationFailure("ApiKey", "API Key is invalid")); + break; + case HttpStatusCode.BadRequest: + _logger.Error(ex, "Prowlarr URL is invalid"); + failures.AddIfNotNull(new ValidationFailure("ProwlarrUrl", "Prowlarr URL is invalid, Whisparr cannot connect to Prowlarr")); + break; + case HttpStatusCode.SeeOther: + _logger.Error(ex, "Whisparr returned redirect and is invalid"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", "Whisparr URL is invalid, Prowlarr cannot connect to Whisparr - are you missing a URL base?")); + break; + default: + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Whisparr. {ex.Message}")); + break; + } + } + catch (JsonReaderException ex) + { + _logger.Error(ex, "Unable to parse JSON response from application"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to parse JSON response from application. {ex.Message}")); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to complete application test"); + failures.AddIfNotNull(new ValidationFailure("BaseUrl", $"Unable to complete application test, cannot connect to Whisparr. {ex.Message}")); } return new ValidationResult(failures); diff --git a/src/NzbDrone.Core/Applications/Whisparr/WhisparrSettings.cs b/src/NzbDrone.Core/Applications/Whisparr/WhisparrSettings.cs index 3f888b769..5fe636747 100644 --- a/src/NzbDrone.Core/Applications/Whisparr/WhisparrSettings.cs +++ b/src/NzbDrone.Core/Applications/Whisparr/WhisparrSettings.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Applications.Whisparr [FieldDefinition(1, Label = "Whisparr Server", HelpText = "URL used to connect to Whisparr server, including http(s)://, port, and urlbase if required", Placeholder = "http://localhost:6969")] public string BaseUrl { get; set; } - [FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")] + [FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Whisparr in Settings/General")] public string ApiKey { get; set; } [FieldDefinition(3, Label = "Sync Categories", Type = FieldType.Select, SelectOptions = typeof(NewznabCategoryFieldConverter), Advanced = true, HelpText = "Only Indexers that support these categories will be synced")] diff --git a/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs b/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs index 62482081f..c605c3fe7 100644 --- a/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs +++ b/src/NzbDrone.Core/Applications/Whisparr/WhisparrV3Proxy.cs @@ -110,38 +110,7 @@ namespace NzbDrone.Core.Applications.Whisparr request.SetContent(indexer.ToJson()); - try - { - Execute(request); - } - catch (HttpException ex) - { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) - { - _logger.Error(ex, "API Key is invalid"); - return new ValidationFailure("ApiKey", "API Key is invalid"); - } - - if (ex.Response.StatusCode == HttpStatusCode.BadRequest) - { - _logger.Error(ex, "Prowlarr URL is invalid"); - return new ValidationFailure("ProwlarrUrl", "Prowlarr url is invalid, Whisparr cannot connect to Prowlarr"); - } - - if (ex.Response.StatusCode == HttpStatusCode.SeeOther) - { - _logger.Error(ex, "Whisparr returned redirect and is invalid"); - return new ValidationFailure("BaseUrl", "Whisparr url is invalid, Prowlarr cannot connect to Whisparr - are you missing a url base?"); - } - - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("BaseUrl", "Unable to complete application test"); - } - catch (Exception ex) - { - _logger.Error(ex, "Unable to send test message"); - return new ValidationFailure("", "Unable to send test message"); - } + _httpClient.Post(request); return null; } @@ -197,7 +166,9 @@ namespace NzbDrone.Core.Applications.Whisparr { var baseUrl = settings.BaseUrl.TrimEnd('/'); - var request = new HttpRequestBuilder(baseUrl).Resource(resource) + var request = new HttpRequestBuilder(baseUrl) + .Resource(resource) + .Accept(HttpAccept.Json) .SetHeader("X-Api-Key", settings.ApiKey) .Build(); @@ -214,9 +185,12 @@ namespace NzbDrone.Core.Applications.Whisparr { var response = _httpClient.Execute(request); - var results = JsonConvert.DeserializeObject(response.Content); + if ((int)response.StatusCode >= 300) + { + throw new HttpException(response); + } - return results; + return Json.Deserialize(response.Content); } } }