Example usage for android.content ContentValues getAsString

List of usage examples for android.content ContentValues getAsString

Introduction

In this page you can find the example usage for android.content ContentValues getAsString.

Prototype

public String getAsString(String key) 

Source Link

Document

Gets a value and converts it to a String.

Usage

From source file:ca.zadrox.dota2esportticker.service.UpdateTeamsService.java

private void updateTopTeams() {

    LOGD(TAG, "starting update");

    // actually, first, check for connectivity:
    if (!checkForConnectivity()) {
        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_NO_CONNECTIVITY));
        LOGD(TAG, "returning due to no connectivity");
        return;// w  w  w .  j  a  va  2  s  .  c  om
    }

    // first, check last update time
    long lastUpdate = PrefUtils.lastTeamsUpdate(this);
    long currentTime = TimeUtils.getUTCTime();

    // if last update is less than 1 hour old, boot user to cursorloader op.
    if (currentTime - lastUpdate < 60000 * 60) {
        LOGD(TAG, "returnning due to too soon");
        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_COMPLETED));
        return;
    }

    // else
    // use local broadcast manager to show loading indicator
    LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_UPDATING));

    final String BASE_URL = "http://www.gosugamers.net/dota2/rankings";
    final String TEAM_LINK_BASE_URL = "http://www.gosugamers.net/dota2/teams/";

    // we see what teams are in top 50. (httpreq -> gosugamers)
    try {

        String rawHtml = new OkHttpClient().newCall(new Request.Builder().url(BASE_URL).build()).execute()
                .body().string();

        String processedHtml = rawHtml.substring(rawHtml.indexOf("<div id=\"col1\" class=\"rows\">"),
                rawHtml.indexOf("<div id=\"col2\" class=\"rows\">"));

        Elements teamRows = Jsoup.parse(processedHtml).getElementsByClass("ranking-link");

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ContentValues[] teamRanks = new ContentValues[50];

        HashMap<ContentValues, Future<String>> newTeamInfo = new HashMap<ContentValues, Future<String>>();
        HashMap<ContentValues, Future<String>> updateTeamInfo = new HashMap<ContentValues, Future<String>>();

        int i = 0;

        for (Element teamRow : teamRows) {
            ContentValues contentValues = new ContentValues();

            String teamId = teamRow.attr("data-id");
            contentValues.put(MatchContract.TeamEntry._ID, teamId);

            String untrimmedTeamName = teamRow.getElementsByTag("h4").first().text();
            String teamUrl = TEAM_LINK_BASE_URL + teamId + "-"
                    + untrimmedTeamName.replaceAll("[\\W]?[\\W][\\W]*", "-").toLowerCase();
            contentValues.put(MatchContract.TeamEntry.COLUMN_TEAM_URL, teamUrl);

            String teamName = untrimmedTeamName.replaceAll(" ?\\.?\\-?-?Dot[aA][\\s]?2", "");
            contentValues.put(MatchContract.TeamEntry.COLUMN_TEAM_NAME, teamName);

            if (teamUrl.charAt(teamUrl.length() - 1) == '-') {
                teamUrl = teamUrl.substring(0, teamUrl.length() - 2);
            }

            // then, we query db for id of the team (
            Cursor cursor = getContentResolver().query(
                    MatchContract.TeamEntry.buildTeamUri(Long.parseLong(teamId)), new String[] {
                            MatchContract.TeamEntry.COLUMN_TEAM_NAME, MatchContract.TeamEntry.COLUMN_TEAM_URL },
                    null, null, null);

            // -> if present, and data remains unchanged, continue.
            // -> if present, but data is changed, add to update queue.
            if (cursor.moveToFirst()) {
                LOGD(TAG, "Have team already?");
                if (!cursor.getString(0).contentEquals(teamName)
                        || !cursor.getString(1).contentEquals(teamUrl)) {
                    LOGD(TAG, "Team has updated values.");
                    updateTeamInfo.put(contentValues, executorService.submit(new TeamGetter(teamUrl)));
                }
            }
            // -> if not present, add to update queue.
            else {
                LOGD(TAG, "Do team update");
                newTeamInfo.put(contentValues, executorService.submit(new TeamGetter(teamUrl)));
            }

            //                LOGD(TAG, "\n" +
            //                        "data-id: " + teamId + "\n" +
            //                        "team-name: " + teamName + "\n" +
            //                        "team-url: " + teamUrl);

            teamRanks[i] = new ContentValues();
            teamRanks[i].put(MatchContract.TeamRankEntry._ID, i + 1);
            teamRanks[i].put(MatchContract.TeamRankEntry.COLUMN_TEAM_ID, teamId);

            cursor.close();
            i++;
        }

        executorService.shutdown();
        executorService.awaitTermination(20, TimeUnit.SECONDS);

        for (ContentValues contentValues : newTeamInfo.keySet()) {
            try {
                String teamLogo = newTeamInfo.get(contentValues).get();
                contentValues.put(MatchContract.TeamEntry.COLUMN_TEAM_LOGO_URL, teamLogo);

            } catch (ExecutionException e) {
                LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_ERROR));
                e.printStackTrace();
            }
        }

        for (ContentValues contentValues : updateTeamInfo.keySet()) {
            try {
                String teamLogo = updateTeamInfo.get(contentValues).get();
                contentValues.put(MatchContract.TeamEntry.COLUMN_TEAM_LOGO_URL, teamLogo);

                String teamId = contentValues.getAsString(MatchContract.TeamEntry._ID);
                contentValues.remove(MatchContract.TeamEntry._ID);

                int updatedRows = getContentResolver().update(MatchContract.TeamEntry.CONTENT_URI,
                        contentValues,
                        MatchContract.TeamEntry.TABLE_NAME + "." + MatchContract.TeamEntry._ID + " = ?",
                        new String[] { teamId });

                LOGD(TAG, "updatedRows: " + updatedRows);

            } catch (ExecutionException e) {
                LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_ERROR));
                e.printStackTrace();
            }
        }

        getContentResolver().bulkInsert(MatchContract.TeamEntry.CONTENT_URI,
                newTeamInfo.keySet().toArray(new ContentValues[newTeamInfo.size()]));
        getContentResolver().bulkInsert(MatchContract.TeamRankEntry.CONTENT_URI, teamRanks);

    } catch (IOException e) {
        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_ERROR));
        e.printStackTrace();
    } catch (InterruptedException e2) {
        LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_ERROR));
        e2.printStackTrace();
    }

    //        String[] projection = new String[]{
    //                MatchContract.TeamEntry.TABLE_NAME + "." + MatchContract.TeamEntry._ID,
    //                MatchContract.TeamEntry.COLUMN_TEAM_NAME,
    //                MatchContract.TeamEntry.COLUMN_TEAM_URL,
    //                MatchContract.TeamEntry.COLUMN_TEAM_LOGO_URL,
    //                MatchContract.TeamEntry.COLUMN_TEAM_STARRED,
    //                MatchContract.TeamRankEntry.TABLE_NAME + "." + MatchContract.TeamRankEntry._ID
    //        };
    //
    //        String sortOrder =
    //                MatchContract.TeamRankEntry.TABLE_NAME + "." +
    //                        MatchContract.TeamRankEntry._ID + " ASC";
    //
    //        Cursor c = getContentResolver().query(
    //                MatchContract.TeamEntry.TOP_50_URI,
    //                projection,
    //                null,
    //                null,
    //                sortOrder
    //        );
    //
    //        while (c.moveToNext()) {
    //            String teamPrintOut =
    //                    "Rank: " + c.getInt(5) + "\n" +
    //                            "teamId: " + c.getInt(0) + " teamName: " + c.getString(1) + "\n" +
    //                            "teamUrl: " + c.getString(2) + "\n" +
    //                            "teamLogoUrl: " + c.getString(3) + "\n" +
    //                            "isFavourited: " + (c.getInt(4) == 0 ? "false" : "true");
    //            LOGD(TAG + "/UTT", teamPrintOut);
    //        }
    //
    //        c.close();

    // use local broadcast manager to hide loading indicator
    // and signal that cursorloader for top50 can happen.
    PrefUtils.setLastTeamUpdate(this, currentTime);
    LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(STATUS_COMPLETED));
}

