Search in sources :

Example 1 with Capture

use of bolts.Capture in project Parse-SDK-Android by ParsePlatform.

the class OfflineStore method findAsync.

/**
   * Runs a ParseQuery against the store's contents. May cause any instances of T to get fetched
   * from the offline database. TODO(klimt): Should the query consider objects that are in memory,
   * but not in the offline store?
   *
   * @param query The query.
   * @param user The user making the query.
   * @param pin (Optional) The pin we are querying across. If null, all pins.
   * @param isCount True if we are doing a count.
   * @param db The SQLiteDatabase.
   * @param <T> Subclass of ParseObject.
   * @return The objects that match the query's constraints.
   */
private <T extends ParseObject> Task<List<T>> findAsync(final ParseQuery.State<T> query, final ParseUser user, final ParsePin pin, final boolean isCount, final ParseSQLiteDatabase db) {
    /*
     * This is currently unused, but is here to allow future querying across objects that are in the
     * process of being deleted eventually.
     */
    final boolean includeIsDeletingEventually = false;
    final OfflineQueryLogic queryLogic = new OfflineQueryLogic(this);
    final List<T> results = new ArrayList<>();
    Task<Cursor> queryTask;
    if (pin == null) {
        String table = OfflineSQLiteOpenHelper.TABLE_OBJECTS;
        String[] select = { OfflineSQLiteOpenHelper.KEY_UUID };
        String where = OfflineSQLiteOpenHelper.KEY_CLASS_NAME + "=?";
        if (!includeIsDeletingEventually) {
            where += " AND " + OfflineSQLiteOpenHelper.KEY_IS_DELETING_EVENTUALLY + "=0";
        }
        String[] args = { query.className() };
        queryTask = db.queryAsync(table, select, where, args);
    } else {
        Task<String> uuidTask = objectToUuidMap.get(pin);
        if (uuidTask == null) {
            // Pin was never saved locally, therefore there won't be any results.
            return Task.forResult(results);
        }
        queryTask = uuidTask.onSuccessTask(new Continuation<String, Task<Cursor>>() {

            @Override
            public Task<Cursor> then(Task<String> task) throws Exception {
                String uuid = task.getResult();
                String table = OfflineSQLiteOpenHelper.TABLE_OBJECTS + " A " + " INNER JOIN " + OfflineSQLiteOpenHelper.TABLE_DEPENDENCIES + " B " + " ON A." + OfflineSQLiteOpenHelper.KEY_UUID + "=B." + OfflineSQLiteOpenHelper.KEY_UUID;
                String[] select = { "A." + OfflineSQLiteOpenHelper.KEY_UUID };
                String where = OfflineSQLiteOpenHelper.KEY_CLASS_NAME + "=?" + " AND " + OfflineSQLiteOpenHelper.KEY_KEY + "=?";
                if (!includeIsDeletingEventually) {
                    where += " AND " + OfflineSQLiteOpenHelper.KEY_IS_DELETING_EVENTUALLY + "=0";
                }
                String[] args = { query.className(), uuid };
                return db.queryAsync(table, select, where, args);
            }
        });
    }
    return queryTask.onSuccessTask(new Continuation<Cursor, Task<Void>>() {

        @Override
        public Task<Void> then(Task<Cursor> task) throws Exception {
            Cursor cursor = task.getResult();
            List<String> uuids = new ArrayList<>();
            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                uuids.add(cursor.getString(0));
            }
            cursor.close();
            // Find objects that match the where clause.
            final ConstraintMatcher<T> matcher = queryLogic.createMatcher(query, user);
            Task<Void> checkedAllObjects = Task.forResult(null);
            for (final String uuid : uuids) {
                final Capture<T> object = new Capture<>();
                checkedAllObjects = checkedAllObjects.onSuccessTask(new Continuation<Void, Task<T>>() {

                    @Override
                    public Task<T> then(Task<Void> task) throws Exception {
                        return getPointerAsync(uuid, db);
                    }
                }).onSuccessTask(new Continuation<T, Task<T>>() {

                    @Override
                    public Task<T> then(Task<T> task) throws Exception {
                        object.set(task.getResult());
                        return fetchLocallyAsync(object.get(), db);
                    }
                }).onSuccessTask(new Continuation<T, Task<Boolean>>() {

                    @Override
                    public Task<Boolean> then(Task<T> task) throws Exception {
                        if (!object.get().isDataAvailable()) {
                            return Task.forResult(false);
                        }
                        return matcher.matchesAsync(object.get(), db);
                    }
                }).onSuccess(new Continuation<Boolean, Void>() {

                    @Override
                    public Void then(Task<Boolean> task) {
                        if (task.getResult()) {
                            results.add(object.get());
                        }
                        return null;
                    }
                });
            }
            return checkedAllObjects;
        }
    }).onSuccessTask(new Continuation<Void, Task<List<T>>>() {

        @Override
        public Task<List<T>> then(Task<Void> task) throws Exception {
            // Sort by any sort operators.
            OfflineQueryLogic.sort(results, query);
            // Apply the skip.
            List<T> trimmedResults = results;
            int skip = query.skip();
            if (!isCount && skip >= 0) {
                skip = Math.min(query.skip(), trimmedResults.size());
                trimmedResults = trimmedResults.subList(skip, trimmedResults.size());
            }
            // Trim to the limit.
            int limit = query.limit();
            if (!isCount && limit >= 0 && trimmedResults.size() > limit) {
                trimmedResults = trimmedResults.subList(0, limit);
            }
            // Fetch the includes.
            Task<Void> fetchedIncludesTask = Task.forResult(null);
            for (final T object : trimmedResults) {
                fetchedIncludesTask = fetchedIncludesTask.onSuccessTask(new Continuation<Void, Task<Void>>() {

                    @Override
                    public Task<Void> then(Task<Void> task) throws Exception {
                        return OfflineQueryLogic.fetchIncludesAsync(OfflineStore.this, object, query, db);
                    }
                });
            }
            final List<T> finalTrimmedResults = trimmedResults;
            return fetchedIncludesTask.onSuccess(new Continuation<Void, List<T>>() {

                @Override
                public List<T> then(Task<Void> task) throws Exception {
                    return finalTrimmedResults;
                }
            });
        }
    });
}
Also used : Task(bolts.Task) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor) Capture(bolts.Capture) ArrayList(java.util.ArrayList) List(java.util.List) LinkedList(java.util.LinkedList) Continuation(bolts.Continuation) JSONException(org.json.JSONException)

