From fe1834e6be044f0fdbe68fb34122c680f29ae04d Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Thu, 14 Mar 2013 15:52:53 -0400 Subject: Add resume capability to GetTempFile --- .../HttpClientManager/HttpClientManager.cs | 192 +++++++++++++-------- 1 file changed, 124 insertions(+), 68 deletions(-) (limited to 'MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs') diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index d653a5c9b..56b4efd2f 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Configuration; +using System.Net.Http.Headers; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Model.Logging; @@ -203,50 +204,96 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// /// Downloads the contents of a given url into a temporary location /// - /// The URL. - /// The resource pool. - /// The cancellation token. - /// The progress. - /// The user agent. + /// The options. /// Task{System.String}. /// progress + /// /// - public async Task GetTempFile(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken, IProgress progress, string userAgent = null) + public Task GetTempFile(HttpRequestOptions options) { - ValidateParams(url, cancellationToken); + var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); + + return GetTempFile(options, tempFile, 0); + } - if (progress == null) + /// + /// Gets the temp file. + /// + /// The options. + /// The temp file. + /// The resume count. + /// Task{System.String}. + /// progress + /// + private async Task GetTempFile(HttpRequestOptions options, string tempFile, int resumeCount) + { + ValidateParams(options.Url, options.CancellationToken); + + if (options.Progress == null) { throw new ArgumentNullException("progress"); } - cancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); - var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); + var message = new HttpRequestMessage(HttpMethod.Get, options.Url); - var message = new HttpRequestMessage(HttpMethod.Get, url); + if (!string.IsNullOrEmpty(options.UserAgent)) + { + message.Headers.Add("User-Agent", options.UserAgent); + } - if (!string.IsNullOrEmpty(userAgent)) + if (options.ResourcePool != null) { - message.Headers.Add("User-Agent", userAgent); + await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); } - if (resourcePool != null) + options.Progress.Report(0); + + _logger.Info("HttpClientManager.GetTempFile url: {0}, temp file: {1}", options.Url, tempFile); + + FileStream tempFileStream; + + if (resumeCount > 0 && File.Exists(tempFile)) { - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + tempFileStream = new FileStream(tempFile, FileMode.Open, FileAccess.Write, FileShare.Read, + StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); + + var startPosition = tempFileStream.Length; + tempFileStream.Seek(startPosition, SeekOrigin.Current); + + message.Headers.Range = new RangeHeaderValue(startPosition, null); + + _logger.Info("Resuming from position {1} for {0}", options.Url, startPosition); + } + else + { + tempFileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, + StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous); } - _logger.Info("HttpClientManager.GetTempFile url: {0}, temp file: {1}", url, tempFile); + var serverSupportsRangeRequests = false; + + Exception downloadException = null; try { - cancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); - using (var response = await GetHttpClient(GetHostFromUrl(url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false)) + using (var response = await GetHttpClient(GetHostFromUrl(options.Url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false)) { EnsureSuccessStatusCode(response); - cancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); + + var rangeValue = string.Join(" ", response.Headers.AcceptRanges.ToArray()); + serverSupportsRangeRequests = rangeValue.IndexOf("bytes", StringComparison.OrdinalIgnoreCase) != -1 || rangeValue.IndexOf("*", StringComparison.OrdinalIgnoreCase) != -1; + + if (!serverSupportsRangeRequests && resumeCount > 0) + { + _logger.Info("Server does not support range requests for {0}", options.Url); + tempFileStream.Position = 0; + } IEnumerable lengthValues; @@ -256,75 +303,97 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager // We're not able to track progress using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { - using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(tempFileStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } } else { var length = long.Parse(string.Join(string.Empty, lengthValues.ToArray())); - using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), progress.Report, length)) + using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, length)) { - using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(tempFileStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } } - progress.Report(100); + options.Progress.Report(100); - cancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); } - - return tempFile; } - catch (OperationCanceledException ex) + catch (Exception ex) { - // Cleanup - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - - throw GetCancellationException(url, cancellationToken, ex); + downloadException = ex; } - catch (HttpRequestException ex) + finally { - _logger.ErrorException("Error getting response from " + url, ex); + tempFileStream.Dispose(); - // Cleanup - if (File.Exists(tempFile)) + if (options.ResourcePool != null) { - File.Delete(tempFile); + options.ResourcePool.Release(); } - - throw new HttpException(ex.Message, ex); } - catch (Exception ex) + + if (downloadException != null) { - _logger.ErrorException("Error getting response from " + url, ex); + await HandleTempFileException(downloadException, options, tempFile, serverSupportsRangeRequests, resumeCount).ConfigureAwait(false); + } + + return tempFile; + } + /// + /// Handles the temp file exception. + /// + /// The ex. + /// The options. + /// The temp file. + /// if set to true [server supports range requests]. + /// The resume count. + /// Task. + /// + private Task HandleTempFileException(Exception ex, HttpRequestOptions options, string tempFile, bool serverSupportsRangeRequests, int resumeCount) + { + var operationCanceledException = ex as OperationCanceledException; + + if (operationCanceledException != null) + { // Cleanup if (File.Exists(tempFile)) { File.Delete(tempFile); } - throw; + throw GetCancellationException(options.Url, options.CancellationToken, operationCanceledException); } - finally + + _logger.ErrorException("Error getting response from " + options.Url, ex); + + var httpRequestException = ex as HttpRequestException; + + // Cleanup + if (File.Exists(tempFile)) { - if (resourcePool != null) + // Try to resume + if (httpRequestException != null && serverSupportsRangeRequests && resumeCount < options.MaxResumeCount && new FileInfo(tempFile).Length > 0) { - resourcePool.Release(); + _logger.Info("Attempting to resume download from {0}", options.Url); + + return GetTempFile(options, tempFile, resumeCount + 1); } + + File.Delete(tempFile); + } + + if (httpRequestException != null) + { + throw new HttpException(ex.Message, ex); } - } + throw ex; + } + /// /// Downloads the contents of a given url into a MemoryStream /// @@ -519,19 +588,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager return Post(url, postData, null, cancellationToken); } - /// - /// Gets the temp file. - /// - /// The URL. - /// The cancellation token. - /// The progress. - /// The user agent. - /// Task{System.String}. - public Task GetTempFile(string url, CancellationToken cancellationToken, IProgress progress, string userAgent = null) - { - return GetTempFile(url, null, cancellationToken, progress, userAgent); - } - /// /// Gets the memory stream. /// -- cgit v1.2.3