Search in sources :

Example 1 with Capture

use of com.parse.boltsinternal.Capture in project Parse-SDK-Android by ParsePlatform.

the class OfflineStore method fetchLocallyAsync.

/**
 * Gets the data for the given object from the offline database. Returns a task that will be
 * completed if data for the object was available. If the object is not in the cache, the task
 * will be faulted, with a CACHE_MISS error.
 *
 * @param object The object to fetch.
 * @param db A database connection to use.
 */
/* package for OfflineQueryLogic */
<T extends ParseObject> Task<T> fetchLocallyAsync(final T object, final ParseSQLiteDatabase db) {
    final TaskCompletionSource<T> tcs = new TaskCompletionSource<>();
    Task<String> uuidTask;
    synchronized (lock) {
        if (fetchedObjects.containsKey(object)) {
            // noinspection unchecked
            return (Task<T>) fetchedObjects.get(object);
        }
        /*
             * Put a placeholder so that anyone else who attempts to fetch this object will just wait for
             * this call to finish doing it.
             */
        // noinspection unchecked
        fetchedObjects.put(object, (Task<ParseObject>) tcs.getTask());
        uuidTask = objectToUuidMap.get(object);
    }
    String className = object.getClassName();
    String objectId = object.getObjectId();
    /*
         * If this gets set, then it will contain data from the offline store that needs to be merged
         * into the existing object in memory.
         */
    Task<String> jsonStringTask = Task.forResult(null);
    if (objectId == null) {
        // This Object has never been saved to Parse.
        if (uuidTask == null) {
        /*
                 * This object was not pulled from the data store or previously saved to it, so there's
                 * nothing that can be fetched from it. This isn't an error, because it's really convenient
                 * to try to fetch objects from the offline store just to make sure they are up-to-date, and
                 * we shouldn't force developers to specially handle this case.
                 */
        } else {
            /*
                 * This object is a new ParseObject that is known to the data store, but hasn't been
                 * fetched. The only way this could happen is if the object had previously been stored in
                 * the offline store, then the object was removed from memory (maybe by rebooting), and then
                 * a object with a pointer to it was fetched, so we only created the pointer. We need to
                 * pull the data out of the database using the UUID.
                 */
            final String[] select = { OfflineSQLiteOpenHelper.KEY_JSON };
            final String where = OfflineSQLiteOpenHelper.KEY_UUID + " = ?";
            final Capture<String> uuid = new Capture<>();
            jsonStringTask = uuidTask.onSuccessTask(task -> {
                uuid.set(task.getResult());
                String[] args = { uuid.get() };
                return db.queryAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, select, where, args);
            }).onSuccess(task -> {
                Cursor cursor = task.getResult();
                cursor.moveToFirst();
                if (cursor.isAfterLast()) {
                    cursor.close();
                    throw new IllegalStateException("Attempted to find non-existent uuid " + uuid.get());
                }
                String json = cursor.getString(0);
                cursor.close();
                return json;
            });
        }
    } else {
        if (uuidTask != null) {
            /*
                 * This object is an existing ParseObject, and we must've already pulled its data out of the
                 * offline store, or else we wouldn't know its UUID. This should never happen.
                 */
            tcs.setError(new IllegalStateException("This object must have already been " + "fetched from the local datastore, but isn't marked as fetched."));
            synchronized (lock) {
                // Forget we even tried to fetch this object, so that retries will actually...
                // retry.
                fetchedObjects.remove(object);
            }
            return tcs.getTask();
        }
        /*
             * We've got a pointer to an existing ParseObject, but we've never pulled its data out of the
             * offline store. Since fetching from the server forces a fetch from the offline store, that
             * means this is a pointer. We need to try to find any existing entry for this object in the
             * database.
             */
        String[] select = { OfflineSQLiteOpenHelper.KEY_JSON, OfflineSQLiteOpenHelper.KEY_UUID };
        String where = String.format("%s = ? AND %s = ?", OfflineSQLiteOpenHelper.KEY_CLASS_NAME, OfflineSQLiteOpenHelper.KEY_OBJECT_ID);
        String[] args = { className, objectId };
        jsonStringTask = db.queryAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, select, where, args).onSuccess(task -> {
            Cursor cursor = task.getResult();
            cursor.moveToFirst();
            if (cursor.isAfterLast()) {
                /*
                                             * This is a pointer that came from Parse that references an object that has
                                             * never been saved in the offline store before. This just means there's no data
                                             * in the store that needs to be merged into the object.
                                             */
                cursor.close();
                throw new ParseException(ParseException.CACHE_MISS, "This object is not available in the offline cache.");
            }
            // we should fetch its data and record its UUID for future
            // reference.
            String jsonString = cursor.getString(0);
            String newUUID = cursor.getString(1);
            cursor.close();
            synchronized (lock) {
                /*
                                             * It's okay to put this object into the uuid map. No one will try to fetch
                                             * it, because it's already in the fetchedObjects map. And no one will try to
                                             * save to it without fetching it first, so everything should be just fine.
                                             */
                objectToUuidMap.put(object, Task.forResult(newUUID));
                uuidToObjectMap.put(newUUID, object);
            }
            return jsonString;
        });
    }
    return jsonStringTask.onSuccessTask((Continuation<String, Task<Void>>) task -> {
        String jsonString = task.getResult();
        if (jsonString == null) {
            return Task.forError(new ParseException(ParseException.CACHE_MISS, "Attempted to fetch an object offline which was never saved to the offline cache."));
        }
        final JSONObject json;
        try {
            json = new JSONObject(jsonString);
        } catch (JSONException e) {
            return Task.forError(e);
        }
        final Map<String, Task<ParseObject>> offlineObjects = new HashMap<>();
        (new ParseTraverser() {

            @Override
            protected boolean visit(Object object1) {
                if (object1 instanceof JSONObject && ((JSONObject) object1).optString("__type").equals("OfflineObject")) {
                    String uuid = ((JSONObject) object1).optString("uuid");
                    offlineObjects.put(uuid, getPointerAsync(uuid, db));
                }
                return true;
            }
        }).setTraverseParseObjects(false).setYieldRoot(false).traverse(json);
        return Task.whenAll(offlineObjects.values()).onSuccess(task1 -> {
            object.mergeREST(object.getState(), json, new OfflineDecoder(offlineObjects));
            return null;
        });
    }).continueWithTask(task -> {
        if (task.isCancelled()) {
            tcs.setCancelled();
        } else if (task.isFaulted()) {
            tcs.setError(task.getError());
        } else {
            tcs.setResult(object);
        }
        return tcs.getTask();
    });
}
Also used : Context(android.content.Context) Arrays(java.util.Arrays) ConstraintMatcher(com.parse.OfflineQueryLogic.ConstraintMatcher) Pair(android.util.Pair) TextUtils(android.text.TextUtils) HashMap(java.util.HashMap) UUID(java.util.UUID) ArrayList(java.util.ArrayList) SQLiteDatabase(android.database.sqlite.SQLiteDatabase) List(java.util.List) JSONException(org.json.JSONException) Capture(com.parse.boltsinternal.Capture) Continuation(com.parse.boltsinternal.Continuation) JSONObject(org.json.JSONObject) Map(java.util.Map) ContentValues(android.content.ContentValues) LinkedList(java.util.LinkedList) Task(com.parse.boltsinternal.Task) TaskCompletionSource(com.parse.boltsinternal.TaskCompletionSource) WeakHashMap(java.util.WeakHashMap) Cursor(android.database.Cursor) Task(com.parse.boltsinternal.Task) Continuation(com.parse.boltsinternal.Continuation) HashMap(java.util.HashMap) WeakHashMap(java.util.WeakHashMap) JSONException(org.json.JSONException) Cursor(android.database.Cursor) Capture(com.parse.boltsinternal.Capture) TaskCompletionSource(com.parse.boltsinternal.TaskCompletionSource) JSONObject(org.json.JSONObject) JSONObject(org.json.JSONObject)