From source file:com.ichi2.anki.provider.CardContentProvider.java

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    Timber.d("CardContentProvider.update");
    Collection col = CollectionHelper.getInstance().getCol(getContext());
    if (col == null) {
        return 0;
    }// w  w w  .  j  ava2  s .c om

    // Find out what data the user is requesting
    int match = sUriMatcher.match(uri);

    int updated = 0; // Number of updated entries (return value)
    switch (match) {
    case NOTES:
        throw new IllegalArgumentException("Not possible to update notes directly (only through data URI)");
    case NOTES_ID: {
        /* Direct access note details
         */
        Note currentNote = getNoteFromUri(uri, col);
        // the key of the ContentValues contains the column name
        // the value of the ContentValues contains the row value.
        Set<Map.Entry<String, Object>> valueSet = values.valueSet();
        for (Map.Entry<String, Object> entry : valueSet) {
            String key = entry.getKey();
            if (key.equals(FlashCardsContract.Note.FLDS)) {
                // Update FLDS
                Timber.d("CardContentProvider: flds update...");
                String newFldsEncoded = (String) entry.getValue();
                String[] flds = Utils.splitFields(newFldsEncoded);
                // Check that correct number of flds specified
                if (flds.length != currentNote.getFields().length) {
                    throw new IllegalArgumentException("Incorrect flds argument : " + newFldsEncoded);
                }
                // Update the note
                for (int idx = 0; idx < flds.length; idx++) {
                    currentNote.setField(idx, flds[idx]);
                }
                updated++;
            } else if (key.equals(FlashCardsContract.Note.TAGS)) {
                // Update tags
                Timber.d("CardContentProvider: tags update...");
                currentNote.setTagsFromStr((String) entry.getValue());
                updated++;
            } else {
                // Unsupported column
                throw new IllegalArgumentException("Unsupported column: " + key);
            }
        }
        Timber.d("CardContentProvider: Saving note...");
        currentNote.flush();
        break;
    }
    case NOTES_ID_CARDS:
        // TODO: To be implemented
        throw new UnsupportedOperationException("Not yet implemented");
        //                break;
    case NOTES_ID_CARDS_ORD: {
        Card currentCard = getCardFromUri(uri, col);
        boolean isDeckUpdate = false;
        long did = -1;
        // the key of the ContentValues contains the column name
        // the value of the ContentValues contains the row value.
        Set<Map.Entry<String, Object>> valueSet = values.valueSet();
        for (Map.Entry<String, Object> entry : valueSet) {
            // Only updates on deck id is supported
            String key = entry.getKey();
            isDeckUpdate = key.equals(FlashCardsContract.Card.DECK_ID);
            did = values.getAsLong(key);
        }

        /* now update the card
         */
        if ((isDeckUpdate) && (did >= 0)) {
            Timber.d("CardContentProvider: Moving card to other deck...");
            col.getDecks().flush();
            currentCard.setDid(did);
            currentCard.flush();
            updated++;
        } else {
            // User tries an operation that is not (yet?) supported.
            throw new IllegalArgumentException("Currently only updates of decks are supported");
        }
        break;
    }
    case MODELS:
        throw new IllegalArgumentException("Cannot update models in bulk");
    case MODELS_ID:
        // Get the input parameters
        String newModelName = values.getAsString(FlashCardsContract.Model.NAME);
        String newCss = values.getAsString(FlashCardsContract.Model.CSS);
        String newDid = values.getAsString(FlashCardsContract.Model.DECK_ID);
        String newFieldList = values.getAsString(FlashCardsContract.Model.FIELD_NAMES);
        if (newFieldList != null) {
            // Changing the field names would require a full-sync
            throw new IllegalArgumentException("Field names cannot be changed via provider");
        }
        // Get the original note JSON
        JSONObject model = col.getModels().get(getModelIdFromUri(uri, col));
        try {
            // Update model name and/or css
            if (newModelName != null) {
                model.put("name", newModelName);
                updated++;
            }
            if (newCss != null) {
                model.put("css", newCss);
                updated++;
            }
            if (newDid != null) {
                model.put("did", newDid);
                updated++;
            }
            col.getModels().save(model);
        } catch (JSONException e) {
            Timber.e(e, "JSONException updating model");
        }
        break;
    case MODELS_ID_TEMPLATES:
        throw new IllegalArgumentException("Cannot update templates in bulk");
    case MODELS_ID_TEMPLATES_ID:
        Long mid = values.getAsLong(CardTemplate.MODEL_ID);
        Integer ord = values.getAsInteger(CardTemplate.ORD);
        String name = values.getAsString(CardTemplate.NAME);
        String qfmt = values.getAsString(CardTemplate.QUESTION_FORMAT);
        String afmt = values.getAsString(CardTemplate.ANSWER_FORMAT);
        String bqfmt = values.getAsString(CardTemplate.BROWSER_QUESTION_FORMAT);
        String bafmt = values.getAsString(CardTemplate.BROWSER_ANSWER_FORMAT);
        // Throw exception if read-only fields are included
        if (mid != null || ord != null) {
            throw new IllegalArgumentException("Can update mid or ord");
        }
        // Update the model
        try {
            Integer templateOrd = Integer.parseInt(uri.getLastPathSegment());
            JSONObject existingModel = col.getModels().get(getModelIdFromUri(uri, col));
            JSONArray templates = existingModel.getJSONArray("tmpls");
            JSONObject template = templates.getJSONObject(templateOrd);
            if (name != null) {
                template.put("name", name);
                updated++;
            }
            if (qfmt != null) {
                template.put("qfmt", qfmt);
                updated++;
            }
            if (afmt != null) {
                template.put("afmt", afmt);
                updated++;
            }
            if (bqfmt != null) {
                template.put("bqfmt", bqfmt);
                updated++;
            }
            if (bafmt != null) {
                template.put("bafmt", bafmt);
                updated++;
            }
            // Save the model
            templates.put(templateOrd, template);
            existingModel.put("tmpls", templates);
            col.getModels().save(existingModel, true);
        } catch (JSONException e) {
            throw new IllegalArgumentException("Model is malformed", e);
        }
        break;
    case SCHEDULE: {
        Set<Map.Entry<String, Object>> valueSet = values.valueSet();
        int cardOrd = -1;
        long noteID = -1;
        int ease = -1;
        long timeTaken = -1;
        for (Map.Entry<String, Object> entry : valueSet) {
            String key = entry.getKey();

            if (key.equals(FlashCardsContract.ReviewInfo.NOTE_ID)) {
                noteID = values.getAsLong(key);
            } else if (key.equals(FlashCardsContract.ReviewInfo.CARD_ORD)) {
                cardOrd = values.getAsInteger(key);
            } else if (key.equals(FlashCardsContract.ReviewInfo.EASE)) {
                ease = values.getAsInteger(key);
            } else if (key.equals(FlashCardsContract.ReviewInfo.TIME_TAKEN)) {
                timeTaken = values.getAsLong(key);
            }
        }
        if (cardOrd != -1 && noteID != -1) {
            Card cardToAnswer = getCard(noteID, cardOrd, col);
            if (cardToAnswer != null) {
                answerCard(col, col.getSched(), cardToAnswer, ease, timeTaken);
                updated++;
            } else {
                Timber.e(
                        "Requested card with noteId %d and cardOrd %d was not found. Either the provided "
                                + "noteId/cardOrd were wrong or the card has been deleted in the meantime.",
                        noteID, cardOrd);
            }
        }
        break;
    }
    case DECKS:
        throw new IllegalArgumentException("Can't update decks in bulk");
    case DECKS_ID:
        throw new UnsupportedOperationException("Not yet implemented");
    case DECK_SELECTED: {
        Set<Map.Entry<String, Object>> valueSet = values.valueSet();
        for (Map.Entry<String, Object> entry : valueSet) {
            String key = entry.getKey();
            if (key.equals(FlashCardsContract.Deck.DECK_ID)) {
                long deckId = values.getAsLong(key);
                if (selectDeckWithCheck(col, deckId)) {
                    updated++;
                }
            }
        }
        break;
    }
    default:
        // Unknown URI type
        throw new IllegalArgumentException("uri " + uri + " is not supported");
    }
    return updated;
}

