Skip to content

Update ParseFileController to allow save file not only byte array #73

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

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
49 changes: 48 additions & 1 deletion Parse/src/main/java/com/parse/ParseFileController.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,54 @@ public ParseFile.State then(Task<JSONObject> task) throws Exception {

return newState;
}
}, Task.BACKGROUND_EXECUTOR);
}, ParseExecutors.io());
}

public Task<ParseFile.State> saveAsync(
final ParseFile.State state,
final File file,
String sessionToken,
ProgressCallback uploadProgressCallback,
Task<Void> cancellationToken) {
if (state.url() != null) { // !isDirty
return Task.forResult(state);
}
if (cancellationToken != null && cancellationToken.isCancelled()) {
return Task.cancelled();
}

final ParseRESTCommand command = new ParseRESTFileCommand.Builder()
.fileName(state.name())
.file(file)
.contentType(state.mimeType())
.sessionToken(sessionToken)
.build();
command.enableRetrying();

return command.executeAsync(
restClient,
uploadProgressCallback,
null,
cancellationToken
).onSuccess(new Continuation<JSONObject, ParseFile.State>() {
@Override
public ParseFile.State then(Task<JSONObject> task) throws Exception {
JSONObject result = task.getResult();
ParseFile.State newState = new ParseFile.State.Builder(state)
.name(result.getString("name"))
.url(result.getString("url"))
.build();

// Write data to cache
try {
ParseFileUtils.copyFile(file, getCacheFile(newState));
} catch (IOException e) {
// do nothing
}

return newState;
}
}, ParseExecutors.io());
}