Example 2 with Capture

use of bolts.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(new Continuation<String, Task<Cursor>>() {

                @Override
                public Task<Cursor> then(Task<String> task) throws Exception {
                    uuid.set(task.getResult());
                    String[] args = { uuid.get() };
                    return db.queryAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, select, where, args);
                }
            }).onSuccess(new Continuation<Cursor, String>() {

                @Override
                public String then(Task<Cursor> task) throws Exception {
                    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(new Continuation<Cursor, String>() {

            @Override
            public String then(Task<Cursor> task) throws Exception {
                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(new Continuation<String, Task<Void>>() {

        @Override
        public Task<Void> then(Task<String> task) throws Exception {
            String jsonString = task.getResult();
            if (jsonString == null) {
                /*
           * This means we tried to fetch an object from the database that was never actually saved
           * locally. This probably means that its parent object was saved locally and we just
           * created a pointer to this object. This should be considered a cache miss.
           */
                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 {
                /*
           * We can assume that whatever is in the database is the last known server state. The only
           * things to maintain from the in-memory object are any changes since the object was last
           * put in the database.
           */
                json = new JSONObject(jsonString);
            } catch (JSONException e) {
                return Task.forError(e);
            }
            // Fetch all the offline objects before we decode.
            final Map<String, Task<ParseObject>> offlineObjects = new HashMap<>();
            (new ParseTraverser() {

                @Override
                protected boolean visit(Object object) {
                    if (object instanceof JSONObject && ((JSONObject) object).optString("__type").equals("OfflineObject")) {
                        String uuid = ((JSONObject) object).optString("uuid");
                        offlineObjects.put(uuid, getPointerAsync(uuid, db));
                    }
                    return true;
                }
            }).setTraverseParseObjects(false).setYieldRoot(false).traverse(json);
            return Task.whenAll(offlineObjects.values()).onSuccess(new Continuation<Void, Void>() {

                @Override
                public Void then(Task<Void> task) throws Exception {
                    object.mergeREST(object.getState(), json, new OfflineDecoder(offlineObjects));
                    return null;
                }
            });
        }
    }).continueWithTask(new Continuation<Void, Task<T>>() {

        @Override
        public Task<T> then(Task<Void> task) throws Exception {
            if (task.isCancelled()) {
                tcs.setCancelled();
            } else if (task.isFaulted()) {
                tcs.setError(task.getError());
            } else {
                tcs.setResult(object);
            }
            return tcs.getTask();
        }
    });
}
Also used : Task(bolts.Task) HashMap(java.util.HashMap) WeakHashMap(java.util.WeakHashMap) Cursor(android.database.Cursor) Capture(bolts.Capture) TaskCompletionSource(bolts.TaskCompletionSource) Continuation(bolts.Continuation) JSONException(org.json.JSONException) JSONException(org.json.JSONException) JSONObject(org.json.JSONObject) JSONObject(org.json.JSONObject)

Example 3 with Capture

use of bolts.Capture in project Parse-SDK-Android by ParsePlatform.

the class LocationNotifier method getCurrentLocationAsync.

/**
   * Asynchronously gets the current location of the device.
   *
   * This will request location updates from the best provider that match the given criteria
   * and return the first location received.
   *
   * 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<ScheduledFuture<?>>();
    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(new Runnable() {

        @Override
        public void run() {
            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(bolts.TaskCompletionSource) Bundle(android.os.Bundle) LocationListener(android.location.LocationListener) Capture(bolts.Capture) ScheduledFuture(java.util.concurrent.ScheduledFuture) Location(android.location.Location)

Example 4 with Capture

use of bolts.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<>();
    push.subscribeInBackground("test", new SaveCallback() {

        @Override
        public void done(ParseException e) {
            exceptionCapture.set(e);
            done.release();
        }
    });
    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(bolts.Capture) Test(org.junit.Test)

Example 5 with Capture

use of bolts.Capture in project Parse-SDK-Android by ParsePlatform.

the class ParsePushTest method testSendInBackgroundWithCallbackSuccess.

@Test
public void testSendInBackgroundWithCallbackSuccess() throws Exception {
    // Mock controller
    ParsePushController controller = mock(ParsePushController.class);
    when(controller.sendInBackground(any(ParsePush.State.class), anyString())).thenReturn(Task.<Void>forResult(null));
    ParseCorePlugins.getInstance().registerPushController(controller);
    // Make sample ParsePush data and call method
    ParsePush push = new ParsePush();
    JSONObject data = new JSONObject();
    data.put("key", "value");
    List<String> channels = new ArrayList<>();
    channels.add("test");
    channels.add("testAgain");
    push.builder.expirationTime((long) 1000).data(data).pushToIOS(true).channelSet(channels);
    final Semaphore done = new Semaphore(0);
    final Capture<Exception> exceptionCapture = new Capture<>();
    push.sendInBackground(new SendCallback() {

        @Override
        public void done(ParseException e) {
            exceptionCapture.set(e);
            done.release();
        }
    });
    // Make sure controller is executed and state parameter is correct
    assertNull(exceptionCapture.get());
    assertTrue(done.tryAcquire(1, 10, TimeUnit.SECONDS));
    ArgumentCaptor<ParsePush.State> stateCaptor = ArgumentCaptor.forClass(ParsePush.State.class);
    verify(controller, times(1)).sendInBackground(stateCaptor.capture(), anyString());
    ParsePush.State state = stateCaptor.getValue();
    assertTrue(state.pushToIOS());
    assertEquals(data, state.data(), JSONCompareMode.NON_EXTENSIBLE);
    assertEquals(2, state.channelSet().size());
    assertTrue(state.channelSet().contains("test"));
    assertTrue(state.channelSet().contains("testAgain"));
}
Also used : ArrayList(java.util.ArrayList) Matchers.anyString(org.mockito.Matchers.anyString) Semaphore(java.util.concurrent.Semaphore) Capture(bolts.Capture) JSONObject(org.json.JSONObject) Test(org.junit.Test)

Aggregations

Capture (bolts.Capture)18 Test (org.junit.Test)12 Semaphore (java.util.concurrent.Semaphore)8 JSONObject (org.json.JSONObject)5 Intent (android.content.Intent)4 Continuation (bolts.Continuation)4 Task (bolts.Task)4 ArrayList (java.util.ArrayList)4 JSONException (org.json.JSONException)4 PrepareForTest (org.powermock.core.classloader.annotations.PrepareForTest)4 Cursor (android.database.Cursor)2 TaskCompletionSource (bolts.TaskCompletionSource)2 LinkedList (java.util.LinkedList)2 List (java.util.List)2 Matchers.anyString (org.mockito.Matchers.anyString)2 ContentValues (android.content.ContentValues)1 Location (android.location.Location)1 LocationListener (android.location.LocationListener)1 LocationManager (android.location.LocationManager)1 Bundle (android.os.Bundle)1