@Override public void handleSuccess(GlobalSession globalSession) { Logger.info(LOG_TAG, "GlobalSession indicated success."); Logger.debug(LOG_TAG, "Prefs target: " + globalSession.config.prefsPath); globalSession.config.persistToPrefs(); notifyMonitor(); }
/** * Create a HistoryRecord object from a cursor row. * * @return a HistoryRecord, or null if this row would produce an invalid record (e.g., with a null * URI or no visits). */ public static HistoryRecord historyFromMirrorCursor(Cursor cur) { final String guid = getStringFromCursor(cur, BrowserContract.SyncColumns.GUID); if (guid == null) { Logger.debug(LOG_TAG, "Skipping history record with null GUID."); return null; } final String historyURI = getStringFromCursor(cur, BrowserContract.History.URL); if (!isValidHistoryURI(historyURI)) { Logger.debug( LOG_TAG, "Skipping history record " + guid + " with unwanted/invalid URI " + historyURI); return null; } final long visitCount = getLongFromCursor(cur, BrowserContract.History.VISITS); if (visitCount <= 0) { Logger.debug(LOG_TAG, "Skipping history record " + guid + " with <= 0 visit count."); return null; } final String collection = "history"; final long lastModified = getLongFromCursor(cur, BrowserContract.SyncColumns.DATE_MODIFIED); final boolean deleted = getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1 ? true : false; final HistoryRecord rec = new HistoryRecord(guid, collection, lastModified, deleted); rec.androidID = getLongFromCursor(cur, BrowserContract.History._ID); rec.fennecDateVisited = getLongFromCursor(cur, BrowserContract.History.DATE_LAST_VISITED); rec.fennecVisitCount = visitCount; rec.histURI = historyURI; rec.title = getStringFromCursor(cur, BrowserContract.History.TITLE); return logHistory(rec); }
/** * Override these in your subclasses. * * @return true if this stage should be executed. * @throws MetaGlobalException */ protected boolean isEnabled() throws MetaGlobalException { EngineSettings engineSettings = null; try { engineSettings = getEngineSettings(); } catch (Exception e) { Logger.warn( LOG_TAG, "Unable to get engine settings for " + this + ": fetching config failed.", e); // Fall through; null engineSettings will pass below. } // We can be disabled by the server's meta/global record, or malformed in the server's // meta/global record. // We catch the subclasses of MetaGlobalException to trigger various resets and wipes in // execute(). boolean enabledInMetaGlobal = session.engineIsEnabled(this.getEngineName(), engineSettings); if (!enabledInMetaGlobal) { Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled by server meta/global."); return false; } // We can also be disabled just for this sync. if (session.config.stagesToSync == null) { return true; } boolean enabledThisSync = session.config.stagesToSync.contains( this.getEngineName()); // For ServerSyncStage, stage name == engine name. if (!enabledThisSync) { Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled just for this sync."); } return enabledThisSync; }
@Override public void execute(final JPakeClient jClient) { Logger.debug(LOG_TAG, "Retrieving next message."); final GetRequestStageDelegate callbackDelegate = new GetRequestStageDelegate() { @Override public void handleSuccess(HttpResponse response) { if (jClient.finished) { Logger.debug(LOG_TAG, "Finished; returning."); return; } JPakeResponse res = new JPakeResponse(response); Header etagHeader = response.getFirstHeader("etag"); if (etagHeader == null) { Logger.error(LOG_TAG, "Server did not supply ETag."); jClient.abort(Constants.JPAKE_ERROR_SERVER); return; } jClient.theirEtag = etagHeader.getValue(); try { jClient.jIncoming = res.jsonObjectBody(); } catch (Exception e) { Logger.error(LOG_TAG, "Illegal state.", e); jClient.abort(Constants.JPAKE_ERROR_INVALID); return; } Logger.debug(LOG_TAG, "incoming message: " + jClient.jIncoming.toJSONString()); jClient.runNextStage(); } @Override public void handleFailure(String error) { jClient.abort(error); } @Override public void handleError(Exception e) { Logger.error(LOG_TAG, "Threw HTTP exception.", e); jClient.abort(Constants.JPAKE_ERROR_NETWORK); } }; Resource httpRequest; try { httpRequest = createGetRequest(callbackDelegate, jClient); } catch (URISyntaxException e) { Logger.error(LOG_TAG, "Incorrect URI syntax.", e); jClient.abort(Constants.JPAKE_ERROR_INVALID); return; } Logger.debug(LOG_TAG, "Scheduling GET request."); getStepTimerTask = new GetStepTimerTask(httpRequest); timerScheduler.schedule(getStepTimerTask, jClient.jpakePollInterval); }
/** * Override this in your subclasses to return values to save between sessions. Note that * RepositorySession automatically bumps the timestamp to the time the last sync began. If * unbundled but not begun, this will be the same as the value in the input bundle. * * <p>The Synchronizer most likely wants to bump the bundle timestamp to be a value return from a * fetch call. * * @param optional * @return */ protected RepositorySessionBundle getBundle(RepositorySessionBundle optional) { Logger.debug(LOG_TAG, "RepositorySession.getBundle(optional)."); // Why don't we just persist the old bundle? RepositorySessionBundle bundle = (optional == null) ? new RepositorySessionBundle() : optional; bundle.put("timestamp", this.lastSyncTimestamp); Logger.debug(LOG_TAG, "Setting bundle timestamp to " + this.lastSyncTimestamp); return bundle; }
public void finish(final RepositorySessionFinishDelegate delegate) { if (this.status == SessionStatus.ACTIVE) { this.status = SessionStatus.DONE; delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle(null)); } else { Logger.error(LOG_TAG, "Tried to finish() an unstarted or already finished session"); Exception e = new InvalidSessionTransitionException(null); delegate.deferredFinishDelegate(delegateQueue).onFinishFailed(e); } Logger.info(LOG_TAG, "Shutting down work queues."); // storeWorkQueue.shutdown(); // delegateQueue.shutdown(); }
/** * Rename mobile folders to "mobile", both in and out. The other half of this logic lives in * {@link #computeParentFields(BookmarkRecord, String, String)}, where the parent name of a record * is set from {@link #SPECIAL_GUIDS_MAP} rather than from source data. * * <p>Apply this approach generally for symmetry. */ @Override protected void fixupRecord(Record record) { final BookmarkRecord r = (BookmarkRecord) record; final String parentName = SPECIAL_GUIDS_MAP.get(r.parentID); if (parentName == null) { return; } if (Logger.logVerbose(LOG_TAG)) { Logger.trace( LOG_TAG, "Replacing parent name \"" + r.parentName + "\" with \"" + parentName + "\"."); } r.parentName = parentName; }
private static BookmarkRecord logBookmark(BookmarkRecord rec) { try { Logger.debug( LOG_TAG, "Returning " + (rec.deleted ? "deleted " : "") + "bookmark record " + rec.guid + " (" + rec.androidID + ", parent " + rec.parentID + ")"); if (!rec.deleted && Logger.LOG_PERSONAL_INFORMATION) { Logger.pii(LOG_TAG, "> Parent name: " + rec.parentName); Logger.pii(LOG_TAG, "> Title: " + rec.title); Logger.pii(LOG_TAG, "> Type: " + rec.type); Logger.pii(LOG_TAG, "> URI: " + rec.bookmarkURI); Logger.pii(LOG_TAG, "> Position: " + rec.androidPosition); if (rec.isFolder()) { Logger.pii( LOG_TAG, "FOLDER: Children are " + (rec.children == null ? "null" : rec.children.toJSONString())); } } } catch (Exception e) { Logger.debug(LOG_TAG, "Exception logging bookmark record " + rec, e); } return rec; }
/** * We synced this engine! Persist timestamps and advance the session. * * @param synchronizer the <code>Synchronizer</code> that succeeded. */ @Override public void onSynchronized(Synchronizer synchronizer) { Logger.debug(LOG_TAG, "onSynchronized."); SynchronizerConfiguration newConfig = synchronizer.save(); if (newConfig != null) { persistConfig(newConfig); } else { Logger.warn(LOG_TAG, "Didn't get configuration from synchronizer after success."); } Logger.info(LOG_TAG, "Advancing session."); session.advance(); }
@SuppressWarnings("unchecked") private void finishUp() { try { flushQueues(); Logger.debug( LOG_TAG, "Have " + parentToChildArray.size() + " folders whose children might need repositioning."); for (Entry<String, JSONArray> entry : parentToChildArray.entrySet()) { String guid = entry.getKey(); JSONArray onServer = entry.getValue(); try { final long folderID = getIDForGUID(guid); final JSONArray inDB = new JSONArray(); final boolean clean = getChildrenArray(folderID, false, inDB); final boolean sameArrays = Utils.sameArrays(onServer, inDB); // If the local children and the remote children are already // the same, then we don't need to bump the modified time of the // parent: we wouldn't upload a different record, so avoid the cycle. if (!sameArrays) { int added = 0; for (Object o : inDB) { if (!onServer.contains(o)) { onServer.add(o); added++; } } Logger.debug(LOG_TAG, "Added " + added + " items locally."); Logger.debug(LOG_TAG, "Untracking and bumping " + guid + "(" + folderID + ")"); dataAccessor.bumpModified(folderID, now()); untrackGUID(guid); } // If the arrays are different, or they're the same but not flushed to disk, // write them out now. if (!sameArrays || !clean) { dataAccessor.updatePositions(new ArrayList<String>(onServer)); } } catch (Exception e) { Logger.warn(LOG_TAG, "Error repositioning children for " + guid, e); } } } finally { super.storeDone(); } }
private String getParentName(String parentGUID) throws ParentNotFoundException, NullCursorException { if (parentGUID == null) { return ""; } if (SPECIAL_GUIDS_MAP.containsKey(parentGUID)) { return SPECIAL_GUIDS_MAP.get(parentGUID); } // Get parent name from database. String parentName = ""; Cursor name = dataAccessor.fetch(new String[] {parentGUID}); try { name.moveToFirst(); if (!name.isAfterLast()) { parentName = RepoUtils.getStringFromCursor(name, BrowserContract.Bookmarks.TITLE); } else { Logger.error( LOG_TAG, "Couldn't find record with guid '" + parentGUID + "' when looking for parent name."); throw new ParentNotFoundException(null); } } finally { name.close(); } return parentName; }
/** Implement method of BookmarksInsertionManager.BookmarkInserter. */ @Override public boolean insertFolder(BookmarkRecord record) { // A folder that is *not* deleted needs its androidID updated, so that // updateBookkeeping can re-parent, etc. Record toStore = prepareRecord(record); try { Uri recordURI = dbHelper.insert(toStore); if (recordURI == null) { delegate.onRecordStoreFailed( new RuntimeException("Got null URI inserting folder with guid " + toStore.guid + "."), record.guid); return false; } toStore.androidID = ContentUris.parseId(recordURI); Logger.debug( LOG_TAG, "Inserted folder with guid " + toStore.guid + " as androidID " + toStore.androidID); updateBookkeeping(toStore); } catch (Exception e) { delegate.onRecordStoreFailed(e, record.guid); return false; } trackRecord(toStore); delegate.onRecordStoreSucceeded(record.guid); return true; }
public Cursor checkNullCursor(String logLabel, Cursor cursor) throws NullCursorException { if (cursor == null) { Logger.error(tag, "Got null cursor exception in " + logLabel); throw new NullCursorException(null); } return cursor; }
/** * Synchronously perform the shared work of beginning. Throws on failure. * * @throws InvalidSessionTransitionException */ protected void sharedBegin() throws InvalidSessionTransitionException { if (this.status == SessionStatus.UNSTARTED) { this.status = SessionStatus.ACTIVE; } else { Logger.error(LOG_TAG, "Tried to begin() an already active or finished session"); throw new InvalidSessionTransitionException(null); } }
private long getIDForGUID(String guid) { Long id = parentGuidToIDMap.get(guid); if (id == null) { Logger.warn(LOG_TAG, "Couldn't find local ID for GUID " + guid); return -1; } return id.longValue(); }
// Create a BookmarkRecord object from a cursor on a row containing a Fennec bookmark. public static BookmarkRecord bookmarkFromMirrorCursor( Cursor cur, String parentGUID, String parentName, JSONArray children) { final String collection = "bookmarks"; final String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID); final long lastModified = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.DATE_MODIFIED); final boolean deleted = isDeleted(cur); BookmarkRecord rec = new BookmarkRecord(guid, collection, lastModified, deleted); // No point in populating it. if (deleted) { return logBookmark(rec); } int rowType = getTypeFromCursor(cur); String typeString = BrowserContractHelpers.typeStringForCode(rowType); if (typeString == null) { Logger.warn(LOG_TAG, "Unsupported type code " + rowType); return null; } else { Logger.trace(LOG_TAG, "Record " + guid + " has type " + typeString); } rec.type = typeString; rec.title = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.TITLE); rec.bookmarkURI = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.URL); rec.description = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.DESCRIPTION); rec.tags = RepoUtils.getJSONArrayFromCursor(cur, BrowserContract.Bookmarks.TAGS); rec.keyword = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.KEYWORD); rec.androidID = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID); rec.androidPosition = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.POSITION); rec.children = children; // Need to restore the parentId since it isn't stored in content provider. // We also take this opportunity to fix up parents for special folders, // allowing us to map between the hierarchies used by Fennec and Places. BookmarkRecord withParentFields = computeParentFields(rec, parentGUID, parentName); if (withParentFields == null) { // Oh dear. Something went wrong. return null; } return logBookmark(withParentFields); }
public void update(String guid, Record newRecord) { String where = BrowserContract.SyncColumns.GUID + " = ?"; String[] args = new String[] {guid}; ContentValues cv = getContentValues(newRecord); int updated = context.getContentResolver().update(getUri(), cv, where, args); if (updated != 1) { Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + guid); } }
public static JSONArray getJSONArrayFromCursor(Cursor cur, String colId) { String jsonArrayAsString = getStringFromCursor(cur, colId); if (jsonArrayAsString == null) { return new JSONArray(); } try { return ExtendedJSONObject.parseJSONArray(getStringFromCursor(cur, colId)); } catch (NonArrayJSONException e) { Logger.error(LOG_TAG, "JSON parsing error for " + colId, e); return null; } catch (IOException e) { Logger.error(LOG_TAG, "JSON parsing error for " + colId, e); return null; } catch (ParseException e) { Logger.error(LOG_TAG, "JSON parsing error for " + colId, e); return null; } }
/** * Synchronously wipe the server. * * <p>Logs and re-throws an exception on failure. */ public void wipeServer() throws Exception { final WipeWaiter monitor = new WipeWaiter(); final Runnable doWipe = new Runnable() { @Override public void run() { wipeServer( session, new WipeServerDelegate() { @Override public void onWiped(long timestamp) { synchronized (monitor) { monitor.notify(); } } @Override public void onWipeFailed(Exception e) { synchronized (monitor) { monitor.notify(e, false); } } }); } }; final Thread wiping = new Thread(doWipe); synchronized (monitor) { wiping.start(); try { monitor.wait(); } catch (InterruptedException e) { Logger.error(LOG_TAG, "Server wipe interrupted."); } } if (!monitor.wipeSucceeded) { Logger.error(LOG_TAG, "Failed to wipe server."); throw monitor.error; } Logger.info(LOG_TAG, "Wiping server complete."); }
/** * Remove matching records from the database entirely, i.e., do not set a deleted flag, delete * entirely. * * @param guid The GUID of the record to be deleted. * @return The number of records deleted. */ public int purgeGuid(String guid) { String where = BrowserContract.SyncColumns.GUID + " = ?"; String[] args = new String[] {guid}; int deleted = context.getContentResolver().delete(getUri(), where, args); if (deleted != 1) { Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " records for guid " + guid); } return deleted; }
/** * Reset timestamps and possibly set syncID. * * @param syncID if non-null, new syncID to persist. */ protected void resetLocal(String syncID) { // Clear both timestamps. SynchronizerConfiguration config; try { config = this.getConfig(); } catch (Exception e) { Logger.warn(LOG_TAG, "Unable to reset " + this + ": fetching config failed.", e); return; } if (syncID != null) { config.syncID = syncID; Logger.info(LOG_TAG, "Setting syncID for " + this + " to '" + syncID + "'."); } config.localBundle.setTimestamp(0L); config.remoteBundle.setTimestamp(0L); persistConfig(config); Logger.info(LOG_TAG, "Reset timestamps for " + this); }
@Override public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException { // Check for the existence of special folders // and insert them if they don't exist. Cursor cur; try { Logger.debug(LOG_TAG, "Check and build special GUIDs."); dataAccessor.checkAndBuildSpecialGuids(); cur = dataAccessor.getGuidsIDsForFolders(); Logger.debug(LOG_TAG, "Got GUIDs for folders."); } catch (android.database.sqlite.SQLiteConstraintException e) { Logger.error(LOG_TAG, "Got sqlite constraint exception working with Fennec bookmark DB.", e); delegate.onBeginFailed(e); return; } catch (NullCursorException e) { delegate.onBeginFailed(e); return; } catch (Exception e) { delegate.onBeginFailed(e); return; } // To deal with parent mapping of bookmarks we have to do some // hairy stuff. Here's the setup for it. Logger.debug(LOG_TAG, "Preparing folder ID mappings."); // Fake our root. Logger.debug(LOG_TAG, "Tracking places root as ID 0."); parentIDToGuidMap.put(0L, "places"); parentGuidToIDMap.put("places", 0L); try { cur.moveToFirst(); while (!cur.isAfterLast()) { String guid = getGUID(cur); long id = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID); parentGuidToIDMap.put(guid, id); parentIDToGuidMap.put(id, guid); Logger.debug(LOG_TAG, "GUID " + guid + " maps to " + id); cur.moveToNext(); } } finally { cur.close(); } deletionManager = new BookmarksDeletionManager(dataAccessor, DEFAULT_DELETION_FLUSH_THRESHOLD); // We just crawled the database enumerating all folders; we'll start the // insertion manager with exactly these folders as the known parents (the // collection is copied) in the manager constructor. insertionManager = new BookmarksInsertionManager( DEFAULT_INSERTION_FLUSH_THRESHOLD, parentGuidToIDMap.keySet(), this); Logger.debug(LOG_TAG, "Done with initial setup of bookmarks session."); super.begin(delegate); }
/** * We failed to sync this engine! Do not persist timestamps (which means that the next sync will * include this sync's data), but do advance the session (if we didn't get a Retry-After header). * * @param synchronizer the <code>Synchronizer</code> that failed. */ @Override public void onSynchronizeFailed( Synchronizer synchronizer, Exception lastException, String reason) { Logger.warn(LOG_TAG, "Synchronize failed: " + reason, lastException); // This failure could be due to a 503 or a 401 and it could have headers. // Interrogate the headers but only abort the global session if Retry-After header is set. if (lastException instanceof HTTPFailureException) { SyncStorageResponse response = ((HTTPFailureException) lastException).response; if (response.retryAfterInSeconds() > 0) { session.handleHTTPError(response, reason); // Calls session.abort(). return; } else { session.interpretHTTPFailure(response.httpResponse()); // Does not call session.abort(). } } Logger.info(LOG_TAG, "Advancing session even though stage failed. Timestamps not persisted."); session.advance(); }
/** * Asynchronously request an immediate sync, optionally syncing only the given named stages. * * <p>Returns immediately. * * @param account the Android <code>Account</code> instance to sync. * @param stageNames stage names to sync, or <code>null</code> to sync all known stages. */ public static void requestImmediateSync(final Account account, final String[] stageNames) { if (account == null) { Logger.warn(LOG_TAG, "Not requesting immediate sync because Android Account is null."); return; } final Bundle extras = new Bundle(); Utils.putStageNamesToSync(extras, stageNames, null); extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); ContentResolver.requestSync(account, BrowserContract.AUTHORITY, extras); }
public void storeDone(final long end) { Logger.debug(LOG_TAG, "Scheduling onStoreCompleted for after storing is done."); Runnable command = new Runnable() { @Override public void run() { delegate.onStoreCompleted(end); } }; storeWorkQueue.execute(command); }
@Override protected void storeRecordDeletion(final Record record, final Record existingRecord) { if (SPECIAL_GUIDS_MAP.containsKey(record.guid)) { Logger.debug(LOG_TAG, "Told to delete record " + record.guid + ". Ignoring."); return; } final BookmarkRecord bookmarkRecord = (BookmarkRecord) record; final BookmarkRecord existingBookmark = (BookmarkRecord) existingRecord; final boolean isFolder = existingBookmark.isFolder(); final String parentGUID = existingBookmark.parentID; deletionManager.deleteRecord(bookmarkRecord.guid, isFolder, parentGUID); }
/** * Produce a record that is some combination of the remote and local records provided. * * <p>The returned record must be produced without mutating either remoteRecord or localRecord. It * is acceptable to return either remoteRecord or localRecord if no modifications are to be * propagated. * * <p>The returned record *should* have the local androidID and the remote GUID, and some optional * merge of data from the two records. * * <p>This method can be called with records that are identical, or differ in any regard. * * <p>This method will not be called if: * * <p>* either record is marked as deleted, or * there is no local mapping for a new remote * record. * * <p>Otherwise, it will be called precisely once. * * <p>Side-effects (e.g., for transactional storage) can be hooked in here. * * @param remoteRecord The record retrieved from upstream, already adjusted for clock skew. * @param localRecord The record retrieved from local storage. * @param lastRemoteRetrieval The timestamp of the last retrieved set of remote records, adjusted * for clock skew. * @param lastLocalRetrieval The timestamp of the last retrieved set of local records. * @return A Record instance to apply, or null to apply nothing. */ protected Record reconcileRecords( final Record remoteRecord, final Record localRecord, final long lastRemoteRetrieval, final long lastLocalRetrieval) { Logger.debug( LOG_TAG, "Reconciling remote " + remoteRecord.guid + " against local " + localRecord.guid); if (localRecord.equalPayloads(remoteRecord)) { if (remoteRecord.lastModified > localRecord.lastModified) { Logger.debug(LOG_TAG, "Records are equal. No record application needed."); return null; } // Local wins. return null; } // TODO: Decide what to do based on: // * Which of the two records is modified; // * Whether they are equal or congruent; // * The modified times of each record (interpreted through the lens of clock skew); // * ... boolean localIsMoreRecent = localRecord.lastModified > remoteRecord.lastModified; Logger.debug(LOG_TAG, "Local record is more recent? " + localIsMoreRecent); Record donor = localIsMoreRecent ? localRecord : remoteRecord; // Modify the local record to match the remote record's GUID and values. // Preserve the local Android ID, and merge data where possible. // It sure would be nice if copyWithIDs didn't give a shit about androidID, mm? Record out = donor.copyWithIDs(remoteRecord.guid, localRecord.androidID); // We don't want to upload the record if the remote record was // applied without changes. // This logic will become more complicated as reconciling becomes smarter. if (!localIsMoreRecent) { trackRecord(out); } return out; }
public static BookmarkRecord computeParentFields( BookmarkRecord rec, String suggestedParentGUID, String suggestedParentName) { final String guid = rec.guid; if (guid == null) { // Oh dear. Logger.error(LOG_TAG, "No guid in computeParentFields!"); return null; } String realParent = SPECIAL_GUID_PARENTS.get(guid); if (realParent == null) { // No magic parent. Use whatever the caller suggests. realParent = suggestedParentGUID; } else { Logger.debug( LOG_TAG, "Ignoring suggested parent ID " + suggestedParentGUID + " for " + guid + "; using " + realParent); } if (realParent == null) { // Oh dear. Logger.error(LOG_TAG, "No parent for record " + guid); return null; } // Always set the parent name for special folders back to default. String parentName = SPECIAL_GUIDS_MAP.get(realParent); if (parentName == null) { parentName = suggestedParentName; } rec.parentID = realParent; rec.parentName = parentName; return rec; }
/** Asynchronously wipe collection on server. */ protected void wipeServer( final CredentialsSource credentials, final WipeServerDelegate wipeDelegate) { SyncStorageRequest request; try { request = new SyncStorageRequest(session.config.collectionURI(getCollection())); } catch (URISyntaxException ex) { Logger.warn(LOG_TAG, "Invalid URI in wipeServer."); wipeDelegate.onWipeFailed(ex); return; } request.delegate = new SyncStorageRequestDelegate() { @Override public String ifUnmodifiedSince() { return null; } @Override public void handleRequestSuccess(SyncStorageResponse response) { BaseResource.consumeEntity(response); resetLocal(); wipeDelegate.onWiped(response.normalizedWeaveTimestamp()); } @Override public void handleRequestFailure(SyncStorageResponse response) { Logger.warn( LOG_TAG, "Got request failure " + response.getStatusCode() + " in wipeServer."); // Process HTTP failures here to pick up backoffs, etc. session.interpretHTTPFailure(response.httpResponse()); BaseResource.consumeEntity( response); // The exception thrown should not need the body of the response. wipeDelegate.onWipeFailed(new HTTPFailureException(response)); } @Override public void handleRequestError(Exception ex) { Logger.warn(LOG_TAG, "Got exception in wipeServer.", ex); wipeDelegate.onWipeFailed(ex); } @Override public String credentials() { return credentials.credentials(); } }; request.delete(); }
@Override protected void updateBookkeeping(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException { super.updateBookkeeping(record); BookmarkRecord bmk = (BookmarkRecord) record; // If record is folder, update maps and re-parent children if necessary. if (!bmk.isFolder()) { Logger.debug(LOG_TAG, "Not a folder. No bookkeeping."); return; } Logger.debug(LOG_TAG, "Updating bookkeeping for folder " + record.guid); // Mappings between ID and GUID. // TODO: update our persisted children arrays! // TODO: if our Android ID just changed, replace parents for all of our children. parentGuidToIDMap.put(bmk.guid, bmk.androidID); parentIDToGuidMap.put(bmk.androidID, bmk.guid); JSONArray childArray = bmk.children; if (Logger.logVerbose(LOG_TAG)) { Logger.trace(LOG_TAG, bmk.guid + " has children " + childArray.toJSONString()); } parentToChildArray.put(bmk.guid, childArray); // Re-parent. if (missingParentToChildren.containsKey(bmk.guid)) { for (String child : missingParentToChildren.get(bmk.guid)) { // This might return -1; that's OK, the bookmark will // be properly repositioned later. long position = childArray.indexOf(child); dataAccessor.updateParentAndPosition(child, bmk.androidID, position); needsReparenting--; } missingParentToChildren.remove(bmk.guid); } }