Merge weave to weave-about-weave with conflicts.
authorEdward Lee <edilee@mozilla.com>
Wed, 16 Sep 2009 13:39:51 -0700
changeset 45756 948dd8614516f29737916b6c3279322d5a64afe4
parent 45755 b155ec29d62f302eec00b707fc5d601190b03ce6 (current diff)
parent 45747 f0fa8ef029c976ba2be51ce7992946c4561c745c (diff)
child 45757 90b37988a63e53198b34eefa23f80288002c593e
push id14033
push useredward.lee@engineering.uiuc.edu
push dateWed, 23 Jun 2010 22:21:35 +0000
treeherdermozilla-central@227db4ad8cdf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
Merge weave to weave-about-weave with conflicts.
services/sync/modules/constants.js
services/sync/modules/engines.js
services/sync/modules/service.js
new file mode 100644
--- /dev/null
+++ b/services/sync/locales/en-US/errors.properties
@@ -0,0 +1,4 @@
+error.login.reason.password   = Your username/password failed
+error.login.reason.unknown    = Unknown error
+error.login.reason.passphrase = Invalid passphrase
+error.login.reason.network    = Network error
--- a/services/sync/locales/en-US/sync.properties
+++ b/services/sync/locales/en-US/sync.properties
@@ -15,11 +15,11 @@ status.offline = Not Signed In
 #status.active = Working...
 
 error.login.title = Error While Signing In
 error.login.description = Weave encountered an error while signing you in: %1$S.  Please try again.
 # should decide if we're going to show this
 error.logout.title = Error While Signing Out
 error.logout.description = Weave encountered an error while signing you out.  It's probably ok, and you don't have to do anything about it.
 error.sync.title = Error While Syncing
-error.sync.description = Weave encountered an error while syncing.  You might want to try syncing manually to make sure you are up-to-date.
+error.sync.description = Weave encountered an error while syncing: %1$S.  Weave will automatically retry this action.
 error.sync.tryAgainButton.label = Sync Now
 error.sync.tryAgainButton.accesskey = S
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -29,16 +29,17 @@
  * 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 ***** */
 
