use of com.parse.boltsinternal.TaskCompletionSource in project Parse-SDK-Android by ParsePlatform.
the class OfflineStore method getOrCreateUUIDAsync.
/**
* Gets the UUID for the given object, if it has one. Otherwise, creates a new UUID for the
* object and adds a new row to the database for the object with no data.
*/
private Task<String> getOrCreateUUIDAsync(final ParseObject object, ParseSQLiteDatabase db) {
final String newUUID = UUID.randomUUID().toString();
final TaskCompletionSource<String> tcs = new TaskCompletionSource<>();
synchronized (lock) {
Task<String> uuidTask = objectToUuidMap.get(object);
if (uuidTask != null) {
return uuidTask;
}
// The object doesn't have a UUID yet, so we're gonna have to make one.
objectToUuidMap.put(object, tcs.getTask());
uuidToObjectMap.put(newUUID, object);
fetchedObjects.put(object, tcs.getTask().onSuccess(task -> object));
}
/*
* We need to put a placeholder row in the database so that later on, the save can just be an
* update. This could be a pointer to an object that itself never gets saved offline, in which
* case the consumer will just have to deal with that.
*/
ContentValues values = new ContentValues();
values.put(OfflineSQLiteOpenHelper.KEY_UUID, newUUID);
values.put(OfflineSQLiteOpenHelper.KEY_CLASS_NAME, object.getClassName());
db.insertOrThrowAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, values).continueWith((Continuation<Void, Void>) task -> {
tcs.setResult(newUUID);
return null;
});
return tcs.getTask();
}
use of com.parse.boltsinternal.TaskCompletionSource in project Parse-SDK-Android by ParsePlatform.
the class ParseCommandCache method enqueueEventuallyAsync.
/**
* Attempts to run the given command and any pending commands. Adds the command to the pending
* set if it can't be run yet.
*
* @param command - The command to run.
* @param preferOldest - When the disk is full, if preferOldest, drop new commands. Otherwise,
* the oldest commands will be deleted to make room.
* @param object - See runEventually.
*/
private Task<JSONObject> enqueueEventuallyAsync(ParseRESTCommand command, boolean preferOldest, ParseObject object) {
Parse.requirePermission(Manifest.permission.ACCESS_NETWORK_STATE);
TaskCompletionSource<JSONObject> tcs = new TaskCompletionSource<>();
byte[] json;
try {
// objectId after the save completes.
if (object != null && object.getObjectId() == null) {
command.setLocalId(object.getOrCreateLocalId());
}
JSONObject jsonObject = command.toJSONObject();
json = jsonObject.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
if (Parse.LOG_LEVEL_WARNING >= Parse.getLogLevel()) {
log.log(Level.WARNING, "UTF-8 isn't supported. This shouldn't happen.", e);
}
notifyTestHelper(TestHelper.COMMAND_NOT_ENQUEUED);
return Task.forResult(null);
}
// even bother trying.
if (json.length > maxCacheSizeBytes) {
if (Parse.LOG_LEVEL_WARNING >= Parse.getLogLevel()) {
log.warning("Unable to save command for later because it's too big.");
}
notifyTestHelper(TestHelper.COMMAND_NOT_ENQUEUED);
return Task.forResult(null);
}
synchronized (lock) {
try {
// Is there enough free storage space?
String[] fileNames = cachePath.list();
if (fileNames != null) {
Arrays.sort(fileNames);
int size = 0;
for (String fileName : fileNames) {
File file = new File(cachePath, fileName);
// Should be safe to convert long to int, because we don't allow
// files larger than 2GB.
size += (int) file.length();
}
size += json.length;
if (size > maxCacheSizeBytes) {
if (preferOldest) {
if (Parse.LOG_LEVEL_WARNING >= Parse.getLogLevel()) {
log.warning("Unable to save command for later because storage is full.");
}
return Task.forResult(null);
} else {
if (Parse.LOG_LEVEL_WARNING >= Parse.getLogLevel()) {
log.warning("Deleting old commands to make room in command cache.");
}
int indexToDelete = 0;
while (size > maxCacheSizeBytes && indexToDelete < fileNames.length) {
File file = new File(cachePath, fileNames[indexToDelete++]);
size -= (int) file.length();
removeFile(file);
}
}
}
}
// Get the current time to store in the filename, so that we process them in order.
String prefix1 = Long.toHexString(System.currentTimeMillis());
if (prefix1.length() < 16) {
char[] zeroes = new char[16 - prefix1.length()];
Arrays.fill(zeroes, '0');
prefix1 = new String(zeroes) + prefix1;
}
// Then add another incrementing number in case we enqueue items faster than the
// system's
// time granularity.
String prefix2 = Integer.toHexString(filenameCounter++);
if (prefix2.length() < 8) {
char[] zeroes = new char[8 - prefix2.length()];
Arrays.fill(zeroes, '0');
prefix2 = new String(zeroes) + prefix2;
}
String prefix = "CachedCommand_" + prefix1 + "_" + prefix2 + "_";
// Get a unique filename to store this command in.
File path = File.createTempFile(prefix, "", cachePath);
// Write the command to that file.
pendingTasks.put(path, tcs);
command.retainLocalIds();
ParseFileUtils.writeByteArrayToFile(path, json);
notifyTestHelper(TestHelper.COMMAND_ENQUEUED);
unprocessedCommandsExist = true;
} catch (IOException e) {
if (Parse.LOG_LEVEL_WARNING >= Parse.getLogLevel()) {
log.log(Level.WARNING, "Unable to save command for later.", e);
}
} finally {
lock.notifyAll();
}
}
return tcs.getTask();
}
use of com.parse.boltsinternal.TaskCompletionSource in project Parse-SDK-Android by ParsePlatform.
the class ParseCommandCache method maybeRunAllCommandsNow.
/**
* Attempts to run every command in the disk queue in order, synchronously. If there is no
* network connection, returns immediately without doing anything. If there is supposedly a
* connection, but parse can't be reached, waits timeoutRetryWaitSeconds before retrying up to
* retriesRemaining times. Blocks until either there's a connection, or the retries are
* exhausted. If any command fails, just deletes it and moves on to the next one.
*/
private void maybeRunAllCommandsNow(int retriesRemaining) {
synchronized (lock) {
unprocessedCommandsExist = false;
if (!isConnected()) {
// There's no way to do work when there's no network connection.
return;
}
String[] fileNames = cachePath.list();
if (fileNames == null || fileNames.length == 0) {
return;
}
Arrays.sort(fileNames);
for (String fileName : fileNames) {
final File file = new File(cachePath, fileName);
// Read one command from the cache.
JSONObject json;
try {
json = ParseFileUtils.readFileToJSONObject(file);
} catch (FileNotFoundException e) {
// This shouldn't really be possible.
if (Parse.LOG_LEVEL_ERROR >= Parse.getLogLevel()) {
log.log(Level.SEVERE, "File disappeared from cache while being read.", e);
}
continue;
} catch (IOException e) {
if (Parse.LOG_LEVEL_ERROR >= Parse.getLogLevel()) {
log.log(Level.SEVERE, "Unable to read contents of file in cache.", e);
}
removeFile(file);
continue;
} catch (JSONException e) {
if (Parse.LOG_LEVEL_ERROR >= Parse.getLogLevel()) {
log.log(Level.SEVERE, "Error parsing JSON found in cache.", e);
}
removeFile(file);
continue;
}
// Convert the command from a string.
final ParseRESTCommand command;
final TaskCompletionSource<JSONObject> tcs = pendingTasks.containsKey(file) ? pendingTasks.get(file) : null;
try {
command = commandFromJSON(json);
} catch (JSONException e) {
if (Parse.LOG_LEVEL_ERROR >= Parse.getLogLevel()) {
log.log(Level.SEVERE, "Unable to create ParseCommand from JSON.", e);
}
removeFile(file);
continue;
}
try {
Task<JSONObject> commandTask;
if (command == null) {
commandTask = Task.forResult(null);
if (tcs != null) {
tcs.setResult(null);
}
notifyTestHelper(TestHelper.COMMAND_OLD_FORMAT_DISCARDED);
} else {
commandTask = command.executeAsync(httpClient).continueWithTask(task -> {
String localId = command.getLocalId();
Exception error = task.getError();
if (error != null) {
if (!(error instanceof ParseException) || ((ParseException) error).getCode() != ParseException.CONNECTION_FAILED) {
if (tcs != null) {
tcs.setError(error);
}
}
return task;
}
JSONObject json1 = task.getResult();
if (tcs != null) {
tcs.setResult(json1);
} else if (localId != null) {
// If this command created a new objectId,
// add it to the map.
String objectId = json1.optString("objectId", null);
if (objectId != null) {
ParseCorePlugins.getInstance().getLocalIdManager().setObjectId(localId, objectId);
}
}
return task;
});
}
waitForTaskWithoutLock(commandTask);
if (tcs != null) {
waitForTaskWithoutLock(tcs.getTask());
}
// The command succeeded. Remove it from the cache.
removeFile(file);
notifyTestHelper(TestHelper.COMMAND_SUCCESSFUL);
} catch (ParseException e) {
if (e.getCode() == ParseException.CONNECTION_FAILED) {
if (retriesRemaining > 0) {
// anything else.
if (Parse.LOG_LEVEL_INFO >= Parse.getLogLevel()) {
log.info("Network timeout in command cache. Waiting for " + timeoutRetryWaitSeconds + " seconds and then retrying " + retriesRemaining + " times.");
}
long currentTime = System.currentTimeMillis();
long waitUntil = currentTime + (long) (timeoutRetryWaitSeconds * 1000);
while (currentTime < waitUntil) {
// or should stop, just quit.
if (!isConnected() || shouldStop) {
if (Parse.LOG_LEVEL_INFO >= Parse.getLogLevel()) {
log.info("Aborting wait because runEventually thread should stop.");
}
return;
}
try {
lock.wait(waitUntil - currentTime);
} catch (InterruptedException ie) {
shouldStop = true;
}
currentTime = System.currentTimeMillis();
if (currentTime < (waitUntil - (long) (timeoutRetryWaitSeconds * 1000))) {
// This situation should be impossible, so it must mean the
// clock changed.
currentTime = (waitUntil - (long) (timeoutRetryWaitSeconds * 1000));
}
}
maybeRunAllCommandsNow(retriesRemaining - 1);
} else {
setConnected(false);
notifyTestHelper(TestHelper.NETWORK_DOWN);
}
} else {
if (Parse.LOG_LEVEL_ERROR >= Parse.getLogLevel()) {
log.log(Level.SEVERE, "Failed to run command.", e);
}
// Delete the command from the cache, even though it failed.
// Otherwise, we'll just keep trying it forever.
removeFile(file);
notifyTestHelper(TestHelper.COMMAND_FAILED, e);
}
}
}
}
}
use of com.parse.boltsinternal.TaskCompletionSource in project Parse-SDK-Android by ParsePlatform.
the class ParseFile method getDataInBackground.
/**
* Asynchronously gets the data from cache if available or fetches its content from the network.
* A {@code ProgressCallback} will be called periodically with progress updates.
*
* @param progressCallback A {@code ProgressCallback} that is called periodically with progress
* updates.
* @return A Task that is resolved when the data has been fetched.
*/
public Task<byte[]> getDataInBackground(final ProgressCallback progressCallback) {
final TaskCompletionSource<Void> cts = new TaskCompletionSource<>();
currentTasks.add(cts);
return taskQueue.enqueue(toAwait -> fetchInBackground(progressCallback, toAwait, cts.getTask()).onSuccess(task -> {
File file = task.getResult();
try {
return ParseFileUtils.readFileToByteArray(file);
} catch (IOException e) {
// do nothing
}
return null;
})).continueWithTask(task -> {
// release
cts.trySetResult(null);
currentTasks.remove(cts);
return task;
});
}
Aggregations