Skip to content

Commit cbdf919

Browse files
committed
Add upload stream api
1 parent d1e2907 commit cbdf919

File tree

2 files changed

+171
-30
lines changed

2 files changed

+171
-30
lines changed

Parse/src/main/java/com/parse/ParseFile.java

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,40 @@ public String url() {
134134
* successfully synced with the server.
135135
*/
136136
/* package for tests */ byte[] data;
137+
/* package for tests */ File file;
137138

138139
/* package for tests */ final TaskQueue taskQueue = new TaskQueue();
139140
private Set<Task<?>.TaskCompletionSource> currentTasks = Collections.synchronizedSet(
140141
new HashSet<Task<?>.TaskCompletionSource>());
141142

143+
/**
144+
* Creates a new file from a file pointer.
145+
*
146+
* @param file
147+
* The file.
148+
*/
149+
public ParseFile(File file) {
150+
this(file, null);
151+
}
152+
153+
/**
154+
* Creates a new file from a file pointer, and content type. Content type will be used instead of
155+
* auto-detection by file extension.
156+
*
157+
* @param file
158+
* The file.
159+
* @param contentType
160+
* The file's content type.
161+
*/
162+
public ParseFile(File file, String contentType) {
163+
this(new State.Builder().name(file.getName()).mimeType(contentType).build());
164+
if (file.length() > MAX_FILE_SIZE) {
165+
throw new IllegalArgumentException(String.format("ParseFile must be less than %d bytes",
166+
MAX_FILE_SIZE));
167+
}
168+
this.file = file;
169+
}
170+
142171
/**
143172
* Creates a new file from a byte array, file name, and content type. Content type will be used
144173
* instead of auto-detection by file extension.
@@ -273,15 +302,30 @@ public Task<Void> then(Task<Void> task) throws Exception {
273302
return Task.cancelled();
274303
}
275304

276-
return getFileController().saveAsync(
277-
state,
278-
data,
279-
sessionToken,
280-
progressCallbackOnMainThread(uploadProgressCallback),
281-
cancellationToken).onSuccessTask(new Continuation<State, Task<Void>>() {
305+
Task<ParseFile.State> saveTask;
306+
if (data != null) {
307+
saveTask = getFileController().saveAsync(
308+
state,
309+
data,
310+
sessionToken,
311+
progressCallbackOnMainThread(uploadProgressCallback),
312+
cancellationToken);
313+
} else {
314+
saveTask = getFileController().saveAsync(
315+
state,
316+
file,
317+
sessionToken,
318+
progressCallbackOnMainThread(uploadProgressCallback),
319+
cancellationToken);
320+
}
321+
322+
return saveTask.onSuccessTask(new Continuation<State, Task<Void>>() {
282323
@Override
283324
public Task<Void> then(Task<State> task) throws Exception {
284325
state = task.getResult();
326+
// Since we have successfully uploaded the file, we do not need to hold the file pointer
327+
// anymore.
328+
file = null;
285329
return task.makeVoid();
286330
}
287331
});

Parse/src/test/java/com/parse/ParseFileTest.java

Lines changed: 121 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.junit.Rule;
1414
import org.junit.Test;
1515
import org.junit.rules.TemporaryFolder;
16+
import org.mockito.ArgumentCaptor;
1617
import org.mockito.Matchers;
1718

1819
import java.io.File;
@@ -21,12 +22,15 @@
2122

2223
import bolts.Task;
2324

25+
import static org.junit.Assert.assertArrayEquals;
2426
import static org.junit.Assert.assertEquals;
2527
import static org.junit.Assert.assertFalse;
28+
import static org.junit.Assert.assertNull;
2629
import static org.junit.Assert.assertTrue;
2730
import static org.mockito.Matchers.any;
2831
import static org.mockito.Mockito.mock;
2932
import static org.mockito.Mockito.never;
33+
import static org.mockito.Mockito.times;
3034
import static org.mockito.Mockito.verify;
3135
import static org.mockito.Mockito.when;
3236

@@ -50,30 +54,41 @@ public void testConstructor() throws Exception {
5054
String name = "name";
5155
byte[] data = "hello".getBytes();
5256
String contentType = "content_type";
53-
54-
ParseFile file = new ParseFile(name, data, contentType);
55-
assertEquals("name", file.getName());
56-
assertEquals("hello", new String(file.getData()));
57-
assertEquals("content_type", file.getState().mimeType());
58-
assertTrue(file.isDirty());
59-
60-
file = new ParseFile(data);
61-
assertEquals("file", file.getName()); // Default
62-
assertEquals("hello", new String(file.getData()));
63-
assertEquals(null, file.getState().mimeType());
64-
assertTrue(file.isDirty());
65-
66-
file = new ParseFile(name, data);
67-
assertEquals("name", file.getName());
68-
assertEquals("hello", new String(file.getData()));
69-
assertEquals(null, file.getState().mimeType());
70-
assertTrue(file.isDirty());
71-
72-
file = new ParseFile(data, contentType);
73-
assertEquals("file", file.getName()); // Default
74-
assertEquals("hello", new String(file.getData()));
75-
assertEquals("content_type", file.getState().mimeType());
76-
assertTrue(file.isDirty());
57+
File file = temporaryFolder.newFile(name);
58+
59+
ParseFile parseFile = new ParseFile(name, data, contentType);
60+
assertEquals("name", parseFile.getName());
61+
assertEquals("hello", new String(parseFile.getData()));
62+
assertEquals("content_type", parseFile.getState().mimeType());
63+
assertTrue(parseFile.isDirty());
64+
65+
parseFile = new ParseFile(data);
66+
assertEquals("file", parseFile.getName()); // Default
67+
assertEquals("hello", new String(parseFile.getData()));
68+
assertEquals(null, parseFile.getState().mimeType());
69+
assertTrue(parseFile.isDirty());
70+
71+
parseFile = new ParseFile(name, data);
72+
assertEquals("name", parseFile.getName());
73+
assertEquals("hello", new String(parseFile.getData()));
74+
assertEquals(null, parseFile.getState().mimeType());
75+
assertTrue(parseFile.isDirty());
76+
77+
parseFile = new ParseFile(data, contentType);
78+
assertEquals("file", parseFile.getName()); // Default
79+
assertEquals("hello", new String(parseFile.getData()));
80+
assertEquals("content_type", parseFile.getState().mimeType());
81+
assertTrue(parseFile.isDirty());
82+
83+
// TODO(mengyan): Test file pointer in ParseFile when we have proper stage strategy
84+
parseFile = new ParseFile(file);
85+
assertEquals(name, parseFile.getName()); // Default
86+
assertEquals(null, parseFile.getState().mimeType());
87+
assertTrue(parseFile.isDirty());
88+
89+
parseFile = new ParseFile(file, contentType);
90+
assertEquals(name, parseFile.getName()); // Default
91+
assertEquals("content_type", parseFile.getState().mimeType());
7792
}
7893

7994
@Test(expected = IllegalArgumentException.class)
@@ -157,6 +172,82 @@ public void testSaveAsyncCancelled() throws Exception {
157172
Matchers.<Task<Void>>any());
158173
}
159174

175+
@Test
176+
public void testSaveAsyncSuccessWithData() throws Exception {
177+
String name = "name";
178+
byte[] data = "hello".getBytes();
179+
String contentType = "content_type";
180+
String url = "url";
181+
ParseFile.State state = new ParseFile.State.Builder()
182+
.url(url)
183+
.build();
184+
ParseFileController controller = mock(ParseFileController.class);
185+
when(controller.saveAsync(
186+
any(ParseFile.State.class),
187+
any(byte[].class),
188+
any(String.class),
189+
any(ProgressCallback.class),
190+
Matchers.<Task<Void>>any())).thenReturn(Task.forResult(state));
191+
ParseCorePlugins.getInstance().registerFileController(controller);
192+
193+
ParseFile parseFile = new ParseFile(name, data, contentType);
194+
ParseTaskUtils.wait(parseFile.saveAsync(null, null, null));
195+
196+
// Verify controller get the correct data
197+
ArgumentCaptor<ParseFile.State> stateCaptor = ArgumentCaptor.forClass(ParseFile.State.class);
198+
ArgumentCaptor<byte[]> dataCaptor = ArgumentCaptor.forClass(byte[].class);
199+
verify(controller, times(1)).saveAsync(
200+
stateCaptor.capture(),
201+
dataCaptor.capture(),
202+
any(String.class),
203+
any(ProgressCallback.class),
204+
Matchers.<Task<Void>>any());
205+
assertNull(stateCaptor.getValue().url());
206+
assertEquals(name, stateCaptor.getValue().name());
207+
assertEquals(contentType, stateCaptor.getValue().mimeType());
208+
assertArrayEquals(data, dataCaptor.getValue());
209+
// Verify the state of ParseFile has been updated
210+
assertEquals(url, parseFile.getUrl());
211+
}
212+
213+
@Test
214+
public void testSaveAsyncSuccessWithFile() throws Exception {
215+
String name = "name";
216+
File file = temporaryFolder.newFile(name);
217+
String contentType = "content_type";
218+
String url = "url";
219+
ParseFile.State state = new ParseFile.State.Builder()
220+
.url(url)
221+
.build();
222+
ParseFileController controller = mock(ParseFileController.class);
223+
when(controller.saveAsync(
224+
any(ParseFile.State.class),
225+
any(File.class),
226+
any(String.class),
227+
any(ProgressCallback.class),
228+
Matchers.<Task<Void>>any())).thenReturn(Task.forResult(state));
229+
ParseCorePlugins.getInstance().registerFileController(controller);
230+
231+
ParseFile parseFile = new ParseFile(file, contentType);
232+
ParseTaskUtils.wait(parseFile.saveAsync(null, null, null));
233+
234+
// Verify controller get the correct data
235+
ArgumentCaptor<ParseFile.State> stateCaptor = ArgumentCaptor.forClass(ParseFile.State.class);
236+
ArgumentCaptor<File> fileCaptor = ArgumentCaptor.forClass(File.class);
237+
verify(controller, times(1)).saveAsync(
238+
stateCaptor.capture(),
239+
fileCaptor.capture(),
240+
any(String.class),
241+
any(ProgressCallback.class),
242+
Matchers.<Task<Void>>any());
243+
assertNull(stateCaptor.getValue().url());
244+
assertEquals(name, stateCaptor.getValue().name());
245+
assertEquals(contentType, stateCaptor.getValue().mimeType());
246+
assertEquals(file, fileCaptor.getValue());
247+
// Verify the state of ParseFile has been updated
248+
assertEquals(url, parseFile.getUrl());
249+
}
250+
160251
// TODO(grantland): testSaveAsyncNotDirtyAfterQueueAwait
161252
// TODO(grantland): testSaveAsyncSuccess
162253
// TODO(grantland): testSaveAsyncFailure
@@ -177,6 +268,12 @@ public void testTaskQueuedMethods() throws Exception {
177268
any(String.class),
178269
any(ProgressCallback.class),
179270
Matchers.<Task<Void>>any())).thenReturn(Task.forResult(state));
271+
when(controller.saveAsync(
272+
any(ParseFile.State.class),
273+
any(File.class),
274+
any(String.class),
275+
any(ProgressCallback.class),
276+
Matchers.<Task<Void>>any())).thenReturn(Task.forResult(state));
180277
when(controller.fetchAsync(
181278
any(ParseFile.State.class),
182279
any(String.class),

0 commit comments

Comments
 (0)