|
@@ -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
|
|
|
/// <summary>
|
|
|
/// Downloads the contents of a given url into a temporary location
|
|
|
/// </summary>
|
|
|
- /// <param name="url">The URL.</param>
|
|
|
- /// <param name="resourcePool">The resource pool.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- /// <param name="progress">The progress.</param>
|
|
|
- /// <param name="userAgent">The user agent.</param>
|
|
|
+ /// <param name="options">The options.</param>
|
|
|
/// <returns>Task{System.String}.</returns>
|
|
|
/// <exception cref="System.ArgumentNullException">progress</exception>
|
|
|
+ /// <exception cref="HttpException"></exception>
|
|
|
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
|
|
- public async Task<string> GetTempFile(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken, IProgress<double> progress, string userAgent = null)
|
|
|
+ public Task<string> GetTempFile(HttpRequestOptions options)
|
|
|
{
|
|
|
- ValidateParams(url, cancellationToken);
|
|
|
+ var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
|
|
|
+
|
|
|
+ return GetTempFile(options, tempFile, 0);
|
|
|
+ }
|
|
|
|
|
|
- if (progress == null)
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the temp file.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="options">The options.</param>
|
|
|
+ /// <param name="tempFile">The temp file.</param>
|
|
|
+ /// <param name="resumeCount">The resume count.</param>
|
|
|
+ /// <returns>Task{System.String}.</returns>
|
|
|
+ /// <exception cref="System.ArgumentNullException">progress</exception>
|
|
|
+ /// <exception cref="HttpException"></exception>
|
|
|
+ private async Task<string> 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<string> 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;
|
|
|
+ }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Handles the temp file exception.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="ex">The ex.</param>
|
|
|
+ /// <param name="options">The options.</param>
|
|
|
+ /// <param name="tempFile">The temp file.</param>
|
|
|
+ /// <param name="serverSupportsRangeRequests">if set to <c>true</c> [server supports range requests].</param>
|
|
|
+ /// <param name="resumeCount">The resume count.</param>
|
|
|
+ /// <returns>Task.</returns>
|
|
|
+ /// <exception cref="HttpException"></exception>
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Downloads the contents of a given url into a MemoryStream
|
|
|
/// </summary>
|
|
@@ -519,19 +588,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|
|
return Post(url, postData, null, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets the temp file.
|
|
|
- /// </summary>
|
|
|
- /// <param name="url">The URL.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- /// <param name="progress">The progress.</param>
|
|
|
- /// <param name="userAgent">The user agent.</param>
|
|
|
- /// <returns>Task{System.String}.</returns>
|
|
|
- public Task<string> GetTempFile(string url, CancellationToken cancellationToken, IProgress<double> progress, string userAgent = null)
|
|
|
- {
|
|
|
- return GetTempFile(url, null, cancellationToken, progress, userAgent);
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Gets the memory stream.
|
|
|
/// </summary>
|