From 3a8b567f14ec3353cf55e296e6f06e13632b2e10 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 5 May 2017 13:55:38 -0400 Subject: [PATCH] update file responses --- .../Data/SqliteItemRepository.cs | 18 +++--------- .../HttpServer/FileWriter.cs | 10 +++++-- .../HttpServer/HttpResultFactory.cs | 5 ++-- .../HttpServer/RangeRequestWriter.cs | 11 ++++++-- .../HttpServer/ResponseFilter.cs | 20 ++----------- .../Library/Resolvers/TV/EpisodeResolver.cs | 1 - .../Library/Resolvers/TV/SeasonResolver.cs | 1 - .../Security/PluginSecurityManager.cs | 6 ++++ .../Session/HttpSessionController.cs | 2 +- .../Sorting/SeriesSortNameComparer.cs | 2 +- .../Playback/BaseStreamingService.cs | 8 +++++- MediaBrowser.Api/Playback/StreamState.cs | 2 ++ MediaBrowser.Controller/Entities/AudioBook.cs | 4 +-- MediaBrowser.Controller/Entities/Book.cs | 4 +-- .../Entities/IHasSeries.cs | 1 - .../Entities/TV/Episode.cs | 5 +--- MediaBrowser.Controller/Entities/TV/Season.cs | 5 +--- .../TV/DummySeasonProvider.cs | 3 +- .../TV/EpisodeMetadataService.cs | 7 ----- .../TV/SeasonMetadataService.cs | 7 ----- .../Net/HttpListenerResponse.cs | 2 ++ .../Net/ResponseStream.cs | 28 ++++++++++--------- 22 files changed, 63 insertions(+), 89 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 688596ab8a..272d79e484 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -254,7 +254,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "SeasonName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "SeasonId", "GUID", existingColumnNames); AddColumn(db, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames); - AddColumn(db, "TypedBaseItems", "SeriesSortName", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Tagline", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "Keywords", "Text", existingColumnNames); @@ -458,7 +457,6 @@ namespace Emby.Server.Implementations.Data "SeasonName", "SeasonId", "SeriesId", - "SeriesSortName", "PresentationUniqueKey", "InheritedParentalRatingValue", "InheritedTags", @@ -591,7 +589,6 @@ namespace Emby.Server.Implementations.Data "SeasonName", "SeasonId", "SeriesId", - "SeriesSortName", "ExternalSeriesId", "Tagline", "Keywords", @@ -1020,13 +1017,11 @@ namespace Emby.Server.Implementations.Data if (hasSeries != null) { saveItemStatement.TryBind("@SeriesId", hasSeries.SeriesId); - saveItemStatement.TryBind("@SeriesSortName", hasSeries.SeriesSortName); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); } else { saveItemStatement.TryBindNull("@SeriesId"); - saveItemStatement.TryBindNull("@SeriesSortName"); saveItemStatement.TryBindNull("@SeriesPresentationUniqueKey"); } @@ -1843,15 +1838,6 @@ namespace Emby.Server.Implementations.Data } index++; - if (hasSeries != null) - { - if (!reader.IsDBNull(index)) - { - hasSeries.SeriesSortName = reader.GetString(index); - } - } - index++; - if (!reader.IsDBNull(index)) { item.PresentationUniqueKey = reader.GetString(index); @@ -2880,6 +2866,10 @@ namespace Emby.Server.Implementations.Data { return new Tuple("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false); } + if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase)) + { + return new Tuple("(Select SortName from TypedBaseItems where B.Guid=A.SeriesId)", false); + } return new Tuple(name, false); } diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index dbaf97b1ee..8cb7b5dbf2 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.HttpServer Headers["Content-Type"] = contentType; TotalContentLength = fileSystem.GetFileInfo(path).Length; + Headers["Accept-Ranges"] = "bytes"; if (string.IsNullOrWhiteSpace(rangeHeader)) { @@ -66,7 +67,6 @@ namespace Emby.Server.Implementations.HttpServer } else { - Headers["Accept-Ranges"] = "bytes"; StatusCode = HttpStatusCode.PartialContent; SetRangeValues(); } @@ -96,8 +96,12 @@ namespace Emby.Server.Implementations.HttpServer 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); + var lengthString = RangeLength.ToString(UsCulture); + Headers["Content-Length"] = lengthString; + var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + Headers["Content-Range"] = rangeString; + + Logger.Info("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 310161d411..687bd62b06 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.HttpServer private bool ShouldCompressResponse(IRequest requestContext, string contentType) { // It will take some work to support compression with byte range requests - if (!string.IsNullOrEmpty(requestContext.Headers.Get("Range"))) + if (!string.IsNullOrWhiteSpace(requestContext.Headers.Get("Range"))) { return false; } @@ -566,7 +566,7 @@ namespace Emby.Server.Implementations.HttpServer }; } - if (!string.IsNullOrEmpty(rangeHeader)) + if (!string.IsNullOrWhiteSpace(rangeHeader)) { var stream = await factoryFn().ConfigureAwait(false); @@ -621,6 +621,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders["Content-Encoding"] = requestedCompressionType; } + responseHeaders["Vary"] = "Accept-Encoding"; responseHeaders["Content-Length"] = content.Length.ToString(UsCulture); if (isHeadRequest) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index e88994bec0..7c967949b8 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -189,10 +189,15 @@ namespace Emby.Server.Implementations.HttpServer 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) + int bytesRead; + while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) { - var bytesToCopy = Math.Min(count, copyLength); + if (bytesRead == 0) + { + break; + } + + var bytesToCopy = Math.Min(bytesRead, copyLength); await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 6d9d7d921d..57eef5db03 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -26,8 +26,8 @@ namespace Emby.Server.Implementations.HttpServer public void FilterResponse(IRequest req, IResponse res, object dto) { // Try to prevent compatibility view - res.AddHeader("X-UA-Compatible", "IE=Edge"); - res.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + //res.AddHeader("X-UA-Compatible", "IE=Edge"); + res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); res.AddHeader("Access-Control-Allow-Origin", "*"); @@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.HttpServer } } - var vary = "Accept-Encoding"; - var hasHeaders = dto as IHasHeaders; var sharpResponse = res as WebSocketSharpResponse; @@ -86,23 +84,9 @@ namespace Emby.Server.Implementations.HttpServer } } } - - string hasHeadersVary; - if (hasHeaders.Headers.TryGetValue("Vary", out hasHeadersVary)) - { - vary = hasHeadersVary; - } - - hasHeaders.Headers["Vary"] = vary; } //res.KeepAlive = false; - - // Per Google PageSpeed - // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed. - // The correct version of the resource is delivered based on the client request header. - // This is a good choice for applications that are singly homed and depend on public proxies for user locality. - res.AddHeader("Vary", vary); } /// diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index cf37366fb5..bdab8552a9 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { episode.SeriesId = series.Id; episode.SeriesName = series.Name; - episode.SeriesSortName = series.SortName; } if (season != null) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index c065feda10..84ceac65e6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -44,7 +44,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV { IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber, SeriesId = series.Id, - SeriesSortName = series.SortName, SeriesName = series.Name }; diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs index d42fae3ad0..7cd0a8d5f3 100644 --- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -101,6 +101,12 @@ namespace Emby.Server.Implementations.Security /// public async Task LoadAllRegistrationInfo() { + var response = await _httpClient.GetResponse(new HttpRequestOptions + { + Url = "http://192.168.1.2:8096/emby/Videos/663c8a38ccfe91af6566852f78e62c26/stream.mkv?Static=true&mediaSourceId=663c8a38ccfe91af6566852f78e62c26&deviceId=hyJA92oXn4RExFTGismCnY6da91kwnTvv8YvsYf0E&Tag=bcdc02b1cdd6f1eb4a57a6812831617b" + + }).ConfigureAwait(false); + var tasks = new List(); ResetSupporterInfo(); diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index cea5d9b406..2acc3902fd 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Session public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) { - return Task.FromResult(true); + return SendMessage("LibraryChanged", info, cancellationToken); } public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index b315d33c30..b441a29c14 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Sorting { var hasSeries = item as IHasSeries; - return hasSeries != null ? hasSeries.SeriesSortName : null; + return hasSeries != null ? hasSeries.FindSeriesSortName() : null; } /// diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index a92cf164ac..daec00e10b 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -675,7 +675,8 @@ namespace MediaBrowser.Api.Playback { Request = request, RequestedUrl = url, - UserAgent = Request.UserAgent + UserAgent = Request.UserAgent, + EnableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) }; var auth = AuthorizationContext.GetAuthorizationInfo(Request); @@ -917,6 +918,11 @@ namespace MediaBrowser.Api.Playback /// true if XXXX, false otherwise protected void AddDlnaHeaders(StreamState state, IDictionary responseHeaders, bool isStaticallyStreamed) { + if (!state.EnableDlnaHeaders) + { + return; + } + var profile = state.DeviceProfile; var transferMode = GetHeader("transferMode.dlna.org"); diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 7f146466d4..4b1687d689 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -138,6 +138,8 @@ namespace MediaBrowser.Api.Playback return MimeTypes.GetMimeType(outputPath); } + public bool EnableDlnaHeaders { get; set; } + public void Dispose() { DisposeTranscodingThrottler(); diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index 8b1c338f16..1bdcfb881e 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -31,12 +31,10 @@ namespace MediaBrowser.Controller.Entities public string SeriesName { get; set; } [IgnoreDataMember] public Guid? SeriesId { get; set; } - [IgnoreDataMember] - public string SeriesSortName { get; set; } public string FindSeriesSortName() { - return SeriesSortName; + return SeriesName; } public string FindSeriesName() { diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index a6da389f05..7cb2425899 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -24,12 +24,10 @@ namespace MediaBrowser.Controller.Entities public string SeriesName { get; set; } [IgnoreDataMember] public Guid? SeriesId { get; set; } - [IgnoreDataMember] - public string SeriesSortName { get; set; } public string FindSeriesSortName() { - return SeriesSortName; + return SeriesName; } public string FindSeriesName() { diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 203be93e88..20efdc2b8b 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -11,7 +11,6 @@ namespace MediaBrowser.Controller.Entities /// The name of the series. string SeriesName { get; set; } string FindSeriesName(); - string SeriesSortName { get; set; } string FindSeriesSortName(); Guid? SeriesId { get; set; } Guid? FindSeriesId(); diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 31bf8d28bd..c2f7a6168b 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -57,13 +57,10 @@ namespace MediaBrowser.Controller.Entities.TV /// The index number. public int? IndexNumberEnd { get; set; } - [IgnoreDataMember] - public string SeriesSortName { get; set; } - public string FindSeriesSortName() { var series = Series; - return series == null ? SeriesSortName : series.SortName; + return series == null ? SeriesName : series.SortName; } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 6c97a32a0e..ed04b5ddcb 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -51,9 +51,6 @@ namespace MediaBrowser.Controller.Entities.TV get { return SeriesId; } } - [IgnoreDataMember] - public string SeriesSortName { get; set; } - public override double? GetDefaultPrimaryImageAspectRatio() { double value = 2; @@ -65,7 +62,7 @@ namespace MediaBrowser.Controller.Entities.TV public string FindSeriesSortName() { var series = Series; - return series == null ? SeriesSortName : series.SortName; + return series == null ? SeriesName : series.SortName; } // Genre, Rating and Stuido will all be the same diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 36701a0bde..279447a189 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -125,8 +125,7 @@ namespace MediaBrowser.Providers.TV Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)), IsVirtualItem = isVirtualItem, SeriesId = series.Id, - SeriesName = series.Name, - SeriesSortName = series.SortName + SeriesName = series.Name }; season.SetParent(series); diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 538d96c176..bef4d88156 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -27,13 +27,6 @@ namespace MediaBrowser.Providers.TV updateType |= ItemUpdateType.MetadataImport; } - var seriesSortName = item.FindSeriesSortName(); - if (!string.Equals(item.SeriesSortName, seriesSortName, StringComparison.Ordinal)) - { - item.SeriesSortName = seriesSortName; - updateType |= ItemUpdateType.MetadataImport; - } - var seasonName = item.FindSeasonName(); if (!string.Equals(item.SeasonName, seasonName, StringComparison.Ordinal)) { diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index af7dea59ee..74c8b4ec34 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -44,13 +44,6 @@ namespace MediaBrowser.Providers.TV updateType |= ItemUpdateType.MetadataImport; } - var seriesSortName = item.FindSeriesSortName(); - if (!string.Equals(item.SeriesSortName, seriesSortName, StringComparison.Ordinal)) - { - item.SeriesSortName = seriesSortName; - updateType |= ItemUpdateType.MetadataImport; - } - var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) { diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index d9f91c0cc7..3cb6a0d752 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -480,6 +480,8 @@ namespace SocketHttpListener.Net headers.SetInternal("Set-Cookie", cookie.ToString()); } + headers.SetInternal("Status", status_code.ToString(CultureInfo.InvariantCulture)); + using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true)) { writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description); diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index 71d0810463..dea4049d56 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -344,13 +344,6 @@ namespace SocketHttpListener.Net private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { - var chunked = response.SendChunked; - - if (!chunked) - { - await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false); - } - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, true)) { if (offset > 0) @@ -358,7 +351,7 @@ namespace SocketHttpListener.Net fs.Position = offset; } - var targetStream = chunked ? this : stream; + var targetStream = this; if (count > 0) { @@ -374,14 +367,23 @@ namespace SocketHttpListener.Net private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { var array = new byte[81920]; - int count; - while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + int bytesRead; + + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) { - var bytesToCopy = Math.Min(count, copyLength); + if (bytesRead == 0) + { + break; + } - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false); + var bytesToWrite = Math.Min(bytesRead, copyLength); - copyLength -= bytesToCopy; + if (bytesToWrite > 0) + { + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } + + copyLength -= bytesToWrite; if (copyLength <= 0) {