@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(); }
/** * 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); }
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(); }
/** * 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(); }
/** * 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."); }
/** * 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(); }
@Override public void execute() throws NoSuchStageException { final String name = getEngineName(); Logger.debug(LOG_TAG, "Starting execute for " + name); try { if (!this.isEnabled()) { Logger.info(LOG_TAG, "Skipping stage " + name + "."); session.advance(); return; } } catch (MetaGlobalException.MetaGlobalMalformedSyncIDException e) { // Bad engine syncID. This should never happen. Wipe the server. try { session.updateMetaGlobalWith( name, new EngineSettings(Utils.generateGuid(), this.getStorageVersion())); Logger.info( LOG_TAG, "Wiping server because malformed engine sync ID was found in meta/global."); wipeServer(); Logger.info(LOG_TAG, "Wiped server after malformed engine sync ID found in meta/global."); } catch (Exception ex) { session.abort( ex, "Failed to wipe server after malformed engine sync ID found in meta/global."); } } catch (MetaGlobalException.MetaGlobalMalformedVersionException e) { // Bad engine version. This should never happen. Wipe the server. try { session.updateMetaGlobalWith( name, new EngineSettings(Utils.generateGuid(), this.getStorageVersion())); Logger.info( LOG_TAG, "Wiping server because malformed engine version was found in meta/global."); wipeServer(); Logger.info(LOG_TAG, "Wiped server after malformed engine version found in meta/global."); } catch (Exception ex) { session.abort( ex, "Failed to wipe server after malformed engine version found in meta/global."); } } catch (MetaGlobalException.MetaGlobalStaleClientSyncIDException e) { // Our syncID is wrong. Reset client and take the server syncID. Logger.warn( LOG_TAG, "Remote engine syncID different from local engine syncID:" + " resetting local engine and assuming remote engine syncID."); this.resetLocal(e.serverSyncID); } catch (MetaGlobalException e) { session.abort(e, "Inappropriate meta/global; refusing to execute " + name + " stage."); return; } Synchronizer synchronizer; try { synchronizer = this.getConfiguredSynchronizer(session); } catch (NoCollectionKeysSetException e) { session.abort(e, "No CollectionKeys."); return; } catch (URISyntaxException e) { session.abort(e, "Invalid URI syntax for server repository."); return; } catch (NonObjectJSONException e) { session.abort(e, "Invalid persisted JSON for config."); return; } catch (IOException e) { session.abort(e, "Invalid persisted JSON for config."); return; } catch (ParseException e) { session.abort(e, "Invalid persisted JSON for config."); return; } Logger.debug(LOG_TAG, "Invoking synchronizer."); synchronizer.synchronize(session.getContext(), this); Logger.debug(LOG_TAG, "Reached end of execute."); }
/** * Synchronously wipe this stage by instantiating a local repository session and wiping that. * * <p>Logs and re-throws an exception on failure. */ @Override public void wipeLocal() throws Exception { // Reset, then clear data. this.resetLocal(); final WipeWaiter monitor = new WipeWaiter(); final Context context = session.getContext(); final Repository r = this.getLocalRepository(); final Runnable doWipe = new Runnable() { @Override public void run() { r.createSession( new RepositorySessionCreationDelegate() { @Override public void onSessionCreated(final RepositorySession session) { try { session.begin( new RepositorySessionBeginDelegate() { @Override public void onBeginSucceeded(final RepositorySession session) { session.wipe( new RepositorySessionWipeDelegate() { @Override public void onWipeSucceeded() { try { session.finish( new RepositorySessionFinishDelegate() { @Override public void onFinishSucceeded( RepositorySession session, RepositorySessionBundle bundle) { // Hurrah. synchronized (monitor) { monitor.notify(); } } @Override public void onFinishFailed(Exception ex) { // Assume that no finish => no wipe. synchronized (monitor) { monitor.notify(ex, true); } } @Override public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) { return this; } }); } catch (InactiveSessionException e) { // Cannot happen. Call for safety. synchronized (monitor) { monitor.notify(e, true); } } } @Override public void onWipeFailed(Exception ex) { session.abort(); synchronized (monitor) { monitor.notify(ex, true); } } @Override public RepositorySessionWipeDelegate deferredWipeDelegate( ExecutorService executor) { return this; } }); } @Override public void onBeginFailed(Exception ex) { session.abort(); synchronized (monitor) { monitor.notify(ex, true); } } @Override public RepositorySessionBeginDelegate deferredBeginDelegate( ExecutorService executor) { return this; } }); } catch (InvalidSessionTransitionException e) { session.abort(); synchronized (monitor) { monitor.notify(e, true); } } } @Override public void onSessionCreateFailed(Exception ex) { synchronized (monitor) { monitor.notify(ex, false); } } @Override public RepositorySessionCreationDelegate deferredCreationDelegate() { return this; } }, context); } }; final Thread wiping = new Thread(doWipe); synchronized (monitor) { wiping.start(); try { monitor.wait(); } catch (InterruptedException e) { Logger.error(LOG_TAG, "Wipe interrupted."); } } if (!monitor.sessionSucceeded) { Logger.error(LOG_TAG, "Failed to create session for wipe."); throw monitor.error; } if (!monitor.wipeSucceeded) { Logger.error(LOG_TAG, "Failed to wipe session."); throw monitor.error; } Logger.info(LOG_TAG, "Wiping stage complete."); }
public void purgeDeleted() throws NullCursorException { String where = BrowserContract.SyncColumns.IS_DELETED + "= 1"; Uri uri = getUri(); Logger.info(LOG_TAG, "Purging deleted from: " + uri); context.getContentResolver().delete(uri, where, null); }
// Implementing GlobalSession callbacks. @Override public void handleError(GlobalSession globalSession, Exception ex) { Logger.info(LOG_TAG, "GlobalSession indicated error."); this.processException(globalSession, ex); }
@Override public void onPerformSync( final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) { Logger.resetLogging(); Utils.reseedSharedRandom(); // Make sure we don't work with the same random seed for too long. // Set these so that we don't need to thread them through assorted calls and callbacks. this.syncResult = syncResult; this.localAccount = account; SyncAccountParameters params; try { params = SyncAccounts.blockingFromAndroidAccountV0( mContext, AccountManager.get(mContext), this.localAccount); } catch (Exception e) { // Updates syncResult and (harmlessly) calls notifyMonitor(). processException(null, e); return; } // params and the following fields are non-null at this point. final String username = params.username; // Encoded with Utils.usernameFromAccount. final String password = params.password; final String serverURL = params.serverURL; final String syncKey = params.syncKey; final AtomicBoolean setNextSync = new AtomicBoolean(true); final SyncAdapter self = this; final Runnable runnable = new Runnable() { @Override public void run() { Logger.trace(LOG_TAG, "AccountManagerCallback invoked."); // TODO: N.B.: Future must not be used on the main thread. try { Logger.info( LOG_TAG, "Syncing account named " + account.name + " for authority " + authority + "."); // We dump this information right away to help with debugging. Logger.debug(LOG_TAG, "Username: "******"Server: " + serverURL); if (Logger.LOG_PERSONAL_INFORMATION) { Logger.debug(LOG_TAG, "Password: "******"Sync key: " + syncKey); } else { Logger.debug(LOG_TAG, "Password? " + (password != null)); Logger.debug(LOG_TAG, "Sync key? " + (syncKey != null)); } // Support multiple accounts by mapping each server/account pair to a branch of the // shared preferences space. final String product = GlobalConstants.BROWSER_INTENT_PACKAGE; final String profile = Constants.DEFAULT_PROFILE; final long version = SyncConfiguration.CURRENT_PREFS_VERSION; self.accountSharedPreferences = Utils.getSharedPreferences( mContext, product, username, serverURL, profile, version); Logger.info( LOG_TAG, "Client is named '" + getClientName() + "'" + ", has client guid " + getAccountGUID() + ", and has " + getClientsCount() + " clients."); thisSyncIsForced = (extras != null) && (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)); long delay = delayMilliseconds(); if (delay > 0) { if (thisSyncIsForced) { Logger.info( LOG_TAG, "Forced sync: overruling remaining backoff of " + delay + "ms."); } else { Logger.info(LOG_TAG, "Not syncing: must wait another " + delay + "ms."); long remainingSeconds = delay / 1000; syncResult.delayUntil = remainingSeconds + BACKOFF_PAD_SECONDS; setNextSync.set(false); self.notifyMonitor(); return; } } final String prefsPath = Utils.getPrefsPath(product, username, serverURL, profile, version); self.performSync( account, extras, authority, provider, syncResult, username, password, prefsPath, serverURL, syncKey); } catch (Exception e) { self.processException(null, e); return; } } }; synchronized (syncMonitor) { // Perform the work in a new thread from within this synchronized block, // which allows us to be waiting on the monitor before the callback can // notify us in a failure case. Oh, concurrent programming. new Thread(runnable).start(); // Start our stale connection monitor thread. ConnectionMonitorThread stale = new ConnectionMonitorThread(); stale.start(); Logger.trace(LOG_TAG, "Waiting on sync monitor."); try { syncMonitor.wait(); if (setNextSync.get()) { long interval = getSyncInterval(); long next = System.currentTimeMillis() + interval; Logger.info( LOG_TAG, "Setting minimum next sync time to " + next + " (" + interval + "ms from now)."); extendEarliestNextSync(next); } Logger.info( LOG_TAG, "Sync took " + Utils.formatDuration(syncStartTimestamp, System.currentTimeMillis()) + "."); } catch (InterruptedException e) { Logger.warn(LOG_TAG, "Waiting on sync monitor interrupted.", e); } finally { // And we're done with HTTP stuff. stale.shutdown(); } } }