@@ -69,7 +69,7 @@ public final class BlobInputStream extends InputStream {
6969 /**
7070 * Holds the stream length.
7171 */
72- private long streamLength = - 1 ;
72+ private long streamLength ;
7373
7474 /**
7575 * Holds the stream read size for both block and page blobs.
@@ -121,9 +121,14 @@ public final class BlobInputStream extends InputStream {
121121 */
122122 private AccessCondition accessCondition = null ;
123123
124+ /**
125+ * Offset of the source blob this class is configured to stream from.
126+ */
127+ private final long blobRangeOffset ;
128+
124129 /**
125130 * Initializes a new instance of the BlobInputStream class.
126- *
131+ *
127132 * @param parentBlob
128133 * A {@link CloudBlob} object which represents the blob that this stream is associated with.
129134 * @param accessCondition
@@ -133,19 +138,50 @@ public final class BlobInputStream extends InputStream {
133138 * request.
134139 * @param opContext
135140 * An {@link OperationContext} object which is used to track the execution of the operation.
136- *
141+ *
137142 * @throws StorageException
138143 * An exception representing any error which occurred during the operation.
139144 */
140145 @ DoesServiceRequest
141146 protected BlobInputStream (final CloudBlob parentBlob , final AccessCondition accessCondition ,
142- final BlobRequestOptions options , final OperationContext opContext ) throws StorageException {
147+ final BlobRequestOptions options , final OperationContext opContext ) throws StorageException {
148+ this (0 , null , parentBlob , accessCondition , options , opContext );
149+ }
150+
151+ /**
152+ * Initializes a new instance of the BlobInputStream class.
153+ * Note that if {@code blobRangeOffset} is not {@code 0} or {@code blobRangeLength} is not {@code null}, there will
154+ * be no content MD5 verification.
155+ *
156+ * @param blobRangeOffset
157+ * The offset of blob data to begin stream.
158+ * @param blobRangeLength
159+ * How much data the stream should return after blobRangeOffset.
160+ * @param parentBlob
161+ * A {@link CloudBlob} object which represents the blob that this stream is associated with.
162+ * @param accessCondition
163+ * An {@link AccessCondition} object which represents the access conditions for the blob.
164+ * @param options
165+ * A {@link BlobRequestOptions} object which represents that specifies any additional options for the
166+ * request.
167+ * @param opContext
168+ * An {@link OperationContext} object which is used to track the execution of the operation.
169+ *
170+ * @throws StorageException
171+ * An exception representing any error which occurred during the operation.
172+ */
173+ @ DoesServiceRequest
174+ protected BlobInputStream (long blobRangeOffset , Long blobRangeLength , final CloudBlob parentBlob ,
175+ final AccessCondition accessCondition , final BlobRequestOptions options , final OperationContext opContext )
176+ throws StorageException {
177+
178+ this .blobRangeOffset = blobRangeOffset ;
143179 this .parentBlobRef = parentBlob ;
144180 this .parentBlobRef .assertCorrectBlobType ();
145181 this .options = new BlobRequestOptions (options );
146182 this .opContext = opContext ;
147183 this .streamFaulted = false ;
148- this .currentAbsoluteReadPosition = 0 ;
184+ this .currentAbsoluteReadPosition = blobRangeOffset ;
149185 this .readSize = parentBlob .getStreamMinimumReadSizeInBytes ();
150186
151187 if (options .getUseTransactionalContentMD5 () && this .readSize > 4 * Constants .MB ) {
@@ -154,12 +190,22 @@ protected BlobInputStream(final CloudBlob parentBlob, final AccessCondition acce
154190
155191 parentBlob .downloadAttributes (accessCondition , this .options , this .opContext );
156192
193+ Utility .assertInBounds ("blobRangeOffset" , blobRangeOffset , 0 , parentBlob .getProperties ().getLength () - 1 );
194+ if (blobRangeLength != null ) {
195+ Utility .assertGreaterThanOrEqual ("blobRangeLength" , blobRangeLength , 0 );
196+ }
197+
157198 this .retrievedContentMD5Value = parentBlob .getProperties ().getContentMD5 ();
158199
159200 // Will validate it if it was returned
160201 this .validateBlobMd5 = !options .getDisableContentMD5Validation ()
161202 && !Utility .isNullOrEmpty (this .retrievedContentMD5Value );
162203
204+ // Need the whole blob to validate MD5. If we download a range, don't bother trying.
205+ if (blobRangeOffset != 0 || blobRangeLength != null ) {
206+ this .validateBlobMd5 = false ;
207+ }
208+
163209 // Validates the first option, and sets future requests to use if match
164210 // request option.
165211
@@ -179,7 +225,9 @@ protected BlobInputStream(final CloudBlob parentBlob, final AccessCondition acce
179225 this .accessCondition = AccessCondition .generateIfMatchCondition (this .parentBlobRef .getProperties ().getEtag ());
180226 this .accessCondition .setLeaseID (previousLeaseId );
181227
182- this .streamLength = parentBlob .getProperties ().getLength ();
228+ this .streamLength = blobRangeLength == null
229+ ? this .parentBlobRef .getProperties ().getLength () - this .blobRangeOffset
230+ : Math .min (this .parentBlobRef .getProperties ().getLength () - this .blobRangeOffset , blobRangeLength );
183231
184232 if (this .validateBlobMd5 ) {
185233 try {
@@ -191,7 +239,7 @@ protected BlobInputStream(final CloudBlob parentBlob, final AccessCondition acce
191239 }
192240 }
193241
194- this .reposition (0 );
242+ this .reposition (blobRangeOffset );
195243 }
196244
197245 /**
@@ -238,7 +286,7 @@ public synchronized void close() throws IOException {
238286 }
239287
240288 /**
241- * Dispatches a read operation of N bytes. When using sparspe page blobs the page ranges are evaluated and zero
289+ * Dispatches a read operation of N bytes. When using sparse page blobs, the page ranges are evaluated and zero
242290 * bytes may be generated on the client side for some ranges that do not exist.
243291 *
244292 * @param readLength
@@ -444,8 +492,8 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
444492
445493 // if buffer is empty do next get operation
446494 if ((this .currentBuffer == null || this .currentBuffer .available () == 0 )
447- && this .currentAbsoluteReadPosition < this .streamLength ) {
448- this .dispatchRead ((int ) Math .min (this .readSize , this .streamLength - this .currentAbsoluteReadPosition ));
495+ && this .currentAbsoluteReadPosition < this .streamLength + this . blobRangeOffset ) {
496+ this .dispatchRead ((int ) Math .min (this .readSize , this .streamLength + this . blobRangeOffset - this .currentAbsoluteReadPosition ));
449497 }
450498
451499 len = Math .min (len , this .readSize );
@@ -459,7 +507,7 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
459507 if (this .validateBlobMd5 ) {
460508 this .md5Digest .update (b , off , numberOfBytesRead );
461509
462- if (this .currentAbsoluteReadPosition == this .streamLength ) {
510+ if (this .currentAbsoluteReadPosition == this .streamLength + this . blobRangeOffset ) {
463511 // Reached end of stream, validate md5.
464512 final String calculatedMd5 = Base64 .encode (this .md5Digest .digest ());
465513 if (!calculatedMd5 .equals (this .retrievedContentMD5Value )) {
@@ -479,7 +527,7 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
479527
480528 // update markers
481529 if (this .markExpiry > 0 && this .markedPosition + this .markExpiry < this .currentAbsoluteReadPosition ) {
482- this .markedPosition = 0 ;
530+ this .markedPosition = this . blobRangeOffset ;
483531 this .markExpiry = 0 ;
484532 }
485533
@@ -532,7 +580,7 @@ public synchronized long skip(final long n) throws IOException {
532580 return 0 ;
533581 }
534582
535- if (n < 0 || this .currentAbsoluteReadPosition + n > this .streamLength ) {
583+ if (n < 0 || this .currentAbsoluteReadPosition + n > this .streamLength + this . blobRangeOffset ) {
536584 throw new IndexOutOfBoundsException ();
537585 }
538586
0 commit comments