From source file:edu.mit.mobile.android.locast.data.Sync.java

/**
 * Given a live cursor pointing to a data item and/or a set of contentValues loaded from the network,
 * attempt to sync.//from   ww w .  ja v a2  s .  c  o  m
 * Either c or cvNet can be null, but not both.
 * @param c A cursor pointing to the data item. Null is OK here.
 * @param jsonObject JSON object for the item as loaded from the network. null is OK here.
 * @param sync An empty JsonSyncableItem object.
 * @param publicPath TODO
 *
 * @return True if the item has been modified on either end.
 * @throws IOException
 */
private boolean syncItem(Uri toSync, Cursor c, JSONObject jsonObject, JsonSyncableItem sync,
        SyncProgressNotifier syncProgress, String publicPath) throws SyncException, IOException {
    boolean modified = false;
    boolean needToCloseCursor = false;
    boolean toSyncIsIndex = false;
    final SyncMap syncMap = sync.getSyncMap();

    Uri locUri = null;
    final Uri origToSync = toSync;
    ContentValues cvNet = null;

    final Context context = getApplicationContext();
    final ContentResolver cr = context.getContentResolver();
    if (jsonObject != null) {
        if ("http".equals(toSync.getScheme()) || "https".equals(toSync.getScheme())) {
            // we successfully loaded it from the 'net, but toSync is really for local URIs. Erase it.

            toSync = sync.getContentUri();
            if (toSync == null) {
                if (DEBUG) {
                    Log.w(TAG, "cannot get local URI for " + origToSync + ". Skipping...");
                }
                return false;
            }
        }
        try {
            cvNet = JsonSyncableItem.fromJSON(context, null, jsonObject, syncMap);
        } catch (final Exception e) {
            final SyncException se = new SyncException("Problem loading JSON object.");
            se.initCause(e);
            throw se;
        }
    }

    final String contentType = cr.getType(toSync);

    if (c != null) {
        if (contentType.startsWith(CONTENT_TYPE_PREFIX_DIR)) {
            locUri = ContentUris.withAppendedId(toSync, c.getLong(c.getColumnIndex(JsonSyncableItem._ID)))
                    .buildUpon().query(null).build();
            toSyncIsIndex = true;
        } else {
            locUri = toSync;
        }

        // skip any items already sync'd
        if (mLastUpdated.isUpdatedRecently(locUri)) {
            return false;
        }

        final int draftCol = c.getColumnIndex(TaggableItem._DRAFT);
        if (draftCol != -1 && c.getInt(draftCol) != 0) {
            if (DEBUG) {
                Log.d(TAG, locUri + " is marked a draft. Not syncing.");
            }
            return false;
        }

        syncMap.onPreSyncItem(cr, locUri, c);
    } else if (contentType.startsWith(CONTENT_TYPE_PREFIX_DIR)) {
        // strip any query strings
        toSync = toSync.buildUpon().query(null).build();
    }
    //      if (c != null){
    //         MediaProvider.dumpCursorToLog(c, sync.getFullProjection());
    //      }
    // when the PUBLIC_URI is null, that means it's only local
    final int pubUriColumn = (c != null) ? c.getColumnIndex(JsonSyncableItem._PUBLIC_URI) : -1;
    if (c != null && (c.isNull(pubUriColumn) || c.getString(pubUriColumn) == "")) {
        // new content on the local side only. Gotta publish.

        try {
            jsonObject = JsonSyncableItem.toJSON(context, locUri, c, syncMap);
            if (publicPath == null) {
                publicPath = MediaProvider.getPostPath(this, locUri);
            }
            if (DEBUG) {
                Log.d(TAG, "Posting " + locUri + " to " + publicPath);
            }

            // The response from a post to create a new item should be the newly created item,
            // which contains the public ID that we need.
            jsonObject = nc.postJson(publicPath, jsonObject);

            final ContentValues cvUpdate = JsonSyncableItem.fromJSON(context, locUri, jsonObject, syncMap);
            if (cr.update(locUri, cvUpdate, null, null) == 1) {
                // at this point, server and client should be in sync.
                mLastUpdated.markUpdated(locUri);
                if (DEBUG) {
                    Log.i(TAG, "Hooray! " + locUri + " has been posted succesfully.");
                }

            } else {
                Log.e(TAG, "update of " + locUri + " failed");
            }
            modified = true;

        } catch (final Exception e) {
            final SyncException se = new SyncException(getString(R.string.error_sync_no_post));
            se.initCause(e);
            throw se;
        }

        // only on the remote side, so pull it in.
    } else if (c == null && cvNet != null) {
        if (DEBUG) {
            Log.i(TAG, "Only on the remote side, using network-provided values.");
        }
        final String[] params = { cvNet.getAsString(JsonSyncableItem._PUBLIC_URI) };
        c = cr.query(toSync, sync.getFullProjection(), JsonSyncableItem._PUBLIC_URI + "=?", params, null);
        needToCloseCursor = true;

        if (!c.moveToFirst()) {
            locUri = cr.insert(toSync, cvNet);
            modified = true;
        } else {
            locUri = ContentUris.withAppendedId(toSync, c.getLong(c.getColumnIndex(JsonSyncableItem._ID)))
                    .buildUpon().query(null).build();
            syncMap.onPreSyncItem(cr, locUri, c);
        }
    }

    // we've now found data on both sides, so sync them.
    if (!modified && c != null) {

        publicPath = c.getString(c.getColumnIndex(JsonSyncableItem._PUBLIC_URI));

        try {

            if (cvNet == null) {
                try {
                    if (publicPath == null && toSyncIsIndex && !MediaProvider.canSync(locUri)) {

                        // At this point, we've already checked the index and it doesn't contain the item (otherwise it would be in the syncdItems).
                        // If we can't sync individual items, it's possible that the index is paged or the item has been deleted.
                        if (DEBUG) {
                            Log.w(TAG, "Asked to sync " + locUri
                                    + " but item wasn't in server index and cannot sync individual entries. Skipping and hoping it is up to date.");
                        }
                        return false;

                    } else {
                        if (mLastUpdated.isUpdatedRecently(nc.getFullUri(publicPath))) {
                            if (DEBUG) {
                                Log.d(TAG, "already sync'd! " + publicPath);
                            }
                            return false;
                        }
                        if (jsonObject == null) {
                            jsonObject = nc.getObject(publicPath);
                        }
                        cvNet = JsonSyncableItem.fromJSON(context, locUri, jsonObject, syncMap);

                    }
                } catch (final HttpResponseException hre) {
                    if (hre.getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                        final SyncItemDeletedException side = new SyncItemDeletedException(locUri);
                        side.initCause(hre);
                        throw side;
                    }
                }
            }
            if (cvNet == null) {
                Log.e(TAG, "got null values from fromJSON() on item " + locUri + ": "
                        + (jsonObject != null ? jsonObject.toString() : "<< no json object >>"));
                return false;
            }
            final Date netLastModified = new Date(cvNet.getAsLong(JsonSyncableItem._MODIFIED_DATE));
            final Date locLastModified = new Date(c.getLong(c.getColumnIndex(JsonSyncableItem._MODIFIED_DATE)));

            if (netLastModified.equals(locLastModified)) {
                // same! yay! We don't need to do anything.
                if (DEBUG) {
                    Log.d("LocastSync", locUri + " doesn't need to sync.");
                }
            } else if (netLastModified.after(locLastModified)) {
                // remote is more up to date, update!
                cr.update(locUri, cvNet, null, null);
                if (DEBUG) {
                    Log.d("LocastSync", cvNet + " is newer than " + locUri);
                }
                modified = true;

            } else if (netLastModified.before(locLastModified)) {
                // local is more up to date, propagate!
                jsonObject = nc.putJson(publicPath, JsonSyncableItem.toJSON(context, locUri, c, syncMap));

                if (DEBUG) {
                    Log.d("LocastSync", cvNet + " is older than " + locUri);
                }
                modified = true;
            }
            mLastUpdated.markUpdated(nc.getFullUri(publicPath));
        } catch (final JSONException e) {
            final SyncException se = new SyncException(
                    "Item sync error for path " + publicPath + ": invalid JSON.");
            se.initCause(e);
            throw se;
        } catch (final NetworkProtocolException e) {
            final SyncException se = new SyncException(
                    "Item sync error for path " + publicPath + ": " + e.getHttpResponseMessage());
            se.initCause(e);
            throw se;
        } finally {
            if (needToCloseCursor) {
                c.close();
                needToCloseCursor = false;
            }
        }
    }

    if (needToCloseCursor) {
        c.close();
    }

    if (locUri == null) {
        throw new RuntimeException("Never got a local URI for a sync'd item.");
    }

    // two calls are made in two different contexts. Which context you use depends on the application.
    syncMap.onPostSyncItem(context, locUri, jsonObject, modified);
    sync.onPostSyncItem(context, locUri, jsonObject, modified);

    mLastUpdated.markUpdated(locUri);

    // needed for things that may have requested a sync with a different URI than what was eventually produced.
    if (origToSync != locUri) {
        mLastUpdated.markUpdated(origToSync);
        cr.notifyChange(origToSync, null);
    }

    return modified;
}

