Skip to content

Commit

Permalink
fix: Wrong progress reported to ProgressListener by `AbstractBoxMul…
Browse files Browse the repository at this point in the history
…tipartRequest` (#1151)

Fixes: #1149
  • Loading branch information
antusus authored Mar 3, 2023
1 parent 48e14f5 commit 947ded3
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 65 deletions.
66 changes: 1 addition & 65 deletions src/main/java/com/box/sdk/AbstractBoxMultipartRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.BufferedSink;

/**
* <p>Base class for multipart uploads</p>
Expand All @@ -20,7 +19,7 @@
*/
abstract class AbstractBoxMultipartRequest extends BoxAPIRequest {
protected static final String BOUNDARY = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
private static final int BUFFER_SIZE = 8192;
static final int BUFFER_SIZE = 8192;
private final StringBuilder loggedRequest = new StringBuilder();
private final Map<String, String> fields = new HashMap<>();
private InputStream inputStream;
Expand Down Expand Up @@ -158,67 +157,4 @@ private RequestBody getBody(ProgressListener progressListener) {
}
}

private static final class RequestBodyFromStream extends RequestBody {
private final InputStream inputStream;
private final ProgressListener progressListener;
private final MediaType mediaType;
private final int contentLength;

private RequestBodyFromStream(InputStream inputStream, MediaType mediaType, ProgressListener progressListener) {
this.inputStream = inputStream;
this.progressListener = progressListener;
this.mediaType = mediaType;
try {
this.contentLength = inputStream.available();
} catch (IOException e) {
throw new RuntimeException("Cannot read input stream for upload", e);
}
}

@Override
public long contentLength() {
return contentLength;
}

@Override
public MediaType contentType() {
return mediaType;
}

@Override
public void writeTo(BufferedSink bufferedSink) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int n = this.inputStream.read(buffer);
int totalWritten = n;
while (n != -1) {
bufferedSink.write(buffer, 0, n);
if (progressListener != null) {
progressListener.onProgressChanged(totalWritten, this.contentLength());
}
totalWritten += n;
n = this.inputStream.read(buffer);
}
}
}

