Skip to content

Commit 00e0b3a

Browse files
committed
Download ParseFile content to file instead of memory
1 parent ed4eb6a commit 00e0b3a

File tree

5 files changed

+59
-24
lines changed

5 files changed

+59
-24
lines changed

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,28 @@
88
*/
99
package com.parse;
1010

11-
import java.io.ByteArrayOutputStream;
11+
import java.io.File;
12+
import java.io.FileOutputStream;
1213
import java.io.IOException;
1314
import java.io.InputStream;
14-
import java.util.concurrent.Callable;
1515

1616
import bolts.Task;
1717

1818
/**
1919
* Request returns a byte array of the response and provides a callback the progress of the data
2020
* read from the network.
2121
*/
22-
/** package */ class ParseAWSRequest extends ParseRequest<byte[]> {
22+
/** package */ class ParseAWSRequest extends ParseRequest<Void> {
2323

24-
public ParseAWSRequest(Method method, String url) {
24+
private File tempFile;
25+
26+
public ParseAWSRequest(Method method, String url, File tempFile) {
2527
super(method, url);
28+
this.tempFile = tempFile;
2629
}
2730

2831
@Override
29-
protected Task<byte[]> onResponseAsync(ParseHttpResponse response,
32+
protected Task<Void> onResponseAsync(ParseHttpResponse response,
3033
final ProgressCallback downloadProgressCallback) {
3134
int statusCode = response.getStatusCode();
3235
if (statusCode >= 200 && statusCode < 300 || statusCode == 304) {
@@ -46,20 +49,20 @@ protected Task<byte[]> onResponseAsync(ParseHttpResponse response,
4649
InputStream responseStream = null;
4750
try {
4851
responseStream = response.getContent();
49-
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
52+
FileOutputStream tempFileStream = new FileOutputStream(tempFile);
5053

5154
int nRead;
5255
byte[] data = new byte[32 << 10]; // 32KB
5356

5457
while ((nRead = responseStream.read(data, 0, data.length)) != -1) {
55-
buffer.write(data, 0, nRead);
58+
tempFileStream.write(data, 0, nRead);
5659
downloadedSize += nRead;
5760
if (downloadProgressCallback != null && totalSize != -1) {
5861
int progressToReport = Math.round((float) downloadedSize / (float) totalSize * 100.0f);
5962
downloadProgressCallback.done(progressToReport);
6063
}
6164
}
62-
return Task.forResult(buffer.toByteArray());
65+
return Task.forResult(null);
6366
} catch (IOException e) {
6467
return Task.forError(e);
6568
} finally {

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ public File getCacheFile(ParseFile.State state) {
5656
return new File(cachePath, state.name());
5757
}
5858

59+
/* package for tests */ File getTempFile(ParseFile.State state) {
60+
if (state.url() == null) {
61+
return null;
62+
}
63+
return new File(cachePath, ParseDigestUtils.md5(state.url()));
64+
}
65+
5966
public boolean isDataAvailable(ParseFile.State state) {
6067
return getCacheFile(state).exists();
6168
}
@@ -125,44 +132,53 @@ public Task<File> fetchAsync(
125132
if (cancellationToken != null && cancellationToken.isCancelled()) {
126133
return Task.cancelled();
127134
}
128-
final File file = getCacheFile(state);
135+
final File cacheFile = getCacheFile(state);
129136
return Task.call(new Callable<Boolean>() {
130137
@Override
131138
public Boolean call() throws Exception {
132-
return file.exists();
139+
return cacheFile.exists();
133140
}
134141
}, Task.BACKGROUND_EXECUTOR).continueWithTask(new Continuation<Boolean, Task<File>>() {
135142
@Override
136143
public Task<File> then(Task<Boolean> task) throws Exception {
137144
boolean result = task.getResult();
138145
if (result) {
139-
return Task.forResult(file);
146+
return Task.forResult(cacheFile);
140147
}
141148
if (cancellationToken != null && cancellationToken.isCancelled()) {
142149
return Task.cancelled();
143150
}
144151

152+
// Generate the temp file path for caching ParseFile content based on ParseFile's url
153+
// The reason we do not write to the cacheFile directly is because there is no way we can
154+
// verify if a cacheFile is complete or not. If download is interrupted in the middle, next
155+
// time when we download the ParseFile, since cacheFile has already existed, we will return
156+
// this incomplete cacheFile
157+
final File tempFile = getTempFile(state);
158+
145159
// network
146-
final ParseAWSRequest request = new ParseAWSRequest(ParseRequest.Method.GET, state.url());
160+
final ParseAWSRequest request =
161+
new ParseAWSRequest(ParseRequest.Method.GET, state.url(), tempFile);
147162

148163
// TODO(grantland): Stream response directly to file t5042019
149164
return request.executeAsync(
150165
awsClient(),
151166
null,
152167
downloadProgressCallback,
153-
cancellationToken).onSuccess(new Continuation<byte[], File>() {
168+
cancellationToken).continueWithTask(new Continuation<Void, Task<File>>() {
154169
@Override
155-
public File then(Task<byte[]> task) throws Exception {
170+
public Task<File> then(Task<Void> task) throws Exception {
156171
// If the top-level task was cancelled, don't actually set the data -- just move on.
157172
if (cancellationToken != null && cancellationToken.isCancelled()) {
158173
throw new CancellationException();
159174
}
160-
161-
byte[] data = task.getResult();
162-
if (data != null) {
163-
ParseFileUtils.writeByteArrayToFile(file, data);
175+
if (task.isFaulted()) {
176+
ParseFileUtils.deleteQuietly(tempFile);
177+
return task.cast();
164178
}
165-
return file;
179+
180+
ParseFileUtils.moveFile(tempFile, cacheFile);
181+
return Task.forResult(cacheFile);
166182
}
167183
});
168184
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212

1313
import org.junit.Rule;
1414
import org.junit.rules.ExpectedException;
15+
import org.junit.rules.TemporaryFolder;
1516

1617
import java.io.ByteArrayInputStream;
18+
import java.io.File;
1719
import java.io.InputStream;
1820

1921
import bolts.Task;
@@ -44,8 +46,9 @@ public void test4XXThrowsException() throws Exception {
4446
ParseHttpClient mockHttpClient = mock(ParseHttpClient.class);
4547
when(mockHttpClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse);
4648

47-
ParseAWSRequest request = new ParseAWSRequest(ParseRequest.Method.GET, "http://parse.com");
48-
Task<byte[]> task = request.executeAsync(mockHttpClient);
49+
ParseAWSRequest request =
50+
new ParseAWSRequest(ParseRequest.Method.GET, "http://parse.com", null);
51+
Task<Void> task = request.executeAsync(mockHttpClient);
4952
task.waitForCompletion();
5053

5154
assertTrue(task.isFaulted());

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static org.junit.Assert.assertThat;
3131
import static org.junit.Assert.assertTrue;
3232
import static org.mockito.Matchers.any;
33+
import static org.mockito.Matchers.contains;
3334
import static org.mockito.Mockito.mock;
3435
import static org.mockito.Mockito.times;
3536
import static org.mockito.Mockito.verify;
@@ -238,13 +239,15 @@ public void testFetchAsyncSuccess() throws Exception {
238239

239240
ParseFile.State state = new ParseFile.State.Builder()
240241
.name("file_name")
242+
.url("url")
241243
.build();
242244
Task<File> task = controller.fetchAsync(state, null, null, null);
243245
File result = ParseTaskUtils.wait(task);
244246

245247
verify(awsClient, times(1)).execute(any(ParseHttpRequest.class));
246248
assertTrue(result.exists());
247249
assertEquals("hello", ParseFileUtils.readFileToString(result, "UTF-8"));
250+
assertFalse(controller.getTempFile(state).exists());
248251
}
249252

250253
@Test
@@ -258,7 +261,9 @@ public void testFetchAsyncFailure() throws Exception {
258261
File root = temporaryFolder.getRoot();
259262
ParseFileController controller = new ParseFileController(null, root).awsClient(awsClient);
260263

264+
// We need to set url to make getTempFile() work and check it
261265
ParseFile.State state = new ParseFile.State.Builder()
266+
.url("test")
262267
.build();
263268
Task<File> task = controller.fetchAsync(state, null, null, null);
264269
task.waitForCompletion();
@@ -270,6 +275,7 @@ public void testFetchAsyncFailure() throws Exception {
270275
assertThat(error, instanceOf(ParseException.class));
271276
assertEquals(ParseException.CONNECTION_FAILED, ((ParseException) error).getCode());
272277
assertEquals(0, root.listFiles().length);
278+
assertFalse(controller.getTempFile(state).exists());
273279
}
274280

275281
//endregion

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
import org.junit.AfterClass;
1313
import org.junit.Before;
1414
import org.junit.BeforeClass;
15+
import org.junit.Rule;
1516
import org.junit.Test;
17+
import org.junit.rules.TemporaryFolder;
1618

1719
import java.io.ByteArrayInputStream;
20+
import java.io.File;
1821
import java.io.IOException;
1922
import java.util.LinkedList;
2023
import java.util.List;
@@ -34,6 +37,9 @@ public class ParseRequestTest {
3437

3538
private static byte[] data;
3639

40+
@Rule
41+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
42+
3743
@BeforeClass
3844
public static void setUpClass() {
3945
char[] chars = new char[64 << 10]; // 64KB
@@ -78,13 +84,14 @@ public void testDownloadProgress() throws Exception {
7884
ParseHttpClient mockHttpClient = mock(ParseHttpClient.class);
7985
when(mockHttpClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse);
8086

81-
ParseAWSRequest request = new ParseAWSRequest(ParseRequest.Method.GET, "localhost");
87+
File tempFile = temporaryFolder.newFile("test");
88+
ParseAWSRequest request = new ParseAWSRequest(ParseRequest.Method.GET, "localhost", tempFile);
8289
TestProgressCallback downloadProgressCallback = new TestProgressCallback();
83-
Task<byte[]> task = request.executeAsync(mockHttpClient, null, downloadProgressCallback);
90+
Task<Void> task = request.executeAsync(mockHttpClient, null, downloadProgressCallback);
8491

8592
task.waitForCompletion();
8693
assertFalse("Download failed: " + task.getError(), task.isFaulted());
87-
assertEquals(data.length, task.getResult().length);
94+
assertEquals(data.length, ParseFileUtils.readFileToByteArray(tempFile).length);
8895

8996
assertProgressCompletedSuccessfully(downloadProgressCallback);
9097
}

0 commit comments

Comments
 (0)