From a8b340cbb29dbcf7fd5d101e640d66470c6d32bf Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 8 Nov 2016 13:44:23 -0500 Subject: update portable projects --- .../HttpServer/RangeRequestWriter.cs | 224 +++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs (limited to 'Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs') diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs new file mode 100644 index 000000000..e88994bec --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -0,0 +1,224 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer +{ + public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult + { + /// + /// Gets or sets the source stream. + /// + /// The source stream. + private Stream SourceStream { get; set; } + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } + + private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } + + public Action OnComplete { get; set; } + private readonly ILogger _logger; + + private const int BufferSize = 81920; + + /// + /// The _options + /// + private readonly Dictionary _options = new Dictionary(); + + /// + /// The us culture + /// + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + public List Cookies { get; private set; } + + /// + /// Additional HTTP Headers + /// + /// The headers. + public IDictionary Headers + { + get { return _options; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The range header. + /// The source. + /// Type of the content. + /// if set to true [is head request]. + public RangeRequestWriter(string rangeHeader, Stream source, string contentType, bool isHeadRequest, ILogger logger) + { + if (string.IsNullOrEmpty(contentType)) + { + throw new ArgumentNullException("contentType"); + } + + RangeHeader = rangeHeader; + SourceStream = source; + IsHeadRequest = isHeadRequest; + this._logger = logger; + + ContentType = contentType; + Headers["Content-Type"] = contentType; + Headers["Accept-Ranges"] = "bytes"; + StatusCode = HttpStatusCode.PartialContent; + + Cookies = new List(); + SetRangeValues(); + } + + /// + /// Sets the range values. + /// + private void SetRangeValues() + { + var requestedRange = RequestedRanges[0]; + + TotalContentLength = SourceStream.Length; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + // Content-Length is the length of what we're serving, not the original content + Headers["Content-Length"] = RangeLength.ToString(UsCulture); + Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + + if (RangeStart > 0) + { + SourceStream.Position = RangeStart; + } + } + + /// + /// The _requested ranges + /// + private List> _requestedRanges; + /// + /// Gets the requested ranges. + /// + /// The requested ranges. + protected List> RequestedRanges + { + get + { + if (_requestedRanges == null) + { + _requestedRanges = new List>(); + + // Example: bytes=0-,32-63 + var ranges = RangeHeader.Split('=')[1].Split(','); + + foreach (var range in ranges) + { + var vals = range.Split('-'); + + long start = 0; + long? end = null; + + if (!string.IsNullOrEmpty(vals[0])) + { + start = long.Parse(vals[0], UsCulture); + } + if (!string.IsNullOrEmpty(vals[1])) + { + end = long.Parse(vals[1], UsCulture); + } + + _requestedRanges.Add(new KeyValuePair(start, end)); + } + } + + return _requestedRanges; + } + } + + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + using (var source = SourceStream) + { + // If the requested range is "0-", we can optimize by just doing a stream copy + if (RangeEnd >= TotalContentLength - 1) + { + await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + } + else + { + await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); + } + } + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) + { + var array = new byte[BufferSize]; + int count; + while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + { + var bytesToCopy = Math.Min(count, copyLength); + + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); + + copyLength -= bytesToCopy; + + if (copyLength <= 0) + { + break; + } + } + } + + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get { return (HttpStatusCode)Status; } + set { Status = (int)value; } + } + + public string StatusDescription { get; set; } + } +} \ No newline at end of file -- cgit v1.2.3