From source file:org.opendatakit.common.android.provider.impl.FormsProviderImpl.java

@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
    List<String> segments = uri.getPathSegments();

    if (segments.size() < 1 || segments.size() > 2) {
        throw new IllegalArgumentException("Unknown URI (incorrect number of segments!) " + uri);
    }/*w w w .  j  a v a 2 s . c om*/

    String appName = segments.get(0);
    ODKFileUtils.verifyExternalStorageAvailability();
    ODKFileUtils.assertDirectoryStructure(appName);
    WebLogger log = WebLogger.getLogger(appName);

    String uriFormId = ((segments.size() == 2) ? segments.get(1) : null);
    boolean isNumericId = StringUtils.isNumeric(uriFormId);

    // Modify the where clause to account for the presence of
    // a form id. Accept either:
    // (1) numeric _ID value
    // (2) string FORM_ID value.
    String whereId;
    String[] whereIdArgs;

    if (uriFormId == null) {
        whereId = where;
        whereIdArgs = whereArgs;
    } else {
        if (TextUtils.isEmpty(where)) {
            whereId = (isNumericId ? FormsColumns._ID : FormsColumns.FORM_ID) + "=?";
            whereIdArgs = new String[1];
            whereIdArgs[0] = uriFormId;
        } else {
            whereId = (isNumericId ? FormsColumns._ID : FormsColumns.FORM_ID) + "=? AND (" + where + ")";
            whereIdArgs = new String[whereArgs.length + 1];
            whereIdArgs[0] = uriFormId;
            for (int i = 0; i < whereArgs.length; ++i) {
                whereIdArgs[i + 1] = whereArgs[i];
            }
        }
    }

    /*
     * First, find out what records match this query, and if they refer to two
     * or more (formId,formVersion) tuples, then be sure to remove all
     * FORM_MEDIA_PATH references. Otherwise, if they are all for the same
     * tuple, and the update specifies a FORM_MEDIA_PATH, move all the
     * non-matching directories elsewhere.
     */
    Integer idValue = null;
    String tableIdValue = null;
    String formIdValue = null;
    HashMap<File, DirType> mediaDirs = new HashMap<File, DirType>();
    boolean multiset = false;
    Cursor c = null;
    try {
        c = this.query(uri, null, whereId, whereIdArgs, null);
        if (c == null) {
            throw new SQLException(
                    "FAILED Update of " + uri + " -- query for existing row did not return a cursor");
        }
        if (c.getCount() >= 1) {
            FormIdVersion ref = null;
            c.moveToPosition(-1);
            while (c.moveToNext()) {
                idValue = ODKDatabaseUtils.get().getIndexAsType(c, Integer.class,
                        c.getColumnIndex(FormsColumns._ID));
                tableIdValue = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.TABLE_ID));
                formIdValue = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.FORM_ID));
                String tableId = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.TABLE_ID));
                String formId = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.FORM_ID));
                String formVersion = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.FORM_VERSION));
                FormIdVersion cur = new FormIdVersion(tableId, formId, formVersion);

                int appRelativeMediaPathIdx = c.getColumnIndex(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH);
                String mediaPath = ODKDatabaseUtils.get().getIndexAsString(c, appRelativeMediaPathIdx);
                if (mediaPath != null) {
                    mediaDirs.put(ODKFileUtils.asAppFile(appName, mediaPath),
                            (tableIdValue == null) ? DirType.FRAMEWORK : DirType.FORMS);
                }

                if (ref != null && !ref.equals(cur)) {
                    multiset = true;
                    break;
                } else {
                    ref = cur;
                }
            }
        }
    } catch (Exception e) {
        log.w(t, "FAILED Update of " + uri + " -- query for existing row failed: " + e.toString());

        if (e instanceof SQLException) {
            throw (SQLException) e;
        } else {
            throw new SQLException(
                    "FAILED Update of " + uri + " -- query for existing row failed: " + e.toString());
        }
    } finally {
        if (c != null) {
            c.close();
        }
    }

    if (multiset) {
        // don't let users manually update media path
        // we are referring to two or more (formId,formVersion) tuples.
        if (values.containsKey(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)) {
            values.remove(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH);
        }
    } else if (values.containsKey(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)) {
        // we are not a multiset and we are setting the media path
        // try to move all the existing non-matching media paths to
        // somewhere else...
        File mediaPath = ODKFileUtils.asAppFile(appName,
                values.getAsString(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH));
        for (HashMap.Entry<File, DirType> entry : mediaDirs.entrySet()) {
            File altPath = entry.getKey();
            if (!altPath.equals(mediaPath)) {
                try {
                    moveDirectory(appName, entry.getValue(), altPath);
                } catch (IOException e) {
                    e.printStackTrace();
                    log.e(t, "Attempt to move " + altPath.getAbsolutePath() + " failed: " + e.toString());
                }
            }
        }
        // OK. we have moved the existing form definitions elsewhere. We can
        // proceed with update...
    }

    // ensure that all values are correct and ignore some user-supplied
    // values...
    patchUpValues(appName, values);

    // Make sure that the necessary fields are all set
    if (values.containsKey(FormsColumns.DATE) == true) {
        Date today = new Date();
        String ts = new SimpleDateFormat(getContext().getString(R.string.added_on_date_at_time),
                Locale.getDefault()).format(today);
        values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
    }

    SQLiteDatabase db = null;
    int count;
    try {
        // OK Finally, now do the update...
        db = DatabaseFactory.get().getDatabase(getContext(), appName);
        db.beginTransaction();
        count = db.update(DatabaseConstants.FORMS_TABLE_NAME, values, whereId, whereIdArgs);
        db.setTransactionSuccessful();
    } catch (Exception e) {
        e.printStackTrace();
        log.w(t, "Unable to perform update " + uri);
        return 0;
    } finally {
        if (db != null) {
            db.endTransaction();
            db.close();
        }
    }

    if (count == 1) {
        Uri formUri = Uri.withAppendedPath(
                Uri.withAppendedPath(Uri.parse("content://" + getFormsAuthority()), appName), formIdValue);
        getContext().getContentResolver().notifyChange(formUri, null);
        Uri idUri = Uri.withAppendedPath(
                Uri.withAppendedPath(Uri.parse("content://" + getFormsAuthority()), appName),
                Long.toString(idValue));
        getContext().getContentResolver().notifyChange(idUri, null);
    } else {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    return count;
}