Example 2 with Capture

use of com.parse.boltsinternal.Capture in project Parse-SDK-Android by ParsePlatform.

the class LocationNotifier method getCurrentLocationAsync.

/**
 * Asynchronously gets the current location of the device.
 *
 * <p>This will request location updates from the best provider that match the given criteria
 * and return the first location received.
 *
 * <p>You can customize the criteria to meet your specific needs. * For higher accuracy, you can
 * set {@link Criteria#setAccuracy(int)}, however result in longer times for a fix. * For better
 * battery efficiency and faster location fixes, you can set {@link
 * Criteria#setPowerRequirement(int)}, however, this will result in lower accuracy.
 *
 * @param context The context used to request location updates.
 * @param timeout The number of milliseconds to allow before timing out.
 * @param criteria The application criteria for selecting a location provider.
 * @see android.location.LocationManager#getBestProvider(android.location.Criteria, boolean)
 * @see android.location.LocationManager#requestLocationUpdates(String, long, float,
 *     android.location.LocationListener)
 */
/* package */
static Task<Location> getCurrentLocationAsync(Context context, long timeout, Criteria criteria) {
    final TaskCompletionSource<Location> tcs = new TaskCompletionSource<>();
    final Capture<ScheduledFuture<?>> timeoutFuture = new Capture<>();
    final LocationManager manager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    final LocationListener listener = new LocationListener() {

        @Override
        public void onLocationChanged(Location location) {
            if (location == null) {
                return;
            }
            timeoutFuture.get().cancel(true);
            tcs.trySetResult(location);
            manager.removeUpdates(this);
        }

        @Override
        public void onProviderDisabled(String provider) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }
    };
    timeoutFuture.set(ParseExecutors.scheduled().schedule(() -> {
        tcs.trySetError(new ParseException(ParseException.TIMEOUT, "Location fetch timed out."));
        manager.removeUpdates(listener);
    }, timeout, TimeUnit.MILLISECONDS));
    String provider = manager.getBestProvider(criteria, true);
    if (provider != null) {
        manager.requestLocationUpdates(provider, /* minTime */
        0, /* minDistance */
        0.0f, listener);
    }
    if (fakeLocation != null) {
        listener.onLocationChanged(fakeLocation);
    }
    return tcs.getTask();
}
Also used : LocationManager(android.location.LocationManager) TaskCompletionSource(com.parse.boltsinternal.TaskCompletionSource) Bundle(android.os.Bundle) LocationListener(android.location.LocationListener) ScheduledFuture(java.util.concurrent.ScheduledFuture) Capture(com.parse.boltsinternal.Capture) Location(android.location.Location)

