Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixed upload when data are coming from a dynamic source #1189

Merged
merged 6 commits into from
Aug 3, 2023
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
149 changes: 147 additions & 2 deletions src/intTest/java/com/box/sdk/BoxFolderIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import static com.box.sdk.UniqueTestFolder.randomizeName;
import static com.box.sdk.UniqueTestFolder.removeUniqueFolder;
import static com.box.sdk.UniqueTestFolder.setupUniqeFolder;
import static com.box.sdk.UniqueTestFolder.uploadFileToUniqueFolder;
import static com.box.sdk.UniqueTestFolder.uploadFileToUniqueFolderWithSomeContent;
import static com.box.sdk.UniqueTestFolder.uploadFileWithSomeContent;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.emptyOrNullString;
Expand All @@ -32,9 +34,13 @@
import com.box.sdk.BoxCollaboration.Role;
import com.box.sdk.sharedlink.BoxSharedLinkRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
Expand All @@ -48,7 +54,10 @@
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.Before;
Expand Down Expand Up @@ -173,7 +182,7 @@ public void uploadFileSucceeds() {
BoxFile uploadedFile = null;

try {
uploadedFile = uploadFileToUniqueFolderWithSomeContent(api, "Test File.txt");
uploadedFile = uploadFileToUniqueFolderWithSomeContent(api, randomizeName("Test File"));

assertThat(rootFolder, hasItem(Matchers.<BoxItem.Info>hasProperty("ID", equalTo(uploadedFile.getID()))));
} finally {
Expand All @@ -192,7 +201,7 @@ public void uploadFileUploadFileCallbackSucceeds() {

try {

final String fileContent = "Test file";
final String fileContent = randomizeName("Test file");
uploadedFile = rootFolder.uploadFile(outputStream -> {
outputStream.write(fileContent.getBytes());
callbackWasCalled.set(true);
Expand Down Expand Up @@ -757,6 +766,142 @@ public void iterateWithMarker() {
}
}

@Test
public void uploadFileVersionInSeparateThreadsSucceeds() throws IOException, InterruptedException {
BoxAPIConnection api = jwtApiForServiceAccount();
Semaphore semaphore = new Semaphore(0);

PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream();
outputStream.connect(inputStream);

String fileContent = "This is only a test";
final BoxFile uploadedFile = uploadFileToUniqueFolder(api, randomizeName("Test File"), fileContent);

new Thread(
() -> {
try {
new BoxFile(api, uploadedFile.getID()).download(outputStream);
} finally {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();

new Thread(
() -> {
new BoxFile(api, uploadedFile.getID()).uploadNewVersion(inputStream);
try {
inputStream.close();
semaphore.release();
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();


semaphore.acquire();
ByteArrayOutputStream output = new ByteArrayOutputStream();
new BoxFile(api, uploadedFile.getID()).download(output);
assertThat(output.toString(), is(fileContent));
}

@Test
public void uploadFileVersionWithProgressInSeparateThreadsSucceeds() throws IOException, InterruptedException {
BoxAPIConnection api = jwtApiForServiceAccount();
Semaphore semaphore = new Semaphore(0);

PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream();
outputStream.connect(inputStream);
AtomicLong bytesUploaded = new AtomicLong(0);
ProgressListener progressListener = (numBytes, totalBytes) -> bytesUploaded.set(numBytes);

String fileContent = "This is only a test";
long fileSize = fileContent.getBytes(UTF_8).length;

final BoxFile uploadedFile = uploadFileToUniqueFolder(api, randomizeName("Test File"), fileContent);

new Thread(
() -> {
try {
new BoxFile(api, uploadedFile.getID()).download(outputStream);
} finally {
semaphore.release();
}
}).start();

new Thread(
() -> {
new BoxFile(api, uploadedFile.getID())
.uploadNewVersion(inputStream, new Date(), fileSize, progressListener);
semaphore.release();
}).start();


semaphore.acquire(2);
ByteArrayOutputStream output = new ByteArrayOutputStream();
new BoxFile(api, uploadedFile.getID()).download(output);
assertThat(output.toString(), is(fileContent));
assertThat(bytesUploaded.get(), is(fileSize));
}

@Test
public void uploadFileInSeparateThreadSucceeds() throws IOException, InterruptedException {
BoxAPIConnection api = jwtApiForServiceAccount();
Semaphore semaphore = new Semaphore(0);

PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream();
outputStream.connect(inputStream);

String fileContent = "Test";
byte[] bytes = fileContent.getBytes(UTF_8);

AtomicReference<String> uploadedFileId = new AtomicReference<>();

new Thread(
() -> {
IntStream.range(0, bytes.length)
.forEach(i -> {
try {
outputStream.write(bytes[i]);
Thread.sleep(100);
} catch (InterruptedException | IOException e) {
throw new RuntimeException(e);
}
});
try {
outputStream.close();
semaphore.release();
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();

new Thread(
() -> {
BoxFile.Info uploadedFile = getUniqueFolder(api)
.uploadFile(inputStream, randomizeName("dynamic_upload"));
uploadedFileId.set(uploadedFile.getID());
try {
inputStream.close();
semaphore.release();
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();


semaphore.acquire(2);
ByteArrayOutputStream output = new ByteArrayOutputStream();
new BoxFile(api, uploadedFileId.get()).download(output);
assertThat(output.toString(), is(fileContent));
}

private Collection<String> getNames(Iterable<BoxItem.Info> page) {
Collection<String> result = new ArrayList<>();
for (BoxItem.Info info : page) {
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/com/box/sdk/AbstractBoxMultipartRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ abstract class AbstractBoxMultipartRequest extends BoxAPIRequest {
private final Map<String, String> fields = new HashMap<>();
private InputStream inputStream;
private String filename;
private long fileSize;
private long fileSize = -1;
private UploadFileCallback callback;

AbstractBoxMultipartRequest(BoxAPIConnection api, URL url) {
Expand All @@ -48,7 +48,9 @@ public void setFile(InputStream inputStream, String filename) {
*
* @param inputStream a stream containing the file contents.
* @param filename the name of the file.
* @param fileSize the size of the file.
* @param fileSize the size of the file. If the file content is coming from the dynamic stream,
* and it's full size cannot be determined on starting upload use fizeSize=-1
* or use {@link AbstractBoxMultipartRequest#setFile(InputStream, String)}
*/
public void setFile(InputStream inputStream, String filename, long fileSize) {
this.setFile(inputStream, filename);
Expand Down Expand Up @@ -151,7 +153,9 @@ protected void writeMethodWithBody(Request.Builder requestBuilder, ProgressListe

private RequestBody getBody(ProgressListener progressListener) {
if (this.callback == null) {
return new RequestBodyFromStream(this.inputStream, getPartContentType(filename), progressListener);
return new RequestBodyFromStream(
this.inputStream, getPartContentType(filename), progressListener, fileSize
);
} else {
return new RequestBodyFromCallback(this.callback, getPartContentType(filename));
}
Expand Down
16 changes: 13 additions & 3 deletions src/main/java/com/box/sdk/BinaryBodyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ private BinaryBodyUtils() {

/**
* Writes response body bytes to output stream. After all closes the input stream.
*
* @param response Response that is going to be written.
* @param output Output stream.
* @param output Output stream.
*/
static void writeStream(BoxAPIResponse response, OutputStream output) {
writeStream(response, output, null);
}

/**
* Writes response body bytes to output stream. After all closes the input stream.
*
* @param response Response that is going to be written.
* @param output Output stream.
* @param output Output stream.
* @param listener Listener that will be notified on writing response. Can be null.
*/

Expand All @@ -46,7 +48,8 @@ static void writeStream(BoxAPIResponse response, OutputStream output, ProgressLi

/**
* Writes content of input stream to provided output. Method is NOT closing input stream.
* @param input Input that will be read.
*
* @param input Input that will be read.
* @param output Output stream.
*/
static void writeStreamTo(InputStream input, OutputStream output) {
Expand All @@ -59,6 +62,13 @@ static void writeStreamTo(InputStream input, OutputStream output) {
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
input.close();
output.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/box/sdk/FileUploadParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public FileUploadParams setModified(Date modified) {

/**
* Gets the size of the file's content used for monitoring the upload's progress.
* If the size cannot be determined value will be -1.
*
* @return the size of the file's content.
*/
Expand All @@ -132,6 +133,9 @@ public long getSize() {

/**
* Sets the size of the file content used for monitoring the upload's progress.
* When the content is coming from a dynamic source - other thread reading value
* set size to -1 to tell SDK that file size cannot be determined. Usefull
* when encuntering problems with writing different size of bytes than assumed.
*
* @param size the size of the file's content.
* @return this FileUploadParams object for chaining.
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/box/sdk/ProgressListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public interface ProgressListener {

/**
* Invoked when the progress of the API call changes.
* In case of file uploads which are coming from a dynamic stream the file size cannot be determined and
* total bytes will be reported as -1.
*
* @param numBytes the number of bytes completed.
* @param totalBytes the total number of bytes.
Expand Down
Loading
Loading