diff options
| author | LukePulverenti <luke.pulverenti@gmail.com> | 2013-03-14 15:52:53 -0400 |
|---|---|---|
| committer | LukePulverenti <luke.pulverenti@gmail.com> | 2013-03-14 15:52:53 -0400 |
| commit | fe1834e6be044f0fdbe68fb34122c680f29ae04d (patch) | |
| tree | d5ec27cc5835dd05d15551523b2fa93870dfbd4a | |
| parent | a7f06c84b14ea79ce5af8e928597f6fd119088a3 (diff) | |
Add resume capability to GetTempFile
| -rw-r--r-- | MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs | 192 | ||||
| -rw-r--r-- | MediaBrowser.Common.Implementations/Updates/PackageManager.cs | 9 | ||||
| -rw-r--r-- | MediaBrowser.Common/MediaBrowser.Common.csproj | 1 | ||||
| -rw-r--r-- | MediaBrowser.Common/Net/HttpRequestOptions.cs | 47 | ||||
| -rw-r--r-- | MediaBrowser.Common/Net/IHttpClient.cs | 20 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.WebDashboard/Html/scripts/site.js | 29 | ||||
| -rw-r--r-- | Nuget/MediaBrowser.Common.Internal.nuspec | 4 | ||||
| -rw-r--r-- | Nuget/MediaBrowser.Common.nuspec | 2 | ||||
| -rw-r--r-- | Nuget/MediaBrowser.Server.Core.nuspec | 4 |
10 files changed, 207 insertions, 107 deletions
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 /// <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> @@ -520,19 +589,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager } /// <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> /// <param name="url">The URL.</param> diff --git a/MediaBrowser.Common.Implementations/Updates/PackageManager.cs b/MediaBrowser.Common.Implementations/Updates/PackageManager.cs index f73857da1..594027bcb 100644 --- a/MediaBrowser.Common.Implementations/Updates/PackageManager.cs +++ b/MediaBrowser.Common.Implementations/Updates/PackageManager.cs @@ -71,7 +71,14 @@ namespace MediaBrowser.Common.Implementations.Updates var target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename); // Download to temporary file so that, if interrupted, it won't destroy the existing installation - var tempFile = await _httpClient.GetTempFile(package.sourceUrl, cancellationToken, progress).ConfigureAwait(false); + var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions + { + Url = package.sourceUrl, + CancellationToken = cancellationToken, + Progress = progress, + MaxResumeCount = 3 + + }).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index d05ebaf74..bc0d79e45 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -61,6 +61,7 @@ <Compile Include="IO\StreamDefaults.cs" /> <Compile Include="Net\BasePeriodicWebSocketListener.cs" /> <Compile Include="Configuration\IApplicationPaths.cs" /> + <Compile Include="Net\HttpRequestOptions.cs" /> <Compile Include="Net\IHttpResultFactory.cs" /> <Compile Include="Net\IServerManager.cs" /> <Compile Include="Net\IWebSocketListener.cs" /> diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs new file mode 100644 index 000000000..a45acb206 --- /dev/null +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; + +namespace MediaBrowser.Common.Net +{ + /// <summary> + /// Class HttpRequestOptions + /// </summary> + public class HttpRequestOptions + { + /// <summary> + /// Gets or sets the URL. + /// </summary> + /// <value>The URL.</value> + public string Url { get; set; } + + /// <summary> + /// Gets or sets the cancellation token. + /// </summary> + /// <value>The cancellation token.</value> + public CancellationToken CancellationToken { get; set; } + + /// <summary> + /// Gets or sets the resource pool. + /// </summary> + /// <value>The resource pool.</value> + public SemaphoreSlim ResourcePool { get; set; } + + /// <summary> + /// Gets or sets the user agent. + /// </summary> + /// <value>The user agent.</value> + public string UserAgent { get; set; } + + /// <summary> + /// Gets or sets the max resume count. + /// </summary> + /// <value>The max resume count.</value> + public int MaxResumeCount { get; set; } + + /// <summary> + /// Gets or sets the progress. + /// </summary> + /// <value>The progress.</value> + public IProgress<double> Progress { get; set; } + } +} diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index cec3ccff2..f443341a0 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -49,29 +49,15 @@ namespace MediaBrowser.Common.Net /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{Stream}.</returns> Task<Stream> Post(string url, Dictionary<string, string> postData, CancellationToken cancellationToken); - + /// <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="MediaBrowser.Model.Net.HttpException"></exception> - Task<string> GetTempFile(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken, IProgress<double> progress, string userAgent = null); - - /// <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> - Task<string> GetTempFile(string url, CancellationToken cancellationToken, IProgress<double> progress, string userAgent = null); + Task<string> GetTempFile(HttpRequestOptions options); /// <summary> /// Downloads the contents of a given url into a MemoryStream diff --git a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs index 56f479205..4cd9b20a1 100644 --- a/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs @@ -100,7 +100,7 @@ namespace MediaBrowser.Controller.Providers { var path = item.Images[image]; - return IsInSameDirectory(item, path) && !item.ResolveArgs.GetMetaFileByPath(path).HasValue; + return IsInMetaLocation(item, path) && !item.ResolveArgs.GetMetaFileByPath(path).HasValue; }).ToList(); // Now remove them from the dictionary @@ -122,7 +122,7 @@ namespace MediaBrowser.Controller.Providers } // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedImages = item.BackdropImagePaths.Where(path => IsInSameDirectory(item, path) && !item.ResolveArgs.GetMetaFileByPath(path).HasValue).ToList(); + var deletedImages = item.BackdropImagePaths.Where(path => IsInMetaLocation(item, path) && !item.ResolveArgs.GetMetaFileByPath(path).HasValue).ToList(); // Now remove them from the dictionary foreach (var path in deletedImages) @@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="path">The path.</param> /// <returns><c>true</c> if [is in same directory] [the specified item]; otherwise, <c>false</c>.</returns> - private bool IsInSameDirectory(BaseItem item, string path) + private bool IsInMetaLocation(BaseItem item, string path) { return string.Equals(Path.GetDirectoryName(path), item.MetaLocation, StringComparison.OrdinalIgnoreCase); } diff --git a/MediaBrowser.WebDashboard/Html/scripts/site.js b/MediaBrowser.WebDashboard/Html/scripts/site.js index cb4ba3bdd..75521f9fd 100644 --- a/MediaBrowser.WebDashboard/Html/scripts/site.js +++ b/MediaBrowser.WebDashboard/Html/scripts/site.js @@ -397,23 +397,26 @@ var Dashboard = { if (!info.HasPendingRestart) {
Dashboard.reloadPage();
} else {
- Dashboard.reloadPageWhenServerAvailable(retryCount);
+ Dashboard.retryReload(retryCount);
}
- }).fail(function () {
-
- setTimeout(function () {
+ }).fail(function() {
+ Dashboard.retryReload(retryCount);
+ });
+ },
+
+ retryReload: function (retryCount) {
+ setTimeout(function () {
- retryCount = retryCount || 0;
- retryCount++;
+ retryCount = retryCount || 0;
+ retryCount++;
- if (retryCount < 10) {
- Dashboard.reloadPageWhenServerAvailable(retryCount);
- } else {
- Dashboard.suppressAjaxErrors = false;
- }
- }, 500);
- });
+ if (retryCount < 10) {
+ Dashboard.reloadPageWhenServerAvailable(retryCount);
+ } else {
+ Dashboard.suppressAjaxErrors = false;
+ }
+ }, 500);
},
getPosterViewHtml: function (options) {
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 09d26ab81..8986333f0 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.45</version> + <version>3.0.47</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.45" /> + <dependency id="MediaBrowser.Common" version="3.0.47" /> <dependency id="NLog" version="2.0.0.2000" /> <dependency id="ServiceStack.Text" version="3.9.38" /> <dependency id="protobuf-net" version="2.0.0.621" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 72eec30d4..847ced431 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.45</version> + <version>3.0.47</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index ba5263d61..46fcd708e 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.45</version> + <version>3.0.47</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.45" /> + <dependency id="MediaBrowser.Common" version="3.0.47" /> </dependencies> </metadata> <files> |
