--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
@@ -1,16 +1,17 @@
/* 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 android.accounts.Account;
import android.content.Context;
+import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
@@ -48,16 +49,17 @@ import org.mozilla.gecko.sync.net.SyncSt
import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate;
import org.mozilla.gecko.sync.net.WBORequestDelegate;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.repositories.domain.ClientRecordFactory;
import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
+import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
import ch.boye.httpclientandroidlib.HttpStatus;
public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
private static final String LOG_TAG = "SyncClientsEngineStage";
public static final String COLLECTION_NAME = "clients";
public static final String STAGE_NAME = COLLECTION_NAME;
@@ -239,29 +241,29 @@ public class SyncClientsEngineStage exte
@Override
public void handleRequestFailure(SyncStorageResponse response) {
BaseResource.consumeEntity(response); // We don't need the response at all, and any exception handling shouldn't need the response body.
localAccountGUIDDownloaded = false;
try {
Logger.info(LOG_TAG, "Client upload failed. Aborting sync.");
- session.abort(new HTTPFailureException(response), "Client download failed.");
+ doAbort(new HTTPFailureException(response), "Client download failed.");
} finally {
// Close the database upon failure.
closeDataAccessor();
}
}
@Override
public void handleRequestError(Exception ex) {
localAccountGUIDDownloaded = false;
try {
Logger.info(LOG_TAG, "Client upload error. Aborting sync.");
- session.abort(ex, "Failure fetching client record.");
+ doAbort(ex, "Failure fetching client record.");
} finally {
// Close the database upon error.
closeDataAccessor();
}
}
@Override
public void handleWBO(CryptoRecord record) {
@@ -271,21 +273,24 @@ public class SyncClientsEngineStage exte
if (clientsDelegate.isLocalGUID(r.guid)) {
Logger.info(LOG_TAG, "Local client GUID exists on server and was downloaded.");
localAccountGUIDDownloaded = true;
handleDownloadedLocalRecord(r);
} else {
// Only need to store record if it isn't our local one.
wipeAndStore(r);
addCommands(r);
+ // Note that we are downloading all client records during every sync. As such, telemetry
+ // will include every client currently present in the constellation of devices.
+ // See the downloadClientRecords method elsewhere in the file.
telemetryStageCollector.getSyncCollector().addDevice(r);
}
RepoUtils.logClient(r);
} catch (Exception e) {
- session.abort(e, "Exception handling client WBO.");
+ doAbort(e, "Exception handling client WBO.");
return;
}
}
@Override
public KeyBundle keyBundle() {
try {
return session.keyBundleForCollection(COLLECTION_NAME);
@@ -330,17 +335,17 @@ public class SyncClientsEngineStage exte
// X-Weave-Timestamp is the modified time of uploaded records.
// Always persist this.
final long responseTimestamp = response.normalizedWeaveTimestamp();
Logger.trace(LOG_TAG, "Timestamp from header is: " + responseTimestamp);
if (responseTimestamp == -1) {
final String message = "Response did not contain a valid timestamp.";
- session.abort(new RuntimeException(message), message);
+ doAbort(new RuntimeException(message), message);
return;
}
BaseResource.consumeEntity(response);
session.config.persistServerClientsTimestamp(responseTimestamp);
// If we're not uploading our record, we're done here; just
// clean up and finish.
@@ -350,17 +355,17 @@ public class SyncClientsEngineStage exte
checkAndUpload();
return;
}
// If we're processing our record, we have a little more cleanup
// to do.
shouldUploadLocalRecord = false;
session.config.persistServerClientRecordTimestamp(responseTimestamp);
- session.advance();
+ doAdvance();
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
int statusCode = response.getStatusCode();
// If upload failed because of `ifUnmodifiedSince` then there are new
// commands uploaded to our record. We must download and process them first.
@@ -368,31 +373,31 @@ public class SyncClientsEngineStage exte
statusCode == HttpStatus.SC_PRECONDITION_FAILED ||
uploadAttemptsCount.incrementAndGet() > MAX_UPLOAD_FAILURE_COUNT) {
Logger.debug(LOG_TAG, "Client upload failed. Aborting sync.");
if (!currentlyUploadingLocalRecord) {
modifiedClientsToUpload.clear(); // These will be redownloaded.
}
BaseResource.consumeEntity(response); // The exception thrown should need the response body.
- session.abort(new HTTPFailureException(response), "Client upload failed.");
+ doAbort(new HTTPFailureException(response), "Client upload failed.");
return;
}
Logger.trace(LOG_TAG, "Retrying upload…");
// Preconditions:
// shouldUploadLocalRecord == true &&
// statusCode != 412 &&
// uploadAttemptCount < MAX_UPLOAD_FAILURE_COUNT
checkAndUpload();
}
@Override
public void handleRequestError(Exception ex) {
Logger.info(LOG_TAG, "Client upload error. Aborting sync.");
- session.abort(ex, "Client upload failed.");
+ doAbort(ex, "Client upload failed.");
}
@Override
public KeyBundle keyBundle() {
try {
return session.keyBundleForCollection(COLLECTION_NAME);
} catch (NoCollectionKeysSetException e) {
return null;
@@ -403,17 +408,17 @@ public class SyncClientsEngineStage exte
@Override
public void execute() throws NoSuchStageException {
// We can be disabled just for this sync.
boolean enabledThisSync = session.isEngineLocallyEnabled(STAGE_NAME);
if (!enabledThisSync) {
// These log messages look best when they match the messages in ServerSyncStage.
Logger.debug(LOG_TAG, "Stage " + STAGE_NAME + " disabled just for this sync.");
Logger.info(LOG_TAG, "Skipping stage " + STAGE_NAME + ".");
- session.advance();
+ doAdvance();
return;
}
if (shouldDownload()) {
downloadClientRecords(); // Will kick off upload, too…
} else {
// Upload if necessary.
}
@@ -587,17 +592,17 @@ public class SyncClientsEngineStage exte
Logger.debug(LOG_TAG, "Uploading records: " + cryptoRecords.size());
clientUploadDelegate.setUploadDetails(false);
this.uploadClientRecords(cryptoRecords);
}
protected void checkAndUpload() {
if (!shouldUpload()) {
Logger.debug(LOG_TAG, "Not uploading client record.");
- session.advance();
+ doAdvance();
return;
}
final ClientRecord localClient = newLocalClientRecord(session.getClientsDelegate());
clientUploadDelegate.setUploadDetails(true);
CryptoRecord cryptoRecord = encryptClientRecord(localClient);
if (cryptoRecord != null) {
this.uploadClientRecord(cryptoRecord);
@@ -607,24 +612,24 @@ public class SyncClientsEngineStage exte
protected CryptoRecord encryptClientRecord(ClientRecord recordToUpload) {
// Generate CryptoRecord from ClientRecord to upload.
final String encryptionFailure = "Couldn't encrypt new client record.";
try {
CryptoRecord cryptoRecord = recordToUpload.getEnvelope();
cryptoRecord.keyBundle = clientUploadDelegate.keyBundle();
if (cryptoRecord.keyBundle == null) {
- session.abort(new NoCollectionKeysSetException(), "No collection keys set.");
+ doAbort(new NoCollectionKeysSetException(), "No collection keys set.");
return null;
}
return cryptoRecord.encrypt();
} catch (UnsupportedEncodingException e) {
- session.abort(e, encryptionFailure + " Unsupported encoding.");
+ doAbort(e, encryptionFailure + " Unsupported encoding.");
} catch (CryptoException e) {
- session.abort(e, encryptionFailure);
+ doAbort(e, encryptionFailure);
}
return null;
}
public void clearRecordsToUpload() {
try {
getClientsDatabaseAccessor().wipeCommandsTable();
modifiedClientsToUpload.clear();
@@ -640,46 +645,46 @@ public class SyncClientsEngineStage exte
try {
final URI getURI = session.config.collectionURI(COLLECTION_NAME, true);
final SyncStorageCollectionRequest request = new SyncStorageCollectionRequest(getURI);
request.delegate = clientDownloadDelegate;
Logger.trace(LOG_TAG, "Downloading client records.");
request.get();
} catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
+ doAbort(e, "Invalid URI.");
}
}
protected void uploadClientRecords(JSONArray records) {
Logger.trace(LOG_TAG, "Uploading " + records.size() + " client records.");
try {
final URI postURI = session.config.collectionURI(COLLECTION_NAME, false);
final SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
request.delegate = clientUploadDelegate;
request.post(records);
} catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
+ doAbort(e, "Invalid URI.");
} catch (Exception e) {
- session.abort(e, "Unable to parse body.");
+ doAbort(e, "Unable to parse body.");
}
}
/**
* Upload a client record via HTTP POST to the parent collection.
*/
protected void uploadClientRecord(CryptoRecord record) {
Logger.debug(LOG_TAG, "Uploading client record " + record.guid);
try {
final URI postURI = session.config.collectionURI(COLLECTION_NAME);
final SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
request.delegate = clientUploadDelegate;
request.post(record);
} catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
+ doAbort(e, "Invalid URI.");
}
}
protected ClientDownloadDelegate makeClientDownloadDelegate() {
return new ClientDownloadDelegate();
}
protected void wipeAndStore(ClientRecord record) {
@@ -687,9 +692,22 @@ public class SyncClientsEngineStage exte
if (shouldWipe) {
db.wipeClientsTable();
shouldWipe = false;
}
if (record != null) {
db.store(record);
}
}
+
+ private void doAdvance() {
+ telemetryStageCollector.finished = SystemClock.elapsedRealtime();
+ session.advance();
+ }
+
+ private void doAbort(Exception e, String reason) {
+ telemetryStageCollector.finished = SystemClock.elapsedRealtime();
+ telemetryStageCollector.error = new TelemetryCollector.StageErrorBuilder()
+ .setLastException(e)
+ .build();
+ session.abort(e, reason);
+ }
}