From source file:org.opendatakit.services.database.utlities.ODKDatabaseImplUtils.java

/**
 * Delete any prior server conflict row.
 * Examine database and incoming server values to determine how to apply the
 * server values to the database. This might delete the existing row, update
 * it, or create a conflict row./*from  w w w  .ja v a  2  s .c o m*/
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param serverValues  field values for this row coming from server.
 *                      All fields must have values. The SyncState field
 *                      (a local field that does not come from the server)
 *                      should be "changed" or "deleted"
 * @param rowId
 * @param activeUser
 * @param rolesList  passed in to determine if the current user is a privileged user
 * @param locale
 */
public void privilegedPerhapsPlaceRowIntoConflictWithId(OdkConnectionInterface db, String tableId,
        OrderedColumns orderedColumns, ContentValues serverValues, String rowId, String activeUser,
        String rolesList, String locale) {

    AccessContext accessContext = getAccessContext(db, tableId, activeUser, rolesList);

    // The rolesList of the activeUser does not impact the execution of this action.
    boolean dbWithinTransaction = db.inTransaction();
    try {
        if (!dbWithinTransaction) {
            db.beginTransactionNonExclusive();
        }

        // delete any existing server conflict row
        this.deleteServerConflictRowWithId(db, tableId, rowId);
        // fetch the current local (possibly-in-conflict) row
        BaseTable baseTable = this.privilegedGetRowsWithId(db, tableId, rowId, activeUser);
        if (baseTable.getNumberOfRows() == 0) {
            throw new IllegalArgumentException("no matching row found for server conflict");
        } else if (baseTable.getNumberOfRows() != 1) {
            throw new IllegalArgumentException("row has checkpoints or database is corrupt");
        }

        Row localRow = baseTable.getRowAtIndex(0);

        if (localRow.getDataByKey(DataTableColumns.SAVEPOINT_TYPE) == null) {
            throw new IllegalArgumentException("row has checkpoints");
        }

        String strSyncState = localRow.getDataByKey(DataTableColumns.SYNC_STATE);
        SyncState state = SyncState.valueOf(strSyncState);

        int localRowConflictTypeBeforeSync = -1;
        if (state == SyncState.in_conflict) {
            // we need to remove the in_conflict records that refer to the
            // prior state of the server
            String localRowConflictTypeBeforeSyncStr = localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE);
            if (localRowConflictTypeBeforeSyncStr == null) {
                // this row is in conflict. It MUST have a non-null conflict type.
                throw new IllegalStateException("conflict type is null on an in-conflict row");
            }

            localRowConflictTypeBeforeSync = Integer.parseInt(localRowConflictTypeBeforeSyncStr);
            if (localRowConflictTypeBeforeSync == ConflictType.SERVER_DELETED_OLD_VALUES
                    || localRowConflictTypeBeforeSync == ConflictType.SERVER_UPDATED_UPDATED_VALUES) {
                // should be impossible
                throw new IllegalStateException("only the local conflict record should remain");
            }
        }

        boolean isServerRowDeleted = serverValues.getAsString(DataTableColumns.SYNC_STATE)
                .equals(SyncState.deleted.name());

        boolean executeDropThrough = false;

        if (isServerRowDeleted) {
            if (state == SyncState.synced) {
                // the server's change should be applied locally.
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else if (state == SyncState.synced_pending_files) {
                // sync logic may want to UPLOAD local files up to server before calling this routine.
                // this would prevent loss of attachments that have not yet been pushed to server.

                // the server's change should be applied locally.
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else if ((state == SyncState.deleted) || ((state == SyncState.in_conflict)
                    && (localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES))) {
                // this occurs if
                // (1) a delete request was never ACKed but it was performed
                // on the server.
                // (2) if there is an unresolved conflict held locally with the
                // local action being to delete the record, and the prior server
                // state being a value change, but the newly sync'd state now
                // reflects a deletion by another party.
                //
                // the server's change should be applied locally.
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else {
                // need to resolve a conflict situation...
                executeDropThrough = true;
            }
        } else if (state == SyncState.synced || state == SyncState.synced_pending_files) {
            // When a prior sync ends with conflicts, we will not update the table's "lastDataETag"
            // and when we next sync, we will pull the same server row updates as when the
            // conflicts were raised (elsewhere in the table).
            //
            // Therefore, we can expect many server row updates to have already been locally
            // applied (for the rows were not in conflict). Detect and ignore these already-
            // processed changes by testing for the server and device having identical field values.
            //

            boolean isDifferent = false;
            for (int i = 0; i < baseTable.getWidth(); ++i) {
                String colName = baseTable.getElementKey(i);
                if (DataTableColumns.ID.equals(colName) || DataTableColumns.CONFLICT_TYPE.equals(colName)
                        || DataTableColumns.EFFECTIVE_ACCESS.equals(colName)
                        || DataTableColumns.SYNC_STATE.equals(colName)) {
                    // these values are ignored during comparisons
                    continue;
                }
                String localValue = localRow.getDataByKey(colName);
                String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName)
                        : null;

                ElementDataType dt = ElementDataType.string;
                try {
                    ColumnDefinition cd = orderedColumns.find(colName);
                    dt = cd.getType().getDataType();
                } catch (IllegalArgumentException e) {
                    // ignore
                }
                if (!identicalValue(localValue, serverValue, dt)) {
                    isDifferent = true;
                    break;
                }
            }

            if (isDifferent) {
                // Local row needs to be updated with server values.
                //
                // detect and handle file attachment column changes and sync state
                // (need to change serverRow value of this)
                boolean hasNonNullAttachments = false;

                for (ColumnDefinition cd : orderedColumns.getColumnDefinitions()) {
                    // todo: does not handle array containing (types containing) rowpath elements
                    if (cd.isUnitOfRetention() && cd.getType().getDataType().equals(ElementDataType.rowpath)) {
                        String uriFragment = serverValues.getAsString(cd.getElementKey());
                        String localUriFragment = localRow.getDataByKey(cd.getElementKey());
                        if (uriFragment != null) {
                            if (localUriFragment == null || !localUriFragment.equals(uriFragment)) {
                                hasNonNullAttachments = true;
                                break;
                            }
                        }
                    }
                }

                // update the row from the changes on the server
                ContentValues values = new ContentValues(serverValues);

                values.put(DataTableColumns.SYNC_STATE,
                        (hasNonNullAttachments || (state == SyncState.synced_pending_files))
                                ? SyncState.synced_pending_files.name()
                                : SyncState.synced.name());
                values.putNull(DataTableColumns.CONFLICT_TYPE);

                this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser, locale,
                        false);
            }

            // and don't execute the drop-through in this case.
        } else {
            executeDropThrough = true;
        }

        if (executeDropThrough) {
            // SyncState.deleted and server is not deleting
            // SyncState.new_row and record exists on server
            // SyncState.changed and new change (or delete) on server
            // SyncState.in_conflict and new change (or delete) on server

            // ALSO: this case can occur when our prior sync attempt pulled down changes that
            // placed local row(s) in conflict -- which we have since resolved -- and we are now
            // issuing a sync to push those changes up to the server.
            //
            // This is because we do not update our local table's "lastDataETag" until we have
            // sync'd and applied all changes from the server and have no local conflicts or
            // checkpoints on the table. Because of this, when we issue a sync after resolving
            // a conflict, we will get the set of server row changes that include the row(s)
            // that were previously in conflict. This will appear as one of:
            //    changed | changed
            //    changed | deleted
            //    deleted | changed
            //    deleted | deleted
            //
            // BUT, this time, however, the local records will have the same rowETag as the
            // server record, indicating that they are valid changes (or deletions) on top of
            // the server's current version of this same row.
            //
            // If this is the case (the rowETags match), then we should not place the row into
            // conflict, but should instead ignore the reported content from the server and push
            // the local row's change or delete up to the server in the next section.
            //
            // If not, when we reach this point in the code, the rowETag of the server row
            // should **not match** our local row -- indicating that the server has a change from
            // another source, and that we should place the row into conflict.
            //
            String localRowETag = localRow.getDataByKey(DataTableColumns.ROW_ETAG);
            String serverRowETag = serverValues.getAsString(DataTableColumns.ROW_ETAG);
            boolean isDifferentRowETag = (localRowETag == null) || !localRowETag.equals(serverRowETag);
            if (!isDifferentRowETag) {
                // ignore the server record.
                // This is an update we will push to the server.
                // todo: make sure local row is not in_conflict at this point.
                if (state == SyncState.in_conflict) {
                    // don't think this is logically possible at this point, but we should
                    // transition record to changed or deleted state.
                    ContentValues values = new ContentValues();
                    for (int i = 0; i < baseTable.getWidth(); ++i) {
                        String colName = baseTable.getElementKey(i);
                        if (DataTableColumns.EFFECTIVE_ACCESS.equals(colName)) {
                            continue;
                        }
                        if (localRow.getDataByIndex(i) == null) {
                            values.putNull(colName);
                        } else {
                            values.put(colName, localRow.getDataByIndex(i));
                        }
                    }
                    values.put(DataTableColumns.ID, rowId);
                    values.putNull(DataTableColumns.CONFLICT_TYPE);
                    values.put(DataTableColumns.SYNC_STATE,
                            (localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES)
                                    ? SyncState.deleted.name()
                                    : SyncState.changed.name());

                    this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser,
                            locale, false);
                }
            } else {
                // figure out what the localRow conflict type should be...
                int localRowConflictType;
                if (state == SyncState.changed) {
                    // SyncState.changed and new change on server
                    localRowConflictType = ConflictType.LOCAL_UPDATED_UPDATED_VALUES;
                } else if (state == SyncState.new_row) {
                    // SyncState.new_row and record exists on server
                    // The 'new_row' case occurs if an insert is never ACKed but
                    // completes successfully on the server.
                    localRowConflictType = ConflictType.LOCAL_UPDATED_UPDATED_VALUES;
                } else if (state == SyncState.deleted) {
                    // SyncState.deleted and server is not deleting
                    localRowConflictType = ConflictType.LOCAL_DELETED_OLD_VALUES;
                } else if (state == SyncState.in_conflict) {
                    // SyncState.in_conflict and new change on server
                    // leave the local conflict type unchanged (retrieve it and
                    // use it).
                    localRowConflictType = localRowConflictTypeBeforeSync;
                } else {
                    throw new IllegalStateException("Unexpected state encountered");
                }

                boolean isDifferentPrivilegedFields = false;
                {
                    String[] privilegedColumns = new String[] { DataTableColumns.FILTER_TYPE,
                            DataTableColumns.FILTER_VALUE };
                    for (int i = 0; i < privilegedColumns.length; ++i) {
                        String colName = privilegedColumns[i];
                        String localValue = localRow.getDataByKey(colName);
                        String serverValue = serverValues.containsKey(colName)
                                ? serverValues.getAsString(colName)
                                : null;

                        ElementDataType dt = ElementDataType.string;
                        try {
                            ColumnDefinition cd = orderedColumns.find(colName);
                            dt = cd.getType().getDataType();
                        } catch (IllegalArgumentException e) {
                            // ignore
                        }

                        boolean sameValue = identicalValue(localValue, serverValue, dt);

                        if (!sameValue) {
                            isDifferentPrivilegedFields = true;
                            break;
                        }
                    }
                }
                boolean isDifferentExcludingPrivilegedFields = false;
                for (int i = 0; i < baseTable.getWidth(); ++i) {
                    String colName = baseTable.getElementKey(i);
                    if (DataTableColumns.ID.equals(colName) || DataTableColumns.CONFLICT_TYPE.equals(colName)
                            || DataTableColumns.EFFECTIVE_ACCESS.equals(colName)
                            || DataTableColumns.SYNC_STATE.equals(colName)
                            || DataTableColumns.ROW_ETAG.equals(colName)
                            || DataTableColumns.FILTER_TYPE.equals(colName)
                            || DataTableColumns.FILTER_VALUE.equals(colName)) {
                        // these values are ignored during this comparison
                        continue;
                    }
                    String localValue = localRow.getDataByKey(colName);
                    String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName)
                            : null;

                    ElementDataType dt = ElementDataType.string;
                    try {
                        ColumnDefinition cd = orderedColumns.find(colName);
                        dt = cd.getType().getDataType();
                    } catch (IllegalArgumentException e) {
                        // ignore
                    }

                    if (serverValue != null && dt == ElementDataType.bool) {
                        serverValue = Integer
                                .toString(DataHelper.boolToInt(serverValues.getAsBoolean(colName)));
                    }
                    boolean sameValue = identicalValue(localValue, serverValue, dt);

                    if (!sameValue) {
                        isDifferentExcludingPrivilegedFields = true;
                        break;
                    }
                }

                boolean hasNonNullDifferingServerAttachments = false;

                if (isDifferentExcludingPrivilegedFields || (state != SyncState.synced)) {
                    for (ColumnDefinition cd : orderedColumns.getColumnDefinitions()) {
                        // todo: does not handle array containing (types containing) rowpath elements
                        if (cd.isUnitOfRetention()
                                && cd.getType().getDataType().equals(ElementDataType.rowpath)) {
                            String uriFragment = serverValues.getAsString(cd.getElementKey());
                            String localUriFragment = localRow.getDataByKey(cd.getElementKey());
                            if (uriFragment != null) {
                                if (localUriFragment == null || !localUriFragment.equals(uriFragment)) {
                                    hasNonNullDifferingServerAttachments = true;
                                }
                            }
                        }
                    }
                }

                ContentValues values = new ContentValues(serverValues);

                if (isDifferentExcludingPrivilegedFields || isDifferentPrivilegedFields || isDifferentRowETag
                        || isServerRowDeleted) {

                    if (isDifferentExcludingPrivilegedFields || isServerRowDeleted
                            || (localRowConflictType == ConflictType.LOCAL_DELETED_OLD_VALUES)
                            || (accessContext.isPrivilegedUser && isDifferentPrivilegedFields)) {

                        this.placeRowIntoConflict(db, tableId, rowId, localRowConflictType);
                        serverValues.put(DataTableColumns.SYNC_STATE, SyncState.in_conflict.name());
                        serverValues.put(DataTableColumns.CONFLICT_TYPE,
                                (isServerRowDeleted ? ConflictType.SERVER_DELETED_OLD_VALUES
                                        : ConflictType.SERVER_UPDATED_UPDATED_VALUES));
                        this.privilegedInsertRowWithId(db, tableId, orderedColumns, serverValues, rowId,
                                activeUser, locale, false);
                    } else {
                        // just apply the server RowETag and filterScope to the local row
                        values.put(DataTableColumns.SYNC_STATE,
                                hasNonNullDifferingServerAttachments ? SyncState.synced_pending_files.name()
                                        : SyncState.synced.name());
                        values.putNull(DataTableColumns.CONFLICT_TYPE);

                        // move the local conflict back into the normal non-conflict (null) state
                        // set the sync state to "changed" temporarily (otherwise we can't update)

                        this.restoreRowFromConflict(db, tableId, rowId, SyncState.changed,
                                localRowConflictTypeBeforeSync);

                        this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser,
                                locale, false);
                    }
                } else {
                    // data matches -- update row to adjust sync state and conflict type
                    // if needed.
                    SyncState destState = (hasNonNullDifferingServerAttachments
                            || (state == SyncState.synced_pending_files)) ? SyncState.synced_pending_files
                                    : SyncState.synced;

                    if ((state != destState) || isDifferentRowETag
                            || (localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE) != null)) {
                        // todo: handle case where local row was in conflict
                        // server has now matched the local row's state. i.e.,
                        // update rowEtag, clear conflictType and adjust syncState on row.
                        values.put(DataTableColumns.SYNC_STATE, destState.name());
                        values.putNull(DataTableColumns.CONFLICT_TYPE);

                        // move the local conflict back into the normal non-conflict (null) state
                        // set the sync state to "changed" temporarily (otherwise we can't update)

                        this.restoreRowFromConflict(db, tableId, rowId, SyncState.changed,
                                localRowConflictTypeBeforeSync);

                        this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser,
                                locale, false);

                        WebLogger.getLogger(db.getAppName()).w(t,
                                "identical rows returned from server -- " + "SHOULDN'T THESE NOT HAPPEN?");
                    }
                }
            }
        }

        if (!dbWithinTransaction) {
            db.setTransactionSuccessful();
        }
    } finally {
        if (!dbWithinTransaction) {
            db.endTransaction();
        }
    }
}