+<<<<<<< local
 const EXPORTED_SYMBOLS = ["WEAVE_VERSION", "COMPATIBLE_VERSION",
 			  "PREFS_BRANCH", "PWDMGR_HOST",
 			  'MODE_RDONLY', 'MODE_WRONLY',
 			  'MODE_CREATE', 'MODE_APPEND', 'MODE_TRUNCATE',
 			  'PERMS_FILE', 'PERMS_PASSFILE', 'PERMS_DIRECTORY',
 			  'ONE_BYTE', 'ONE_KILOBYTE', 'ONE_MEGABYTE',
                           'CONNECTION_TIMEOUT', 'MAX_UPLOAD_RECORDS',
                           'WEAVE_STATUS_OK', 'WEAVE_STATUS_FAILED',
@@ -50,87 +51,103 @@ const EXPORTED_SYMBOLS = ["WEAVE_VERSION
                           'VERSION_OUT_OF_DATE', 'DESKTOP_VERSION_OUT_OF_DATE',
                           'KEYS_DOWNLOAD_FAIL', 'NO_KEYS_NO_KEYGEN', 'KEYS_UPLOAD_FAIL',
                           'SETUP_FAILED_NO_PASSPHRASE', 'ABORT_SYNC_COMMAND',
                           'kSyncWeaveDisabled', 'kSyncNotLoggedIn',
                           'kSyncNetworkOffline', 'kSyncInPrivateBrowsing',
                           'kSyncNotScheduled',
                           'FIREFOX_ID', 'THUNDERBIRD_ID', 'FENNEC_ID', 'SEAMONKEY_ID',
                           'UI_DATA_TYPES_PER_ROW'];
-
-const WEAVE_VERSION = "@weave_version@";
+=======
+// Process each item in the "constants hash" to add to "global" and give a name
+let EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({
+>>>>>>> other
 
-// last client version's server storage this version supports
-// e.g. if set to the current version, this client will wipe the server
-// data stored by any older client
-const COMPATIBLE_VERSION = "@compatible_version@";
+WEAVE_VERSION:                         "@weave_version@",
 
-const PREFS_BRANCH = "extensions.weave.";
+// Last client version this client can read. If the server contains an older
+// version, this client will wipe the data on the server first.
+COMPATIBLE_VERSION:                    "@compatible_version@",
+
+PREFS_BRANCH:                          "extensions.weave.",
 
 // Host "key" to access Weave Identity in the password manager
-const PWDMGR_HOST = "chrome://weave";
+PWDMGR_HOST:                           "chrome://weave",
 
-const MODE_RDONLY   = 0x01;
-const MODE_WRONLY   = 0x02;
-const MODE_CREATE   = 0x08;
-const MODE_APPEND   = 0x10;
-const MODE_TRUNCATE = 0x20;
+// File IO Flags
+MODE_RDONLY:                           0x01,
+MODE_WRONLY:                           0x02,
+MODE_CREATE:                           0x08,
+MODE_APPEND:                           0x10,
+MODE_TRUNCATE:                         0x20,
 
-const PERMS_FILE      = 0644;
-const PERMS_PASSFILE  = 0600;
-const PERMS_DIRECTORY = 0755;
+// File Permission flags
+PERMS_FILE:                            0644,
+PERMS_PASSFILE:                        0600,
+PERMS_DIRECTORY:                       0755,
 
-const ONE_BYTE = 1;
-const ONE_KILOBYTE = 1024 * ONE_BYTE;
-const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
-
-const CONNECTION_TIMEOUT = 30000;
-
-// How many records to upload in a single POST
-// If there are more, multiple POST calls will be made.
+// Number of records to upload in a single POST (multiple POSTS if exceeded)
 // Record size limit is currently 10K, so 100 is a bit over 1MB
-const MAX_UPLOAD_RECORDS = 100;
+MAX_UPLOAD_RECORDS:                    100,
 
 // Top-level statuses:
-const WEAVE_STATUS_OK = "Sync succeeded.";
-const WEAVE_STATUS_FAILED = "Sync failed.";
-const WEAVE_STATUS_PARTIAL = "Sync partially succeeded, some data failed to sync.";
+STATUS_OK:                             "success.status_ok",
+SYNC_FAILED:                           "error.sync.failed",
+LOGIN_FAILED:                          "error.login.failed",
+SYNC_FAILED_PARTIAL:                   "error.sync.failed_partial",
+STATUS_DISABLED:                       "service.disabled",
 
-// Server statuses (Not mutually exclusive):
-const SERVER_LOW_QUOTA = "Getting close to your Weave server storage quota.";
-const SERVER_DOWNTIME = "Weave server is overloaded, try agian in 30 sec.";
-const SERVER_UNREACHABLE = "Weave server is unreachable.";
+// success states
+LOGIN_SUCCEEDED:                       "success.login",
+SYNC_SUCCEEDED:                        "success.sync",
+ENGINE_SUCCEEDED:                      "success.engine",
+
+// login failure status codes:
+LOGIN_FAILED_NO_USERNAME:              "error.login.reason.no_username",
+LOGIN_FAILED_NO_PASSWORD:              "error.login.reason.no_password",
+LOGIN_FAILED_NETWORK_ERROR:            "error.login.reason.network",
+LOGIN_FAILED_INVALID_PASSPHRASE:       "error.login.reason.passphrase.",
+LOGIN_FAILED_LOGIN_REJECTED:           "error.login.reason.password",
 
-// Ways that a sync can fail during setup or login:
-const LOGIN_FAILED_NO_USERNAME = "No username set, login failed.";
-const LOGIN_FAILED_NO_PASSWORD = "No password set, login failed.";
-const LOGIN_FAILED_NETWORK_ERROR = "Weave failed to connect to the server.";
-const LOGIN_FAILED_INVALID_PASSPHRASE = "Incorrect passphrase given.";
-const LOGIN_FAILED_LOGIN_REJECTED = "Incorrect username or password.";
-const METARECORD_DOWNLOAD_FAIL = "Can't download metadata record, HTTP error.";
-const VERSION_OUT_OF_DATE = "This copy of Weave needs to be updated.";
-const DESKTOP_VERSION_OUT_OF_DATE = "Weave needs updating on your desktop browser.";
-const KEYS_DOWNLOAD_FAIL = "Can't download keys from server, HTTP error.";
-const NO_KEYS_NO_KEYGEN = "Key generation disabled. Sync from the desktop first.";
-const KEYS_UPLOAD_FAIL = "Could not upload keys.";
-const SETUP_FAILED_NO_PASSPHRASE = "Could not get encryption passphrase.";
+// sync failure status codes
+METARECORD_DOWNLOAD_FAIL:              "error.sync.reason.metarecord_download_fail",
+VERSION_OUT_OF_DATE:                   "error.sync.reason.version_out_of_date",
+DESKTOP_VERSION_OUT_OF_DATE:           "error.sync.reason.desktop_version_out_of_date",
+KEYS_DOWNLOAD_FAIL:                    "error.sync.reason.keys_download_fail",
+NO_KEYS_NO_KEYGEN:                     "error.sync.reason.no_keys_no_keygen",
+KEYS_UPLOAD_FAIL:                      "error.sync.reason.keys_upload_fail",
+SETUP_FAILED_NO_PASSPHRASE:            "error.sync.reason.setup_failed_no_passphrase",
+ABORT_SYNC_COMMAND:                    "aborting sync, process commands said so",
 
-// Ways that a sync can be disabled
-const kSyncWeaveDisabled = "Weave is disabled";
-const kSyncNotLoggedIn = "User is not logged in";
-const kSyncNetworkOffline = "Network is offline";
-const kSyncInPrivateBrowsing = "Private browsing is enabled";
-const kSyncNotScheduled = "Not scheduled to do sync";
-// If one of these happens, leave the top-level status the same!
+// engine failure status codes
+ENGINE_UPLOAD_FAIL:                    "error.engine.reason.record_upload_fail",
+ENGINE_DOWNLOAD_FAIL:                  "error.engine.reason.record_download_fail",
+ENGINE_UNKNOWN_FAIL:                   "error.engine.reason.unknown_fail",
+ENGINE_METARECORD_UPLOAD_FAIL:         "error.engine.reason.metarecord_upload_fail",
 
-// Ways that a sync can be aborted:
-const ABORT_SYNC_COMMAND = "aborting sync, process commands said so";
+// Ways that a sync can be disabled (messages only to be printed in debug log)
+kSyncWeaveDisabled:                    "Weave is disabled",
+kSyncNotLoggedIn:                      "User is not logged in",
+kSyncNetworkOffline:                   "Network is offline",
+kSyncInPrivateBrowsing:                "Private browsing is enabled",
+kSyncNotScheduled:                     "Not scheduled to do sync",
+kSyncBackoffNotMet:                    "Trying to sync before the server said it's okay",
 
 // Application IDs
+<<<<<<< local
 const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
 const THUNDERBIRD_ID = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
 const FENNEC_ID = "{a23983c0-fd0e-11dc-95ff-0800200c9a66}";
 const SEAMONKEY_ID = "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}";
+=======
+FIREFOX_ID:                            "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+THUNDERBIRD_ID:                        "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
+FENNEC_ID:                             "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
+SEAMONKEY_ID:                          "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+>>>>>>> other
 
+<<<<<<< local
 //UI constants
 
 // How many data types (bookmarks, history, etc) to display per row
-const UI_DATA_TYPES_PER_ROW = 3;
\ No newline at end of file
+const UI_DATA_TYPES_PER_ROW = 3;=======
+}))];
+>>>>>>> other
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -357,19 +357,22 @@ SyncEngine.prototype = {
     let meta = CryptoMetas.get(this.cryptoMetaURL);
     if (!meta) {
       let symkey = Svc.Crypto.generateRandomKey();
       let pubkey = PubKeys.getDefaultKey();
       meta = new CryptoMeta(this.cryptoMetaURL);
       meta.generateIV();
       meta.addUnwrappedKey(pubkey, symkey);
       let res = new Resource(meta.uri);
-      let resp = res.put(meta);
-      if (!resp.success)
+      let resp = res.put(meta.serialize());
+      if (!resp.success) {
+        this._log.debug("Metarecord upload fail:" + resp);
+        resp.failureCode = ENGINE_METARECORD_UPLOAD_FAIL;
         throw resp;
+      }
 
       // Cache the cryto meta that we just put on the server
       CryptoMetas.set(meta.uri, meta);
     }
 
     // first sync special case: upload all items
     // NOTE: we use a backdoor (of sorts) to the tracker so it
     // won't save to disk this list over and over
@@ -426,18 +429,20 @@ SyncEngine.prototype = {
       }
       this._tracker.ignoreAll = false;
       Sync.sleep(0);
     });
 
     // Only bother getting data from the server if there's new things
     if (this.lastModified > this.lastSync) {
       let resp = newitems.get();
-      if (!resp.success)
+      if (!resp.success) {
+        resp.failureCode = ENGINE_DOWNLOAD_FAIL;
         throw resp;
+      }
     }
 
     // Check if we got the maximum that we requested; get the rest if so
     if (handled.length == newitems.limit) {
       let guidColl = new Collection(this.engineURL);
       guidColl.newer = this.lastSync;
       guidColl.sort = "index";
 
@@ -460,18 +465,21 @@ SyncEngine.prototype = {
 
       // Get the first bunch of records and save the rest for later, but don't
       // get too many records as there's a maximum server URI length (HTTP 414)
       newitems.ids = this.toFetch.slice(0, 150);
       this.toFetch = this.toFetch.slice(150);
 
       // Reuse the existing record handler set earlier
       let resp = newitems.get();
-      if (!resp.success)
+      if (!resp.success) {
+        resp.failureCode = ENGINE_DOWNLOAD_FAIL;
         throw resp;
+      }
+        
     }
 
     if (this.lastSync < this.lastModified)
       this.lastSync = this.lastModified;
 
     this._log.info(["Records:", count.applied, "applied,", count.reconciled,
       "reconciled,", this.toFetch.length, "left to fetch"].join(" "));
 
@@ -587,18 +595,21 @@ SyncEngine.prototype = {
       // collection we'll upload
       let up = new Collection(this.engineURL);
       let count = 0;
 
       // Upload what we've got so far in the collection
       let doUpload = Utils.bind2(this, function(desc) {
         this._log.info("Uploading " + desc + " of " + outnum + " records");
         let resp = up.post();
-        if (!resp.success)
+        if (!resp.success) {
+          this._log.debug("Uploading records failed: " + resp);
+          resp.failureCode = ENGINE_UPLOAD_FAIL;
           throw resp;
+        }
 
         // Record the modified time of the upload
         let modified = resp.headers["X-Weave-Timestamp"];
         if (modified > this.lastSync)
           this.lastSync = modified;
 
         up.clearRecords();
       });
--- a/services/sync/modules/resource.js
+++ b/services/sync/modules/resource.js
@@ -247,16 +247,20 @@ Resource.prototype = {
         // Only log the full response body (may be HTML) when Trace logging
         if (this._log.level <= Log4Moz.Level.Trace) {
           log = "trace";
           mesg += " " + this._data;
         }
 
         this._log[log](mesg);
       }
+
+      // this is a server-side safety valve to allow slowing down clients without hurting performance
+      if (headers["X-Weave-Backoff"])
+        Observers.notify("weave:service:backoff:interval", parseInt(headers["X-Weave-Backoff"], 10))
     }
     // Got a response but no header; must be cached (use default values)
     catch(ex) {
       this._log.debug(action + " cached: " + status);
     }
 
     let ret = new String(this._data);
     ret.headers = headers;
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -117,31 +117,54 @@ Utils.lazy(Weave, 'Service', WeaveSvc);
  * Service status query system.  See constants defined in constants.js.
  */
 
 function StatusRecord() {
   this._init();
 }
 StatusRecord.prototype = {
   _init: function() {
-    this.server = [];
+    this.service = null;
     this.sync = null;
+    this.login = null;
     this.engines = {};
-  },
-
-  addServerStatus: function(statusCode) {
-    this.server.push(statusCode);
+    this._resetBackoff();
   },
 
   setSyncStatus: function(statusCode) {
+    if (statusCode == SYNC_SUCCEEDED) {
+      this.service == STATUS_OK;
+      this._resetBackoff();
+    }
+    else
+      this.service = SYNC_FAILED;
+
     this.sync = statusCode;
   },
 
+  setLoginStatus: function(statusCode) {
+    this.service = statusCode == LOGIN_SUCCEEDED ? STATUS_OK : LOGIN_FAILED;
+    this.login = statusCode;
+  },
+
   setEngineStatus: function(engineName, statusCode) {
+    if (statusCode != ENGINE_SUCCEEDED)
+      this.service = this.sync = SYNC_FAILED_PARTIAL;
+
     this.engines[engineName] = statusCode;
+  },
+
+  resetEngineStatus: function() {
+    this.engines = {};
+  },
+  
+  _resetBackoff: function () {
+    this.enforceBackoff = false;
+    this.backoffInterval = 0;
+    this.minimumNextSync = 0;
   }
 };
 
 
 
 /*
  * Service singleton
  * Main entry point into Weave's sync framework
@@ -154,20 +177,18 @@ WeaveSvc.prototype = {
 
   _lock: Utils.lock,
   _catch: Utils.catch,
   _isQuitting: false,
   _loggedIn: false,
   _syncInProgress: false,
   _keyGenEnabled: true,
 
-  // WEAVE_STATUS_OK, WEAVE_STATUS_FAILED, or WEAVE_STATUS_PARTIAL
-  _weaveStatusCode: null,
-  // More detailed status info:
-  _detailedStatus: null,
+  // the status object
+  _status: null,
 
   // object for caching public and private keys
   _keyPair: {},
 
   // Timer object for automagically syncing
   _syncTimer: null,
 
   get username() {
@@ -226,35 +247,29 @@ WeaveSvc.prototype = {
   set isQuitting(value) { this._isQuitting = value; },
 
   get keyGenEnabled() { return this._keyGenEnabled; },
   set keyGenEnabled(value) { this._keyGenEnabled = value; },
 
   get enabled() { return Svc.Prefs.get("enabled"); },
   set enabled(value) { Svc.Prefs.set("enabled", value); },
 
-  get statusCode() { return this._weaveStatusCode; },
-  get detailedStatus() { return this._detailedStatus; },
+  get status() { return this._status; },
 
   get locked() { return this._locked; },
   lock: function Svc_lock() {
     if (this._locked)
       return false;
     this._locked = true;
     return true;
   },
   unlock: function Svc_unlock() {
     this._locked = false;
   },
 
-  _setSyncFailure: function WeavSvc__setSyncFailure(code) {
-    this._weaveStatusCode = WEAVE_STATUS_FAILED;
-    this._detailedStatus.setSyncStatus(code);
-  },
-
   _genKeyURLs: function WeaveSvc__genKeyURLs() {
     let url = this.userURL;
     PubKeys.defaultKeyUri = url + "/storage/keys/pubkey";
     PrivKeys.defaultKeyUri = url + "/storage/keys/privkey";
   },
 
   _checkCrypto: function WeaveSvc__checkCrypto() {
     let ok = false;
@@ -276,17 +291,17 @@ WeaveSvc.prototype = {
 
   // one-time initialization like setting up observers and the like
   // xxx we might need to split some of this out into something we can call
   //     again when username/server/etc changes
   onStartup: function WeaveSvc_onStartup() {
     this._initLogs();
     this._log.info("Weave " + WEAVE_VERSION + " initializing");
     this._registerEngines();
-    this._detailedStatus = new StatusRecord();
+    this._status = new StatusRecord();
 
     // Reset our sync id if we're upgrading, so sync knows to reset local data
     if (WEAVE_VERSION != Svc.Prefs.get("lastversion")) {
       this._log.info("Resetting client syncID from _onStartup.");
       Clients.resetSyncID();
     }
 
     let ua = Cc["@mozilla.org/network/protocol;1?name=http"].
@@ -300,16 +315,17 @@ WeaveSvc.prototype = {
     }
 
     Utils.prefs.addObserver("", this, false);
     Svc.Observer.addObserver(this, "network:offline-status-changed", true);
     Svc.Observer.addObserver(this, "private-browsing", true);
     Svc.Observer.addObserver(this, "quit-application", true);
     Svc.Observer.addObserver(this, "weave:service:sync:finish", true);
     Svc.Observer.addObserver(this, "weave:service:sync:error", true);
+    Svc.Observer.addObserver(this, "weave:service:backoff:interval", true);
 
     if (!this.enabled)
       this._log.info("Weave Sync disabled");
 
     // Create Weave identities (for logging in, and for encryption)
     ID.set('WeaveID', new Identity('Mozilla Services Password', this.username));
     Auth.defaultAuthenticator = new BasicAuthenticator(ID.get('WeaveID'));
 
@@ -412,17 +428,22 @@ WeaveSvc.prototype = {
       case "quit-application":
         this._onQuitApplication();
         break;
       case "weave:service:sync:error":
         this._handleSyncError();
         break;
       case "weave:service:sync:finish":
         this._scheduleNextSync();
-        this._serverErrors = 0;
+        this._syncErrors = 0;
+        break;
+      case "weave:service:backoff:interval":
+        let interval = data + Math.random() * data * 0.25; // required backoff + up to 25%
+        this.status.backoffInterval = interval;
+        this.status.minimumNextSync = Date.now() + data;
         break;
       case "idle":
         this._log.trace("Idle time hit, trying to sync");
         Svc.Idle.removeIdleObserver(this, IDLE_TIME);
         this.sync(false);
         break;
     }
   },
@@ -447,17 +468,17 @@ WeaveSvc.prototype = {
         case 200:
           return node;
         default:
           this._log.debug("Unexpected response code: " + node.status);
           break;
       }
     } catch (e) {
       this._log.debug("Network error on findCluster");
-      this._setSyncFailure(LOGIN_FAILED_NETWORK_ERROR);
+      this.status.setLoginStatus(LOGIN_FAILED_NETWORK_ERROR);
       throw e;
     }
   },
 
   // gets cluster from central LDAP server and sets this.clusterURL
   _setCluster: function _setCluster() {
     let cluster = this._findCluster();
     if (cluster) {
@@ -490,34 +511,36 @@ WeaveSvc.prototype = {
     this._catch(this._notify("verify-login", "", function() {
       this._setCluster();
       let res = new Resource(this.infoURL);
       try {
         let test = res.get();
         switch (test.status) {
           case 200:
             if (!this._verifyPassphrase()) {
-              this._setSyncFailure(LOGIN_FAILED_INVALID_PASSPHRASE);
+              this.status.setLoginStatus(LOGIN_FAILED_INVALID_PASSPHRASE);
               return false;
             }
+            this.status.setLoginStatus(LOGIN_SUCCEEDED);
             return true;
           case 401:
             if (this._updateCluster())
               return this._verifyLogin();
 
-            this._setSyncFailure(LOGIN_FAILED_LOGIN_REJECTED);
+            this.status.setLoginStatus(LOGIN_FAILED_LOGIN_REJECTED);
             this._log.debug("verifyLogin failed: login failed")
             return false;
           default:
+            this._checkServerError(test.status);
             throw "unexpected HTTP response: " + test.status;
         }
       } catch (e) {
         // if we get here, we have either a busted channel or a network error
         this._log.debug("verifyLogin failed: " + e)
-        this._setSyncFailure(LOGIN_FAILED_NETWORK_ERROR);
+        this.status.setLoginStatus(LOGIN_FAILED_NETWORK_ERROR);
         throw e;
       }
     }))(),
 
   _verifyPassphrase: function _verifyPassphrase()
     this._catch(this._notify("verify-passphrase", "", function() {
       try {
         let pubkey = PubKeys.getDefaultKey();
@@ -601,35 +624,34 @@ WeaveSvc.prototype = {
       let failureReason;
       if (Svc.IO.offline)
         failureReason = "Application is offline";
       else if (this.login()) {
         this.syncOnIdle();
         return;
       }
 
-      failureReason = this.detailedStatus.sync;
+      failureReason = this.status.sync;
     }
     catch (ex) {
       failureReason = ex;
     }
 
     this._log.debug("Autoconnect failed: " + failureReason);
 
     let listener = new Utils.EventListener(Utils.bind2(this,
       function WeaveSvc__autoConnectCallback(timer) {
         this._autoConnectTimer = null;
         this._autoConnect();
       }));
     this._autoConnectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
-    // back off slowly, with some random fuzz to avoid repeated collisions
-    let interval = Math.floor(Math.random() * SCHEDULED_SYNC_INTERVAL +
-                              SCHEDULED_SYNC_INTERVAL * this._autoConnectAttempts);
     this._autoConnectAttempts++;
+    let interval = this._calculateBackoff(this._autoConnectAttempts, 
+                                          SCHEDULED_SYNC_INTERVAL);
     this._autoConnectTimer.initWithCallback(listener, interval,
                                             Ci.nsITimer.TYPE_ONE_SHOT);
     this._log.debug("Scheduling next autoconnect attempt in " +
                     interval / 1000 + " seconds.");
   },
 
   persistLogin: function persistLogin() {
     // Canceled master password prompt can prevent these from succeeding
@@ -638,44 +660,43 @@ WeaveSvc.prototype = {
       ID.get("WeaveCryptoID").persist();
     }
     catch(ex) {}
   },
 
   login: function WeaveSvc_login(username, password, passphrase)
     this._catch(this._lock(this._notify("login", "", function() {
       this._loggedIn = false;
-      this._detailedStatus = new StatusRecord();
       if (Svc.IO.offline)
         throw "Application is offline, login should not be called";
 
       if (username)
         this.username = username;
       if (password)
         this.password = password;
       if (passphrase)
         this.passphrase = passphrase;
 
       if (!this.username) {
-        this._setSyncFailure(LOGIN_FAILED_NO_USERNAME);
+        this.status.setLoginStatus(LOGIN_FAILED_NO_USERNAME);
         throw "No username set, login failed";
       }
       if (!this.password) {
-        this._setSyncFailure(LOGIN_FAILED_NO_PASSWORD);
+        this.status.setLoginStatus(LOGIN_FAILED_NO_PASSWORD);
         throw "No password given or found in password manager";
       }
       this._log.info("Logging in user " + this.username);
 
       if (!this._verifyLogin()) {
         // verifyLogin sets the failure states here
-        throw "Login failed: " + this.detailedStatus.sync;
+        throw "Login failed: " + this.status.login;
       }
 
+      this._loggedIn = true;
       // Try starting the sync timer now that we're logged in
-      this._loggedIn = true;
       this._checkSyncStatus();
       if (this._autoConnectTimer) {
         this._autoConnectTimer.cancel();
         this._autoConnectTimer = null;
       }
 
       return true;
     })))(),
@@ -780,48 +801,49 @@ WeaveSvc.prototype = {
       COMPATIBLE_VERSION, "Remote:", remoteVersion].join(" "));
 
     if (!meta || !meta.payload.storageVersion || !meta.payload.syncID ||
         Svc.Version.compare(COMPATIBLE_VERSION, remoteVersion) > 0) {
 
       // abort the server wipe if the GET status was anything other than 404 or 200
       let status = Records.response.status;
       if (status != 200 && status != 404) {
-        this._setSyncFailure(METARECORD_DOWNLOAD_FAIL);
+        this._checkServerError(Records.response);
+        this.status.setSyncStatus(METARECORD_DOWNLOAD_FAIL);
         this._log.warn("Unknown error while downloading metadata record. " +
                        "Aborting sync.");
         return false;
       }
 
       if (!meta)
         this._log.info("No metadata record, server wipe needed");
       if (meta && !meta.payload.syncID)
         this._log.warn("No sync id, server wipe needed");
       if (Svc.Version.compare(COMPATIBLE_VERSION, remoteVersion) > 0)
         this._log.info("Server data is older than what Weave supports, server wipe needed");
 
       if (!this._keyGenEnabled) {
         this._log.info("...and key generation is disabled.  Not wiping. " +
                        "Aborting sync.");
-        this._setSyncFailure(DESKTOP_VERSION_OUT_OF_DATE);
+        this.status.setSyncStatus(DESKTOP_VERSION_OUT_OF_DATE);
         return false;
       }
       reset = true;
       this._log.info("Wiping server data");
       this._freshStart();
 
       if (status == 404)
         this._log.info("Metadata record not found, server wiped to ensure " +
                        "consistency.");
       else // 200
         this._log.info("Server data wiped to ensure consistency after client " +
                        "upgrade (" + remoteVersion + " -> " + WEAVE_VERSION + ")");
 
     } else if (Svc.Version.compare(remoteVersion, WEAVE_VERSION) > 0) {
-      this._setSyncFailure(VERSION_OUT_OF_DATE);
+      this.status.setSyncStatus(VERSION_OUT_OF_DATE);
       this._log.warn("Server data is of a newer Weave version, this client " +
                      "needs to be upgraded.  Aborting sync.");
       return false;
 
     } else if (meta.payload.syncID != Clients.syncID) {
       this._log.warn("Meta.payload.syncID is " + meta.payload.syncID +
                      ", Clients.syncID is " + Clients.syncID);
       this.resetClient();
@@ -851,24 +873,26 @@ WeaveSvc.prototype = {
         return true;
     }
 
     if (needKeys) {
       if (PubKeys.response.status != 404 && PrivKeys.response.status != 404) {
         this._log.warn("Couldn't download keys from server, aborting sync");
         this._log.debug("PubKey HTTP status: " + PubKeys.response.status);
         this._log.debug("PrivKey HTTP status: " + PrivKeys.response.status);
-        this._setSyncFailure(KEYS_DOWNLOAD_FAIL);
+        this._checkServerError(PubKeys.response);
+        this._checkServerError(PrivKeys.response);
+        this.status.setSyncStatus(KEYS_DOWNLOAD_FAIL);
         return false;
       }
 
       if (!this._keyGenEnabled) {
         this._log.warn("Couldn't download keys from server, and key generation" +
                        "is disabled.  Aborting sync");
-        this._setSyncFailure(NO_KEYS_NO_KEYGEN);
+        this.status.setSyncStatus(NO_KEYS_NO_KEYGEN);
         return false;
       }
 
       if (!reset) {
         this._log.warn("Calling freshStart from !reset case.");
         this._freshStart();
         this._log.info("Server data wiped to ensure consistency due to missing keys");
       }
@@ -879,21 +903,21 @@ WeaveSvc.prototype = {
                                          PrivKeys.defaultKeyUri);
         try {
           // Upload and cache the keypair
           PubKeys.uploadKeypair(keys);
           PubKeys.set(keys.pubkey.uri, keys.pubkey);
           PrivKeys.set(keys.privkey.uri, keys.privkey);
           return true;
         } catch (e) {
-          this._setSyncFailure(KEYS_UPLOAD_FAIL);
+          this.status.setSyncStatus(KEYS_UPLOAD_FAIL);
           this._log.error("Could not upload keys: " + Utils.exceptionStr(e));
         }
       } else {
-        this._setSyncFailure(SETUP_FAILED_NO_PASSPHRASE);
+        this.status.setSyncStatus(SETUP_FAILED_NO_PASSPHRASE);
         this._log.warn("Could not get encryption passphrase");
       }
     }
 
     return false;
   },
 
   /**
@@ -916,35 +940,40 @@ WeaveSvc.prototype = {
       reason = kSyncNotLoggedIn;
     else if (Svc.IO.offline)
       reason = kSyncNetworkOffline;
     else if (Svc.Private && Svc.Private.privateBrowsingEnabled)
       // Svc.Private doesn't exist on Fennec -- don't assume it's there.
       reason = kSyncInPrivateBrowsing;
     else if (Svc.Prefs.get("schedule", 0) != 1)
       reason = kSyncNotScheduled;
+    else if (this.status.minimumNextSync > Date.now())
+      reason = kSyncBackoffNotMet;
 
     return reason;
   },
 
   /**
    * Check if we should be syncing and schedule the next sync, if it's not scheduled
    */
   _checkSyncStatus: function WeaveSvc__checkSyncStatus() {
     // Should we be syncing now, if not, cancel any sync timers and return
-    if (this._checkSync()) {
+    // if we're in backoff, we'll schedule the next sync
+    let reason = this._checkSync();
+    if (reason && reason != kSyncBackoffNotMet) {
       if (this._syncTimer) {
         this._syncTimer.cancel();
         this._syncTimer = null;
       }
 
       try {
         Svc.Idle.removeIdleObserver(this, IDLE_TIME);
       } catch(e) {} // this throws if there isn't an observer, but that's fine
 
+      this.status.service = STATUS_DISABLED;
       return;
     }
 
     // otherwise, schedule the sync
     this._scheduleNextSync();
   },
 
   /**
@@ -957,17 +986,17 @@ WeaveSvc.prototype = {
     Svc.Idle.addIdleObserver(this, IDLE_TIME);
   },
 
   /**
    * Set a timer for the next sync
    */
   _scheduleNextSync: function WeaveSvc__scheduleNextSync(interval) {
     if (!interval)
-      interval = SCHEDULED_SYNC_INTERVAL;
+      interval = this.status.backoffInterval || SCHEDULED_SYNC_INTERVAL;
 
     // if there's an existing timer, cancel it and restart
     if (this._syncTimer)
       this._syncTimer.cancel();
     else
       this._syncTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     let listener = new Utils.EventListener(Utils.bind2(this,
@@ -975,100 +1004,71 @@ WeaveSvc.prototype = {
         this._syncTimer = null;
         this.syncOnIdle();
       }));
     this._syncTimer.initWithCallback(listener, interval,
                                      Ci.nsITimer.TYPE_ONE_SHOT);
     this._log.debug("Next sync call in: " + this._syncTimer.delay / 1000 + " seconds.")
   },
 
-  _serverErrors: 0,
+  _syncErrors: 0,
   /**
    * Deal with sync errors appropriately
    */
   _handleSyncError: function WeaveSvc__handleSyncError() {
-    let shouldBackoff = false;
-
-    let err = Weave.Service.detailedStatus.sync;
-    // we'll assume the server is just borked a little for these
-    switch (err) {
-      case METARECORD_DOWNLOAD_FAIL:
-      case KEYS_DOWNLOAD_FAIL:
-      case KEYS_UPLOAD_FAIL:
-        shouldBackoff = true;
-    }
+    this._syncErrors++;
 
-    // specifcally handle 500, 502, 503, 504 errors
-    // xxxmpc: what else should be in this list?
-    // this is sort of pseudocode, need a way to get at the
-    if (!shouldBackoff) {
-      try {
-        shouldBackoff = Utils.checkStatus(Records.response.status, null,
-          [500, [502, 504]]);
+    // do nothing on the first couple of failures, if we're not in backoff due to 5xx errors
+    if (!this.status.enforceBackoff) {
+      if (this._syncErrors < 3) {
+        this._scheduleNextSync();
+        return;
       }
-      catch (e) {
-        // if responseStatus throws, we have a network issue in play
-        shouldBackoff = true;
-      }
+      this.status.enforceBackoff = true;
     }
 
-    // if this is a client error, do the next sync as normal and return
-    if (!shouldBackoff) {
-      this._scheduleNextSync();
-      return;
-    }
-
-    // ok, something failed connecting to the server, rev the counter
-    this._serverErrors++;
+    const MINIMUM_BACKOFF_INTERVAL = 15 * 60 * 1000;     // 15 minutes
+    let interval = this._calculateBackoff(this._syncErrors, MINIMUM_BACKOFF_INTERVAL);
 
-    // do nothing on the first failure, if we fail again we'll back off
-    if (this._serverErrors < 2) {
-      this._scheduleNextSync();
-      return;
-    }
+    this._scheduleNextSync(interval);
 
-    // 30-60 minute backoff interval, increasing each time
-    const MINIMUM_BACKOFF_INTERVAL = 15 * 60 * 1000;     // 15 minutes * >= 2
-    const MAXIMUM_BACKOFF_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours
-    let backoffInterval = this._serverErrors *
-                          (Math.floor(Math.random() * MINIMUM_BACKOFF_INTERVAL) +
-                           MINIMUM_BACKOFF_INTERVAL);
-    backoffInterval = Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL);
-    this._scheduleNextSync(backoffInterval);
-
-    let d = new Date(Date.now() + backoffInterval);
+    let d = new Date(Date.now() + interval);
     this._log.config("Starting backoff, next sync at:" + d.toString());
   },
 
   /**
    * Sync up engines with the server.
    *
    * @param fullSync
    *        True to unconditionally sync all engines
    */
   sync: function WeaveSvc_sync(fullSync)
     this._catch(this._lock(this._notify("sync", "", function() {
-
+    this.status.resetEngineStatus();
     fullSync = true; // not doing thresholds yet
 
     // Use thresholds to determine what to sync only if it's not a full sync
     let useThresh = !fullSync;
 
     // Make sure we should sync or record why we shouldn't. We always obey the
     // reason if we're using thresholds (not a full sync); otherwise, allow
     // "not scheduled" as future syncs have already been canceled by checkSync.
     let reason = this._checkSync();
     if (reason && (useThresh || reason != kSyncNotScheduled)) {
       // this is a purposeful abort rather than a failure, so don't set
-      // WEAVE_STATUS_FAILED; instead, leave it as it was.
-      this._detailedStatus.setSyncStatus(reason);
+      // any status bits
       reason = "Can't sync: " + reason;
       throw reason;
     }
 
+    if (this._autoConnectTimer) {
+      this._autoConnectTimer.cancel();
+      this._autoConnectTimer = null;
+    }
+
     if (!(this._remoteSetup()))
       throw "aborting sync, remote setup failed";
 
     // Figure out what the last modified time is for each collection
     let info = new Resource(this.infoURL).get();
     if (!info.success)
       throw "aborting sync, failed to get collections";
 
@@ -1078,17 +1078,17 @@ WeaveSvc.prototype = {
 
     this._log.trace("Refreshing client list");
     Clients.sync();
 
     // Process the incoming commands if we have any
     if (Clients.getClients()[Clients.clientID].commands) {
       try {
         if (!(this.processCommands())) {
-          this._detailedStatus.setSyncStatus(ABORT_SYNC_COMMAND);
+          this.status.setSyncStatus(ABORT_SYNC_COMMAND);
           throw "aborting sync, process commands said so";
         }
 
         // Repeat remoteSetup in-case the commands forced us to reset
         if (!(this._remoteSetup()))
           throw "aborting sync, remote setup failed after processing commands";
       }
       finally {
@@ -1127,32 +1127,32 @@ WeaveSvc.prototype = {
             this._syncThresh[name] = Math.max(thresh - THRESHOLD_DECREMENT_STEP, 1);
 
             // No need to sync this engine for now
             continue;
           }
         }
 
         // If there's any problems with syncing the engine, report the failure
-        if (!(this._syncEngine(engine))) {
+        if (!(this._syncEngine(engine)) || this.status.enforceBackoff) {
           this._log.info("Aborting sync");
           break;
         }
 
         // We've successfully synced, so reset the threshold. We do this after
         // a successful sync so failures can try again on next sync, but this
         // could trigger too many syncs if the server is having problems.
         resetThresh(useThresh);
       }
 
       if (this._syncError)
         this._log.warn("Some engines did not sync correctly");
       else {
         Svc.Prefs.set("lastSync", new Date().toString());
-        this._weaveStatusCode = WEAVE_STATUS_OK;
+        this.status.setSyncStatus(SYNC_SUCCEEDED);
         this._log.info("Sync completed successfully");
       }
     } finally {
       this._syncError = false;
     }
   })))(),
 
   // returns true if sync should proceed
@@ -1162,19 +1162,20 @@ WeaveSvc.prototype = {
       engine.sync();
       return true;
     }
     catch(e) {
       // maybe a 401, cluster update needed?
       if (e.status == 401 && this._updateCluster())
         return this._syncEngine(engine);
 
+      this._checkServerError(e);
+
+      this.status.setEngineStatus(engine.name, e.failureCode || ENGINE_UNKNOWN_FAIL);
       this._syncError = true;
-      this._weaveStatusCode = WEAVE_STATUS_PARTIAL;
-      this._detailedStatus.setEngineStatus(engine.name, e);
       this._log.debug(Utils.exceptionStr(e));
       return true;
     }
   },
 
   _freshStart: function WeaveSvc__freshStart() {
     this.resetClient();
     this._log.info("Reset client data from freshStart.");
@@ -1196,16 +1197,40 @@ WeaveSvc.prototype = {
       return;
 
     this._log.debug("Setting meta payload storage version to " + WEAVE_VERSION);
     meta.payload.storageVersion = WEAVE_VERSION;
     let resp = new Resource(meta.uri).put(meta);
     if (!resp.success)
       throw resp;
   },
+  
+  
+  /**
+   * Check to see if this is a failure
+   *
+   */
+  _checkServerError: function WeaveSvc__checkServerError(resp) {
+    if (Utils.checkStatus(resp.status, null, [500, [502, 504]])) {
+      this.status.enforceBackoff = true;
+      if (resp.status == 503 && resp.headers["Retry-After"])
+        Observers.notify("weave:service:backoff:interval", parseInt(resp.headers["Retry-After"], 10));
+    }
+  },
+  /**
+   * Return a value for a backoff interval.  Maximum is eight hours, unless this.status.backoffInterval is higher.
+   *
+   */
+  _calculateBackoff: function WeaveSvc__calculateBackoff(attempts, base_interval) {
+    const MAXIMUM_BACKOFF_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours
+    let backoffInterval = attempts *
+                          (Math.floor(Math.random() * base_interval) +
+                           base_interval);
+    return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL), this.status.backoffInterval);
+  },
 
   /**
    * Wipe all user data from the server.
    *
    * @param engines [optional]
    *        Array of engine names to wipe. If not given, all engines are used.
    */
   wipeServer: function WeaveSvc_wipeServer(engines)
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -694,31 +694,22 @@ let Utils = {
   get _errorBundle() {
     if (!this.__errorBundle) {
       this.__errorBundle = new StringBundle("chrome://weave/locales/errors.properties");
     }
     return this.__errorBundle;
   },
 
   getErrorString: function Utils_getErrorString(error, args) {
-    switch (error) {
-      case Weave.LOGIN_FAILED_NETWORK_ERROR:
-        errorString = "error.login.reason.network";
-        break;
-      case Weave.LOGIN_FAILED_INVALID_PASSPHRASE:
-        errorString = "error.login.reason.passphrase";
-        break;
-      case Weave.LOGIN_FAILED_LOGIN_REJECTED:
-        errorString = "error.login.reason.password";
-        break;
-      default:
-        errorString = "error.login.reason.unknown";
-        break;
-    }
-    return this._errorBundle.get(errorString, args || null);
+    try {
+      return this._errorBundle.get(error, args || null);
+    } catch (e) {}
+    
+    // basically returns "Unknown Error"
+    return this._errorBundle.get("error.reason.unknown");
   },
 
   // assumes an nsIConverterInputStream
   readStream: function Weave_readStream(is) {
     let ret = "", str = {};
     while (is.readString(4096, str) != 0) {
       ret += str.value;
     }