Skip to content
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

feat: Implementing encrypted local storage for user sessions with tests #1211

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Prev Previous commit
Next Next commit
fix: ci failing on PR #1179
  • Loading branch information
rommansabbir committed Aug 23, 2022
commit ec87160a0836137b1392996141d46cc195395e3b
144 changes: 72 additions & 72 deletions parse/src/main/java/com/parse/ParseFileController.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class ParseFileController {
private final File cachePath;
private final List<String> currentlyDownloadedFilesNames = new ArrayList<>();


private ParseHttpClient fileClient;

public ParseFileController(ParseHttpClient restClient, File cachePath) {
Expand Down Expand Up @@ -172,79 +171,80 @@ public Task<File> fetchAsync(
if (cancellationToken != null && cancellationToken.isCancelled()) {
return Task.cancelled();
}
return Task.call(() -> {
final File cacheFile = getCacheFile(state);
return Task.call(
() -> {
final File cacheFile = getCacheFile(state);

synchronized (lock) {
if (currentlyDownloadedFilesNames.contains(state.name())) {
while (currentlyDownloadedFilesNames.contains(state.name())) {
lock.wait();
}
}

synchronized (lock) {
if (currentlyDownloadedFilesNames.contains(state.name())) {
while (currentlyDownloadedFilesNames.contains(state.name())) {
lock.wait();
if (cacheFile.exists()) {
return cacheFile;
} else {
currentlyDownloadedFilesNames.add(state.name());
}
}
}

if (cacheFile.exists()) {
return cacheFile;
} else {
currentlyDownloadedFilesNames.add(state.name());
}
}

try {
if (cancellationToken != null && cancellationToken.isCancelled()) {
throw new CancellationException();
}

// Generate the temp file path for caching ParseFile content based on
// ParseFile's url
// The reason we do not write to the cacheFile directly is because there
// is no way we can
// verify if a cacheFile is complete or not. If download is interrupted
// in the middle, next
// time when we download the ParseFile, since cacheFile has already
// existed, we will return
// this incomplete cacheFile
final File tempFile = getTempFile(state);

// network
final ParseFileRequest request =
new ParseFileRequest(
ParseHttpRequest.Method.GET, state.url(), tempFile);

// We do not need to delete the temp file since we always try to
// overwrite it
Task<Void> downloadTask = request.executeAsync(
fileClient(),
null,
downloadProgressCallback,
cancellationToken
);
ParseTaskUtils.wait(downloadTask);

// If the top-level task was cancelled, don't
// actually set the data -- just move on.
if (cancellationToken != null && cancellationToken.isCancelled()) {
throw new CancellationException();
}
if (downloadTask.isFaulted()) {
ParseFileUtils.deleteQuietly(tempFile);
throw new RuntimeException(downloadTask.getError());
}

// Since we give the cacheFile pointer to
// developers, it is not safe to guarantee
// cacheFile always does not exist here, so it is
// better to delete it manually,
// otherwise moveFile may throw an exception.
ParseFileUtils.deleteQuietly(cacheFile);
ParseFileUtils.moveFile(tempFile, cacheFile);
return cacheFile;
} finally {
synchronized (lock) {
currentlyDownloadedFilesNames.remove(state.name());
lock.notifyAll();
}
}

}, ParseExecutors.io());
try {
if (cancellationToken != null && cancellationToken.isCancelled()) {
throw new CancellationException();
}

// Generate the temp file path for caching ParseFile content based on
// ParseFile's url
// The reason we do not write to the cacheFile directly is because there
// is no way we can
// verify if a cacheFile is complete or not. If download is interrupted
// in the middle, next
// time when we download the ParseFile, since cacheFile has already
// existed, we will return
// this incomplete cacheFile
final File tempFile = getTempFile(state);

// network
final ParseFileRequest request =
new ParseFileRequest(
ParseHttpRequest.Method.GET, state.url(), tempFile);

// We do not need to delete the temp file since we always try to
// overwrite it
Task<Void> downloadTask =
request.executeAsync(
fileClient(),
null,
downloadProgressCallback,
cancellationToken);
ParseTaskUtils.wait(downloadTask);

// If the top-level task was cancelled, don't
// actually set the data -- just move on.
if (cancellationToken != null && cancellationToken.isCancelled()) {
throw new CancellationException();
}
if (downloadTask.isFaulted()) {
ParseFileUtils.deleteQuietly(tempFile);
throw new RuntimeException(downloadTask.getError());
}

// Since we give the cacheFile pointer to
// developers, it is not safe to guarantee
// cacheFile always does not exist here, so it is
// better to delete it manually,
// otherwise moveFile may throw an exception.
ParseFileUtils.deleteQuietly(cacheFile);
ParseFileUtils.moveFile(tempFile, cacheFile);
return cacheFile;
} finally {
synchronized (lock) {
currentlyDownloadedFilesNames.remove(state.name());
lock.notifyAll();
}
}
},
ParseExecutors.io());
}
}
60 changes: 33 additions & 27 deletions parse/src/test/java/com/parse/ParseFileControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
Expand Down Expand Up @@ -344,16 +343,15 @@ public void testFetchAsyncFailure() throws Exception {
assertFalse(controller.getTempFile(state).exists());
}


@Test
public void testFetchAsyncConcurrentCallsSuccess() throws Exception {
byte[] data = "hello".getBytes();
ParseHttpResponse mockResponse =
new ParseHttpResponse.Builder()
.setStatusCode(200)
.setTotalSize((long) data.length)
.setContent(new ByteArrayInputStream(data))
.build();
new ParseHttpResponse.Builder()
.setStatusCode(200)
.setTotalSize((long) data.length)
.setContent(new ByteArrayInputStream(data))
.build();

ParseHttpClient fileClient = mock(ParseHttpClient.class);
when(fileClient.execute(any(ParseHttpRequest.class))).thenReturn(mockResponse);
Expand All @@ -366,31 +364,39 @@ public void testFetchAsyncConcurrentCallsSuccess() throws Exception {
AtomicReference<File> file1Ref = new AtomicReference<>();
AtomicReference<File> file2Ref = new AtomicReference<>();

new Thread(() -> {
try {
file1Ref.set(ParseTaskUtils.wait(controller.fetchAsync(state, null, null, null)));
} catch (ParseException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
}).start();

new Thread(() -> {
try {
file2Ref.set(ParseTaskUtils.wait(controller.fetchAsync(state, null, null, null)));
} catch (ParseException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
}).start();
new Thread(
() -> {
try {
file1Ref.set(
ParseTaskUtils.wait(
controller.fetchAsync(state, null, null, null)));
} catch (ParseException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
})
.start();

new Thread(
() -> {
try {
file2Ref.set(
ParseTaskUtils.wait(
controller.fetchAsync(state, null, null, null)));
} catch (ParseException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
})
.start();

countDownLatch.await();

File result1 = file1Ref.get();
File result2 = file2Ref.get();

assertTrue(result1.exists());
assertEquals("hello", ParseFileUtils.readFileToString(result1, "UTF-8"));
assertTrue(result2.exists());
Expand Down