public Task<File> fetchAsync(
Expand Down
23 changes: 21 additions & 2 deletions Parse/src/main/java/com/parse/ParseRESTFileCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
/**
* REST network command for creating & uploading {@link ParseFile}s.
*/

import java.io.File;

/** package */ class ParseRESTFileCommand extends ParseRESTCommand {

public static class Builder extends Init<Builder> {

private byte[] data = null;
private String contentType = null;
private File file;

public Builder() {
// We only ever use ParseRESTFileCommand for file uploads, so default to POST.
Expand All @@ -37,6 +41,11 @@ public Builder contentType(String contentType) {
return this;
}

public Builder file(File file) {
this.file = file;
return this;
}

@Override
/* package */ Builder self() {
return this;
Expand All @@ -49,18 +58,28 @@ public ParseRESTFileCommand build() {

private final byte[] data;
private final String contentType;
private final File file;

public ParseRESTFileCommand(Builder builder) {
super(builder);
if (builder.file != null && builder.data != null) {
throw new IllegalArgumentException("File and data can not be set at the same time");
}
this.data = builder.data;
this.contentType = builder.contentType;
this.file = builder.file;
}

@Override
protected ParseHttpBody newBody(final ProgressCallback progressCallback) {
// TODO(mengyan): Delete ParseByteArrayHttpBody when we change input byte array to staged file
// in ParseFileController
if (progressCallback == null) {
return new ParseByteArrayHttpBody(data, contentType);
return data != null ?
new ParseByteArrayHttpBody(data, contentType) : new ParseFileHttpBody(file, contentType);
}
return new ParseCountingByteArrayHttpBody(data, contentType, progressCallback);
return data != null ?
new ParseCountingByteArrayHttpBody(data, contentType, progressCallback) :
new ParseCountingFileHttpBody(file, contentType, progressCallback);
}
}
72 changes: 68 additions & 4 deletions Parse/src/test/java/com/parse/ParseFileControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void testSaveAsyncNotDirty() throws Exception {
ParseFile.State state = new ParseFile.State.Builder()
.url("http://example.com")
.build();
Task<ParseFile.State> task = controller.saveAsync(state, null, null, null, null);
Task<ParseFile.State> task = controller.saveAsync(state, (byte[])null, null, null, null);
task.waitForCompletion();

verify(restClient, times(0)).execute(any(ParseHttpRequest.class));
Expand All @@ -113,15 +113,15 @@ public void testSaveAsyncAlreadyCancelled() throws Exception {

ParseFile.State state = new ParseFile.State.Builder().build();
Task<Void> cancellationToken = Task.cancelled();
Task<ParseFile.State> task = controller.saveAsync(state, null, null, null, cancellationToken);
Task<ParseFile.State> task = controller.saveAsync(state, (byte[])null, null, null, cancellationToken);
task.waitForCompletion();

verify(restClient, times(0)).execute(any(ParseHttpRequest.class));
assertTrue(task.isCancelled());
}

@Test
public void testSaveAsyncSuccess() throws Exception {
public void testSaveAsyncSuccessWithByteArray() throws Exception {
JSONObject json = new JSONObject();
json.put("name", "new_file_name");
json.put("url", "http://example.com");
Expand Down Expand Up @@ -155,7 +155,43 @@ public void testSaveAsyncSuccess() throws Exception {
}

@Test
public void testSaveAsyncFailure() throws Exception {
public void testSaveAsyncSuccessWithFile() throws Exception {
JSONObject json = new JSONObject();
json.put("name", "new_file_name");
json.put("url", "http://example.com");
String content = json.toString();

ParseHttpResponse response = mock(ParseHttpResponse.class);
when(response.getStatusCode()).thenReturn(200);
when(response.getContent()).thenReturn(new ByteArrayInputStream(content.getBytes()));
when(response.getTotalSize()).thenReturn((long) content.length());

ParseHttpClient restClient = mock(ParseHttpClient.class);
when(restClient.execute(any(ParseHttpRequest.class))).thenReturn(response);

File root = temporaryFolder.getRoot();
ParseFileController controller = new ParseFileController(restClient, root);

File file = new File(root, "test");
ParseFileUtils.writeStringToFile(file, "content", "UTF-8");
ParseFile.State state = new ParseFile.State.Builder()
.name("file_name")
.mimeType("mime_type")
.build();
Task<ParseFile.State> task = controller.saveAsync(state, file, null, null, null);
ParseFile.State result = ParseTaskUtils.wait(task);

verify(restClient, times(1)).execute(any(ParseHttpRequest.class));
assertEquals("new_file_name", result.name());
assertEquals("http://example.com", result.url());
File cachedFile = new File(root, "new_file_name");
assertTrue(cachedFile.exists());
assertTrue(file.exists());
assertEquals("content", ParseFileUtils.readFileToString(cachedFile, "UTF-8"));
}

@Test
public void testSaveAsyncFailureWithByteArray() throws Exception {
// TODO(grantland): Remove once we no longer rely on retry logic.
ParseRequest.setDefaultInitialRetryDelay(1L);

Expand All @@ -180,6 +216,34 @@ public void testSaveAsyncFailure() throws Exception {
assertEquals(0, root.listFiles().length);
}

@Test
public void testSaveAsyncFailureWithFile() throws Exception {
// TODO(grantland): Remove once we no longer rely on retry logic.
ParseRequest.setDefaultInitialRetryDelay(1L);

ParseHttpClient restClient = mock(ParseHttpClient.class);
when(restClient.execute(any(ParseHttpRequest.class))).thenThrow(new IOException());

File root = temporaryFolder.getRoot();
ParseFileController controller = new ParseFileController(restClient, root);

File file = temporaryFolder.newFile("test");
ParseFile.State state = new ParseFile.State.Builder()
.build();
Task<ParseFile.State> task = controller.saveAsync(state, file, null, null, null);
task.waitForCompletion();

// TODO(grantland): Abstract out command runner so we don't have to account for retries.
verify(restClient, times(5)).execute(any(ParseHttpRequest.class));
assertTrue(task.isFaulted());
Exception error = task.getError();
assertThat(error, instanceOf(ParseException.class));
assertEquals(ParseException.CONNECTION_FAILED, ((ParseException) error).getCode());
// Make sure the original file is not deleted and there is no cache file in the folder
assertEquals(1, root.listFiles().length);
assertTrue(file.exists());
}

//endregion

//region testFetchAsync
Expand Down