Skip to content

Commit 77b39f2

Browse files
authored
fix: Fixed upload when data are coming from a dynamic source (#1189)
Closes #1183 #1190
1 parent 9f4f3f3 commit 77b39f2

File tree

7 files changed

+246
-38
lines changed

7 files changed

+246
-38
lines changed

src/intTest/java/com/box/sdk/BoxFolderIT.java

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import static com.box.sdk.UniqueTestFolder.randomizeName;
1414
import static com.box.sdk.UniqueTestFolder.removeUniqueFolder;
1515
import static com.box.sdk.UniqueTestFolder.setupUniqeFolder;
16+
import static com.box.sdk.UniqueTestFolder.uploadFileToUniqueFolder;
1617
import static com.box.sdk.UniqueTestFolder.uploadFileToUniqueFolderWithSomeContent;
1718
import static com.box.sdk.UniqueTestFolder.uploadFileWithSomeContent;
19+
import static java.nio.charset.StandardCharsets.UTF_8;
1820
import static org.hamcrest.MatcherAssert.assertThat;
1921
import static org.hamcrest.Matchers.contains;
2022
import static org.hamcrest.Matchers.emptyOrNullString;
@@ -32,9 +34,13 @@
3234
import com.box.sdk.BoxCollaboration.Role;
3335
import com.box.sdk.sharedlink.BoxSharedLinkRequest;
3436
import java.io.ByteArrayInputStream;
37+
import java.io.ByteArrayOutputStream;
3538
import java.io.File;
3639
import java.io.FileInputStream;
40+
import java.io.IOException;
3741
import java.io.InputStream;
42+
import java.io.PipedInputStream;
43+
import java.io.PipedOutputStream;
3844
import java.net.MalformedURLException;
3945
import java.net.URL;
4046
import java.text.SimpleDateFormat;
@@ -48,7 +54,10 @@
4854
import java.util.Map;
4955
import java.util.TimeZone;
5056
import java.util.UUID;
57+
import java.util.concurrent.Semaphore;
58+
import java.util.concurrent.atomic.AtomicLong;
5159
import java.util.concurrent.atomic.AtomicReference;
60+
import java.util.stream.IntStream;
5261
import org.hamcrest.Matchers;
5362
import org.junit.AfterClass;
5463
import org.junit.Before;
@@ -173,7 +182,7 @@ public void uploadFileSucceeds() {
173182
BoxFile uploadedFile = null;
174183

175184
try {
176-
uploadedFile = uploadFileToUniqueFolderWithSomeContent(api, "Test File.txt");
185+
uploadedFile = uploadFileToUniqueFolderWithSomeContent(api, randomizeName("Test File"));
177186

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

193202
try {
194203

195-
final String fileContent = "Test file";
204+
final String fileContent = randomizeName("Test file");
196205
uploadedFile = rootFolder.uploadFile(outputStream -> {
197206
outputStream.write(fileContent.getBytes());
198207
callbackWasCalled.set(true);
@@ -757,6 +766,142 @@ public void iterateWithMarker() {
757766
}
758767
}
759768

769+
@Test
770+
public void uploadFileVersionInSeparateThreadsSucceeds() throws IOException, InterruptedException {
771+
BoxAPIConnection api = jwtApiForServiceAccount();
772+
Semaphore semaphore = new Semaphore(0);
773+
774+
PipedOutputStream outputStream = new PipedOutputStream();
775+
PipedInputStream inputStream = new PipedInputStream();
776+
outputStream.connect(inputStream);
777+
778+
String fileContent = "This is only a test";
779+
final BoxFile uploadedFile = uploadFileToUniqueFolder(api, randomizeName("Test File"), fileContent);
780+
781+
new Thread(
782+
() -> {
783+
try {
784+
new BoxFile(api, uploadedFile.getID()).download(outputStream);
785+
} finally {
786+
try {
787+
outputStream.close();
788+
} catch (IOException e) {
789+
throw new RuntimeException(e);
790+
}
791+
}
792+
}).start();
793+
794+
new Thread(
795+
() -> {
796+
new BoxFile(api, uploadedFile.getID()).uploadNewVersion(inputStream);
797+
try {
798+
inputStream.close();
799+
semaphore.release();
800+
} catch (IOException e) {
801+
throw new RuntimeException(e);
802+
}
803+
}).start();
804+
805+
806+
semaphore.acquire();
807+
ByteArrayOutputStream output = new ByteArrayOutputStream();
808+
new BoxFile(api, uploadedFile.getID()).download(output);
809+
assertThat(output.toString(), is(fileContent));
810+
}
811+
812+
@Test
813+
public void uploadFileVersionWithProgressInSeparateThreadsSucceeds() throws IOException, InterruptedException {
814+
BoxAPIConnection api = jwtApiForServiceAccount();
815+
Semaphore semaphore = new Semaphore(0);
816+
817+
PipedOutputStream outputStream = new PipedOutputStream();
818+
PipedInputStream inputStream = new PipedInputStream();
819+
outputStream.connect(inputStream);
820+
AtomicLong bytesUploaded = new AtomicLong(0);
821+
ProgressListener progressListener = (numBytes, totalBytes) -> bytesUploaded.set(numBytes);
822+
823+
String fileContent = "This is only a test";
824+
long fileSize = fileContent.getBytes(UTF_8).length;
825+
826+
final BoxFile uploadedFile = uploadFileToUniqueFolder(api, randomizeName("Test File"), fileContent);
827+
828+
new Thread(
829+
() -> {
830+
try {
831+
new BoxFile(api, uploadedFile.getID()).download(outputStream);
832+
} finally {
833+
semaphore.release();
834+
}
835+
}).start();
836+
837+
new Thread(
838+
() -> {
839+
new BoxFile(api, uploadedFile.getID())
840+
.uploadNewVersion(inputStream, new Date(), fileSize, progressListener);
841+
semaphore.release();
842+
}).start();
843+
844+
845+
semaphore.acquire(2);
846+
ByteArrayOutputStream output = new ByteArrayOutputStream();
847+
new BoxFile(api, uploadedFile.getID()).download(output);
848+
assertThat(output.toString(), is(fileContent));
849+
assertThat(bytesUploaded.get(), is(fileSize));
850+
}
851+
852+
@Test
853+
public void uploadFileInSeparateThreadSucceeds() throws IOException, InterruptedException {
854+
BoxAPIConnection api = jwtApiForServiceAccount();
855+
Semaphore semaphore = new Semaphore(0);
856+
857+
PipedOutputStream outputStream = new PipedOutputStream();
858+
PipedInputStream inputStream = new PipedInputStream();
859+
outputStream.connect(inputStream);
860+
861+
String fileContent = "Test";
862+
byte[] bytes = fileContent.getBytes(UTF_8);
863+
864+
AtomicReference<String> uploadedFileId = new AtomicReference<>();
865+
866+
new Thread(
867+
() -> {
868+
IntStream.range(0, bytes.length)
869+
.forEach(i -> {
870+
try {
871+
outputStream.write(bytes[i]);
872+
Thread.sleep(100);
873+
} catch (InterruptedException | IOException e) {
874+
throw new RuntimeException(e);
875+
}
876+
});
877+
try {
878+
outputStream.close();
879+
semaphore.release();
880+
} catch (IOException e) {
881+
throw new RuntimeException(e);
882+
}
883+
}).start();
884+
885+
new Thread(
886+
() -> {
887+
BoxFile.Info uploadedFile = getUniqueFolder(api)
888+
.uploadFile(inputStream, randomizeName("dynamic_upload"));
889+
uploadedFileId.set(uploadedFile.getID());
890+
try {
891+
inputStream.close();
892+
semaphore.release();
893+
} catch (IOException e) {
894+
throw new RuntimeException(e);
895+
}
896+
}).start();
897+
898+
899+
semaphore.acquire(2);
900+
ByteArrayOutputStream output = new ByteArrayOutputStream();
901+
new BoxFile(api, uploadedFileId.get()).download(output);
902+
assertThat(output.toString(), is(fileContent));
903+
}
904+
760905
private Collection<String> getNames(Iterable<BoxItem.Info> page) {
761906
Collection<String> result = new ArrayList<>();
762907
for (BoxItem.Info info : page) {

src/main/java/com/box/sdk/AbstractBoxMultipartRequest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ abstract class AbstractBoxMultipartRequest extends BoxAPIRequest {
2424
private final Map<String, String> fields = new HashMap<>();
2525
private InputStream inputStream;
2626
private String filename;
27-
private long fileSize;
27+
private long fileSize = -1;
2828
private UploadFileCallback callback;
2929

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

152154
private RequestBody getBody(ProgressListener progressListener) {
153155
if (this.callback == null) {
154-
return new RequestBodyFromStream(this.inputStream, getPartContentType(filename), progressListener);
156+
return new RequestBodyFromStream(
157+
this.inputStream, getPartContentType(filename), progressListener, fileSize
158+
);
155159
} else {
156160
return new RequestBodyFromCallback(this.callback, getPartContentType(filename));
157161
}

src/main/java/com/box/sdk/BinaryBodyUtils.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ private BinaryBodyUtils() {
1616

1717
/**
1818
* Writes response body bytes to output stream. After all closes the input stream.
19+
*
1920
* @param response Response that is going to be written.
20-
* @param output Output stream.
21+
* @param output Output stream.
2122
*/
2223
static void writeStream(BoxAPIResponse response, OutputStream output) {
2324
writeStream(response, output, null);
2425
}
2526

2627
/**
2728
* Writes response body bytes to output stream. After all closes the input stream.
29+
*
2830
* @param response Response that is going to be written.
29-
* @param output Output stream.
31+
* @param output Output stream.
3032
* @param listener Listener that will be notified on writing response. Can be null.
3133
*/
3234

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

4749
/**
4850
* Writes content of input stream to provided output. Method is NOT closing input stream.
49-
* @param input Input that will be read.
51+
*
52+
* @param input Input that will be read.
5053
* @param output Output stream.
5154
*/
5255
static void writeStreamTo(InputStream input, OutputStream output) {
@@ -59,6 +62,13 @@ static void writeStreamTo(InputStream input, OutputStream output) {
5962
}
6063
} catch (IOException e) {
6164
throw new RuntimeException(e);
65+
} finally {
66+
try {
67+
input.close();
68+
output.close();
69+
} catch (IOException e) {
70+
throw new RuntimeException(e);
71+
}
6272
}
6373
}
6474
}

src/main/java/com/box/sdk/FileUploadParams.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public FileUploadParams setModified(Date modified) {
123123

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

133134
/**
134135
* Sets the size of the file content used for monitoring the upload's progress.
136+
* When the content is coming from a dynamic source - other thread reading value
137+
* set size to -1 to tell SDK that file size cannot be determined. Usefull
138+
* when encuntering problems with writing different size of bytes than assumed.
135139
*
136140
* @param size the size of the file's content.
137141
* @return this FileUploadParams object for chaining.

src/main/java/com/box/sdk/ProgressListener.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public interface ProgressListener {
77

88
/**
99
* Invoked when the progress of the API call changes.
10+
* In case of file uploads which are coming from a dynamic stream the file size cannot be determined and
11+
* total bytes will be reported as -1.
1012
*
1113
* @param numBytes the number of bytes completed.
1214
* @param totalBytes the total number of bytes.

0 commit comments

Comments
 (0)