From bbce1beb1d136d849141a5a5e634fed729fc6698 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 10 Jan 2024 16:54:33 -0500 Subject: [PATCH 1/4] Don't re-use HttpRequestMessage on re-try in SchedulesDirect --- src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs index 3b20cd160b..5c0e96c67b 100644 --- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs +++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs @@ -598,14 +598,14 @@ namespace Jellyfin.LiveTv.Listings } private async Task Send( - HttpRequestMessage options, + HttpRequestMessage message, bool enableRetry, ListingsProviderInfo providerInfo, CancellationToken cancellationToken, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { - var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false); + using var client = _httpClientFactory.CreateClient(NamedClient.Default); + var response = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); if (response.IsSuccessStatusCode) { return response; @@ -625,8 +625,13 @@ namespace Jellyfin.LiveTv.Listings #pragma warning restore IDISP016, IDISP017 _tokens.Clear(); - options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false)); - return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false); + using var retryMessage = new HttpRequestMessage(message.Method, message.RequestUri); + retryMessage.Content = message.Content; + retryMessage.Headers.TryAddWithoutValidation( + "token", + await GetToken(providerInfo, cancellationToken).ConfigureAwait(false)); + + return await Send(retryMessage, false, providerInfo, cancellationToken).ConfigureAwait(false); } private async Task GetTokenInternal( From f87a5490adce83c32362a14518d6f6e3e5a24917 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 10 Jan 2024 17:06:40 -0500 Subject: [PATCH 2/4] Fix disposable analyzer warnings in SchedulesDirect --- .../Listings/SchedulesDirect.cs | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs index 5c0e96c67b..5728146f78 100644 --- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs +++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs @@ -105,8 +105,7 @@ namespace Jellyfin.LiveTv.Listings using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules"); options.Content = JsonContent.Create(requestList, options: _jsonOptions); options.Headers.TryAddWithoutValidation("token", token); - using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); - var dailySchedules = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false); + var dailySchedules = await Request>(options, true, info, cancellationToken).ConfigureAwait(false); if (dailySchedules is null) { return Array.Empty(); @@ -120,8 +119,8 @@ namespace Jellyfin.LiveTv.Listings var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions); - using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); - var programDetails = await innerResponse.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false); + var programDetails = await Request>(programRequestOptions, true, info, cancellationToken) + .ConfigureAwait(false); if (programDetails is null) { return Array.Empty(); @@ -471,16 +470,13 @@ namespace Jellyfin.LiveTv.Listings str.Length--; str.Append(']'); - using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs") - { - Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json) - }; + using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs"); message.Headers.TryAddWithoutValidation("token", token); + message.Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json); try { - using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); - return await innerResponse2.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false); + return await Request>(message, true, info, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -506,8 +502,7 @@ namespace Jellyfin.LiveTv.Listings try { - using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false); - var root = await httpResponse.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await Request>(options, false, info, cancellationToken).ConfigureAwait(false); if (root is not null) { foreach (HeadendsDto headend in root) @@ -597,7 +592,7 @@ namespace Jellyfin.LiveTv.Listings } } - private async Task Send( + private async Task Request( HttpRequestMessage message, bool enableRetry, ListingsProviderInfo providerInfo, @@ -605,16 +600,12 @@ namespace Jellyfin.LiveTv.Listings HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { using var client = _httpClientFactory.CreateClient(NamedClient.Default); - var response = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); + using var response = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); if (response.IsSuccessStatusCode) { - return response; + return await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); } - // Response is automatically disposed in the calling function, - // so dispose manually if not returning. -#pragma warning disable IDISP016, IDISP017 - response.Dispose(); if (!enableRetry || (int)response.StatusCode >= 500) { throw new HttpRequestException( @@ -622,7 +613,6 @@ namespace Jellyfin.LiveTv.Listings null, response.StatusCode); } -#pragma warning restore IDISP016, IDISP017 _tokens.Clear(); using var retryMessage = new HttpRequestMessage(message.Method, message.RequestUri); @@ -631,7 +621,7 @@ namespace Jellyfin.LiveTv.Listings "token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false)); - return await Send(retryMessage, false, providerInfo, cancellationToken).ConfigureAwait(false); + return await Request(retryMessage, false, providerInfo, cancellationToken).ConfigureAwait(false); } private async Task GetTokenInternal( @@ -648,9 +638,7 @@ namespace Jellyfin.LiveTv.Listings string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant(); options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + hashedPassword + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json); - using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - var root = await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await Request(options, false, null, cancellationToken).ConfigureAwait(false); if (string.Equals(root?.Message, "OK", StringComparison.Ordinal)) { _logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token); @@ -689,9 +677,7 @@ namespace Jellyfin.LiveTv.Listings try { - using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false); - httpResponse.EnsureSuccessStatusCode(); - var root = await httpResponse.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await Request(options, false, null, cancellationToken).ConfigureAwait(false); return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false; } catch (HttpRequestException ex) @@ -744,8 +730,7 @@ namespace Jellyfin.LiveTv.Listings using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId); options.Headers.TryAddWithoutValidation("token", token); - using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false); - var root = await httpResponse.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); + var root = await Request(options, true, info, cancellationToken).ConfigureAwait(false); if (root is null) { return new List(); From 604f4b2742416abf3149e95d8168b538ecb8b5f1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 24 Jan 2024 11:17:45 -0500 Subject: [PATCH 3/4] Log SchedulesDirect response on request error --- .../Listings/SchedulesDirect.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs index 5728146f78..eaf5495c7f 100644 --- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs +++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs @@ -608,6 +608,11 @@ namespace Jellyfin.LiveTv.Listings if (!enableRetry || (int)response.StatusCode >= 500) { + _logger.LogError( + "Request to {Url} failed with response {Response}", + message.RequestUri, + await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)); + throw new HttpRequestException( string.Format(CultureInfo.InvariantCulture, "Request failed: {0}", response.ReasonPhrase), null, @@ -655,11 +660,22 @@ namespace Jellyfin.LiveTv.Listings ArgumentException.ThrowIfNullOrEmpty(token); ArgumentException.ThrowIfNullOrEmpty(info.ListingsId); - _logger.LogInformation("Adding new LineUp "); + _logger.LogInformation("Adding new lineup {Id}", info.ListingsId); - using var options = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId); - options.Headers.TryAddWithoutValidation("token", token); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + using var message = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId); + message.Headers.TryAddWithoutValidation("token", token); + + using var client = _httpClientFactory.CreateClient(NamedClient.Default); + using var response = await client + .SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + _logger.LogError( + "Error adding lineup to account: {Response}", + await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)); + } } private async Task HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken) From 584636bdd8ea95d56b3c1cda97ce6efa8ce1543c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 6 Feb 2024 09:37:02 -0500 Subject: [PATCH 4/4] Don't dispose HttpClients --- src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs index eaf5495c7f..64b64c0aeb 100644 --- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs +++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs @@ -599,8 +599,9 @@ namespace Jellyfin.LiveTv.Listings CancellationToken cancellationToken, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { - using var client = _httpClientFactory.CreateClient(NamedClient.Default); - using var response = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(message, completionOption, cancellationToken) + .ConfigureAwait(false); if (response.IsSuccessStatusCode) { return await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken).ConfigureAwait(false); @@ -665,8 +666,7 @@ namespace Jellyfin.LiveTv.Listings using var message = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId); message.Headers.TryAddWithoutValidation("token", token); - using var client = _httpClientFactory.CreateClient(NamedClient.Default); - using var response = await client + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken) .ConfigureAwait(false);