2012-03-12 19:17:56 -07:00
/ * This Source Code Form is subject to the terms of the Mozilla Public
2012-05-24 13:17:46 -07:00
* License , v . 2 . 0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/. */
2011-12-21 08:44:08 -08:00
package org.mozilla.gecko.sync ;
import java.io.IOException ;
import java.net.URI ;
import java.net.URISyntaxException ;
2012-04-21 21:15:27 -07:00
import java.util.ArrayList ;
import java.util.Collection ;
import java.util.Collections ;
2011-12-21 08:44:08 -08:00
import java.util.HashMap ;
2012-04-21 21:15:27 -07:00
import java.util.HashSet ;
2012-03-23 11:03:48 -07:00
import java.util.List ;
2011-12-21 08:44:08 -08:00
import java.util.Map ;
2012-05-17 13:20:49 -07:00
import java.util.Map.Entry ;
2012-05-17 13:20:49 -07:00
import java.util.Set ;
2012-03-27 18:41:34 -07:00
import java.util.concurrent.atomic.AtomicLong ;
2011-12-21 08:44:08 -08:00
import org.json.simple.parser.ParseException ;
2013-02-27 15:44:21 -08:00
import org.mozilla.gecko.background.common.log.Logger ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.crypto.CryptoException ;
import org.mozilla.gecko.sync.crypto.KeyBundle ;
2013-12-15 15:53:59 -08:00
import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback ;
2012-03-05 20:53:14 -08:00
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.delegates.FreshStartDelegate ;
2012-12-10 23:03:14 -08:00
import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.delegates.KeyUploadDelegate ;
import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate ;
2013-12-15 15:53:59 -08:00
import org.mozilla.gecko.sync.delegates.NodeAssignmentCallback ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.delegates.WipeServerDelegate ;
2013-11-13 19:36:02 -08:00
import org.mozilla.gecko.sync.net.AuthHeaderProvider ;
2012-03-12 19:17:56 -07:00
import org.mozilla.gecko.sync.net.BaseResource ;
2012-03-27 18:41:34 -07:00
import org.mozilla.gecko.sync.net.HttpResponseObserver ;
import org.mozilla.gecko.sync.net.SyncResponse ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.net.SyncStorageRecordRequest ;
import org.mozilla.gecko.sync.net.SyncStorageRequest ;
import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate ;
import org.mozilla.gecko.sync.net.SyncStorageResponse ;
import org.mozilla.gecko.sync.stage.AndroidBrowserBookmarksServerSyncStage ;
import org.mozilla.gecko.sync.stage.AndroidBrowserHistoryServerSyncStage ;
import org.mozilla.gecko.sync.stage.CheckPreconditionsStage ;
import org.mozilla.gecko.sync.stage.CompletedStage ;
import org.mozilla.gecko.sync.stage.EnsureClusterURLStage ;
2012-04-12 20:15:53 -07:00
import org.mozilla.gecko.sync.stage.EnsureCrypto5KeysStage ;
2012-03-09 15:53:23 -08:00
import org.mozilla.gecko.sync.stage.FennecTabsServerSyncStage ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.stage.FetchInfoCollectionsStage ;
import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage ;
2012-04-21 21:15:27 -07:00
import org.mozilla.gecko.sync.stage.FormHistoryServerSyncStage ;
2011-12-21 08:44:08 -08:00
import org.mozilla.gecko.sync.stage.GlobalSyncStage ;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage ;
import org.mozilla.gecko.sync.stage.NoSuchStageException ;
2012-04-21 21:15:27 -07:00
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage ;
2012-03-05 20:53:14 -08:00
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage ;
2012-05-17 13:20:49 -07:00
import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage ;
2011-12-21 08:44:08 -08:00
import android.content.Context ;
2012-01-14 09:20:31 -08:00
import android.content.SharedPreferences ;
2011-12-21 08:44:08 -08:00
import android.os.Bundle ;
2012-02-15 22:05:52 -08:00
import ch.boye.httpclientandroidlib.HttpResponse ;
2011-12-21 08:44:08 -08:00
2013-11-13 19:36:02 -08:00
public class GlobalSession implements PrefsSource , HttpResponseObserver {
2012-01-14 09:20:31 -08:00
private static final String LOG_TAG = " GlobalSession " ;
2011-12-21 08:44:08 -08:00
public static final long STORAGE_VERSION = 5 ;
2012-01-14 09:20:31 -08:00
2011-12-21 08:44:08 -08:00
public SyncConfiguration config = null ;
protected Map < Stage , GlobalSyncStage > stages ;
public Stage currentState = Stage . idle ;
2013-12-15 15:53:59 -08:00
public final BaseGlobalSessionCallback callback ;
protected final Context context ;
protected final ClientsDataDelegate clientsDelegate ;
protected final NodeAssignmentCallback nodeAssignmentCallback ;
2011-12-21 08:44:08 -08:00
2012-05-17 13:20:49 -07:00
/ * *
* Map from engine name to new settings for an updated meta / global record .
2012-10-09 15:09:08 -07:00
* Engines to remove will have < code > null < / code > EngineSettings .
2012-05-17 13:20:49 -07:00
* /
public final Map < String , EngineSettings > enginesToUpdate = new HashMap < String , EngineSettings > ( ) ;
2012-10-09 15:09:08 -07:00
/ *
2011-12-21 08:44:08 -08:00
* Key accessors .
* /
2012-04-12 20:15:53 -07:00
public KeyBundle keyBundleForCollection ( String collection ) throws NoCollectionKeysSetException {
return config . getCollectionKeys ( ) . keyBundleForCollection ( collection ) ;
2011-12-21 08:44:08 -08:00
}
/ *
* Config passthrough for convenience .
* /
2013-11-13 19:36:02 -08:00
public AuthHeaderProvider getAuthHeaderProvider ( ) {
return config . getAuthHeaderProvider ( ) ;
2011-12-21 08:44:08 -08:00
}
public URI wboURI ( String collection , String id ) throws URISyntaxException {
return config . wboURI ( collection , id ) ;
}
2014-01-23 16:48:53 -08:00
public GlobalSession ( SyncConfiguration config ,
2013-12-15 15:53:59 -08:00
BaseGlobalSessionCallback callback ,
2011-12-21 08:44:08 -08:00
Context context ,
2012-03-05 20:53:14 -08:00
Bundle extras ,
2014-01-23 16:48:53 -08:00
ClientsDataDelegate clientsDelegate , NodeAssignmentCallback nodeAssignmentCallback )
throws SyncConfigurationException , IllegalArgumentException , IOException , ParseException , NonObjectJSONException {
2011-12-21 08:44:08 -08:00
if ( callback = = null ) {
throw new IllegalArgumentException ( " Must provide a callback to GlobalSession constructor. " ) ;
}
2012-07-06 13:01:10 -07:00
Logger . debug ( LOG_TAG , " GlobalSession initialized with bundle " + extras ) ;
2011-12-21 08:44:08 -08:00
2012-01-14 09:20:31 -08:00
this . callback = callback ;
this . context = context ;
2012-03-05 20:53:14 -08:00
this . clientsDelegate = clientsDelegate ;
2013-12-15 15:53:59 -08:00
this . nodeAssignmentCallback = nodeAssignmentCallback ;
2012-01-14 09:20:31 -08:00
2014-01-23 16:48:53 -08:00
this . config = config ;
2012-03-23 11:03:48 -07:00
registerCommands ( ) ;
2011-12-21 08:44:08 -08:00
prepareStages ( ) ;
2012-10-09 15:09:08 -07:00
Collection < String > knownStageNames = SyncConfiguration . validEngineNames ( ) ;
2012-06-25 13:31:42 -07:00
config . stagesToSync = Utils . getStagesToSyncFromBundle ( knownStageNames , extras ) ;
2012-04-21 21:15:27 -07:00
// TODO: data-driven plan for the sync, referring to prepareStages.
2011-12-21 08:44:08 -08:00
}
2012-06-19 10:54:23 -07:00
/ * *
* Register commands this global session knows how to process .
* < p >
* Re - registering a command overwrites any existing registration .
* /
protected static void registerCommands ( ) {
2012-06-08 17:09:54 -07:00
final CommandProcessor processor = CommandProcessor . getProcessor ( ) ;
2012-03-23 11:03:48 -07:00
2012-06-08 17:09:54 -07:00
processor . registerCommand ( " resetEngine " , new CommandRunner ( 1 ) {
2012-03-23 11:03:48 -07:00
@Override
2012-06-19 10:54:23 -07:00
public void executeCommand ( final GlobalSession session , List < String > args ) {
2012-04-21 21:15:27 -07:00
HashSet < String > names = new HashSet < String > ( ) ;
names . add ( args . get ( 0 ) ) ;
2012-06-19 10:54:23 -07:00
session . resetStagesByName ( names ) ;
2012-03-23 11:03:48 -07:00
}
} ) ;
2012-06-08 17:09:54 -07:00
processor . registerCommand ( " resetAll " , new CommandRunner ( 0 ) {
2012-03-23 11:03:48 -07:00
@Override
2012-06-19 10:54:23 -07:00
public void executeCommand ( final GlobalSession session , List < String > args ) {
session . resetAllStages ( ) ;
2012-04-21 21:15:27 -07:00
}
} ) ;
2012-06-08 17:09:54 -07:00
processor . registerCommand ( " wipeEngine " , new CommandRunner ( 1 ) {
2012-04-21 21:15:27 -07:00
@Override
2012-06-19 10:54:23 -07:00
public void executeCommand ( final GlobalSession session , List < String > args ) {
2012-04-21 21:15:27 -07:00
HashSet < String > names = new HashSet < String > ( ) ;
names . add ( args . get ( 0 ) ) ;
2012-06-19 10:54:23 -07:00
session . wipeStagesByName ( names ) ;
2012-04-21 21:15:27 -07:00
}
} ) ;
2012-06-08 17:09:54 -07:00
processor . registerCommand ( " wipeAll " , new CommandRunner ( 0 ) {
2012-04-21 21:15:27 -07:00
@Override
2012-06-19 10:54:23 -07:00
public void executeCommand ( final GlobalSession session , List < String > args ) {
session . wipeAllStages ( ) ;
2012-03-23 11:03:48 -07:00
}
} ) ;
2012-03-27 20:09:14 -07:00
2012-06-08 17:09:54 -07:00
processor . registerCommand ( " displayURI " , new CommandRunner ( 3 ) {
2012-03-27 20:09:14 -07:00
@Override
2012-06-19 10:54:23 -07:00
public void executeCommand ( final GlobalSession session , List < String > args ) {
CommandProcessor . displayURI ( args , session . getContext ( ) ) ;
2012-03-27 20:09:14 -07:00
}
} ) ;
2012-03-23 11:03:48 -07:00
}
2011-12-21 08:44:08 -08:00
protected void prepareStages ( ) {
2012-04-21 21:15:27 -07:00
HashMap < Stage , GlobalSyncStage > stages = new HashMap < Stage , GlobalSyncStage > ( ) ;
2013-01-22 17:23:33 -08:00
stages . put ( Stage . checkPreconditions , new CheckPreconditionsStage ( ) ) ;
2013-12-15 15:53:59 -08:00
stages . put ( Stage . ensureClusterURL , new EnsureClusterURLStage ( nodeAssignmentCallback ) ) ;
2013-01-22 17:23:33 -08:00
stages . put ( Stage . fetchInfoCollections , new FetchInfoCollectionsStage ( ) ) ;
stages . put ( Stage . fetchMetaGlobal , new FetchMetaGlobalStage ( ) ) ;
stages . put ( Stage . ensureKeysStage , new EnsureCrypto5KeysStage ( ) ) ;
stages . put ( Stage . syncClientsEngine , new SyncClientsEngineStage ( ) ) ;
stages . put ( Stage . syncTabs , new FennecTabsServerSyncStage ( ) ) ;
stages . put ( Stage . syncPasswords , new PasswordsServerSyncStage ( ) ) ;
stages . put ( Stage . syncBookmarks , new AndroidBrowserBookmarksServerSyncStage ( ) ) ;
stages . put ( Stage . syncHistory , new AndroidBrowserHistoryServerSyncStage ( ) ) ;
stages . put ( Stage . syncFormHistory , new FormHistoryServerSyncStage ( ) ) ;
stages . put ( Stage . uploadMetaGlobal , new UploadMetaGlobalStage ( ) ) ;
stages . put ( Stage . completed , new CompletedStage ( ) ) ;
2012-04-21 21:15:27 -07:00
this . stages = Collections . unmodifiableMap ( stages ) ;
}
public GlobalSyncStage getSyncStageByName ( String name ) throws NoSuchStageException {
return getSyncStageByName ( Stage . byName ( name ) ) ;
}
public GlobalSyncStage getSyncStageByName ( Stage next ) throws NoSuchStageException {
2011-12-21 08:44:08 -08:00
GlobalSyncStage stage = stages . get ( next ) ;
if ( stage = = null ) {
throw new NoSuchStageException ( next ) ;
}
return stage ;
}
2012-04-21 21:15:27 -07:00
public Collection < GlobalSyncStage > getSyncStagesByEnum ( Collection < Stage > enums ) {
ArrayList < GlobalSyncStage > out = new ArrayList < GlobalSyncStage > ( ) ;
for ( Stage name : enums ) {
try {
GlobalSyncStage stage = this . getSyncStageByName ( name ) ;
out . add ( stage ) ;
} catch ( NoSuchStageException e ) {
Logger . warn ( LOG_TAG , " Unable to find stage with name " + name ) ;
}
}
return out ;
}
public Collection < GlobalSyncStage > getSyncStagesByName ( Collection < String > names ) {
ArrayList < GlobalSyncStage > out = new ArrayList < GlobalSyncStage > ( ) ;
for ( String name : names ) {
try {
GlobalSyncStage stage = this . getSyncStageByName ( name ) ;
out . add ( stage ) ;
} catch ( NoSuchStageException e ) {
Logger . warn ( LOG_TAG , " Unable to find stage with name " + name ) ;
}
}
return out ;
}
2011-12-21 08:44:08 -08:00
/ * *
* Advance and loop around the stages of a sync .
* @param current
* @return
2012-03-05 20:53:13 -08:00
* The next stage to execute .
2011-12-21 08:44:08 -08:00
* /
public static Stage nextStage ( Stage current ) {
int index = current . ordinal ( ) + 1 ;
int max = Stage . completed . ordinal ( ) + 1 ;
return Stage . values ( ) [ index % max ] ;
}
/ * *
* Move to the next stage in the syncing process .
* /
public void advance ( ) {
2012-03-27 18:41:34 -07:00
// If we have a backoff, request a backoff and don't advance to next stage.
long existingBackoff = largestBackoffObserved . get ( ) ;
if ( existingBackoff > 0 ) {
this . abort ( null , " Aborting sync because of backoff of " + existingBackoff + " milliseconds. " ) ;
return ;
}
2011-12-21 08:44:08 -08:00
this . callback . handleStageCompleted ( this . currentState , this ) ;
Stage next = nextStage ( this . currentState ) ;
GlobalSyncStage nextStage ;
try {
2012-04-21 21:15:27 -07:00
nextStage = this . getSyncStageByName ( next ) ;
2011-12-21 08:44:08 -08:00
} catch ( NoSuchStageException e ) {
this . abort ( e , " No such stage " + next ) ;
return ;
}
this . currentState = next ;
2012-03-12 19:17:56 -07:00
Logger . info ( LOG_TAG , " Running next stage " + next + " ( " + nextStage + " )... " ) ;
2011-12-21 08:44:08 -08:00
try {
2013-01-22 17:23:33 -08:00
nextStage . execute ( this ) ;
2011-12-21 08:44:08 -08:00
} catch ( Exception ex ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Caught exception " + ex + " running stage " + next ) ;
2011-12-21 08:44:08 -08:00
this . abort ( ex , " Uncaught exception in stage. " ) ;
2012-03-27 18:41:34 -07:00
return ;
2011-12-21 08:44:08 -08:00
}
}
2012-01-14 09:20:31 -08:00
/ *
* PrefsSource methods .
* /
@Override
public SharedPreferences getPrefs ( String name , int mode ) {
return this . getContext ( ) . getSharedPreferences ( name , mode ) ;
}
2011-12-21 08:44:08 -08:00
public Context getContext ( ) {
return this . context ;
}
/ * *
* Begin a sync .
2012-03-27 18:41:34 -07:00
* < p >
2011-12-21 08:44:08 -08:00
* The caller is responsible for :
2012-03-27 18:41:34 -07:00
* < ul >
* < li > Verifying that any backoffs / minimum next sync requests are respected . < / li >
* < li > Ensuring that the device is online . < / li >
* < li > Ensuring that dependencies are ready . < / li >
* < / ul >
2011-12-21 08:44:08 -08:00
*
* @throws AlreadySyncingException
* /
public void start ( ) throws AlreadySyncingException {
if ( this . currentState ! = GlobalSyncStage . Stage . idle ) {
throw new AlreadySyncingException ( this . currentState ) ;
}
2012-03-27 18:41:34 -07:00
installAsHttpResponseObserver ( ) ; // Uninstalled by completeSync or abort.
2011-12-21 08:44:08 -08:00
this . advance ( ) ;
}
/ * *
* Stop this sync and start again .
* @throws AlreadySyncingException
* /
protected void restart ( ) throws AlreadySyncingException {
this . currentState = GlobalSyncStage . Stage . idle ;
2014-02-13 19:46:23 -08:00
if ( callback . shouldBackOffStorage ( ) ) {
2012-01-14 09:20:31 -08:00
this . callback . handleAborted ( this , " Told to back off. " ) ;
return ;
}
2011-12-21 08:44:08 -08:00
this . start ( ) ;
}
2012-06-19 10:54:23 -07:00
/ * *
* We ' re finished ( aborted or succeeded ) : release resources .
* /
protected void cleanUp ( ) {
2012-03-27 18:41:34 -07:00
uninstallAsHttpResponseObserver ( ) ;
2012-06-19 10:54:23 -07:00
this . stages = null ;
}
public void completeSync ( ) {
cleanUp ( ) ;
2011-12-21 08:44:08 -08:00
this . currentState = GlobalSyncStage . Stage . idle ;
this . callback . handleSuccess ( this ) ;
}
2012-05-17 13:20:49 -07:00
/ * *
* Record that an updated meta / global record should be uploaded with the given
* settings for the given engine .
*
* @param engineName engine to update .
* @param engineSettings new syncID and version .
* /
2012-10-09 15:09:08 -07:00
public void recordForMetaGlobalUpdate ( String engineName , EngineSettings engineSettings ) {
2012-05-17 13:20:49 -07:00
enginesToUpdate . put ( engineName , engineSettings ) ;
}
2012-10-09 15:09:08 -07:00
/ * *
* Record that an updated meta / global record should be uploaded without the
* given engine name .
*
* @param engineName
* engine to remove .
* /
public void removeEngineFromMetaGlobal ( String engineName ) {
enginesToUpdate . put ( engineName , null ) ;
}
2012-05-17 13:20:49 -07:00
public boolean hasUpdatedMetaGlobal ( ) {
if ( enginesToUpdate . isEmpty ( ) ) {
Logger . info ( LOG_TAG , " Not uploading updated meta/global record since there are no engines requesting upload. " ) ;
return false ;
}
2012-06-12 12:12:43 -07:00
if ( Logger . shouldLogVerbose ( LOG_TAG ) ) {
2012-10-09 15:09:08 -07:00
Logger . trace ( LOG_TAG , " Uploading updated meta/global record since there are engine changes to meta/global. " ) ;
Logger . trace ( LOG_TAG , " Engines requesting update [ " + Utils . toCommaSeparatedString ( enginesToUpdate . keySet ( ) ) + " ] " ) ;
2012-05-17 13:20:49 -07:00
}
return true ;
}
public void updateMetaGlobalInPlace ( ) {
ExtendedJSONObject engines = config . metaGlobal . getEngines ( ) ;
for ( Entry < String , EngineSettings > pair : enginesToUpdate . entrySet ( ) ) {
2012-10-09 15:09:08 -07:00
if ( pair . getValue ( ) = = null ) {
engines . remove ( pair . getKey ( ) ) ;
} else {
engines . put ( pair . getKey ( ) , pair . getValue ( ) . toJSONObject ( ) ) ;
}
2012-05-17 13:20:49 -07:00
}
2012-10-09 15:09:08 -07:00
2012-05-17 13:20:49 -07:00
enginesToUpdate . clear ( ) ;
}
/ * *
* Synchronously upload an updated meta / global .
* < p >
* All problems are logged and ignored .
* /
public void uploadUpdatedMetaGlobal ( ) {
updateMetaGlobalInPlace ( ) ;
Logger . debug ( LOG_TAG , " Uploading updated meta/global record. " ) ;
final Object monitor = new Object ( ) ;
Runnable doUpload = new Runnable ( ) {
@Override
public void run ( ) {
config . metaGlobal . upload ( new MetaGlobalDelegate ( ) {
@Override
public void handleSuccess ( MetaGlobal global , SyncStorageResponse response ) {
Logger . info ( LOG_TAG , " Successfully uploaded updated meta/global record. " ) ;
2012-10-09 15:09:08 -07:00
// Engine changes are stored as diffs, so update enabled engines in config to match uploaded meta/global.
config . enabledEngineNames = config . metaGlobal . getEnabledEngineNames ( ) ;
// Clear userSelectedEngines because they are updated in config and meta/global.
config . userSelectedEngines = null ;
2012-05-17 13:20:49 -07:00
synchronized ( monitor ) {
monitor . notify ( ) ;
}
}
@Override
public void handleMissing ( MetaGlobal global , SyncStorageResponse response ) {
Logger . warn ( LOG_TAG , " Got 404 missing uploading updated meta/global record; shouldn't happen. Ignoring. " ) ;
synchronized ( monitor ) {
monitor . notify ( ) ;
}
}
@Override
public void handleFailure ( SyncStorageResponse response ) {
Logger . warn ( LOG_TAG , " Failed to upload updated meta/global record; ignoring. " ) ;
synchronized ( monitor ) {
monitor . notify ( ) ;
}
}
@Override
public void handleError ( Exception e ) {
Logger . warn ( LOG_TAG , " Got exception trying to upload updated meta/global record; ignoring. " , e ) ;
synchronized ( monitor ) {
monitor . notify ( ) ;
}
}
} ) ;
}
} ;
final Thread upload = new Thread ( doUpload ) ;
synchronized ( monitor ) {
try {
upload . start ( ) ;
monitor . wait ( ) ;
Logger . debug ( LOG_TAG , " Uploaded updated meta/global record. " ) ;
} catch ( InterruptedException e ) {
Logger . error ( LOG_TAG , " Uploading updated meta/global interrupted; continuing. " ) ;
}
}
}
2011-12-21 08:44:08 -08:00
public void abort ( Exception e , String reason ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Aborting sync: " + reason , e ) ;
2012-06-19 10:54:23 -07:00
cleanUp ( ) ;
2012-03-27 18:41:34 -07:00
long existingBackoff = largestBackoffObserved . get ( ) ;
if ( existingBackoff > 0 ) {
callback . requestBackoff ( existingBackoff ) ;
}
2012-05-17 13:20:49 -07:00
if ( ! ( e instanceof HTTPFailureException ) ) {
// e is null, or we aborted for a non-HTTP reason; okay to upload new meta/global record.
if ( this . hasUpdatedMetaGlobal ( ) ) {
this . uploadUpdatedMetaGlobal ( ) ; // Only logs errors; does not call abort.
}
}
2011-12-21 08:44:08 -08:00
this . callback . handleError ( this , e ) ;
}
public void handleHTTPError ( SyncStorageResponse response , String reason ) {
// TODO: handling of 50x (backoff), 401 (node reassignment or auth error).
// Fall back to aborting.
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Aborting sync due to HTTP " + response . getStatusCode ( ) ) ;
2012-01-14 09:20:31 -08:00
this . interpretHTTPFailure ( response . httpResponse ( ) ) ;
2011-12-21 08:44:08 -08:00
this . abort ( new HTTPFailureException ( response ) , reason ) ;
}
2012-01-14 09:20:31 -08:00
/ * *
* Perform appropriate backoff etc . extraction .
* /
public void interpretHTTPFailure ( HttpResponse response ) {
// TODO: handle permanent rejection.
2012-03-27 18:41:34 -07:00
long responseBackoff = ( new SyncResponse ( response ) ) . totalBackoffInMilliseconds ( ) ;
if ( responseBackoff > 0 ) {
callback . requestBackoff ( responseBackoff ) ;
2012-01-14 09:20:31 -08:00
}
2011-12-21 08:44:08 -08:00
2012-05-04 17:25:34 -07:00
if ( response . getStatusLine ( ) ! = null ) {
final int statusCode = response . getStatusLine ( ) . getStatusCode ( ) ;
switch ( statusCode ) {
case 400 :
SyncStorageResponse storageResponse = new SyncStorageResponse ( response ) ;
this . interpretHTTPBadRequestBody ( storageResponse ) ;
break ;
case 401 :
/ *
* Alert our callback we have a 401 on a cluster URL . This GlobalSession
* will fail , but the next one will fetch a new cluster URL and will
* distinguish between " node reassignment " and " user password changed " .
* /
callback . informUnauthorizedResponse ( this , config . getClusterURL ( ) ) ;
break ;
}
}
}
protected void interpretHTTPBadRequestBody ( final SyncStorageResponse storageResponse ) {
try {
final String body = storageResponse . body ( ) ;
if ( body = = null ) {
return ;
}
if ( SyncStorageResponse . RESPONSE_CLIENT_UPGRADE_REQUIRED . equals ( body ) ) {
callback . informUpgradeRequiredResponse ( this ) ;
return ;
}
} catch ( Exception e ) {
Logger . warn ( LOG_TAG , " Exception parsing HTTP 400 body. " , e ) ;
2012-03-12 19:17:56 -07:00
}
}
2011-12-21 08:44:08 -08:00
2012-12-10 23:03:14 -08:00
public void fetchInfoCollections ( JSONRecordFetchDelegate callback ) throws URISyntaxException {
2013-11-13 19:36:02 -08:00
final JSONRecordFetcher fetcher = new JSONRecordFetcher ( config . infoCollectionsURL ( ) , getAuthHeaderProvider ( ) ) ;
2012-12-10 23:03:14 -08:00
fetcher . fetch ( callback ) ;
2011-12-21 08:44:08 -08:00
}
2012-05-04 12:27:06 -07:00
/ * *
* Upload new crypto / keys .
*
* @param keys
* new keys .
* @param keyUploadDelegate
* a delegate .
* /
public void uploadKeys ( final CollectionKeys keys ,
2011-12-21 08:44:08 -08:00
final KeyUploadDelegate keyUploadDelegate ) {
SyncStorageRecordRequest request ;
try {
request = new SyncStorageRecordRequest ( this . config . keysURI ( ) ) ;
} catch ( URISyntaxException e ) {
keyUploadDelegate . onKeyUploadFailed ( e ) ;
return ;
}
request . delegate = new SyncStorageRequestDelegate ( ) {
@Override
public String ifUnmodifiedSince ( ) {
return null ;
}
@Override
public void handleRequestSuccess ( SyncStorageResponse response ) {
2012-05-17 13:20:49 -07:00
Logger . debug ( LOG_TAG , " Keys uploaded. " ) ;
2012-03-12 19:17:56 -07:00
BaseResource . consumeEntity ( response ) ; // We don't need the response at all.
2011-12-21 08:44:08 -08:00
keyUploadDelegate . onKeysUploaded ( ) ;
}
@Override
public void handleRequestFailure ( SyncStorageResponse response ) {
2012-05-17 13:20:49 -07:00
Logger . debug ( LOG_TAG , " Failed to upload keys. " ) ;
2013-11-13 19:36:02 -08:00
GlobalSession . this . interpretHTTPFailure ( response . httpResponse ( ) ) ;
2012-03-12 19:17:56 -07:00
BaseResource . consumeEntity ( response ) ; // The exception thrown should not need the body of the response.
2011-12-21 08:44:08 -08:00
keyUploadDelegate . onKeyUploadFailed ( new HTTPFailureException ( response ) ) ;
}
@Override
public void handleRequestError ( Exception ex ) {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Got exception trying to upload keys " , ex ) ;
2011-12-21 08:44:08 -08:00
keyUploadDelegate . onKeyUploadFailed ( ex ) ;
}
@Override
2013-11-13 19:36:02 -08:00
public AuthHeaderProvider getAuthHeaderProvider ( ) {
return GlobalSession . this . getAuthHeaderProvider ( ) ;
2011-12-21 08:44:08 -08:00
}
} ;
2012-05-17 13:20:49 -07:00
// Convert keys to an encrypted crypto record.
2012-05-04 12:27:06 -07:00
CryptoRecord keysRecord ;
2011-12-21 08:44:08 -08:00
try {
2012-05-04 12:27:06 -07:00
keysRecord = keys . asCryptoRecord ( ) ;
keysRecord . setKeyBundle ( config . syncKeyBundle ) ;
2011-12-21 08:44:08 -08:00
keysRecord . encrypt ( ) ;
2012-05-17 13:20:49 -07:00
} catch ( Exception e ) {
Logger . warn ( LOG_TAG , " Got exception trying creating crypto record from keys " , e ) ;
2012-05-04 12:27:06 -07:00
keyUploadDelegate . onKeyUploadFailed ( e ) ;
return ;
2011-12-21 08:44:08 -08:00
}
2012-05-04 12:27:06 -07:00
2011-12-21 08:44:08 -08:00
request . put ( keysRecord ) ;
}
/ *
* meta / global callbacks .
* /
public void processMetaGlobal ( MetaGlobal global ) {
2012-04-12 20:15:53 -07:00
config . metaGlobal = global ;
2011-12-21 08:44:08 -08:00
Long storageVersion = global . getStorageVersion ( ) ;
2012-05-17 13:20:49 -07:00
if ( storageVersion = = null ) {
Logger . warn ( LOG_TAG , " Malformed remote meta/global: could not retrieve remote storage version. " ) ;
freshStart ( ) ;
return ;
}
2011-12-21 08:44:08 -08:00
if ( storageVersion < STORAGE_VERSION ) {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Outdated server: reported " +
" remote storage version " + storageVersion + " < " +
" local storage version " + STORAGE_VERSION ) ;
2011-12-21 08:44:08 -08:00
freshStart ( ) ;
return ;
}
if ( storageVersion > STORAGE_VERSION ) {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Outdated client: reported " +
" remote storage version " + storageVersion + " > " +
" local storage version " + STORAGE_VERSION ) ;
2011-12-21 08:44:08 -08:00
requiresUpgrade ( ) ;
return ;
}
String remoteSyncID = global . getSyncID ( ) ;
if ( remoteSyncID = = null ) {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Malformed remote meta/global: could not retrieve remote syncID. " ) ;
2011-12-21 08:44:08 -08:00
freshStart ( ) ;
return ;
}
2012-05-17 13:20:49 -07:00
String localSyncID = config . syncID ;
2011-12-21 08:44:08 -08:00
if ( ! remoteSyncID . equals ( localSyncID ) ) {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Remote syncID different from local syncID: resetting client and assuming remote syncID. " ) ;
2012-04-21 21:15:27 -07:00
resetAllStages ( ) ;
2012-04-12 20:15:53 -07:00
config . purgeCryptoKeys ( ) ;
2011-12-21 08:44:08 -08:00
config . syncID = remoteSyncID ;
}
2012-10-09 15:09:08 -07:00
// Compare lastModified timestamps for remote/local engine selection times.
Logger . debug ( LOG_TAG , " Comparing local engine selection timestamp [ " + config . userSelectedEnginesTimestamp + " ] to server meta/global timestamp [ " + config . persistedMetaGlobal ( ) . lastModified ( ) + " ]. " ) ;
if ( config . userSelectedEnginesTimestamp < config . persistedMetaGlobal ( ) . lastModified ( ) ) {
// Remote has later meta/global timestamp. Don't upload engine changes.
config . userSelectedEngines = null ;
}
2012-05-17 13:20:49 -07:00
// Persist enabled engine names.
2012-05-04 14:30:15 -07:00
config . enabledEngineNames = global . getEnabledEngineNames ( ) ;
2012-05-17 13:20:49 -07:00
if ( config . enabledEngineNames = = null ) {
Logger . warn ( LOG_TAG , " meta/global reported no enabled engine names! " ) ;
} else {
2012-06-12 12:12:43 -07:00
if ( Logger . shouldLogVerbose ( LOG_TAG ) ) {
2012-05-17 13:20:49 -07:00
Logger . trace ( LOG_TAG , " Persisting enabled engine names ' " +
Utils . toCommaSeparatedString ( config . enabledEngineNames ) + " ' from meta/global. " ) ;
}
}
2012-01-14 09:20:31 -08:00
config . persistToPrefs ( ) ;
2011-12-21 08:44:08 -08:00
advance ( ) ;
}
public void processMissingMetaGlobal ( MetaGlobal global ) {
freshStart ( ) ;
}
/ * *
* Do a fresh start then quietly finish the sync , starting another .
* /
2012-05-17 13:20:49 -07:00
public void freshStart ( ) {
2011-12-21 08:44:08 -08:00
final GlobalSession globalSession = this ;
freshStart ( this , new FreshStartDelegate ( ) {
@Override
public void onFreshStartFailed ( Exception e ) {
globalSession . abort ( e , " Fresh start failed. " ) ;
}
@Override
public void onFreshStart ( ) {
try {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Fresh start succeeded; restarting global session. " ) ;
2012-01-14 09:20:31 -08:00
globalSession . config . persistToPrefs ( ) ;
2011-12-21 08:44:08 -08:00
globalSession . restart ( ) ;
} catch ( Exception e ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Got exception when restarting sync after freshStart. " , e ) ;
2011-12-21 08:44:08 -08:00
globalSession . abort ( e , " Got exception after freshStart. " ) ;
}
}
} ) ;
}
/ * *
* Clean the server , aborting the current sync .
2012-05-17 13:20:49 -07:00
* < p >
* < ol >
* < li > Wipe the server storage . < / li >
* < li > Reset all stages and purge cached state : ( meta / global and crypto / keys records ) . < / li >
* < li > Upload fresh meta / global record . < / li >
* < li > Upload fresh crypto / keys record . < / li >
* < li > Restart the sync entirely in order to re - download meta / global and crypto / keys record . < / li >
* < / ol >
* @param session the current session .
* @param freshStartDelegate delegate to notify on fresh start or failure .
2011-12-21 08:44:08 -08:00
* /
2012-05-17 13:20:49 -07:00
protected static void freshStart ( final GlobalSession session , final FreshStartDelegate freshStartDelegate ) {
Logger . debug ( LOG_TAG , " Fresh starting. " ) ;
2011-12-21 08:44:08 -08:00
2012-05-17 13:20:49 -07:00
final MetaGlobal mg = session . generateNewMetaGlobal ( ) ;
2011-12-21 08:44:08 -08:00
2013-11-13 19:36:02 -08:00
session . wipeServer ( session . getAuthHeaderProvider ( ) , new WipeServerDelegate ( ) {
2011-12-21 08:44:08 -08:00
@Override
public void onWiped ( long timestamp ) {
2012-05-17 13:20:49 -07:00
Logger . debug ( LOG_TAG , " Successfully wiped server. Resetting all stages and purging cached meta/global and crypto/keys records. " ) ;
2012-04-21 21:15:27 -07:00
session . resetAllStages ( ) ;
2012-05-17 13:20:49 -07:00
session . config . purgeMetaGlobal ( ) ;
2012-04-12 20:15:53 -07:00
session . config . purgeCryptoKeys ( ) ;
2012-01-14 09:20:31 -08:00
session . config . persistToPrefs ( ) ;
2011-12-21 08:44:08 -08:00
2012-05-17 13:20:49 -07:00
Logger . info ( LOG_TAG , " Uploading new meta/global with sync ID " + mg . syncID + " . " ) ;
2011-12-21 08:44:08 -08:00
// It would be good to set the X-If-Unmodified-Since header to `timestamp`
// for this PUT to ensure at least some level of transactionality.
// Unfortunately, the servers don't support it after a wipe right now
// (bug 693893), so we're going to defer this until bug 692700.
mg . upload ( new MetaGlobalDelegate ( ) {
@Override
2012-05-17 13:20:49 -07:00
public void handleSuccess ( MetaGlobal uploadedGlobal , SyncStorageResponse uploadResponse ) {
Logger . info ( LOG_TAG , " Uploaded new meta/global with sync ID " + uploadedGlobal . syncID + " . " ) ;
2011-12-21 08:44:08 -08:00
2012-05-17 13:20:49 -07:00
// Generate new keys.
CollectionKeys keys = null ;
2011-12-21 08:44:08 -08:00
try {
2012-05-17 13:20:49 -07:00
keys = session . generateNewCryptoKeys ( ) ;
2011-12-21 08:44:08 -08:00
} catch ( CryptoException e ) {
2012-05-17 13:20:49 -07:00
Logger . warn ( LOG_TAG , " Got exception generating new keys; failing fresh start. " , e ) ;
2011-12-21 08:44:08 -08:00
freshStartDelegate . onFreshStartFailed ( e ) ;
}
2012-05-17 13:20:49 -07:00
if ( keys = = null ) {
Logger . warn ( LOG_TAG , " Got null keys from generateNewKeys; failing fresh start. " ) ;
freshStartDelegate . onFreshStartFailed ( null ) ;
}
// Upload new keys.
Logger . info ( LOG_TAG , " Uploading new crypto/keys. " ) ;
session . uploadKeys ( keys , new KeyUploadDelegate ( ) {
@Override
public void onKeysUploaded ( ) {
Logger . info ( LOG_TAG , " Uploaded new crypto/keys. " ) ;
freshStartDelegate . onFreshStart ( ) ;
}
@Override
public void onKeyUploadFailed ( Exception e ) {
Logger . warn ( LOG_TAG , " Got exception uploading new keys. " , e ) ;
freshStartDelegate . onFreshStartFailed ( e ) ;
}
} ) ;
2011-12-21 08:44:08 -08:00
}
@Override
2012-01-14 09:20:31 -08:00
public void handleMissing ( MetaGlobal global , SyncStorageResponse response ) {
2012-05-17 13:20:49 -07:00
// Shouldn't happen on upload.
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Got 'missing' response uploading new meta/global. " ) ;
2012-05-17 13:20:49 -07:00
freshStartDelegate . onFreshStartFailed ( new Exception ( " meta/global missing while uploading. " ) ) ;
2011-12-21 08:44:08 -08:00
}
@Override
public void handleFailure ( SyncStorageResponse response ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Got failure " + response . getStatusCode ( ) + " uploading new meta/global. " ) ;
2012-01-14 09:20:31 -08:00
session . interpretHTTPFailure ( response . httpResponse ( ) ) ;
2011-12-21 08:44:08 -08:00
freshStartDelegate . onFreshStartFailed ( new HTTPFailureException ( response ) ) ;
}
@Override
public void handleError ( Exception e ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Got error uploading new meta/global. " , e ) ;
2011-12-21 08:44:08 -08:00
freshStartDelegate . onFreshStartFailed ( e ) ;
}
} ) ;
}
@Override
public void onWipeFailed ( Exception e ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Wipe failed. " ) ;
2011-12-21 08:44:08 -08:00
freshStartDelegate . onFreshStartFailed ( e ) ;
}
} ) ;
}
2012-04-21 21:15:27 -07:00
// Note that we do not yet implement wipeRemote: it's only necessary for
// first sync options.
// -- reset local stages, wipe server for each stage *except* clients
// (stages only, not whole server!), send wipeEngine commands to each client.
//
// Similarly for startOver (because we don't receive that notification).
// -- remove client data from server, reset local stages, clear keys, reset
// backoff, clear all prefs, discard credentials.
//
// Change passphrase: wipe entire server, reset client to force upload, sync.
//
// When an engine is disabled: wipe its collections on the server, reupload
// meta/global.
//
// On syncing each stage: if server has engine version 0 or old, wipe server,
// reset client to prompt reupload.
// If sync ID mismatch: take that syncID and reset client.
2013-11-13 19:36:02 -08:00
protected void wipeServer ( final AuthHeaderProvider authHeaderProvider , final WipeServerDelegate wipeDelegate ) {
2011-12-21 08:44:08 -08:00
SyncStorageRequest request ;
2012-01-14 09:20:31 -08:00
final GlobalSession self = this ;
2011-12-21 08:44:08 -08:00
try {
2013-11-13 19:36:02 -08:00
request = new SyncStorageRequest ( config . storageURL ( ) ) ;
2011-12-21 08:44:08 -08:00
} catch ( URISyntaxException ex ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Invalid URI in wipeServer. " ) ;
2011-12-21 08:44:08 -08:00
wipeDelegate . onWipeFailed ( ex ) ;
return ;
}
request . delegate = new SyncStorageRequestDelegate ( ) {
@Override
public String ifUnmodifiedSince ( ) {
return null ;
}
@Override
public void handleRequestSuccess ( SyncStorageResponse response ) {
2012-03-12 19:17:56 -07:00
BaseResource . consumeEntity ( response ) ;
2011-12-21 08:44:08 -08:00
wipeDelegate . onWiped ( response . normalizedWeaveTimestamp ( ) ) ;
}
@Override
public void handleRequestFailure ( SyncStorageResponse response ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Got request failure " + response . getStatusCode ( ) + " in wipeServer. " ) ;
2012-01-14 09:20:31 -08:00
// Process HTTP failures here to pick up backoffs, etc.
self . interpretHTTPFailure ( response . httpResponse ( ) ) ;
2012-03-12 19:17:56 -07:00
BaseResource . consumeEntity ( response ) ; // The exception thrown should not need the body of the response.
2011-12-21 08:44:08 -08:00
wipeDelegate . onWipeFailed ( new HTTPFailureException ( response ) ) ;
}
@Override
public void handleRequestError ( Exception ex ) {
2012-03-12 19:17:56 -07:00
Logger . warn ( LOG_TAG , " Got exception in wipeServer. " , ex ) ;
2011-12-21 08:44:08 -08:00
wipeDelegate . onWipeFailed ( ex ) ;
}
@Override
2013-11-13 19:36:02 -08:00
public AuthHeaderProvider getAuthHeaderProvider ( ) {
return GlobalSession . this . getAuthHeaderProvider ( ) ;
2011-12-21 08:44:08 -08:00
}
} ;
request . delete ( ) ;
}
2012-04-21 21:15:27 -07:00
public void wipeAllStages ( ) {
Logger . info ( LOG_TAG , " Wiping all stages. " ) ;
// Includes "clients".
this . wipeStagesByEnum ( Stage . getNamedStages ( ) ) ;
}
2013-01-22 17:23:33 -08:00
public void wipeStages ( Collection < GlobalSyncStage > stages ) {
2012-04-21 21:15:27 -07:00
for ( GlobalSyncStage stage : stages ) {
try {
Logger . info ( LOG_TAG , " Wiping " + stage ) ;
2013-01-22 17:23:33 -08:00
stage . wipeLocal ( this ) ;
2012-04-21 21:15:27 -07:00
} catch ( Exception e ) {
Logger . error ( LOG_TAG , " Ignoring wipe failure for stage " + stage , e ) ;
}
}
}
public void wipeStagesByEnum ( Collection < Stage > stages ) {
2013-01-22 17:23:33 -08:00
wipeStages ( this . getSyncStagesByEnum ( stages ) ) ;
2012-04-21 21:15:27 -07:00
}
public void wipeStagesByName ( Collection < String > names ) {
2013-01-22 17:23:33 -08:00
wipeStages ( this . getSyncStagesByName ( names ) ) ;
2012-04-21 21:15:27 -07:00
}
public void resetAllStages ( ) {
Logger . info ( LOG_TAG , " Resetting all stages. " ) ;
// Includes "clients".
this . resetStagesByEnum ( Stage . getNamedStages ( ) ) ;
}
2013-01-22 17:23:33 -08:00
public void resetStages ( Collection < GlobalSyncStage > stages ) {
2012-04-21 21:15:27 -07:00
for ( GlobalSyncStage stage : stages ) {
try {
Logger . info ( LOG_TAG , " Resetting " + stage ) ;
2013-01-22 17:23:33 -08:00
stage . resetLocal ( this ) ;
2012-04-21 21:15:27 -07:00
} catch ( Exception e ) {
Logger . error ( LOG_TAG , " Ignoring reset failure for stage " + stage , e ) ;
}
}
}
public void resetStagesByEnum ( Collection < Stage > stages ) {
2013-01-22 17:23:33 -08:00
resetStages ( this . getSyncStagesByEnum ( stages ) ) ;
2012-04-21 21:15:27 -07:00
}
public void resetStagesByName ( Collection < String > names ) {
2013-01-22 17:23:33 -08:00
resetStages ( this . getSyncStagesByName ( names ) ) ;
2011-12-21 08:44:08 -08:00
}
2012-05-17 13:20:49 -07:00
/ * *
* Engines to include in a fresh meta / global record .
* < p >
* Returns either the persisted engine names ( perhaps we have been node
* re - assigned and are initializing a clean server : we want to upload the
* persisted engine names so that we don ' t accidentally disable engines that
* Android Sync doesn ' t recognize ) , or the set of engines names that Android
* Sync implements .
*
* @return set of engine names .
* /
protected Set < String > enabledEngineNames ( ) {
if ( config . enabledEngineNames ! = null ) {
return config . enabledEngineNames ;
}
2012-10-09 15:09:08 -07:00
2014-01-30 17:58:01 -08:00
// These are the default set of engine names.
Set < String > validEngineNames = SyncConfiguration . validEngineNames ( ) ;
// If the user hasn't set any selected engines, that's okay -- default to
// everything.
if ( config . userSelectedEngines = = null ) {
return validEngineNames ;
}
// userSelectedEngines has keys that are engine names, and boolean values
// corresponding to whether the user asked for the engine to sync or not. If
// an engine is not present, that means the user didn't change its sync
// setting. Since we default to everything on, that means the user didn't
// turn it off; therefore, it's included in the set of engines to sync.
Set < String > validAndSelectedEngineNames = new HashSet < String > ( ) ;
for ( String engineName : validEngineNames ) {
if ( config . userSelectedEngines . containsKey ( engineName ) & &
! config . userSelectedEngines . get ( engineName ) ) {
continue ;
}
validAndSelectedEngineNames . add ( engineName ) ;
}
return validAndSelectedEngineNames ;
2012-05-17 13:20:49 -07:00
}
/ * *
* Generate fresh crypto / keys collection .
* @return crypto / keys collection .
* @throws CryptoException
* /
2012-12-10 23:03:14 -08:00
@SuppressWarnings ( " static-method " )
2012-05-17 13:20:49 -07:00
public CollectionKeys generateNewCryptoKeys ( ) throws CryptoException {
return CollectionKeys . generateCollectionKeys ( ) ;
}
/ * *
* Generate a fresh meta / global record .
* @return meta / global record .
* /
public MetaGlobal generateNewMetaGlobal ( ) {
final String newSyncID = Utils . generateGuid ( ) ;
final String metaURL = this . config . metaURL ( ) ;
ExtendedJSONObject engines = new ExtendedJSONObject ( ) ;
for ( String engineName : enabledEngineNames ( ) ) {
EngineSettings engineSettings = null ;
try {
GlobalSyncStage globalStage = this . getSyncStageByName ( engineName ) ;
Integer version = globalStage . getStorageVersion ( ) ;
if ( version = = null ) {
continue ; // Don't want this stage to be included in meta/global.
}
engineSettings = new EngineSettings ( Utils . generateGuid ( ) , version . intValue ( ) ) ;
} catch ( NoSuchStageException e ) {
// No trouble; Android Sync might not recognize this engine yet.
// By default, version 0. Other clients will see the 0 version and reset/wipe accordingly.
engineSettings = new EngineSettings ( Utils . generateGuid ( ) , 0 ) ;
}
engines . put ( engineName , engineSettings . toJSONObject ( ) ) ;
}
2013-11-13 19:36:02 -08:00
MetaGlobal metaGlobal = new MetaGlobal ( metaURL , this . getAuthHeaderProvider ( ) ) ;
2012-05-17 13:20:49 -07:00
metaGlobal . setSyncID ( newSyncID ) ;
metaGlobal . setStorageVersion ( STORAGE_VERSION ) ;
metaGlobal . setEngines ( engines ) ;
return metaGlobal ;
}
2011-12-21 08:44:08 -08:00
/ * *
* Suggest that your Sync client needs to be upgraded to work
* with this server .
* /
public void requiresUpgrade ( ) {
2012-03-12 19:17:56 -07:00
Logger . info ( LOG_TAG , " Client outdated storage version; requires update. " ) ;
2011-12-21 08:44:08 -08:00
// TODO: notify UI.
2012-05-17 13:20:49 -07:00
this . abort ( null , " Requires upgrade " ) ;
2011-12-21 08:44:08 -08:00
}
/ * *
* If meta / global is missing or malformed , throws a MetaGlobalException .
* Otherwise , returns true if there is an entry for this engine in the
* meta / global " engines " object .
*
2012-04-21 21:15:27 -07:00
* @param engineName the name to check ( e . g . , " bookmarks " ) .
* @param engineSettings
* if non - null , verify that the server engine settings are congruent
* with this , throwing the appropriate MetaGlobalException if not .
2011-12-21 08:44:08 -08:00
* @return
2012-03-05 20:53:13 -08:00
* true if the engine with the provided name is present in the
2012-04-21 21:15:27 -07:00
* meta / global " engines " object , and verification passed .
2012-03-05 20:53:13 -08:00
*
2011-12-21 08:44:08 -08:00
* @throws MetaGlobalException
* /
2012-04-21 21:15:27 -07:00
public boolean engineIsEnabled ( String engineName , EngineSettings engineSettings ) throws MetaGlobalException {
2012-05-17 13:20:49 -07:00
if ( this . config . metaGlobal = = null ) {
throw new MetaGlobalNotSetException ( ) ;
}
2012-05-04 14:30:15 -07:00
// This should not occur.
if ( this . config . enabledEngineNames = = null ) {
Logger . error ( LOG_TAG , " No enabled engines in config. Giving up. " ) ;
2012-04-21 21:15:27 -07:00
throw new MetaGlobalMissingEnginesException ( ) ;
}
2012-05-04 14:30:15 -07:00
if ( ! ( this . config . enabledEngineNames . contains ( engineName ) ) ) {
2012-04-21 21:15:27 -07:00
Logger . debug ( LOG_TAG , " Engine " + engineName + " not enabled: no meta/global entry. " ) ;
return false ;
}
2012-05-04 14:30:15 -07:00
// If we have a meta/global, check that it's safe for us to sync.
// (If we don't, we'll create one later, which is why we return `true` above.)
2012-04-21 21:15:27 -07:00
if ( engineSettings ! = null ) {
2012-05-04 14:30:15 -07:00
// Throws if there's a problem.
this . config . metaGlobal . verifyEngineSettings ( engineName , engineSettings ) ;
2012-04-21 21:15:27 -07:00
}
2012-05-04 14:30:15 -07:00
2012-04-21 21:15:27 -07:00
return true ;
2011-12-21 08:44:08 -08:00
}
2012-03-05 20:53:14 -08:00
public ClientsDataDelegate getClientsDelegate ( ) {
return this . clientsDelegate ;
}
2012-03-27 18:41:34 -07:00
/ * *
* The longest backoff observed to date ; - 1 means no backoff observed .
* /
protected final AtomicLong largestBackoffObserved = new AtomicLong ( - 1 ) ;
/ * *
* Reset any observed backoff and start observing HTTP responses for backoff
* requests .
* /
protected void installAsHttpResponseObserver ( ) {
Logger . debug ( LOG_TAG , " Installing " + this + " as BaseResource HttpResponseObserver. " ) ;
BaseResource . setHttpResponseObserver ( this ) ;
largestBackoffObserved . set ( - 1 ) ;
}
/ * *
* Stop observing HttpResponses for backoff requests .
* /
protected void uninstallAsHttpResponseObserver ( ) {
Logger . debug ( LOG_TAG , " Uninstalling " + this + " as BaseResource HttpResponseObserver. " ) ;
BaseResource . setHttpResponseObserver ( null ) ;
}
/ * *
* Observe all HTTP response for backoff requests on all status codes , not just errors .
* /
@Override
public void observeHttpResponse ( HttpResponse response ) {
long responseBackoff = ( new SyncResponse ( response ) ) . totalBackoffInMilliseconds ( ) ; // TODO: don't allocate object?
if ( responseBackoff < = 0 ) {
return ;
}
Logger . debug ( LOG_TAG , " Observed " + responseBackoff + " millisecond backoff request. " ) ;
while ( true ) {
long existingBackoff = largestBackoffObserved . get ( ) ;
if ( existingBackoff > = responseBackoff ) {
return ;
}
if ( largestBackoffObserved . compareAndSet ( existingBackoff , responseBackoff ) ) {
return ;
}
}
}
2011-12-21 08:44:08 -08:00
}