diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs index 9b8c9e23..960c67ca 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs @@ -42,8 +42,8 @@ public abstract class ArrClient : IArrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - - using HttpResponseMessage response = await _httpClient.SendAsync(request); + + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); try { @@ -54,13 +54,12 @@ public abstract class ArrClient : IArrClient _logger.LogError("queue list failed | {uri}", uriBuilder.Uri); throw; } - - string responseBody = await response.Content.ReadAsStringAsync(); - QueueListResponse? queueResponse = JsonConvert.DeserializeObject(responseBody); + + QueueListResponse? queueResponse = await DeserializeStreamAsync(response); if (queueResponse is null) { - throw new Exception($"unrecognized queue list response | {uriBuilder.Uri} | {responseBody}"); + throw new Exception($"unrecognized queue list response | {uriBuilder.Uri}"); } return queueResponse; @@ -236,11 +235,10 @@ public abstract class ArrClient : IArrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(responseBody); + var result = await DeserializeStreamAsync(response); return result ?? new ArrCommandStatus(commandId, "unknown", null); } @@ -269,6 +267,26 @@ public abstract class ArrClient : IArrClient return response; } + protected static async Task DeserializeStreamAsync(HttpResponseMessage response) + { + using Stream stream = await response.Content.ReadAsStreamAsync(); + using StreamReader sr = new(stream); + using JsonTextReader reader = new(sr); + return JsonSerializer.CreateDefault().Deserialize(reader); + } + + protected static async Task ReadCommandIdAsync(HttpResponseMessage response) + { + CommandIdResponse? result = await DeserializeStreamAsync(response); + return result?.Id; + } + + private sealed class CommandIdResponse + { + [JsonProperty("id")] + public long? Id { get; init; } + } + /// /// Determines whether the failed import record should be skipped /// diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs index cc182c3f..280d3513 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs @@ -139,15 +139,14 @@ public class LidarrClient : ArrClient, ILidarrClient UriBuilder uriBuilder = new(arrInstance.Url); uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/v1/album"; uriBuilder.Query = string.Join('&', albumIds.Select(x => $"albumIds={x}")); - + using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using var response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(responseBody); + return await DeserializeStreamAsync>(response); } private List GetSearchCommands(HashSet items) diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs index 9209eac2..7c8711d2 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs @@ -88,11 +88,9 @@ public class RadarrClient : ArrClient, IRadarrClient return []; } - string responseBody = await response.Content.ReadAsStringAsync(); + long? commandId = await ReadCommandIdAsync(response); response.Dispose(); - long? commandId = JsonConvert.DeserializeObject(responseBody)?.id; - _logger.LogInformation("{log}", GetSearchLog(arrInstance.Url, command, true, logContext)); return commandId.HasValue ? [commandId.Value] : []; @@ -168,11 +166,10 @@ public class RadarrClient : ArrClient, IRadarrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(responseBody) ?? []; + return await DeserializeStreamAsync>(response) ?? []; } public async Task> GetMovieFileScoresAsync(ArrInstance arrInstance, List movieFileIds) @@ -189,11 +186,10 @@ public class RadarrClient : ArrClient, IRadarrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - List files = JsonConvert.DeserializeObject>(responseBody) ?? []; + List files = await DeserializeStreamAsync>(response) ?? []; foreach (MediaFileScore file in files) { @@ -208,14 +204,13 @@ public class RadarrClient : ArrClient, IRadarrClient { UriBuilder uriBuilder = new(arrInstance.Url); uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/v3/movie/{movieId}"; - + using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseBody); + + return await DeserializeStreamAsync(response); } } \ No newline at end of file diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs index 444c307b..f86cb442 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs @@ -136,14 +136,13 @@ public class ReadarrClient : ArrClient, IReadarrClient { UriBuilder uriBuilder = new(arrInstance.Url); uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/v1/book/{bookId}"; - + using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseBody); + + return await DeserializeStreamAsync(response); } } \ No newline at end of file diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs index 42a3f3aa..9a74b03f 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs @@ -83,10 +83,9 @@ public class SonarrClient : ArrClient, ISonarrClient if (response is not null) { - string responseBody = await response.Content.ReadAsStringAsync(); + long? commandId = await ReadCommandIdAsync(response); response.Dispose(); - long? commandId = JsonConvert.DeserializeObject(responseBody)?.id; if (commandId.HasValue) { commandIds.Add(commandId.Value); @@ -237,11 +236,10 @@ public class SonarrClient : ArrClient, ISonarrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(responseBody) ?? []; + return await DeserializeStreamAsync>(response) ?? []; } public async Task> GetEpisodeFilesAsync(ArrInstance arrInstance, long seriesId) @@ -253,11 +251,10 @@ public class SonarrClient : ArrClient, ISonarrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(responseBody) ?? []; + return await DeserializeStreamAsync>(response) ?? []; } public async Task> GetQualityProfilesAsync(ArrInstance arrInstance) @@ -268,11 +265,10 @@ public class SonarrClient : ArrClient, ISonarrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(responseBody) ?? []; + return await DeserializeStreamAsync>(response) ?? []; } public async Task> GetEpisodeFileScoresAsync(ArrInstance arrInstance, List episodeFileIds) @@ -289,11 +285,10 @@ public class SonarrClient : ArrClient, ISonarrClient using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - string responseBody = await response.Content.ReadAsStringAsync(); - List files = JsonConvert.DeserializeObject>(responseBody) ?? []; + List files = await DeserializeStreamAsync>(response) ?? []; foreach (MediaFileScore file in files) { @@ -309,30 +304,28 @@ public class SonarrClient : ArrClient, ISonarrClient UriBuilder uriBuilder = new(arrInstance.Url); uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/v3/episode"; uriBuilder.Query = string.Join('&', episodeIds.Select(x => $"episodeIds={x}")); - + using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(responseBody); + + return await DeserializeStreamAsync>(response); } private async Task GetSeriesAsync(ArrInstance arrInstance, long seriesId) { UriBuilder uriBuilder = new(arrInstance.Url); uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/v3/series/{seriesId}"; - + using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseBody); + + return await DeserializeStreamAsync(response); } private List GetSearchCommands(HashSet items) diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs index 8b090428..07db1f8b 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs @@ -206,10 +206,10 @@ public class WhisparrV2Client : ArrClient, IWhisparrV2Client using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - HttpResponseMessage response = await SendRequestAsync(request); - string responseContent = await response.Content.ReadAsStringAsync(); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); - return JsonConvert.DeserializeObject>(responseContent); + return await DeserializeStreamAsync>(response); } private async Task GetSeriesAsync(ArrInstance arrInstance, long seriesId) @@ -220,10 +220,10 @@ public class WhisparrV2Client : ArrClient, IWhisparrV2Client using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - HttpResponseMessage response = await SendRequestAsync(request); - string responseContent = await response.Content.ReadAsStringAsync(); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); - return JsonConvert.DeserializeObject(responseContent); + return await DeserializeStreamAsync(response); } private List GetSearchCommands(HashSet items) diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs index d15db1b7..58f868b8 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs @@ -137,14 +137,13 @@ public class WhisparrV3Client : ArrClient, IWhisparrV3Client { UriBuilder uriBuilder = new(arrInstance.Url); uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/v3/movie/{movieId}"; - + using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); SetApiKey(request, arrInstance.ApiKey); - using HttpResponseMessage response = await _httpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); - - string responseBody = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(responseBody); + + return await DeserializeStreamAsync(response); } } \ No newline at end of file