< Summary

Class:Azure.Storage.LazyLoadingReadOnlyStream`2
Assembly:Azure.Storage.Blobs
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\LazyLoadingReadOnlyStream.cs
Covered lines:76
Uncovered lines:29
Coverable lines:105
Total lines:298
Line coverage:72.3% (76 of 105)
Covered branches:26
Total branches:34
Branch coverage:76.4% (26 of 34)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
Read(...)-0%100%
ReadAsync()-100%100%
ReadInternal()-88.24%75%
DownloadInternal()-80%75%
ValidateReadParameters(...)-100%100%
Dispose(...)-0%0%
GetBlobLengthInternal()-83.33%50%
GetBlobLengthFromResponse(...)-80%50%
get_CanRead()-0%100%
get_CanSeek()-0%100%
get_CanWrite()-0%100%
get_Length()-100%100%
get_Position()-0%100%
set_Position(...)-0%100%
Seek(...)-0%100%
SetLength(...)-0%100%
Write(...)-0%100%
Flush()-0%100%
FlushAsync(...)-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\LazyLoadingReadOnlyStream.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Buffers;
 6using System.Globalization;
 7using System.IO;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using Azure.Core;
 11using Azure.Core.Pipeline;
 12using Azure.Storage.Shared;
 13
 14namespace Azure.Storage
 15{
 16    /// <summary>
 17    /// Used for Open Read APIs.
 18    /// </summary>
 19    internal class LazyLoadingReadOnlyStream<TRequestConditions, TProperties> : Stream
 20    {
 21        /// <summary>
 22        /// The current position within the blob or file.
 23        /// </summary>
 24        private long _position;
 25
 26        /// <summary>
 27        /// Last known length of underlying blob or file.
 28        /// </summary>
 29        private long _length;
 30
 31        /// <summary>
 32        /// The number of bytes to download per call.
 33        /// </summary>
 34        private readonly int _bufferSize;
 35
 36        /// <summary>
 37        /// The backing buffer.
 38        /// </summary>
 39        private byte[] _buffer;
 40
 41        /// <summary>
 42        /// The current position within the buffer.
 43        /// </summary>
 44        private int _bufferPosition;
 45
 46        /// <summary>
 47        /// The current length of the buffer that is populated.
 48        /// </summary>
 49        private int _bufferLength;
 50
 51        /// <summary>
 52        /// If we are allowing the blob to be modifed while we read it.
 53        /// </summary>
 54        private bool _allowBlobModifications;
 55
 56        /// <summary>
 57        /// Request conditions to send on the download requests.
 58        /// </summary>
 59        private TRequestConditions _requestConditions;
 60
 61        /// <summary>
 62        /// Download() function.
 63        /// </summary>
 64        private readonly Func<HttpRange, TRequestConditions, bool, bool, CancellationToken, Task<Response<IDownloadedCon
 65
 66        /// <summary>
 67        /// Function to create RequestConditions.
 68        /// </summary>
 69        private readonly Func<ETag?, TRequestConditions> _createRequestConditionsFunc;
 70
 71        /// <summary>
 72        /// Function to get properties.
 73        /// </summary>
 74        private readonly Func<bool, CancellationToken, Task<Response<TProperties>>> _getPropertiesInternalFunc;
 75
 14876        public LazyLoadingReadOnlyStream(
 14877            Func<HttpRange, TRequestConditions, bool, bool, CancellationToken, Task<Response<IDownloadedContent>>> downl
 14878            Func<ETag?, TRequestConditions> createRequestConditionsFunc,
 14879            Func<bool, CancellationToken, Task<Response<TProperties>>> getPropertiesFunc,
 14880            long position = 0,
 14881            int? bufferSize = default,
 14882            TRequestConditions requestConditions = default)
 83        {
 14884            _downloadInternalFunc = downloadInternalFunc;
 14885            _createRequestConditionsFunc = createRequestConditionsFunc;
 14886            _getPropertiesInternalFunc = getPropertiesFunc;
 14887            _position = position;
 14888            _bufferSize = bufferSize ?? Constants.DefaultStreamingDownloadSize;
 14889            _buffer = ArrayPool<byte>.Shared.Rent(_bufferSize);
 14890            _bufferPosition = 0;
 14891            _bufferLength = 0;
 14892            _requestConditions = requestConditions;
 14893            _length = -1;
 14894            _allowBlobModifications = !(_requestConditions == null && _createRequestConditionsFunc != null);
 14895        }
 96
 97        public override int Read(byte[] buffer, int offset, int count)
 098            => ReadInternal(
 099                buffer,
 0100                offset,
 0101                count,
 0102                async: false,
 0103                default)
 0104            .EnsureCompleted();
 105
 106        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationTo
 612107            => await ReadInternal(
 612108                buffer,
 612109                offset,
 612110                count,
 612111                async: true,
 612112                cancellationToken)
 612113                .ConfigureAwait(false);
 114
 115        public async Task<int> ReadInternal(byte[] buffer, int offset, int count, bool async, CancellationToken cancella
 116        {
 612117            ValidateReadParameters(buffer, offset, count);
 118
 580119            if (_position == _length)
 120            {
 12121                if (_allowBlobModifications)
 122                {
 123                    // In case the blob grow since our last download call.
 8124                    _length = await GetBlobLengthInternal(async, cancellationToken).ConfigureAwait(false);
 125
 8126                    if (_position == _length)
 127                    {
 0128                        return 0;
 129                    }
 130                }
 131                else
 132                {
 4133                    return 0;
 134                }
 135
 136            }
 137
 576138            if (_bufferPosition == 0 || _bufferPosition == _buffer.Length)
 139            {
 576140                int lastDownloadedBytes = await DownloadInternal(async, cancellationToken).ConfigureAwait(false);
 520141                if (lastDownloadedBytes == 0)
 142                {
 0143                    return 0;
 144                }
 145            }
 146
 520147            int remainingBytesInBuffer = _bufferLength - _bufferPosition;
 148
 149            // We will return the minimum of remainingBytesInBuffer and the count provided by the user
 520150            int bytesToWrite = Math.Min(remainingBytesInBuffer, count);
 151
 520152            Array.Copy(_buffer, _bufferPosition, buffer, offset, bytesToWrite);
 153
 520154            _position += bytesToWrite;
 155
 520156            return bytesToWrite;
 524157        }
 158
 159        private async Task<int> DownloadInternal(bool async, CancellationToken cancellationToken)
 160        {
 161            Response<IDownloadedContent> response;
 162
 576163            HttpRange range = new HttpRange(_position, _bufferSize);
 164
 165#pragma warning disable AZC0110 // DO NOT use await keyword in possibly synchronous scope.
 576166            response = await _downloadInternalFunc(range, _requestConditions, default, async, cancellationToken).Configu
 167#pragma warning restore AZC0110 // DO NOT use await keyword in possibly synchronous scope.
 168
 520169            using Stream networkStream = response.Value.Content;
 170
 171            int copiedBytes;
 172
 520173            if (async)
 174            {
 520175                copiedBytes = await networkStream.ReadAsync(
 520176                    buffer: _buffer,
 520177                    offset: 0,
 520178                    count: _buffer.Length,
 520179                    cancellationToken: cancellationToken).ConfigureAwait(false);
 180            }
 181            else
 182            {
 0183                copiedBytes = networkStream.Read(
 0184                    buffer: _buffer,
 0185                    offset: 0,
 0186                    count: _buffer.Length);
 187            }
 188
 520189            _bufferPosition = 0;
 520190            _bufferLength = copiedBytes;
 520191            _length = GetBlobLengthFromResponse(response.GetRawResponse());
 192
 193            // Set _requestConditions If-Match if we are not allowing the blob to be modified.
 520194            if (!_allowBlobModifications)
 195            {
 312196                _requestConditions = _createRequestConditionsFunc(response.GetRawResponse().Headers.ETag);
 197            }
 198
 520199            return response.GetRawResponse().Headers.ContentLength.GetValueOrDefault();
 520200        }
 201
 202        private static void ValidateReadParameters(byte[] buffer, int offset, int count)
 203        {
 612204            if (buffer == null)
 205            {
 8206                throw new ArgumentNullException($"{nameof(buffer)}", $"{nameof(buffer)} cannot be null.");
 207            }
 208
 604209            if (offset < 0)
 210            {
 8211                throw new ArgumentOutOfRangeException($"{nameof(offset)} cannot be less than 0.");
 212            }
 213
 596214            if (offset > buffer.Length)
 215            {
 8216                throw new ArgumentOutOfRangeException($"{nameof(offset)} cannot exceed {nameof(buffer)} length.");
 217            }
 218
 588219            if (count < 0)
 220            {
 8221                throw new ArgumentOutOfRangeException($"{nameof(count)} cannot be less than 0.");
 222            }
 580223        }
 224
 225        protected override void Dispose(bool disposing)
 226        {
 227            // Return the buffer to the pool if we're called from Dispose or a finalizer
 0228            if (_buffer != null)
 229            {
 0230                ArrayPool<byte>.Shared.Return(_buffer, clearArray: true);
 0231                _buffer = null;
 232            }
 0233        }
 234
 235        private async Task<long> GetBlobLengthInternal(bool async, CancellationToken cancellationToken)
 236        {
 237#pragma warning disable AZC0110 // DO NOT use await keyword in possibly synchronous scope.
 8238            Response<TProperties> response = await _getPropertiesInternalFunc(async, cancellationToken).ConfigureAwait(f
 239#pragma warning restore AZC0110 // DO NOT use await keyword in possibly synchronous scope.
 240
 8241            response.GetRawResponse().Headers.TryGetValue("Content-Length", out string lengthString);
 242
 8243            if (lengthString == null)
 244            {
 0245                throw new ArgumentException($"{HttpHeader.Names.ContentLength} header is mssing on get properties respon
 246            }
 247
 8248            return Convert.ToInt64(lengthString, CultureInfo.InvariantCulture);
 8249        }
 250
 251        private static long GetBlobLengthFromResponse(Response response)
 252        {
 520253            response.Headers.TryGetValue("Content-Range", out string lengthString);
 254
 520255            if (lengthString == null)
 256            {
 0257                throw new ArgumentException("Content-Range header is mssing on download response.");
 258            }
 259
 520260            string[] split = lengthString.Split('/');
 520261            return Convert.ToInt64(split[1], CultureInfo.InvariantCulture);
 262        }
 263
 0264        public override bool CanRead => true;
 265
 0266        public override bool CanSeek => false;
 267
 0268        public override bool CanWrite => false;
 269
 72270        public override long Length => _length;
 271
 272        public override long Position
 273        {
 0274            get => _position;
 0275            set => throw new NotSupportedException();
 276        }
 277
 278        public override long Seek(long offset, SeekOrigin origin)
 279        {
 0280            throw new NotSupportedException();
 281        }
 282
 283        public override void SetLength(long value)
 284        {
 0285            throw new NotSupportedException();
 286        }
 287
 288        public override void Write(byte[] buffer, int offset, int count)
 289        {
 0290            throw new NotSupportedException();
 291        }
 292
 0293        public override void Flush() { }
 294
 295        public override Task FlushAsync(CancellationToken cancellationToken)
 0296            => Task.CompletedTask;
 297    }
 298}