private static final class RequestBodyFromCallback extends RequestBody {

private final UploadFileCallback callback;
private final MediaType mediaType;

private RequestBodyFromCallback(UploadFileCallback callback, MediaType mediaType) {
this.callback = callback;
this.mediaType = mediaType;
}

@Override
public MediaType contentType() {
return mediaType;
}

@Override
public void writeTo(BufferedSink bufferedSink) throws IOException {
callback.writeToStream(bufferedSink.outputStream());
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/box/sdk/RequestBodyFromCallback.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.box.sdk;

import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;

/**
*
*/
final class RequestBodyFromCallback extends RequestBody {

private final UploadFileCallback callback;
private final MediaType mediaType;

RequestBodyFromCallback(UploadFileCallback callback, MediaType mediaType) {
this.callback = callback;
this.mediaType = mediaType;
}

@Override
public MediaType contentType() {
return mediaType;
}

@Override
public void writeTo(BufferedSink bufferedSink) throws IOException {
callback.writeToStream(bufferedSink.outputStream());
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/box/sdk/RequestBodyFromStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.box.sdk;

import java.io.IOException;
import java.io.InputStream;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.BufferedSink;

/**
* Utility class to write body from stream to BufferedSink used by OkHttp.
*/
final class RequestBodyFromStream extends RequestBody {
private final InputStream inputStream;
private final ProgressListener progressListener;
private final MediaType mediaType;
private final int contentLength;

RequestBodyFromStream(InputStream inputStream, MediaType mediaType, ProgressListener progressListener) {
this.inputStream = inputStream;
this.progressListener = progressListener;
this.mediaType = mediaType;
try {
this.contentLength = inputStream.available();
} catch (IOException e) {
throw new RuntimeException("Cannot read input stream for upload", e);
}
}

@Override
public long contentLength() {
return contentLength;
}

@Override
public MediaType contentType() {
return mediaType;
}

@Override
public void writeTo(BufferedSink bufferedSink) throws IOException {
byte[] buffer = new byte[AbstractBoxMultipartRequest.BUFFER_SIZE];
int n = this.inputStream.read(buffer);
int totalWritten = 0;
while (n != -1) {
bufferedSink.write(buffer, 0, n);
totalWritten += n;
if (progressListener != null) {
progressListener.onProgressChanged(totalWritten, this.contentLength());
}
n = this.inputStream.read(buffer);
}
}
}
90 changes: 90 additions & 0 deletions src/test/java/com/box/sdk/RequestBodyFromStreamTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.box.sdk;

import static com.box.sdk.AbstractBoxMultipartRequest.BUFFER_SIZE;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.MediaType;
import okio.Buffer;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Test;

public class RequestBodyFromStreamTest {

@Test
public void reportCorrectProgressWhenFileIsEmpty() throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[]{});
ProgressListener progressListener = (numBytes, totalBytes) -> {
MatcherAssert.assertThat(numBytes, Matchers.is((long) 0));
MatcherAssert.assertThat(totalBytes, Matchers.is((long) 0));
};

RequestBodyFromStream request = new RequestBodyFromStream(
inputStream, MediaType.parse("application/json"), progressListener
);

request.writeTo(new Buffer());
}
@Test
public void reportCorrectProgressWhenFileSizeIfLessThanBuffer() throws IOException {
int howManyBytes = 1000;
ByteArrayInputStream inputStream = new ByteArrayInputStream(generateBytes(howManyBytes));
ProgressListener progressListener = (numBytes, totalBytes) -> {
MatcherAssert.assertThat(numBytes, Matchers.is((long) howManyBytes));
MatcherAssert.assertThat(totalBytes, Matchers.is((long) howManyBytes));
};

RequestBodyFromStream request = new RequestBodyFromStream(
inputStream, MediaType.parse("application/json"), progressListener
);

request.writeTo(new Buffer());
}

@Test
public void reportCorrectProgressWhenFileSizeIfEqualToBuffer() throws IOException {
int howManyBytes = BUFFER_SIZE;
ByteArrayInputStream inputStream = new ByteArrayInputStream(generateBytes(howManyBytes));
ProgressListener progressListener = (numBytes, totalBytes) -> {
MatcherAssert.assertThat(numBytes, Matchers.is((long) howManyBytes));
MatcherAssert.assertThat(totalBytes, Matchers.is((long) howManyBytes));
};

RequestBodyFromStream request = new RequestBodyFromStream(
inputStream, MediaType.parse("application/json"), progressListener
);

request.writeTo(new Buffer());
}

@Test
public void reportCorrectProgressWhenFileSizeIfGreaterThanBuffer() throws IOException {
int howManyBytes = BUFFER_SIZE + 1000;
ByteArrayInputStream inputStream = new ByteArrayInputStream(generateBytes(howManyBytes));
AtomicInteger counter = new AtomicInteger(0);
ProgressListener progressListener = (numBytes, totalBytes) -> {
if (counter.getAndIncrement() == 0) {
MatcherAssert.assertThat(numBytes, Matchers.is((long) BUFFER_SIZE));
MatcherAssert.assertThat(totalBytes, Matchers.is((long) howManyBytes));
} else {
MatcherAssert.assertThat(numBytes, Matchers.is((long) howManyBytes));
MatcherAssert.assertThat(totalBytes, Matchers.is((long) howManyBytes));
}
};

RequestBodyFromStream request = new RequestBodyFromStream(
inputStream, MediaType.parse("application/json"), progressListener
);

request.writeTo(new Buffer());
}

private byte[] generateBytes(int howManyBytes) {
byte[] bytes = new byte[howManyBytes];
new Random().nextBytes(bytes);
return bytes;
}
}

0 comments on commit 947ded3

Please sign in to comment.