Example 3 with Capture

use of com.parse.boltsinternal.Capture in project Parse-SDK-Android by ParsePlatform.

the class ParsePushTest method testSubscribeInBackgroundWithCallbackSuccess.

@Test
public void testSubscribeInBackgroundWithCallbackSuccess() throws Exception {
    final ParsePushChannelsController controller = mock(ParsePushChannelsController.class);
    when(controller.subscribeInBackground(anyString())).thenReturn(Task.<Void>forResult(null));
    ParseCorePlugins.getInstance().registerPushChannelsController(controller);
    ParsePush push = new ParsePush();
    final Semaphore done = new Semaphore(0);
    final Capture<Exception> exceptionCapture = new Capture<>();
    ParsePush.subscribeInBackground("test", e -> {
        exceptionCapture.set(e);
        done.release();
    });
    shadowMainLooper().idle();
    assertNull(exceptionCapture.get());
    assertTrue(done.tryAcquire(1, 10, TimeUnit.SECONDS));
    verify(controller, times(1)).subscribeInBackground("test");
}
Also used : Semaphore(java.util.concurrent.Semaphore) Capture(com.parse.boltsinternal.Capture) Test(org.junit.Test)

Example 4 with Capture

use of com.parse.boltsinternal.Capture in project Parse-SDK-Android by ParsePlatform.

the class ParsePushTest method testUnsubscribeInBackgroundWithCallbackSuccess.

@Test
public void testUnsubscribeInBackgroundWithCallbackSuccess() throws Exception {
    final ParsePushChannelsController controller = mock(ParsePushChannelsController.class);
    when(controller.unsubscribeInBackground(anyString())).thenReturn(Task.<Void>forResult(null));
    ParseCorePlugins.getInstance().registerPushChannelsController(controller);
    final Semaphore done = new Semaphore(0);
    final Capture<Exception> exceptionCapture = new Capture<>();
    ParsePush.unsubscribeInBackground("test", e -> {
        exceptionCapture.set(e);
        done.release();
    });
    shadowMainLooper().idle();
    assertNull(exceptionCapture.get());
    assertTrue(done.tryAcquire(1, 10, TimeUnit.SECONDS));
    verify(controller, times(1)).unsubscribeInBackground("test");
}
Also used : Semaphore(java.util.concurrent.Semaphore) Capture(com.parse.boltsinternal.Capture) Test(org.junit.Test)

Example 5 with Capture

use of com.parse.boltsinternal.Capture in project Parse-SDK-Android by ParsePlatform.

the class ParsePushTest method testSubscribeInBackgroundWithCallbackFail.

@Test
public void testSubscribeInBackgroundWithCallbackFail() throws Exception {
    ParsePushChannelsController controller = mock(ParsePushChannelsController.class);
    final ParseException exception = new ParseException(ParseException.OTHER_CAUSE, "error");
    when(controller.subscribeInBackground(anyString())).thenReturn(Task.<Void>forError(exception));
    ParseCorePlugins.getInstance().registerPushChannelsController(controller);
    ParsePush push = new ParsePush();
    final Semaphore done = new Semaphore(0);
    final Capture<Exception> exceptionCapture = new Capture<>();
    ParsePush.subscribeInBackground("test", e -> {
        exceptionCapture.set(e);
        done.release();
    });
    shadowMainLooper().idle();
    assertSame(exception, exceptionCapture.get());
    assertTrue(done.tryAcquire(1, 10, TimeUnit.SECONDS));
    verify(controller, times(1)).subscribeInBackground("test");
}
Also used : Semaphore(java.util.concurrent.Semaphore) Capture(com.parse.boltsinternal.Capture) Test(org.junit.Test)

Aggregations

Capture (com.parse.boltsinternal.Capture)14 Semaphore (java.util.concurrent.Semaphore)9 Test (org.junit.Test)9 JSONObject (org.json.JSONObject)8 ArrayList (java.util.ArrayList)6 Task (com.parse.boltsinternal.Task)5 Continuation (com.parse.boltsinternal.Continuation)4 TaskCompletionSource (com.parse.boltsinternal.TaskCompletionSource)4 HashMap (java.util.HashMap)4 Map (java.util.Map)4 Matchers.anyString (org.mockito.Matchers.anyString)4 ContentValues (android.content.ContentValues)3 Context (android.content.Context)3 Cursor (android.database.Cursor)3 SQLiteDatabase (android.database.sqlite.SQLiteDatabase)3 TextUtils (android.text.TextUtils)3 Pair (android.util.Pair)3 ConstraintMatcher (com.parse.OfflineQueryLogic.ConstraintMatcher)3 Arrays (java.util.Arrays)3 LinkedList (java.util.LinkedList)3