Bug 1253111 - Part 1: Introduce new sync stage to handle info/configuration r=rnewman
MozReview-Commit-ID: 7MOgR7A5SOF
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -888,16 +888,17 @@ sync_java_files = [TOPSRCDIR + '/mobile/
'sync/delegates/KeyUploadDelegate.java',
'sync/delegates/MetaGlobalDelegate.java',
'sync/delegates/WipeServerDelegate.java',
'sync/EngineSettings.java',
'sync/ExtendedJSONObject.java',
'sync/GlobalSession.java',
'sync/HTTPFailureException.java',
'sync/InfoCollections.java',
+ 'sync/InfoConfiguration.java',
'sync/InfoCounts.java',
'sync/JSONRecordFetcher.java',
'sync/KeyBundleProvider.java',
'sync/MetaGlobal.java',
'sync/MetaGlobalException.java',
'sync/MetaGlobalMissingEnginesException.java',
'sync/MetaGlobalNotSetException.java',
'sync/middleware/Crypto5MiddlewareRepository.java',
@@ -1025,16 +1026,17 @@ sync_java_files = [TOPSRCDIR + '/mobile/
'sync/stage/AbstractSessionManagingSyncStage.java',
'sync/stage/AndroidBrowserBookmarksServerSyncStage.java',
'sync/stage/AndroidBrowserHistoryServerSyncStage.java',
'sync/stage/CheckPreconditionsStage.java',
'sync/stage/CompletedStage.java',
'sync/stage/EnsureCrypto5KeysStage.java',
'sync/stage/FennecTabsServerSyncStage.java',
'sync/stage/FetchInfoCollectionsStage.java',
+ 'sync/stage/FetchInfoConfigurationStage.java',
'sync/stage/FetchMetaGlobalStage.java',
'sync/stage/FormHistoryServerSyncStage.java',
'sync/stage/GlobalSyncStage.java',
'sync/stage/NoSuchStageException.java',
'sync/stage/PasswordsServerSyncStage.java',
'sync/stage/SafeConstrainedServer11Repository.java',
'sync/stage/ServerSyncStage.java',
'sync/stage/SyncClientsEngineStage.java',
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
@@ -27,16 +27,17 @@ import org.mozilla.gecko.sync.net.SyncSt
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.EnsureCrypto5KeysStage;
import org.mozilla.gecko.sync.stage.FennecTabsServerSyncStage;
import org.mozilla.gecko.sync.stage.FetchInfoCollectionsStage;
+import org.mozilla.gecko.sync.stage.FetchInfoConfigurationStage;
import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage;
import org.mozilla.gecko.sync.stage.FormHistoryServerSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import org.mozilla.gecko.sync.stage.NoSuchStageException;
import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
@@ -172,16 +173,18 @@ public class GlobalSession implements Ht
}
protected void prepareStages() {
Map<Stage, GlobalSyncStage> stages = new EnumMap<Stage, GlobalSyncStage>(Stage.class);
stages.put(Stage.checkPreconditions, new CheckPreconditionsStage());
stages.put(Stage.fetchInfoCollections, new FetchInfoCollectionsStage());
stages.put(Stage.fetchMetaGlobal, new FetchMetaGlobalStage());
+ stages.put(Stage.fetchInfoConfiguration, new FetchInfoConfigurationStage(
+ config.infoConfigurationURL(), getAuthHeaderProvider()));
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());
new file mode 100644
--- /dev/null
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java
@@ -0,0 +1,93 @@
+/* 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;
+
+import android.util.Log;
+
+import org.mozilla.gecko.background.common.log.Logger;
+
+/**
+ * Wraps and provides access to configuration data returned from info/configuration.
+ * Docs: https://docs.services.mozilla.com/storage/apis-1.5.html#general-info
+ *
+ * - <bold>max_request_bytes</bold>: the maximum size in bytes of the overall
+ * HTTP request body that will be accepted by the server.
+ *
+ * - <bold>max_post_records</bold>: the maximum number of records that can be
+ * uploaded to a collection in a single POST request.
+ *
+ * - <bold>max_post_bytes</bold>: the maximum combined size in bytes of the
+ * record payloads that can be uploaded to a collection in a single
+ * POST request.
+ *
+ * - <bold>max_total_records</bold>: the maximum number of records that can be
+ * uploaded to a collection as part of a batched upload.
+ *
+ * - <bold>max_total_bytes</bold>: the maximum combined size in bytes of the
+ * record payloads that can be uploaded to a collection as part of
+ * a batched upload.
+ */
+public class InfoConfiguration {
+ private static final String LOG_TAG = "InfoConfiguration";
+
+ public static final String MAX_REQUEST_BYTES = "max_request_bytes";
+ public static final String MAX_POST_RECORDS = "max_post_records";
+ public static final String MAX_POST_BYTES = "max_post_bytes";
+ public static final String MAX_TOTAL_RECORDS = "max_total_records";
+ public static final String MAX_TOTAL_BYTES = "max_total_bytes";
+
+ private static final long DEFAULT_MAX_REQUEST_BYTES = 1048576;
+ private static final long DEFAULT_MAX_POST_RECORDS = 100;
+ private static final long DEFAULT_MAX_POST_BYTES = 1048576;
+ private static final long DEFAULT_MAX_TOTAL_RECORDS = 10000;
+ private static final long DEFAULT_MAX_TOTAL_BYTES = 104857600;
+
+ // While int's upper range is (2^31-1), which in bytes is equivalent to 2.147 GB, let's be optimistic
+ // about the future and use long here, so that this code works if the server decides its clients are
+ // all on fiber and have congress-library sized bookmark collections.
+ // Record counts are long for the sake of simplicity.
+ public final long maxRequestBytes;
+ public final long maxPostRecords;
+ public final long maxPostBytes;
+ public final long maxTotalRecords;
+ public final long maxTotalBytes;
+
+ public InfoConfiguration() {
+ Logger.debug(LOG_TAG, "info/configuration is unavailable, using defaults");
+
+ maxRequestBytes = DEFAULT_MAX_REQUEST_BYTES;
+ maxPostRecords = DEFAULT_MAX_POST_RECORDS;
+ maxPostBytes = DEFAULT_MAX_POST_BYTES;
+ maxTotalRecords = DEFAULT_MAX_TOTAL_RECORDS;
+ maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES;
+ }
+
+ public InfoConfiguration(final ExtendedJSONObject record) {
+ Logger.debug(LOG_TAG, "info/configuration is " + record.toJSONString());
+
+ maxRequestBytes = getValueFromRecord(record, MAX_REQUEST_BYTES, DEFAULT_MAX_REQUEST_BYTES);
+ maxPostRecords = getValueFromRecord(record, MAX_POST_RECORDS, DEFAULT_MAX_POST_RECORDS);
+ maxPostBytes = getValueFromRecord(record, MAX_POST_BYTES, DEFAULT_MAX_POST_BYTES);
+ maxTotalRecords = getValueFromRecord(record, MAX_TOTAL_RECORDS, DEFAULT_MAX_TOTAL_RECORDS);
+ maxTotalBytes = getValueFromRecord(record, MAX_TOTAL_BYTES, DEFAULT_MAX_TOTAL_BYTES);
+ }
+
+ private static Long getValueFromRecord(ExtendedJSONObject record, String key, long defaultValue) {
+ if (!record.containsKey(key)) {
+ return defaultValue;
+ }
+
+ try {
+ Long val = record.getLong(key);
+ if (val == null) {
+ return defaultValue;
+ }
+ return val;
+ } catch (NumberFormatException e) {
+ Log.w(LOG_TAG, "Could not parse key " + key + " from record: " + record, e);
+ return defaultValue;
+ }
+ }
+}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java
@@ -25,16 +25,18 @@ import android.content.SharedPreferences
public class SyncConfiguration {
private static final String LOG_TAG = "SyncConfiguration";
// These must be set in GlobalSession's constructor.
public URI clusterURL;
public KeyBundle syncKeyBundle;
+ public InfoConfiguration infoConfiguration;
+
public CollectionKeys collectionKeys;
public InfoCollections infoCollections;
public MetaGlobal metaGlobal;
public String syncID;
protected final String username;
/**
@@ -361,16 +363,20 @@ public class SyncConfiguration {
protected String infoBaseURL() {
return clusterURL + "/info/";
}
public String infoCollectionsURL() {
return infoBaseURL() + "collections";
}
+ public String infoConfigurationURL() {
+ return infoBaseURL() + "configuration";
+ }
+
public String infoCollectionCountsURL() {
return infoBaseURL() + "collection_counts";
}
public String metaURL() {
return storageURL() + "/meta/global";
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java
@@ -4,15 +4,16 @@
package org.mozilla.gecko.sync.delegates;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
/**
* A fairly generic delegate to handle fetches of single JSON object blobs, as
- * provided by <code>info/collections</code> and <code>info/collection_counts</code>.
+ * provided by <code>info/configuration</code>, <code>info/collections</code>
+ * and <code>info/collection_counts</code>.
*/
public interface JSONRecordFetchDelegate {
public void handleSuccess(ExtendedJSONObject body);
public void handleFailure(SyncStorageResponse response);
public void handleError(Exception e);
}
new file mode 100644
--- /dev/null
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java
@@ -0,0 +1,59 @@
+/* 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.ExtendedJSONObject;
+import org.mozilla.gecko.sync.InfoConfiguration;
+import org.mozilla.gecko.sync.JSONRecordFetcher;
+import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
+import org.mozilla.gecko.sync.net.AuthHeaderProvider;
+import org.mozilla.gecko.sync.net.SyncStorageResponse;
+
+/**
+ * Fetches configuration data from info/configurations endpoint.
+ */
+public class FetchInfoConfigurationStage extends AbstractNonRepositorySyncStage {
+ private final String configurationURL;
+ private final AuthHeaderProvider authHeaderProvider;
+
+ public FetchInfoConfigurationStage(final String configurationURL, final AuthHeaderProvider authHeaderProvider) {
+ super();
+ this.configurationURL = configurationURL;
+ this.authHeaderProvider = authHeaderProvider;
+ }
+
+ public class StageInfoConfigurationDelegate implements JSONRecordFetchDelegate {
+ @Override
+ public void handleSuccess(final ExtendedJSONObject result) {
+ session.config.infoConfiguration = new InfoConfiguration(result);
+ session.advance();
+ }
+
+ @Override
+ public void handleFailure(final SyncStorageResponse response) {
+ // Handle all non-404 failures upstream.
+ if (response.getStatusCode() != 404) {
+ session.handleHTTPError(response, "Failure fetching info/configuration");
+ return;
+ }
+
+ // End-point might not be available (404) if server is running an older version.
+ // We will use default config values in this case.
+ session.config.infoConfiguration = new InfoConfiguration();
+ session.advance();
+ }
+
+ @Override
+ public void handleError(final Exception e) {
+ session.abort(e, "Failure fetching info/configuration");
+ }
+ }
+ @Override
+ public void execute() {
+ final StageInfoConfigurationDelegate delegate = new StageInfoConfigurationDelegate();
+ final JSONRecordFetcher fetcher = new JSONRecordFetcher(configurationURL, authHeaderProvider);
+ fetcher.fetch(delegate);
+ }
+}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java
@@ -13,16 +13,17 @@ import java.util.Map;
import org.mozilla.gecko.sync.GlobalSession;
public interface GlobalSyncStage {
public static enum Stage {
idle, // Start state.
checkPreconditions, // Preparation of the basics. TODO: clear status
fetchInfoCollections, // Take a look at timestamps.
+ fetchInfoConfiguration, // Fetch server upload limits
fetchMetaGlobal,
ensureKeysStage,
/*
ensureSpecialRecords,
updateEngineTimestamps,
*/
syncClientsEngine(SyncClientsEngineStage.STAGE_NAME),
/*