From source file:org.opendatakit.services.database.utilities.ODKDatabaseImplUtils.java

/**
 * Delete any prior server conflict row.
 * Examine database and incoming server values to determine how to apply the
 * server values to the database. This might delete the existing row, update
 * it, or create a conflict row.// ww  w .  j a  va 2 s .c  o m
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param serverValues  field values for this row coming from server.
 *                      All fields must have values. The SyncState field
 *                      (a local field that does not come from the server)
 *                      should be "changed" or "deleted"
 * @param rowId
 * @param activeUser
 * @param rolesList  passed in to determine if the current user is a privileged user
 * @param locale
 */
public void privilegedPerhapsPlaceRowIntoConflictWithId(OdkConnectionInterface db, String tableId,
        OrderedColumns orderedColumns, ContentValues serverValues, String rowId, String activeUser,
        String rolesList, String locale) {

    AccessContext accessContext = getAccessContext(db, tableId, activeUser, rolesList);

    boolean dbWithinTransaction = db.inTransaction();
    try {
        if (!dbWithinTransaction) {
            db.beginTransactionNonExclusive();
        }

        // The rolesList of the activeUser does not impact the execution of this portion of
        // the action.

        // PLAN:
        // (1) delete any existing server-conflict row.
        // (2) if the local row was synced or synced pending changes, then either delete
        //     the row from the device (if server deleted row) or accept the
        //     server changes and place the row into synced_pending_changes status.
        // (3) if the local row was in the new_row state, move it into changed (prior
        //     to creating a conflict pair in step 5)
        // (4) if the local row was in conflict, restore it to its pre-conflict state
        //     (either deleted or changed).
        // (5) move the local row into conflict and insert the server row, placing it
        //     into conflict.
        // (6) enforce permissions on the change. This may immediately resolve conflict
        //     by taking the server changes or may overwrite the local row's permissions
        //     column values with those from the server.
        // (7) optimize the conflict -- perhaps immediately resolving it based upon
        //     whether the user actually has the privileges to do anything other than
        //     taking the server changes or if the changes only update the tracking
        //     and (perhaps) the metadata fields.

        // Do it...

        // (1) delete any existing server conflict row
        this.deleteServerConflictRowWithId(db, tableId, rowId);
        // fetch the current local (possibly-in-conflict) row
        BaseTable baseTable = this.privilegedGetRowsWithId(db, tableId, rowId, activeUser);
        // throws will abort the transaction, rolling back these changes
        if (baseTable.getNumberOfRows() == 0) {
            throw new IllegalArgumentException("no matching row found for server conflict");
        } else if (baseTable.getNumberOfRows() != 1) {
            throw new IllegalArgumentException("row has checkpoints or database is corrupt");
        }

        Row localRow = baseTable.getRowAtIndex(0);

        if (localRow.getDataByKey(DataTableColumns.SAVEPOINT_TYPE) == null) {
            throw new IllegalArgumentException("row has checkpoints");
        }

        boolean isServerRowDeleted = serverValues.getAsString(DataTableColumns.SYNC_STATE)
                .equals(SyncState.deleted.name());

        SyncState state;
        {
            String strSyncState = localRow.getDataByKey(DataTableColumns.SYNC_STATE);
            state = SyncState.valueOf(strSyncState);
        }
        SyncState initialLocalRowState = state;

        if (state == SyncState.synced || state == SyncState.synced_pending_files) {
            // (2) if the local row was synced or synced pending changes, then either delete
            //     the row from the device (if server deleted row) or accept the
            //     server changes and place the row into synced_pending_changes status.

            // the server's change should be applied locally.
            if (isServerRowDeleted) {
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else {
                // Local row needs to be updated with server values.
                //
                // detect and handle file attachment column changes
                if (state == SyncState.synced) {
                    // determine whether there are any changes in the columns that hold file attachments.
                    // if there are, then we need to transition into synced_pending_files. Otherwise, we
                    // can remain in the synced state.

                    for (ColumnDefinition cd : orderedColumns.getColumnDefinitions()) {
                        // todo: does not handle array containing (types containing) rowpath elements
                        if (cd.isUnitOfRetention()
                                && cd.getType().getDataType().equals(ElementDataType.rowpath)) {
                            String uriFragment = serverValues.getAsString(cd.getElementKey());
                            String localUriFragment = localRow.getDataByKey(cd.getElementKey());
                            if (uriFragment != null) {
                                if (localUriFragment == null || !localUriFragment.equals(uriFragment)) {
                                    state = SyncState.synced_pending_files;
                                    WebLogger.getLogger(db.getAppName()).i(t,
                                            "privilegedPerhapsPlaceRowIntoConflictWithId: revising from synced to "
                                                    + "synced_pending_files");
                                    break;
                                }
                            }
                        }
                    }
                }

                // update the row from the changes on the server
                serverValues.put(DataTableColumns.SYNC_STATE, state.name());
                serverValues.putNull(DataTableColumns.CONFLICT_TYPE);
                this.privilegedUpdateRowWithId(db, tableId, orderedColumns, serverValues, rowId, activeUser,
                        locale, false);
            }

            // In either case (delete or sync outcome), be sure to commit our changes!!!!
            if (!dbWithinTransaction) {
                db.setTransactionSuccessful();
            }
            return;

        } else if (state == SyncState.new_row) {
            // (3) if the local row was in the new_row state, move it into changed (prior
            //     to creating a conflict pair in step 5)

            // update the row with all of the local columns as-is, except the sync state.
            ContentValues values = new ContentValues();
            for (int i = 0; i < baseTable.getWidth(); ++i) {
                String colName = baseTable.getElementKey(i);
                if (DataTableColumns.EFFECTIVE_ACCESS.equals(colName)) {
                    continue;
                }
                if (localRow.getDataByIndex(i) == null) {
                    values.putNull(colName);
                } else {
                    values.put(colName, localRow.getDataByIndex(i));
                }
            }
            // move this into the changed state...
            state = SyncState.changed;
            values.put(DataTableColumns.SYNC_STATE, state.name());

            this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, accessContext.activeUser,
                    locale, false);

        } else if (state == SyncState.in_conflict) {
            // (4) if the local row was in conflict, restore it to its pre-conflict state
            //     (either deleted or changed).

            // we need to remove the in_conflict records that refer to the
            // prior state of the server
            String localRowConflictTypeBeforeSyncStr = localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE);
            if (localRowConflictTypeBeforeSyncStr == null) {
                // this row is in conflict. It MUST have a non-null conflict type.
                throw new IllegalStateException("conflict type is null on an in-conflict row");
            }

            int localRowConflictTypeBeforeSync = Integer.parseInt(localRowConflictTypeBeforeSyncStr);
            if (localRowConflictTypeBeforeSync == ConflictType.SERVER_DELETED_OLD_VALUES
                    || localRowConflictTypeBeforeSync == ConflictType.SERVER_UPDATED_UPDATED_VALUES) {
                // should be impossible
                throw new IllegalStateException("only the local conflict record should remain");
            }

            // move the local conflict back into the normal non-conflict (null) state
            // set the sync state to "changed" temporarily (otherwise we can't update)

            state = ((localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES)
                    ? SyncState.deleted
                    : SyncState.changed);

            this.restoreRowFromConflict(db, tableId, rowId, state, localRowConflictTypeBeforeSync);
        }
        // and drop through if SyncState is changed or deleted

        // (5) move the local row into conflict and insert the server row, placing it
        //     into conflict.
        int localRowConflictType = (state == SyncState.deleted) ? ConflictType.LOCAL_DELETED_OLD_VALUES
                : ConflictType.LOCAL_UPDATED_UPDATED_VALUES;

        this.placeRowIntoConflict(db, tableId, rowId, localRowConflictType);

        serverValues.put(DataTableColumns.SYNC_STATE, SyncState.in_conflict.name());
        serverValues.put(DataTableColumns.CONFLICT_TYPE,
                (isServerRowDeleted ? ConflictType.SERVER_DELETED_OLD_VALUES
                        : ConflictType.SERVER_UPDATED_UPDATED_VALUES));
        this.privilegedInsertRowWithId(db, tableId, orderedColumns, serverValues, rowId, activeUser, locale,
                false);

        // To get here, the original local row was in some state other than synced or
        // synced_pending_files. Therefore, any non-empty rowpath fields should drive
        // the row into the synced_pending_files state if we resolve the row early.

        // (6) enforce permissions on the change. This may immediately resolve conflict
        //     by taking the server changes or may overwrite the local row's permissions
        //     column values with those from the server.
        if (enforcePermissionsAndOptimizeConflictProcessing(db, tableId, orderedColumns, rowId,
                initialLocalRowState, accessContext, locale)) {
            // and...
            // (7) optimize the conflict -- perhaps immediately resolving it based upon
            //     whether the user actually has the privileges to do anything other than
            //     taking the server changes or if the changes only update the tracking
            //     and (perhaps) the metadata fields.
            optimizeConflictProcessing(db, tableId, orderedColumns, rowId, initialLocalRowState, accessContext,
                    locale);
        }

        if (!dbWithinTransaction) {
            db.setTransactionSuccessful();
        }
    } finally {
        if (!dbWithinTransaction) {
            db.endTransaction();
        }
    }
}