From c7554b977da5a4f5149759de573b1213bfbc660c Mon Sep 17 00:00:00 2001 From: wangmengyan95 Date: Wed, 26 Aug 2015 15:42:47 -0700 Subject: [PATCH] Add ParseFileHttpBody and ParseCountingFileHttpBody --- .../main/java/com/parse/ParseAWSRequest.java | 2 +- .../java/com/parse/ParseApacheHttpClient.java | 2 +- .../com/parse/ParseCountingFileHttpBody.java | 58 ++++++++++++ .../java/com/parse/ParseFileHttpBody.java | 48 ++++++++++ .../main/java/com/parse/ParseHttpBody.java | 8 +- .../java/com/parse/ParseHttpResponse.java | 8 +- .../java/com/parse/ParseAWSRequestTest.java | 2 +- .../parse/ParseCloudCodeControllerTest.java | 4 +- .../parse/ParseCountingFileHttpBodyTest.java | 88 ++++++++++++++++++ .../com/parse/ParseFileControllerTest.java | 4 +- .../java/com/parse/ParseFileHttpBodyTest.java | 92 +++++++++++++++++++ .../java/com/parse/ParseRESTCommandTest.java | 4 +- .../test/java/com/parse/ParseRequestTest.java | 2 +- 13 files changed, 304 insertions(+), 18 deletions(-) create mode 100644 Parse/src/main/java/com/parse/ParseCountingFileHttpBody.java create mode 100644 Parse/src/main/java/com/parse/ParseFileHttpBody.java create mode 100644 Parse/src/test/java/com/parse/ParseCountingFileHttpBodyTest.java create mode 100644 Parse/src/test/java/com/parse/ParseFileHttpBodyTest.java diff --git a/Parse/src/main/java/com/parse/ParseAWSRequest.java b/Parse/src/main/java/com/parse/ParseAWSRequest.java index e296a2438..5c64ce317 100644 --- a/Parse/src/main/java/com/parse/ParseAWSRequest.java +++ b/Parse/src/main/java/com/parse/ParseAWSRequest.java @@ -41,7 +41,7 @@ protected Task onResponseAsync(ParseHttpResponse response, return null; } - int totalSize = response.getTotalSize(); + long totalSize = response.getTotalSize(); int downloadedSize = 0; InputStream responseStream = null; try { diff --git a/Parse/src/main/java/com/parse/ParseApacheHttpClient.java b/Parse/src/main/java/com/parse/ParseApacheHttpClient.java index 7ceb387f3..1d95805ba 100644 --- a/Parse/src/main/java/com/parse/ParseApacheHttpClient.java +++ b/Parse/src/main/java/com/parse/ParseApacheHttpClient.java @@ -212,7 +212,7 @@ public ParseApacheHttpClient(int socketOperationTimeout, SSLSessionCache sslSess private static class ParseApacheHttpEntity extends InputStreamEntity { private ParseHttpBody parseBody; - public ParseApacheHttpEntity(ParseHttpBody parseBody) { + public ParseApacheHttpEntity(ParseHttpBody parseBody) throws IOException { super(parseBody.getContent(), parseBody.getContentLength()); super.setContentType(parseBody.getContentType()); this.parseBody = parseBody; diff --git a/Parse/src/main/java/com/parse/ParseCountingFileHttpBody.java b/Parse/src/main/java/com/parse/ParseCountingFileHttpBody.java new file mode 100644 index 000000000..725836419 --- /dev/null +++ b/Parse/src/main/java/com/parse/ParseCountingFileHttpBody.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** package */ class ParseCountingFileHttpBody extends ParseFileHttpBody { + + private static final int DEFAULT_CHUNK_SIZE = 4096; + private static final int EOF = -1; + + private final ProgressCallback progressCallback; + + public ParseCountingFileHttpBody(File file, ProgressCallback progressCallback) { + this(file, null, progressCallback); + } + + public ParseCountingFileHttpBody( + File file, String contentType, ProgressCallback progressCallback) { + super(file, contentType); + this.progressCallback = progressCallback; + } + + @Override + public void writeTo(OutputStream output) throws IOException { + if (output == null) { + throw new IllegalArgumentException("Output stream may not be null"); + } + + final FileInputStream fileInput = new FileInputStream(file);; + try { + byte[] buffer = new byte[DEFAULT_CHUNK_SIZE]; + int n; + long totalLength = file.length(); + long position = 0; + while (EOF != (n = fileInput.read(buffer))) { + output.write(buffer, 0, n); + position += n; + + if (progressCallback != null) { + int progress = (int) (100 * position / totalLength); + progressCallback.done(progress); + } + } + } finally { + ParseIOUtils.closeQuietly(fileInput); + } + } +} diff --git a/Parse/src/main/java/com/parse/ParseFileHttpBody.java b/Parse/src/main/java/com/parse/ParseFileHttpBody.java new file mode 100644 index 000000000..010060e3e --- /dev/null +++ b/Parse/src/main/java/com/parse/ParseFileHttpBody.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** package */ class ParseFileHttpBody extends ParseHttpBody { + + /* package */ final File file; + + public ParseFileHttpBody(File file) { + this(file, null); + } + + public ParseFileHttpBody(File file, String contentType) { + super(contentType, file.length()); + this.file = file; + } + + @Override + public InputStream getContent() throws IOException { + return new FileInputStream(file); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (out == null) { + throw new IllegalArgumentException("Output stream can not be null"); + } + + final FileInputStream fileInput = new FileInputStream(file); + try { + ParseIOUtils.copy(fileInput, out); + } finally { + ParseIOUtils.closeQuietly(fileInput); + } + } +} diff --git a/Parse/src/main/java/com/parse/ParseHttpBody.java b/Parse/src/main/java/com/parse/ParseHttpBody.java index f11e3127f..0df0e92af 100644 --- a/Parse/src/main/java/com/parse/ParseHttpBody.java +++ b/Parse/src/main/java/com/parse/ParseHttpBody.java @@ -18,17 +18,17 @@ */ /** package */ abstract class ParseHttpBody { protected final String contentType; - protected final int contentLength; + protected final long contentLength; - public abstract InputStream getContent(); + public abstract InputStream getContent() throws IOException; public abstract void writeTo(OutputStream out) throws IOException; - public ParseHttpBody(String contentType, int contentLength) { + public ParseHttpBody(String contentType, long contentLength) { this.contentType = contentType; this.contentLength = contentLength; } - public int getContentLength() { + public long getContentLength() { return contentLength; } diff --git a/Parse/src/main/java/com/parse/ParseHttpResponse.java b/Parse/src/main/java/com/parse/ParseHttpResponse.java index 8af95e558..bae5be7ff 100644 --- a/Parse/src/main/java/com/parse/ParseHttpResponse.java +++ b/Parse/src/main/java/com/parse/ParseHttpResponse.java @@ -22,7 +22,7 @@ /* package */ static abstract class Init> { private int statusCode; private InputStream content; - private int totalSize; + private long totalSize; private String reasonPhrase; private Map headers; private String contentType; @@ -39,7 +39,7 @@ public T setContent(InputStream content) { return self(); } - public T setTotalSize(int totalSize) { + public T setTotalSize(long totalSize) { this.totalSize = totalSize; return self(); } @@ -74,7 +74,7 @@ public ParseHttpResponse build() { /* package */ int statusCode; /* package */ InputStream content; - /* package */ int totalSize; + /* package */ long totalSize; /* package */ String reasonPhrase; /* package */ Map headers; /* package */ String contentType; @@ -96,7 +96,7 @@ public InputStream getContent() { return content; } - public int getTotalSize() { + public long getTotalSize() { return totalSize; } diff --git a/Parse/src/test/java/com/parse/ParseAWSRequestTest.java b/Parse/src/test/java/com/parse/ParseAWSRequestTest.java index f5799c6a6..68f3ed2a8 100644 --- a/Parse/src/test/java/com/parse/ParseAWSRequestTest.java +++ b/Parse/src/test/java/com/parse/ParseAWSRequestTest.java @@ -37,7 +37,7 @@ public void test4XXThrowsException() throws Exception { "An Error occurred while saving".getBytes()); ParseHttpResponse mockResponse = mock(ParseHttpResponse.class); when(mockResponse.getStatusCode()).thenReturn(400); - when(mockResponse.getTotalSize()).thenReturn(0); + when(mockResponse.getTotalSize()).thenReturn(0L); when(mockResponse.getReasonPhrase()).thenReturn("Bad Request"); when(mockResponse.getContent()).thenReturn(mockInputStream); diff --git a/Parse/src/test/java/com/parse/ParseCloudCodeControllerTest.java b/Parse/src/test/java/com/parse/ParseCloudCodeControllerTest.java index a86d04c79..9770b870e 100644 --- a/Parse/src/test/java/com/parse/ParseCloudCodeControllerTest.java +++ b/Parse/src/test/java/com/parse/ParseCloudCodeControllerTest.java @@ -107,7 +107,7 @@ public void testCallFunctionInBackgroundSuccessWithResult() throws Exception { ParseHttpResponse mockResponse = mock(ParseHttpResponse.class); when(mockResponse.getStatusCode()).thenReturn(200); when(mockResponse.getContent()).thenReturn(new ByteArrayInputStream(content.getBytes())); - when(mockResponse.getTotalSize()).thenReturn(content.length()); + when(mockResponse.getTotalSize()).thenReturn((long) content.length()); ParseHttpClient restClient = mockParseHttpClientWithReponse(mockResponse); ParseCloudCodeController controller = new ParseCloudCodeController(restClient); @@ -128,7 +128,7 @@ public void testCallFunctionInBackgroundSuccessWithoutResult() throws Exception ParseHttpResponse mockResponse = mock(ParseHttpResponse.class); when(mockResponse.getStatusCode()).thenReturn(200); when(mockResponse.getContent()).thenReturn(new ByteArrayInputStream(content.getBytes())); - when(mockResponse.getTotalSize()).thenReturn(content.length()); + when(mockResponse.getTotalSize()).thenReturn((long) content.length()); ParseHttpClient restClient = mockParseHttpClientWithReponse(mockResponse); ParseCloudCodeController controller = new ParseCloudCodeController(restClient); diff --git a/Parse/src/test/java/com/parse/ParseCountingFileHttpBodyTest.java b/Parse/src/test/java/com/parse/ParseCountingFileHttpBodyTest.java new file mode 100644 index 000000000..e58d923c1 --- /dev/null +++ b/Parse/src/test/java/com/parse/ParseCountingFileHttpBodyTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class ParseCountingFileHttpBodyTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testWriteTo() throws Exception { + final Semaphore didReportIntermediateProgress = new Semaphore(0); + final Semaphore finish = new Semaphore(0); + + ParseCountingFileHttpBody body = new ParseCountingFileHttpBody( + makeTestFile(temporaryFolder.getRoot()), new ProgressCallback() { + Integer maxProgressSoFar = 0; + @Override + public void done(Integer percentDone) { + if (percentDone > maxProgressSoFar) { + maxProgressSoFar = percentDone; + assertTrue(percentDone >= 0 && percentDone <= 100); + + if (percentDone < 100 && percentDone > 0) { + didReportIntermediateProgress.release(); + } else if (percentDone == 100) { + finish.release(); + } else if (percentDone == 0) { + // do nothing + } else { + fail("percentDone should be within 0 - 100"); + } + } + } + }); + + // Check content + ByteArrayOutputStream output = new ByteArrayOutputStream(); + body.writeTo(output); + assertArrayEquals(getData().getBytes(), output.toByteArray()); + // Check progress callback + assertTrue(didReportIntermediateProgress.tryAcquire(5, TimeUnit.SECONDS)); + assertTrue(finish.tryAcquire(5, TimeUnit.SECONDS)); + } + + @Test(expected = IllegalArgumentException.class) + public void testWriteToWithNullOutput() throws Exception { + ParseCountingFileHttpBody body = new ParseCountingFileHttpBody( + makeTestFile(temporaryFolder.getRoot()), null); + body.writeTo(null); + } + + private static String getData() { + char[] chars = new char[64 << 14]; // 1MB + Arrays.fill(chars, '1'); + return new String(chars); + } + + private static File makeTestFile(File root) throws IOException { + File file = new File(root, "test"); + FileWriter writer = new FileWriter(file); + writer.write(getData()); + writer.close(); + return file; + } +} diff --git a/Parse/src/test/java/com/parse/ParseFileControllerTest.java b/Parse/src/test/java/com/parse/ParseFileControllerTest.java index 23c610490..5f211f305 100644 --- a/Parse/src/test/java/com/parse/ParseFileControllerTest.java +++ b/Parse/src/test/java/com/parse/ParseFileControllerTest.java @@ -130,7 +130,7 @@ public void testSaveAsyncSuccess() throws Exception { ParseHttpResponse response = mock(ParseHttpResponse.class); when(response.getStatusCode()).thenReturn(200); when(response.getContent()).thenReturn(new ByteArrayInputStream(content.getBytes())); - when(response.getTotalSize()).thenReturn(content.length()); + when(response.getTotalSize()).thenReturn((long) content.length()); ParseHttpClient restClient = mock(ParseHttpClient.class); when(restClient.execute(any(ParseHttpRequest.class))).thenReturn(response); @@ -229,7 +229,7 @@ public void testFetchAsyncSuccess() throws Exception { ParseHttpResponse response = mock(ParseHttpResponse.class); when(response.getStatusCode()).thenReturn(200); when(response.getContent()).thenReturn(new ByteArrayInputStream(data)); - when(response.getTotalSize()).thenReturn(data.length); + when(response.getTotalSize()).thenReturn((long) data.length); ParseHttpClient awsClient = mock(ParseHttpClient.class); when(awsClient.execute(any(ParseHttpRequest.class))).thenReturn(response); diff --git a/Parse/src/test/java/com/parse/ParseFileHttpBodyTest.java b/Parse/src/test/java/com/parse/ParseFileHttpBodyTest.java new file mode 100644 index 000000000..02c94b1d0 --- /dev/null +++ b/Parse/src/test/java/com/parse/ParseFileHttpBodyTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ParseFileHttpBodyTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testInitializeWithFileAndContentType() throws IOException { + String contentType = "text/plain"; + File file = makeTestFile(temporaryFolder.getRoot()); + + ParseFileHttpBody body = new ParseFileHttpBody(file, contentType); + + assertEquals(file.length(), body.getContentLength()); + assertEquals(contentType, body.getContentType()); + // Verify file content + InputStream content = body.getContent(); + byte[] contentBytes = ParseIOUtils.toByteArray(content); + ParseIOUtils.closeQuietly(content); + verifyTestFileContent(contentBytes); + } + + @Test + public void testInitializeWithFile() throws IOException { + File file = makeTestFile(temporaryFolder.getRoot()); + + ParseFileHttpBody body = new ParseFileHttpBody(file); + + assertEquals(file.length(), body.getContentLength()); + assertNull(body.getContentType()); + // Verify file content + InputStream content = body.getContent(); + byte[] contentBytes = ParseIOUtils.toByteArray(content); + ParseIOUtils.closeQuietly(content); + verifyTestFileContent(contentBytes); + } + + @Test + public void testWriteTo() throws IOException { + File file = makeTestFile(temporaryFolder.getRoot()); + ParseFileHttpBody body = new ParseFileHttpBody(file); + + // Check content + ByteArrayOutputStream output = new ByteArrayOutputStream(); + body.writeTo(output); + verifyTestFileContent(output.toByteArray()); + } + + @Test(expected = IllegalArgumentException.class) + public void testWriteToWithNullOutput() throws Exception { + ParseFileHttpBody body = new ParseFileHttpBody(makeTestFile(temporaryFolder.getRoot())); + body.writeTo(null); + } + + // Generate a test file used for create ParseFileHttpBody, if you change file's content, make sure + // you also change the test file content in verifyTestFileContent(). + private static File makeTestFile(File root) throws IOException { + File file = new File(root, "test"); + String content = "content"; + FileWriter writer = new FileWriter(file); + writer.write(content); + writer.close(); + return file; + } + + private static void verifyTestFileContent(byte[] bytes) throws IOException { + assertArrayEquals("content".getBytes(), bytes); + } +} diff --git a/Parse/src/test/java/com/parse/ParseRESTCommandTest.java b/Parse/src/test/java/com/parse/ParseRESTCommandTest.java index b44da9b62..5f909e507 100644 --- a/Parse/src/test/java/com/parse/ParseRESTCommandTest.java +++ b/Parse/src/test/java/com/parse/ParseRESTCommandTest.java @@ -54,7 +54,7 @@ private static ParseHttpResponse newMockParseHttpResponse(int statusCode, String ParseHttpResponse response = mock(ParseHttpResponse.class); when(response.getStatusCode()).thenReturn(statusCode); when(response.getContent()).thenReturn(new ByteArrayInputStream(body.getBytes())); - when(response.getTotalSize()).thenReturn(body.length()); + when(response.getTotalSize()).thenReturn((long) body.length()); return response; } @@ -471,7 +471,7 @@ public void testOnResponseCloseNetworkStreamWithNormalResponse() throws Exceptio ParseHttpResponse response = mock(ParseHttpResponse.class); when(response.getStatusCode()).thenReturn(statusCode); when(response.getContent()).thenReturn(mockResponseStream); - when(response.getTotalSize()).thenReturn(bodyStr.length()); + when(response.getTotalSize()).thenReturn((long) bodyStr.length()); ParseRESTCommand command = new ParseRESTCommand.Builder().build(); JSONObject json = ParseTaskUtils.wait(command.onResponseAsync(response, null)); diff --git a/Parse/src/test/java/com/parse/ParseRequestTest.java b/Parse/src/test/java/com/parse/ParseRequestTest.java index 920418cac..f16365bcc 100644 --- a/Parse/src/test/java/com/parse/ParseRequestTest.java +++ b/Parse/src/test/java/com/parse/ParseRequestTest.java @@ -73,7 +73,7 @@ public void testDownloadProgress() throws Exception { ParseHttpResponse mockResponse = mock(ParseHttpResponse.class); when(mockResponse.getStatusCode()).thenReturn(200); when(mockResponse.getContent()).thenReturn(new ByteArrayInputStream(data)); - when(mockResponse.getTotalSize()).thenReturn(data.length); + when(mockResponse.getTotalSize()).thenReturn((long) data.length); ParseHttpClient mockHttpClient = mock(ParseHttpClient.class); when(mockHttpClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse);