diff --git a/Parse/src/main/java/com/parse/ParseFile.java b/Parse/src/main/java/com/parse/ParseFile.java index 919cda549..c8b693fc5 100644 --- a/Parse/src/main/java/com/parse/ParseFile.java +++ b/Parse/src/main/java/com/parse/ParseFile.java @@ -134,11 +134,40 @@ public String url() { * successfully synced with the server. */ /* package for tests */ byte[] data; + /* package for tests */ File file; /* package for tests */ final TaskQueue taskQueue = new TaskQueue(); private Set.TaskCompletionSource> currentTasks = Collections.synchronizedSet( new HashSet.TaskCompletionSource>()); + /** + * Creates a new file from a file pointer. + * + * @param file + * The file. + */ + public ParseFile(File file) { + this(file, null); + } + + /** + * Creates a new file from a file pointer, and content type. Content type will be used instead of + * auto-detection by file extension. + * + * @param file + * The file. + * @param contentType + * The file's content type. + */ + public ParseFile(File file, String contentType) { + this(new State.Builder().name(file.getName()).mimeType(contentType).build()); + if (file.length() > MAX_FILE_SIZE) { + throw new IllegalArgumentException(String.format("ParseFile must be less than %d bytes", + MAX_FILE_SIZE)); + } + this.file = file; + } + /** * Creates a new file from a byte array, file name, and content type. Content type will be used * instead of auto-detection by file extension. @@ -273,15 +302,30 @@ public Task then(Task task) throws Exception { return Task.cancelled(); } - return getFileController().saveAsync( - state, - data, - sessionToken, - progressCallbackOnMainThread(uploadProgressCallback), - cancellationToken).onSuccessTask(new Continuation>() { + Task saveTask; + if (data != null) { + saveTask = getFileController().saveAsync( + state, + data, + sessionToken, + progressCallbackOnMainThread(uploadProgressCallback), + cancellationToken); + } else { + saveTask = getFileController().saveAsync( + state, + file, + sessionToken, + progressCallbackOnMainThread(uploadProgressCallback), + cancellationToken); + } + + return saveTask.onSuccessTask(new Continuation>() { @Override public Task then(Task task) throws Exception { state = task.getResult(); + // Since we have successfully uploaded the file, we do not need to hold the file pointer + // anymore. + file = null; return task.makeVoid(); } }); diff --git a/Parse/src/test/java/com/parse/ParseFileTest.java b/Parse/src/test/java/com/parse/ParseFileTest.java index d7c8cdcfe..668545504 100644 --- a/Parse/src/test/java/com/parse/ParseFileTest.java +++ b/Parse/src/test/java/com/parse/ParseFileTest.java @@ -13,6 +13,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; import org.mockito.Matchers; import java.io.File; @@ -21,12 +22,15 @@ import bolts.Task; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -50,30 +54,41 @@ public void testConstructor() throws Exception { String name = "name"; byte[] data = "hello".getBytes(); String contentType = "content_type"; - - ParseFile file = new ParseFile(name, data, contentType); - assertEquals("name", file.getName()); - assertEquals("hello", new String(file.getData())); - assertEquals("content_type", file.getState().mimeType()); - assertTrue(file.isDirty()); - - file = new ParseFile(data); - assertEquals("file", file.getName()); // Default - assertEquals("hello", new String(file.getData())); - assertEquals(null, file.getState().mimeType()); - assertTrue(file.isDirty()); - - file = new ParseFile(name, data); - assertEquals("name", file.getName()); - assertEquals("hello", new String(file.getData())); - assertEquals(null, file.getState().mimeType()); - assertTrue(file.isDirty()); - - file = new ParseFile(data, contentType); - assertEquals("file", file.getName()); // Default - assertEquals("hello", new String(file.getData())); - assertEquals("content_type", file.getState().mimeType()); - assertTrue(file.isDirty()); + File file = temporaryFolder.newFile(name); + + ParseFile parseFile = new ParseFile(name, data, contentType); + assertEquals("name", parseFile.getName()); + assertEquals("hello", new String(parseFile.getData())); + assertEquals("content_type", parseFile.getState().mimeType()); + assertTrue(parseFile.isDirty()); + + parseFile = new ParseFile(data); + assertEquals("file", parseFile.getName()); // Default + assertEquals("hello", new String(parseFile.getData())); + assertEquals(null, parseFile.getState().mimeType()); + assertTrue(parseFile.isDirty()); + + parseFile = new ParseFile(name, data); + assertEquals("name", parseFile.getName()); + assertEquals("hello", new String(parseFile.getData())); + assertEquals(null, parseFile.getState().mimeType()); + assertTrue(parseFile.isDirty()); + + parseFile = new ParseFile(data, contentType); + assertEquals("file", parseFile.getName()); // Default + assertEquals("hello", new String(parseFile.getData())); + assertEquals("content_type", parseFile.getState().mimeType()); + assertTrue(parseFile.isDirty()); + + // TODO(mengyan): Test file pointer in ParseFile when we have proper stage strategy + parseFile = new ParseFile(file); + assertEquals(name, parseFile.getName()); // Default + assertEquals(null, parseFile.getState().mimeType()); + assertTrue(parseFile.isDirty()); + + parseFile = new ParseFile(file, contentType); + assertEquals(name, parseFile.getName()); // Default + assertEquals("content_type", parseFile.getState().mimeType()); } @Test(expected = IllegalArgumentException.class) @@ -157,6 +172,82 @@ public void testSaveAsyncCancelled() throws Exception { Matchers.>any()); } + @Test + public void testSaveAsyncSuccessWithData() throws Exception { + String name = "name"; + byte[] data = "hello".getBytes(); + String contentType = "content_type"; + String url = "url"; + ParseFile.State state = new ParseFile.State.Builder() + .url(url) + .build(); + ParseFileController controller = mock(ParseFileController.class); + when(controller.saveAsync( + any(ParseFile.State.class), + any(byte[].class), + any(String.class), + any(ProgressCallback.class), + Matchers.>any())).thenReturn(Task.forResult(state)); + ParseCorePlugins.getInstance().registerFileController(controller); + + ParseFile parseFile = new ParseFile(name, data, contentType); + ParseTaskUtils.wait(parseFile.saveAsync(null, null, null)); + + // Verify controller get the correct data + ArgumentCaptor stateCaptor = ArgumentCaptor.forClass(ParseFile.State.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(byte[].class); + verify(controller, times(1)).saveAsync( + stateCaptor.capture(), + dataCaptor.capture(), + any(String.class), + any(ProgressCallback.class), + Matchers.>any()); + assertNull(stateCaptor.getValue().url()); + assertEquals(name, stateCaptor.getValue().name()); + assertEquals(contentType, stateCaptor.getValue().mimeType()); + assertArrayEquals(data, dataCaptor.getValue()); + // Verify the state of ParseFile has been updated + assertEquals(url, parseFile.getUrl()); + } + + @Test + public void testSaveAsyncSuccessWithFile() throws Exception { + String name = "name"; + File file = temporaryFolder.newFile(name); + String contentType = "content_type"; + String url = "url"; + ParseFile.State state = new ParseFile.State.Builder() + .url(url) + .build(); + ParseFileController controller = mock(ParseFileController.class); + when(controller.saveAsync( + any(ParseFile.State.class), + any(File.class), + any(String.class), + any(ProgressCallback.class), + Matchers.>any())).thenReturn(Task.forResult(state)); + ParseCorePlugins.getInstance().registerFileController(controller); + + ParseFile parseFile = new ParseFile(file, contentType); + ParseTaskUtils.wait(parseFile.saveAsync(null, null, null)); + + // Verify controller get the correct data + ArgumentCaptor stateCaptor = ArgumentCaptor.forClass(ParseFile.State.class); + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + verify(controller, times(1)).saveAsync( + stateCaptor.capture(), + fileCaptor.capture(), + any(String.class), + any(ProgressCallback.class), + Matchers.>any()); + assertNull(stateCaptor.getValue().url()); + assertEquals(name, stateCaptor.getValue().name()); + assertEquals(contentType, stateCaptor.getValue().mimeType()); + assertEquals(file, fileCaptor.getValue()); + // Verify the state of ParseFile has been updated + assertEquals(url, parseFile.getUrl()); + } + // TODO(grantland): testSaveAsyncNotDirtyAfterQueueAwait // TODO(grantland): testSaveAsyncSuccess // TODO(grantland): testSaveAsyncFailure @@ -177,6 +268,12 @@ public void testTaskQueuedMethods() throws Exception { any(String.class), any(ProgressCallback.class), Matchers.>any())).thenReturn(Task.forResult(state)); + when(controller.saveAsync( + any(ParseFile.State.class), + any(File.class), + any(String.class), + any(ProgressCallback.class), + Matchers.>any())).thenReturn(Task.forResult(state)); when(controller.fetchAsync( any(ParseFile.State.class), any(String.class),