improved API calls to use streams

This commit is contained in:
Flaminel
2026-03-23 14:14:24 +02:00
parent 662059263f
commit 2c8bd0675e
7 changed files with 70 additions and 67 deletions

View File

@@ -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<QueueListResponse>(responseBody);
QueueListResponse? queueResponse = await DeserializeStreamAsync<QueueListResponse>(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<ArrCommandStatus>(responseBody);
var result = await DeserializeStreamAsync<ArrCommandStatus>(response);
return result ?? new ArrCommandStatus(commandId, "unknown", null);
}
@@ -269,6 +267,26 @@ public abstract class ArrClient : IArrClient
return response;
}
protected static async Task<T?> DeserializeStreamAsync<T>(HttpResponseMessage response)
{
using Stream stream = await response.Content.ReadAsStreamAsync();
using StreamReader sr = new(stream);
using JsonTextReader reader = new(sr);
return JsonSerializer.CreateDefault().Deserialize<T>(reader);
}
protected static async Task<long?> ReadCommandIdAsync(HttpResponseMessage response)
{
CommandIdResponse? result = await DeserializeStreamAsync<CommandIdResponse>(response);
return result?.Id;
}
private sealed class CommandIdResponse
{
[JsonProperty("id")]
public long? Id { get; init; }
}
/// <summary>
/// Determines whether the failed import record should be skipped
/// </summary>

View File

@@ -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<List<Album>>(responseBody);
return await DeserializeStreamAsync<List<Album>>(response);
}
private List<LidarrCommand> GetSearchCommands(HashSet<SearchItem> items)

View File

@@ -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<dynamic>(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<List<ArrQualityProfile>>(responseBody) ?? [];
return await DeserializeStreamAsync<List<ArrQualityProfile>>(response) ?? [];
}
public async Task<Dictionary<long, int>> GetMovieFileScoresAsync(ArrInstance arrInstance, List<long> 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<MediaFileScore> files = JsonConvert.DeserializeObject<List<MediaFileScore>>(responseBody) ?? [];
List<MediaFileScore> files = await DeserializeStreamAsync<List<MediaFileScore>>(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<Movie>(responseBody);
return await DeserializeStreamAsync<Movie>(response);
}
}

View File

@@ -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<Book>(responseBody);
return await DeserializeStreamAsync<Book>(response);
}
}

View File

@@ -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<dynamic>(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<List<SearchableEpisode>>(responseBody) ?? [];
return await DeserializeStreamAsync<List<SearchableEpisode>>(response) ?? [];
}
public async Task<List<ArrEpisodeFile>> 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<List<ArrEpisodeFile>>(responseBody) ?? [];
return await DeserializeStreamAsync<List<ArrEpisodeFile>>(response) ?? [];
}
public async Task<List<ArrQualityProfile>> 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<List<ArrQualityProfile>>(responseBody) ?? [];
return await DeserializeStreamAsync<List<ArrQualityProfile>>(response) ?? [];
}
public async Task<Dictionary<long, int>> GetEpisodeFileScoresAsync(ArrInstance arrInstance, List<long> 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<MediaFileScore> files = JsonConvert.DeserializeObject<List<MediaFileScore>>(responseBody) ?? [];
List<MediaFileScore> files = await DeserializeStreamAsync<List<MediaFileScore>>(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<List<Episode>>(responseBody);
return await DeserializeStreamAsync<List<Episode>>(response);
}
private async Task<Series?> 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<Series>(responseBody);
return await DeserializeStreamAsync<Series>(response);
}
private List<SonarrCommand> GetSearchCommands(HashSet<SeriesSearchItem> items)

View File

@@ -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<List<Episode>>(responseContent);
return await DeserializeStreamAsync<List<Episode>>(response);
}
private async Task<Series?> 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<Series>(responseContent);
return await DeserializeStreamAsync<Series>(response);
}
private List<WhisparrV2Command> GetSearchCommands(HashSet<SeriesSearchItem> items)

View File

@@ -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<Movie>(responseBody);
return await DeserializeStreamAsync<Movie>(response);
}
}