зеркало из https://github.com/mozilla/gecko-dev.git
Bug 709311 - Handle changed meta/global. r=rnewman, a=blocking-fennec
This commit is contained in:
Родитель
9a9d84392f
Коммит
e1a6551162
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -14,6 +14,7 @@ import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
|
||||||
import org.mozilla.gecko.sync.stage.NoSuchStageException;
|
import org.mozilla.gecko.sync.stage.NoSuchStageException;
|
||||||
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
|
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
|
||||||
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
|
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
|
||||||
|
import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -70,6 +72,11 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
|
||||||
private Context context;
|
private Context context;
|
||||||
private ClientsDataDelegate clientsDelegate;
|
private ClientsDataDelegate clientsDelegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map from engine name to new settings for an updated meta/global record.
|
||||||
|
*/
|
||||||
|
public final Map<String, EngineSettings> enginesToUpdate = new HashMap<String, EngineSettings>();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Key accessors.
|
* Key accessors.
|
||||||
*/
|
*/
|
||||||
|
@ -218,6 +225,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
|
||||||
stages.put(Stage.syncHistory, new AndroidBrowserHistoryServerSyncStage(this));
|
stages.put(Stage.syncHistory, new AndroidBrowserHistoryServerSyncStage(this));
|
||||||
stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage(this));
|
stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage(this));
|
||||||
|
|
||||||
|
stages.put(Stage.uploadMetaGlobal, new UploadMetaGlobalStage(this));
|
||||||
stages.put(Stage.completed, new CompletedStage(this));
|
stages.put(Stage.completed, new CompletedStage(this));
|
||||||
|
|
||||||
this.stages = Collections.unmodifiableMap(stages);
|
this.stages = Collections.unmodifiableMap(stages);
|
||||||
|
@ -355,6 +363,102 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
|
||||||
this.callback.handleSuccess(this);
|
this.callback.handleSuccess(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public void updateMetaGlobalWith(String engineName, EngineSettings engineSettings) {
|
||||||
|
enginesToUpdate.put(engineName, engineSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Logger.logVerbose(LOG_TAG)) {
|
||||||
|
Logger.trace(LOG_TAG, "Uploading updated meta/global record since there are engines requesting upload: " +
|
||||||
|
Utils.toCommaSeparatedString(enginesToUpdate.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateMetaGlobalInPlace() {
|
||||||
|
ExtendedJSONObject engines = config.metaGlobal.getEngines();
|
||||||
|
for (Entry<String, EngineSettings> pair : enginesToUpdate.entrySet()) {
|
||||||
|
engines.put(pair.getKey(), pair.getValue().toJSONObject());
|
||||||
|
}
|
||||||
|
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.");
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void abort(Exception e, String reason) {
|
public void abort(Exception e, String reason) {
|
||||||
Logger.warn(LOG_TAG, "Aborting sync: " + reason, e);
|
Logger.warn(LOG_TAG, "Aborting sync: " + reason, e);
|
||||||
uninstallAsHttpResponseObserver();
|
uninstallAsHttpResponseObserver();
|
||||||
|
@ -362,6 +466,12 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
|
||||||
if (existingBackoff > 0) {
|
if (existingBackoff > 0) {
|
||||||
callback.requestBackoff(existingBackoff);
|
callback.requestBackoff(existingBackoff);
|
||||||
}
|
}
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
this.callback.handleError(this, e);
|
this.callback.handleError(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,8 +94,8 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
|
||||||
public void setFromRecord(CryptoRecord record) throws IllegalStateException, IOException, ParseException, NonObjectJSONException {
|
public void setFromRecord(CryptoRecord record) throws IllegalStateException, IOException, ParseException, NonObjectJSONException {
|
||||||
Logger.info(LOG_TAG, "meta/global is " + record.payload.toJSONString());
|
Logger.info(LOG_TAG, "meta/global is " + record.payload.toJSONString());
|
||||||
this.storageVersion = (Long) record.payload.get("storageVersion");
|
this.storageVersion = (Long) record.payload.get("storageVersion");
|
||||||
this.engines = record.payload.getObject("engines");
|
|
||||||
this.syncID = (String) record.payload.get("syncID");
|
this.syncID = (String) record.payload.get("syncID");
|
||||||
|
setEngines(record.payload.getObject("engines"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getStorageVersion() {
|
public Long getStorageVersion() {
|
||||||
|
@ -111,6 +111,9 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEngines(ExtendedJSONObject engines) {
|
public void setEngines(ExtendedJSONObject engines) {
|
||||||
|
if (engines == null) {
|
||||||
|
engines = new ExtendedJSONObject();
|
||||||
|
}
|
||||||
this.engines = engines;
|
this.engines = engines;
|
||||||
final int count = engines.size();
|
final int count = engines.size();
|
||||||
versions = new HashMap<String, Integer>(count);
|
versions = new HashMap<String, Integer>(count);
|
||||||
|
@ -122,6 +125,7 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
|
||||||
recordEngineState(engineName, engineEntry);
|
recordEngineState(engineName, engineEntry);
|
||||||
} catch (NonObjectJSONException e) {
|
} catch (NonObjectJSONException e) {
|
||||||
Logger.error(LOG_TAG, "Engine field for " + engineName + " in meta/global is not an object.");
|
Logger.error(LOG_TAG, "Engine field for " + engineName + " in meta/global is not an object.");
|
||||||
|
recordEngineState(engineName, new ExtendedJSONObject()); // Doesn't have a version or syncID, for example, so will be server wiped.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,6 +141,22 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
|
||||||
if (engineEntry == null) {
|
if (engineEntry == null) {
|
||||||
throw new IllegalArgumentException("engineEntry cannot be null.");
|
throw new IllegalArgumentException("engineEntry cannot be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record syncID first, so that engines with bad versions are recorded.
|
||||||
|
try {
|
||||||
|
String syncID = engineEntry.getString("syncID");
|
||||||
|
if (syncID == null) {
|
||||||
|
Logger.warn(LOG_TAG, "No syncID for " + engineName + ". Recording exception.");
|
||||||
|
exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
|
||||||
|
}
|
||||||
|
syncIDs.put(engineName, syncID);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// Malformed syncID on the server. Wipe the server.
|
||||||
|
Logger.warn(LOG_TAG, "Malformed syncID " + engineEntry.get("syncID") +
|
||||||
|
" for " + engineName + ". Recording exception.");
|
||||||
|
exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Integer version = engineEntry.getIntegerSafely("version");
|
Integer version = engineEntry.getIntegerSafely("version");
|
||||||
Logger.trace(LOG_TAG, "Engine " + engineName + " has server version " + version);
|
Logger.trace(LOG_TAG, "Engine " + engineName + " has server version " + version);
|
||||||
|
@ -156,20 +176,6 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
|
||||||
exceptions.put(engineName, new MetaGlobalMalformedVersionException());
|
exceptions.put(engineName, new MetaGlobalMalformedVersionException());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
String syncID = engineEntry.getString("syncID");
|
|
||||||
if (syncID == null) {
|
|
||||||
Logger.warn(LOG_TAG, "No syncID for " + engineName + ". Recording exception.");
|
|
||||||
exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
|
|
||||||
}
|
|
||||||
syncIDs.put(engineName, syncID);
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
// Malformed syncID on the server. Wipe the server.
|
|
||||||
Logger.warn(LOG_TAG, "Malformed syncID " + engineEntry.get("syncID") +
|
|
||||||
" for " + engineName + ". Recording exception.");
|
|
||||||
exceptions.put(engineName, new MetaGlobalException.MetaGlobalMalformedSyncIDException());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,18 +207,21 @@ public class MetaGlobal implements SyncStorageRequestDelegate {
|
||||||
throw new IllegalArgumentException("engineSettings cannot be null.");
|
throw new IllegalArgumentException("engineSettings cannot be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String syncID = syncIDs.get(engineName);
|
// First, see if we had a parsing problem.
|
||||||
if (syncID == null) {
|
|
||||||
throw new IllegalArgumentException("Unknown engine " + engineName);
|
|
||||||
}
|
|
||||||
|
|
||||||
final MetaGlobalException exception = exceptions.get(engineName);
|
final MetaGlobalException exception = exceptions.get(engineName);
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Integer version = versions.get(engineName);
|
final String syncID = syncIDs.get(engineName);
|
||||||
|
if (syncID == null) {
|
||||||
|
// We have checked engineName against enabled engine names before this, so
|
||||||
|
// we should either have a syncID or an exception for this engine already.
|
||||||
|
throw new IllegalArgumentException("Unknown engine " + engineName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we don't have an exception, and we do have a syncID, we should have a version.
|
||||||
|
final Integer version = versions.get(engineName);
|
||||||
if (version > engineSettings.version) {
|
if (version > engineSettings.version) {
|
||||||
// We're out of date.
|
// We're out of date.
|
||||||
throw new MetaGlobalException.MetaGlobalStaleClientVersionException(version);
|
throw new MetaGlobalException.MetaGlobalStaleClientVersionException(version);
|
||||||
|
|
|
@ -44,7 +44,6 @@ import org.mozilla.gecko.sync.SyncConfiguration.ConfigurationBranch;
|
||||||
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
|
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
|
||||||
|
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class SynchronizerConfiguration {
|
public class SynchronizerConfiguration {
|
||||||
private static final String LOG_TAG = "SynczrConfiguration";
|
private static final String LOG_TAG = "SynczrConfiguration";
|
||||||
|
@ -63,14 +62,6 @@ public class SynchronizerConfiguration {
|
||||||
this.localBundle = localBundle;
|
this.localBundle = localBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] toStringValues() {
|
|
||||||
String[] out = new String[3];
|
|
||||||
out[0] = syncID;
|
|
||||||
out[1] = remoteBundle.toJSONString();
|
|
||||||
out[2] = localBundle.toJSONString();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should get partly shuffled back into SyncConfiguration, I think.
|
// This should get partly shuffled back into SyncConfiguration, I think.
|
||||||
public void load(ConfigurationBranch config) throws NonObjectJSONException, IOException, ParseException {
|
public void load(ConfigurationBranch config) throws NonObjectJSONException, IOException, ParseException {
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
|
@ -89,7 +80,7 @@ public class SynchronizerConfiguration {
|
||||||
syncID = config.getString("syncID", null);
|
syncID = config.getString("syncID", null);
|
||||||
remoteBundle = rB;
|
remoteBundle = rB;
|
||||||
localBundle = lB;
|
localBundle = lB;
|
||||||
Log.i(LOG_TAG, "Initialized SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
|
Logger.debug(LOG_TAG, "Loaded SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void persist(ConfigurationBranch config) {
|
public void persist(ConfigurationBranch config) {
|
||||||
|
@ -105,5 +96,6 @@ public class SynchronizerConfiguration {
|
||||||
|
|
||||||
// Synchronous.
|
// Synchronous.
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
Logger.debug(LOG_TAG, "Persisted SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,8 @@ public interface GlobalSyncStage {
|
||||||
syncBookmarks("bookmarks"),
|
syncBookmarks("bookmarks"),
|
||||||
syncHistory("history"),
|
syncHistory("history"),
|
||||||
syncFormHistory("forms"),
|
syncFormHistory("forms"),
|
||||||
|
|
||||||
|
uploadMetaGlobal,
|
||||||
completed;
|
completed;
|
||||||
|
|
||||||
// Maintain a mapping from names ("bookmarks") to Stage enumerations (syncBookmarks).
|
// Maintain a mapping from names ("bookmarks") to Stage enumerations (syncBookmarks).
|
||||||
|
|
|
@ -9,6 +9,8 @@ import java.net.URISyntaxException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import org.json.simple.parser.ParseException;
|
import org.json.simple.parser.ParseException;
|
||||||
|
import org.mozilla.gecko.sync.CredentialsSource;
|
||||||
|
import org.mozilla.gecko.sync.EngineSettings;
|
||||||
import org.mozilla.gecko.sync.GlobalSession;
|
import org.mozilla.gecko.sync.GlobalSession;
|
||||||
import org.mozilla.gecko.sync.HTTPFailureException;
|
import org.mozilla.gecko.sync.HTTPFailureException;
|
||||||
import org.mozilla.gecko.sync.Logger;
|
import org.mozilla.gecko.sync.Logger;
|
||||||
|
@ -16,8 +18,14 @@ import org.mozilla.gecko.sync.MetaGlobalException;
|
||||||
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
|
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
|
||||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||||
import org.mozilla.gecko.sync.SynchronizerConfiguration;
|
import org.mozilla.gecko.sync.SynchronizerConfiguration;
|
||||||
|
import org.mozilla.gecko.sync.Utils;
|
||||||
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
||||||
|
import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
|
||||||
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
|
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
|
||||||
|
import org.mozilla.gecko.sync.net.BaseResource;
|
||||||
|
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.repositories.InactiveSessionException;
|
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
|
||||||
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
|
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
|
||||||
import org.mozilla.gecko.sync.repositories.RecordFactory;
|
import org.mozilla.gecko.sync.repositories.RecordFactory;
|
||||||
|
@ -63,9 +71,30 @@ public abstract class ServerSyncStage implements
|
||||||
* @throws MetaGlobalException
|
* @throws MetaGlobalException
|
||||||
*/
|
*/
|
||||||
protected boolean isEnabled() throws MetaGlobalException {
|
protected boolean isEnabled() throws MetaGlobalException {
|
||||||
// TODO: pass EngineSettings here to check syncID and storage version.
|
EngineSettings engineSettings = null;
|
||||||
// Catch the subclasses of MetaGlobalException to trigger various resets and wipes.
|
try {
|
||||||
return session.engineIsEnabled(this.getEngineName(), null);
|
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 catch the subclasses of MetaGlobalException to trigger various resets and wipes in execute().
|
||||||
|
return session.engineIsEnabled(this.getEngineName(), engineSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EngineSettings getEngineSettings() throws NonObjectJSONException, IOException, ParseException {
|
||||||
|
Integer version = getStorageVersion();
|
||||||
|
if (version == null) {
|
||||||
|
Logger.warn(LOG_TAG, "null storage version for " + this + "; using version 0.");
|
||||||
|
version = new Integer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SynchronizerConfiguration config = this.getConfig();
|
||||||
|
if (config == null) {
|
||||||
|
return new EngineSettings(null, version.intValue());
|
||||||
|
}
|
||||||
|
return new EngineSettings(config.syncID, version.intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract String getCollection();
|
protected abstract String getCollection();
|
||||||
|
@ -113,17 +142,24 @@ public abstract class ServerSyncStage implements
|
||||||
Synchronizer synchronizer = new Synchronizer();
|
Synchronizer synchronizer = new Synchronizer();
|
||||||
synchronizer.repositoryA = remote;
|
synchronizer.repositoryA = remote;
|
||||||
synchronizer.repositoryB = this.getLocalRepository();
|
synchronizer.repositoryB = this.getLocalRepository();
|
||||||
|
synchronizer.load(getConfig());
|
||||||
|
|
||||||
SynchronizerConfiguration config = this.getConfig();
|
|
||||||
synchronizer.load(config);
|
|
||||||
|
|
||||||
// TODO: should wipe in either direction?
|
|
||||||
// TODO: syncID?!
|
|
||||||
return synchronizer;
|
return synchronizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset timestamps.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void resetLocal() {
|
public void resetLocal() {
|
||||||
|
resetLocal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset timestamps and possibly set syncID.
|
||||||
|
* @param syncID if non-null, new syncID to persist.
|
||||||
|
*/
|
||||||
|
public void resetLocal(String syncID) {
|
||||||
// Clear both timestamps.
|
// Clear both timestamps.
|
||||||
SynchronizerConfiguration config;
|
SynchronizerConfiguration config;
|
||||||
try {
|
try {
|
||||||
|
@ -133,10 +169,14 @@ public abstract class ServerSyncStage implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (syncID != null) {
|
||||||
|
config.syncID = syncID;
|
||||||
|
Logger.info(LOG_TAG, "Setting syncID for " + this + " to '" + syncID + "'.");
|
||||||
|
}
|
||||||
config.localBundle.setTimestamp(0L);
|
config.localBundle.setTimestamp(0L);
|
||||||
config.remoteBundle.setTimestamp(0L);
|
config.remoteBundle.setTimestamp(0L);
|
||||||
Logger.info(LOG_TAG, "Reset timestamps for " + this);
|
|
||||||
persistConfig(config);
|
persistConfig(config);
|
||||||
|
Logger.info(LOG_TAG, "Reset timestamps for " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not thread-safe. Use with caution.
|
// Not thread-safe. Use with caution.
|
||||||
|
@ -156,8 +196,8 @@ public abstract class ServerSyncStage implements
|
||||||
/**
|
/**
|
||||||
* Synchronously wipe this stage by instantiating a local repository session
|
* Synchronously wipe this stage by instantiating a local repository session
|
||||||
* and wiping that.
|
* and wiping that.
|
||||||
*
|
* <p>
|
||||||
* Logs and rethrows an exception on failure.
|
* Logs and re-throws an exception on failure.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void wipeLocal() throws Exception {
|
public void wipeLocal() throws Exception {
|
||||||
|
@ -290,6 +330,105 @@ public abstract class ServerSyncStage implements
|
||||||
Logger.info(LOG_TAG, "Wiping stage complete.");
|
Logger.info(LOG_TAG, "Wiping stage complete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws NoSuchStageException {
|
public void execute() throws NoSuchStageException {
|
||||||
final String name = getEngineName();
|
final String name = getEngineName();
|
||||||
|
@ -301,12 +440,36 @@ public abstract class ServerSyncStage implements
|
||||||
session.advance();
|
session.advance();
|
||||||
return;
|
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) {
|
} catch (MetaGlobalException e) {
|
||||||
session.abort(e, "Inappropriate meta/global; refusing to execute " + name + " stage.");
|
session.abort(e, "Inappropriate meta/global; refusing to execute " + name + " stage.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Synchronizer synchronizer;
|
Synchronizer synchronizer;
|
||||||
try {
|
try {
|
||||||
synchronizer = this.getConfiguredSynchronizer(session);
|
synchronizer = this.getConfiguredSynchronizer(session);
|
||||||
|
@ -336,11 +499,11 @@ public abstract class ServerSyncStage implements
|
||||||
public void onSynchronized(Synchronizer synchronizer) {
|
public void onSynchronized(Synchronizer synchronizer) {
|
||||||
Logger.debug(LOG_TAG, "onSynchronized.");
|
Logger.debug(LOG_TAG, "onSynchronized.");
|
||||||
|
|
||||||
SynchronizerConfiguration synchronizerConfiguration = synchronizer.save();
|
SynchronizerConfiguration newConfig = synchronizer.save();
|
||||||
if (synchronizerConfiguration != null) {
|
if (newConfig != null) {
|
||||||
persistConfig(synchronizerConfiguration);
|
persistConfig(newConfig);
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(LOG_TAG, "Didn't get configuration from synchronizer after success");
|
Logger.warn(LOG_TAG, "Didn't get configuration from synchronizer after success.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info(LOG_TAG, "Advancing session.");
|
Logger.info(LOG_TAG, "Advancing session.");
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.sync.stage;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.sync.GlobalSession;
|
||||||
|
|
||||||
|
public class UploadMetaGlobalStage extends AbstractNonRepositorySyncStage {
|
||||||
|
public static final String LOG_TAG = "UploadMGStage";
|
||||||
|
|
||||||
|
public UploadMetaGlobalStage(GlobalSession session) {
|
||||||
|
super(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws NoSuchStageException {
|
||||||
|
if (session.hasUpdatedMetaGlobal()) {
|
||||||
|
session.uploadUpdatedMetaGlobal();
|
||||||
|
}
|
||||||
|
session.advance();
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import android.util.Log;
|
||||||
* updated bundle information.
|
* updated bundle information.
|
||||||
*/
|
*/
|
||||||
public class Synchronizer {
|
public class Synchronizer {
|
||||||
|
protected String configSyncID; // Used to pass syncID from load() back into save().
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I translate the fine-grained feedback of a SynchronizerSessionDelegate into
|
* I translate the fine-grained feedback of a SynchronizerSessionDelegate into
|
||||||
|
@ -109,8 +110,7 @@ public class Synchronizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SynchronizerConfiguration save() {
|
public SynchronizerConfiguration save() {
|
||||||
String syncID = null; // TODO: syncID.
|
return new SynchronizerConfiguration(configSyncID, bundleA, bundleB);
|
||||||
return new SynchronizerConfiguration(syncID, bundleA, bundleB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,6 +123,6 @@ public class Synchronizer {
|
||||||
public void load(SynchronizerConfiguration config) {
|
public void load(SynchronizerConfiguration config) {
|
||||||
bundleA = config.remoteBundle;
|
bundleA = config.remoteBundle;
|
||||||
bundleB = config.localBundle;
|
bundleB = config.localBundle;
|
||||||
// TODO: syncID.
|
configSyncID = config.syncID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,7 @@ sync/stage/NoSyncIDException.java
|
||||||
sync/stage/PasswordsServerSyncStage.java
|
sync/stage/PasswordsServerSyncStage.java
|
||||||
sync/stage/ServerSyncStage.java
|
sync/stage/ServerSyncStage.java
|
||||||
sync/stage/SyncClientsEngineStage.java
|
sync/stage/SyncClientsEngineStage.java
|
||||||
|
sync/stage/UploadMetaGlobalStage.java
|
||||||
sync/StubActivity.java
|
sync/StubActivity.java
|
||||||
sync/syncadapter/SyncAdapter.java
|
sync/syncadapter/SyncAdapter.java
|
||||||
sync/syncadapter/SyncService.java
|
sync/syncadapter/SyncService.java
|
||||||
|
@ -205,7 +206,6 @@ sync/synchronizer/SynchronizerSessionDelegate.java
|
||||||
sync/synchronizer/UnbundleError.java
|
sync/synchronizer/UnbundleError.java
|
||||||
sync/synchronizer/UnexpectedSessionException.java
|
sync/synchronizer/UnexpectedSessionException.java
|
||||||
sync/SynchronizerConfiguration.java
|
sync/SynchronizerConfiguration.java
|
||||||
sync/SynchronizerConfigurations.java
|
|
||||||
sync/ThreadPool.java
|
sync/ThreadPool.java
|
||||||
sync/UnexpectedJSONException.java
|
sync/UnexpectedJSONException.java
|
||||||
sync/UnknownSynchronizerConfigurationVersionException.java
|
sync/UnknownSynchronizerConfigurationVersionException.java
|
||||||
|
|
Загрузка…
Ссылка в новой задаче