Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public enum UploadState {
/**
* The content buffer of the current request or {@code null} for none. It is used for resumable
* media upload when the media content length is not specified. It is instantiated for every
* request in {@link #setContentAndHeadersOnCurrentRequest} and is set to {@code null} when the
* request in {@link #buildContentChunk()} and is set to {@code null} when the
* request is completed in {@link #upload}.
*/
private byte currentRequestContentBuffer[];
Expand Down Expand Up @@ -405,8 +405,11 @@ private HttpResponse resumableUpload(GenericUrl initiationRequestUrl) throws IOE
HttpResponse response;
// Upload the media content in chunks.
while (true) {
ContentChunk contentChunk = buildContentChunk();
currentRequest = requestFactory.buildPutRequest(uploadUrl, null);
setContentAndHeadersOnCurrentRequest();
currentRequest.setContent(contentChunk.getContent());
currentRequest.getHeaders().setContentRange(contentChunk.getContentRange());

// set mediaErrorHandler as I/O exception handler and as unsuccessful response handler for
// calling to serverErrorCallback on an I/O exception or an abnormal HTTP response
new MediaUploadErrorHandler(this, currentRequest);
Expand Down Expand Up @@ -567,7 +570,7 @@ private HttpResponse executeCurrentRequest(HttpRequest request) throws IOExcepti
* Sets the HTTP media content chunk and the required headers that should be used in the upload
* request.
*/
private void setContentAndHeadersOnCurrentRequest() throws IOException {
private ContentChunk buildContentChunk() throws IOException {
int blockSize;
if (isMediaLengthKnown()) {
// We know exactly what the blockSize will be because we know the media content length.
Expand Down Expand Up @@ -652,15 +655,35 @@ private void setContentAndHeadersOnCurrentRequest() throws IOException {
}

currentChunkLength = actualBlockSize;
currentRequest.setContent(contentChunk);

String contentRange;
if (actualBlockSize == 0) {
// No bytes to upload. Either zero content media being uploaded, or a server failure on the
// last write, even though the write actually succeeded. Either way,
// mediaContentLengthStr will contain the actual media length.
currentRequest.getHeaders().setContentRange("bytes */" + mediaContentLengthStr);
contentRange = "bytes */" + mediaContentLengthStr;
} else {
currentRequest.getHeaders().setContentRange("bytes " + totalBytesServerReceived + "-"
+ (totalBytesServerReceived + actualBlockSize - 1) + "/" + mediaContentLengthStr);
contentRange = "bytes " + totalBytesServerReceived + "-"
+ (totalBytesServerReceived + actualBlockSize - 1) + "/" + mediaContentLengthStr;
}
return new ContentChunk(contentChunk, contentRange);
}

private static class ContentChunk {
private final AbstractInputStreamContent content;
private final String contentRange;

ContentChunk(AbstractInputStreamContent content, String contentRange) {
this.content = content;
this.contentRange = contentRange;
}

AbstractInputStreamContent getContent() {
return content;
}

String getContentRange() {
return contentRange;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Arrays;
import java.util.Random;
import java.util.logging.Level;
Expand Down Expand Up @@ -1103,4 +1105,68 @@ public void testResumableMediaUploadWithoutContentClose() throws Exception {
uploader.upload(new GenericUrl(TEST_RESUMABLE_REQUEST_URL));
assertFalse(inputStream.isClosed());
}

class SlowWriter implements Runnable {
final private OutputStream outputStream;
final private int contentLength;

SlowWriter(OutputStream outputStream, int contentLength) {
this.outputStream = outputStream;
this.contentLength = contentLength;
}

@Override
public void run() {
try {
for (int i = 0; i < contentLength; i++) {
outputStream.write(i);
Thread.sleep(1000);
}
outputStream.close();
} catch (IOException e) {
// ignore
} catch (InterruptedException e) {
// ignore
}
}
}

class TimeoutRequestInitializer implements HttpRequestInitializer {
class TimingInterceptor implements HttpExecuteInterceptor {
private long initTime;

TimingInterceptor() {
initTime = System.currentTimeMillis();
}

@Override
public void intercept(HttpRequest request) {
assertTrue(
"Request initialization to execute should be fast",
System.currentTimeMillis() - initTime < 100L
);
}
}

@Override
public void initialize(HttpRequest request) {
request.setInterceptor(new TimingInterceptor());
}
}

public void testResumableSlowUpload() throws Exception {
int contentLength = 3;
MediaTransport fakeTransport = new MediaTransport(contentLength);
fakeTransport.contentLengthNotSpecified = true;
PipedOutputStream outputStream = new PipedOutputStream();
InputStream inputStream = new PipedInputStream(outputStream);

Thread thread = new Thread(new SlowWriter(outputStream, contentLength));
thread.start();

InputStreamContent mediaContent = new InputStreamContent(TEST_CONTENT_TYPE, inputStream);
MediaHttpUploader uploader = new MediaHttpUploader(mediaContent, fakeTransport, new TimeoutRequestInitializer());
uploader.setDirectUploadEnabled(false);
uploader.upload(new GenericUrl(TEST_RESUMABLE_REQUEST_URL));
}
}