diff --git a/Source/ZoomNet/Resources/CloudRecordings.cs b/Source/ZoomNet/Resources/CloudRecordings.cs index 5fbe2e5e..66204e7e 100644 --- a/Source/ZoomNet/Resources/CloudRecordings.cs +++ b/Source/ZoomNet/Resources/CloudRecordings.cs @@ -4,10 +4,12 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using ZoomNet.Models; +using ZoomNet.Utilities; namespace ZoomNet.Resources { @@ -356,20 +358,44 @@ public Task RejectRegistrantsAsync(long meetingId, IEnumerable registran return UpdateRegistrantsStatusAsync(meetingId, registrantIds, "deny", cancellationToken); } - /// - /// Download the recording file. - /// - /// The URL of the recording file to download. - /// Cancellation token. - /// - /// The containing the file. - /// - public Task DownloadFileAsync(string downloadUrl, CancellationToken cancellationToken = default) + /// + public async Task DownloadFileAsync(string downloadUrl, CancellationToken cancellationToken = default) { - return _client + /* + * PLEASE NOTE: + * + * The HttpRequestMessage in this method is dispatched with its completion option set to "ResponseHeadersRead". + * This ensures the content of the response is streamed rather than buffered in memory. + * This is important in cases where the downloaded file is quite large. + * In this scenario, we don't want the entirety of the file to be buffered in a MemoryStream because + * it could lead to "out of memory" exceptions if the file is large enough. + * See https://github.com/Jericho/ZoomNet/pull/342 for a discussion on this topic. + * + * Forthermore, as of this writing, the FluentHttp library does not allow us to stream the content of responses + * which means that the code in this method cannot be simplified like so: + return _client .GetAsync(downloadUrl) .WithCancellationToken(cancellationToken) .AsStream(); + * + * The downside of not using the FluentHttp library to dispatch the request is that we lose automatic retries, + * error handling, logging, etc. + */ + + using (var request = new HttpRequestMessage(HttpMethod.Get, downloadUrl)) + { + var tokenHandler = _client.Filters.OfType().SingleOrDefault(); + if (tokenHandler != null) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenHandler.Token); + } + + var response = await _client.BaseClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStreamAsync(); + } } private Task UpdateRegistrantsStatusAsync(long meetingId, IEnumerable registrantIds, string status, CancellationToken cancellationToken = default)