@@ -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
@@ -176,10 +222,15 @@ protected BlobInputStream(final CloudBlob parentBlob, final AccessCondition acce
176222 }
177223 }
178224
179- this .accessCondition = AccessCondition . generateIfMatchCondition ( this . parentBlobRef . getProperties (). getEtag () );
225+ this .accessCondition = new AccessCondition ( );
180226 this .accessCondition .setLeaseID (previousLeaseId );
227+ if (!options .getSkipEtagLocking ()) {
228+ this .accessCondition .setIfMatch (this .parentBlobRef .getProperties ().getEtag ());
229+ }
181230
182- this .streamLength = parentBlob .getProperties ().getLength ();
231+ this .streamLength = blobRangeLength == null
232+ ? this .parentBlobRef .getProperties ().getLength () - this .blobRangeOffset
233+ : Math .min (this .parentBlobRef .getProperties ().getLength () - this .blobRangeOffset , blobRangeLength );
183234
184235 if (this .validateBlobMd5 ) {
185236 try {
@@ -191,7 +242,7 @@ protected BlobInputStream(final CloudBlob parentBlob, final AccessCondition acce
191242 }
192243 }
193244
194- this .reposition (0 );
245+ this .reposition (blobRangeOffset );
195246 }
196247
197248 /**
@@ -238,7 +289,7 @@ public synchronized void close() throws IOException {
238289 }
239290
240291 /**
241- * Dispatches a read operation of N bytes. When using sparspe page blobs the page ranges are evaluated and zero
292+ * Dispatches a read operation of N bytes. When using sparse page blobs, the page ranges are evaluated and zero
242293 * bytes may be generated on the client side for some ranges that do not exist.
243294 *
244295 * @param readLength
@@ -444,8 +495,8 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
444495
445496 // if buffer is empty do next get operation
446497 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 ));
498+ && this .currentAbsoluteReadPosition < this .streamLength + this . blobRangeOffset ) {
499+ this .dispatchRead ((int ) Math .min (this .readSize , this .streamLength + this . blobRangeOffset - this .currentAbsoluteReadPosition ));
449500 }
450501
451502 len = Math .min (len , this .readSize );
@@ -459,7 +510,7 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
459510 if (this .validateBlobMd5 ) {
460511 this .md5Digest .update (b , off , numberOfBytesRead );
461512
462- if (this .currentAbsoluteReadPosition == this .streamLength ) {
513+ if (this .currentAbsoluteReadPosition == this .streamLength + this . blobRangeOffset ) {
463514 // Reached end of stream, validate md5.
464515 final String calculatedMd5 = Base64 .encode (this .md5Digest .digest ());
465516 if (!calculatedMd5 .equals (this .retrievedContentMD5Value )) {
@@ -479,7 +530,7 @@ private synchronized int readInternal(final byte[] b, final int off, int len) th
479530
480531 // update markers
481532 if (this .markExpiry > 0 && this .markedPosition + this .markExpiry < this .currentAbsoluteReadPosition ) {
482- this .markedPosition = 0 ;
533+ this .markedPosition = this . blobRangeOffset ;
483534 this .markExpiry = 0 ;
484535 }
485536
@@ -532,7 +583,7 @@ public synchronized long skip(final long n) throws IOException {
532583 return 0 ;
533584 }
534585
535- if (n < 0 || this .currentAbsoluteReadPosition + n > this .streamLength ) {
586+ if (n < 0 || this .currentAbsoluteReadPosition + n > this .streamLength + this . blobRangeOffset ) {
536587 throw new IndexOutOfBoundsException ();
537588 }
538589
0 commit comments