Bug 747065 - Precondition failed on clients PUT. r=nalexander, a=blocking-fennec

This commit is contained in:
Richard Newman 2012-04-24 11:50:37 -07:00
Родитель 97b5e3bdb0
Коммит 0cdab01fea
6 изменённых файлов: 91 добавлений и 100 удалений

Просмотреть файл

@ -191,7 +191,9 @@ public class SyncConfiguration implements CredentialsSource {
public String prefsPath;
public PrefsSource prefsSource;
public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp";
public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched.
public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched.
public static final String PREF_CLUSTER_URL = "clusterURL";
public static final String PREF_SYNC_ID = "syncID";
@ -298,6 +300,10 @@ public class SyncConfiguration implements CredentialsSource {
(trailingSlash ? "/storage/" : "/storage");
}
public URI collectionURI(String collection) throws URISyntaxException {
return new URI(storageURL(true) + collection);
}
public URI collectionURI(String collection, boolean full) throws URISyntaxException {
// Do it this way to make it easier to add more params later.
// It's pretty ugly, I'll grant.
@ -371,6 +377,10 @@ public class SyncConfiguration implements CredentialsSource {
return this.getPrefs().edit();
}
/**
* We persist two different clients timestamps: our own record's,
* and the timestamp for the collection.
*/
public void persistServerClientRecordTimestamp(long timestamp) {
getEditor().putLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, timestamp).commit();
}
@ -379,6 +389,14 @@ public class SyncConfiguration implements CredentialsSource {
return getPrefs().getLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, 0);
}
public void persistServerClientsTimestamp(long timestamp) {
getEditor().putLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, timestamp).commit();
}
public long getPersistedServerClientsTimestamp() {
return getPrefs().getLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, 0);
}
public void purgeCryptoKeys() {
if (collectionKeys != null) {
collectionKeys.clear();

Просмотреть файл

@ -180,7 +180,12 @@ public class Utils {
}
public static String millisecondsToDecimalSecondsString(long ms) {
return new BigDecimal(ms).movePointLeft(3).toString();
return millisecondsToDecimalSeconds(ms).toString();
}
// For dumping into JSON without quotes.
public static BigDecimal millisecondsToDecimalSeconds(long ms) {
return new BigDecimal(ms).movePointLeft(3);
}
// This lives until Bug 708956 lands, and we don't have to do it any more.

Просмотреть файл

@ -1,39 +1,6 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.net;
@ -41,10 +8,12 @@ import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ThreadPool;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.entity.StringEntity;
/**
@ -88,20 +57,36 @@ public class SyncStorageRecordRequest extends SyncStorageRequest {
return new SyncStorageRecordResourceDelegate(request);
}
/**
* Helper for turning a JSON object into a payload.
* @throws UnsupportedEncodingException
*/
protected StringEntity jsonEntity(JSONObject body) throws UnsupportedEncodingException {
StringEntity e = new StringEntity(body.toJSONString(), "UTF-8");
protected static StringEntity stringEntity(String s) throws UnsupportedEncodingException {
StringEntity e = new StringEntity(s, "UTF-8");
e.setContentType("application/json");
return e;
}
/**
* Helper for turning a JSON object into a payload.
* @throws UnsupportedEncodingException
*/
protected static StringEntity jsonEntity(JSONObject body) throws UnsupportedEncodingException {
return stringEntity(body.toJSONString());
}
/**
* Helper for turning a JSON array into a payload.
* @throws UnsupportedEncodingException
*/
protected static HttpEntity jsonEntity(JSONArray toPOST) throws UnsupportedEncodingException {
return stringEntity(toPOST.toJSONString());
}
@SuppressWarnings("unchecked")
public void post(JSONObject body) {
// Let's do this the trivial way for now.
// Note that POSTs should be an array, so we wrap here.
final JSONArray toPOST = new JSONArray();
toPOST.add(body);
try {
this.resource.post(jsonEntity(body));
this.resource.post(jsonEntity(toPOST));
} catch (UnsupportedEncodingException e) {
this.delegate.handleRequestError(e);
}

Просмотреть файл

@ -1,47 +1,13 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Android Sync Client.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* 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.net;
import java.io.IOException;
import java.util.HashMap;
import android.util.Log;
import org.mozilla.gecko.sync.Logger;
import ch.boye.httpclientandroidlib.HttpResponse;
@ -81,7 +47,7 @@ public class SyncStorageResponse extends SyncResponse {
SERVER_ERROR_MESSAGES = errors;
}
public static String getServerErrorMessage(String body) {
Log.d(LOG_TAG, "Looking up message for body \"" + body + "\"");
Logger.debug(LOG_TAG, "Looking up message for body \"" + body + "\"");
if (SERVER_ERROR_MESSAGES.containsKey(body)) {
return SERVER_ERROR_MESSAGES.get(body);
}

Просмотреть файл

@ -64,11 +64,21 @@ public class ClientRecord extends Record {
putPayload(payload, "type", this.type);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ClientRecord) || !super.equals(o)) {
return false;
}
return this.equalPayloads(o);
}
@Override
public boolean equalPayloads(Object o) {
if (!(o instanceof ClientRecord) || !super.equalPayloads(o)) {
return false;
}
ClientRecord other = (ClientRecord) o;
if (!RepoUtils.stringsEqual(other.name, this.name) ||
!RepoUtils.stringsEqual(other.type, this.type)) {

Просмотреть файл

@ -109,7 +109,11 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
BaseResource.consumeEntity(response); // We don't need the response at all.
// Hang onto the server's last modified timestamp to use
// in X-If-Unmodified-Since for upload.
session.config.persistServerClientsTimestamp(response.normalizedWeaveTimestamp());
BaseResource.consumeEntity(response);
// If we successfully downloaded all records but ours was not one of them
// then reset the timestamp.
@ -170,13 +174,9 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
r = (ClientRecord) factory.createRecord(record.decrypt());
if (clientsDelegate.isLocalGUID(r.guid)) {
Logger.info(LOG_TAG, "Local client GUID exists on server and was downloaded");
localAccountGUIDDownloaded = true;
// Oh hey! Our record is on the server. This is the authoritative
// server timestamp, so let's hang on to it to decide whether we
// need to upload.
session.config.persistServerClientRecordTimestamp(r.lastModified);
// Process commands.
localAccountGUIDDownloaded = true;
session.config.persistServerClientRecordTimestamp(r.lastModified);
processCommands(r.commands);
}
RepoUtils.logClient(r);
@ -208,7 +208,8 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
@Override
public String ifUnmodifiedSince() {
Long timestampInMilliseconds = session.config.getPersistedServerClientRecordTimestamp();
// Use the timestamp for the whole collection per Sync storage 1.1 spec.
Long timestampInMilliseconds = session.config.getPersistedServerClientsTimestamp();
// It's the first upload so we don't care about X-If-Unmodified-Since.
if (timestampInMilliseconds == 0) {
@ -225,12 +226,14 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
commandsProcessedShouldUpload = false;
uploadAttemptsCount.set(0);
long timestamp = Utils.decimalSecondsToMilliseconds(response.body());
// Persist the timestamp for the record we just uploaded,
// and bump the collection timestamp, too.
long timestamp = response.normalizedWeaveTimestamp();
session.config.persistServerClientRecordTimestamp(timestamp);
session.config.persistServerClientsTimestamp(timestamp);
BaseResource.consumeEntity(response);
Logger.debug(LOG_TAG, "Timestamp from body is: " + timestamp);
Logger.debug(LOG_TAG, "Timestamp from header is: " + response.normalizedWeaveTimestamp());
Logger.debug(LOG_TAG, "Timestamp is " + timestamp);
} catch (Exception e) {
session.abort(e, "Unable to fetch timestamp.");
return;
@ -289,7 +292,9 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
@Override
public void resetLocal() {
// Clear timestamps and local data.
session.config.persistServerClientRecordTimestamp(0L);
session.config.persistServerClientRecordTimestamp(0L); // TODO: roll these into one.
session.config.persistServerClientsTimestamp(0L);
session.getClientsDelegate().setClientsCount(0);
try {
getClientsDatabaseAccessor().wipe();
@ -391,14 +396,16 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
}
}
/**
* 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 {
URI putURI = session.config.wboURI(COLLECTION_NAME, record.guid);
SyncStorageRecordRequest request = new SyncStorageRecordRequest(putURI);
URI postURI = session.config.collectionURI(COLLECTION_NAME);
SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
request.delegate = clientUploadDelegate;
request.put(record);
request.post(record);
} catch (URISyntaxException e) {
session.abort(e, "Invalid URI.");
}