Bug 617271 - Merge fx-sync to mozilla-central. a=blockers
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Tue, 07 Dec 2010 22:43:36 -0800
changeset 58880 95452499f3d6a92feae380e5432def073742f3fd
parent 58876 a89f24bf179869eab5697ac82c0c42b626d87e4c (current diff)
parent 58879 81bce58ee4a31cb55986dfe160cbc2ebb4b08462 (diff)
child 58881 2690831325004a0f53318931f1ad1fddbd4fba2a
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersblockers
bugs617271
milestone2.0b8pre
Bug 617271 - Merge fx-sync to mozilla-central. a=blockers
services/sync/modules/base_records/crypto.js
services/sync/modules/engines/bookmarks.js
services/sync/modules/service.js
--- a/services/sync/modules/base_records/crypto.js
+++ b/services/sync/modules/base_records/crypto.js
@@ -163,16 +163,23 @@ function CollectionKeyManager() {
   
   this._log = Log4Moz.repository.getLogger("CollectionKeys");
 }
 
 // TODO: persist this locally as an Identity. Bug 610913.
 // Note that the last modified time needs to be preserved.
 CollectionKeyManager.prototype = {
   
+  clear: function clear() {
+    this._log.info("Clearing CollectionKeys...");
+    this._lastModified = 0;
+    this._collections = {};
+    this._default = null;
+  },
+  
   keyForCollection: function(collection) {
                       
     // Moderately temporary debugging code.
     this._log.trace("keyForCollection: " + collection + ". Default is " + (this._default ? "not null." : "null."));
     
     if (collection && this._collections[collection])
       return this._collections[collection];
     
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -235,24 +235,23 @@ BookmarksEngine.prototype = {
   _findDupe: function _findDupe(item) {
     // Don't bother finding a dupe if the incoming item has duplicates
     if (item.hasDupe)
       return;
     return this._lazyMap(item);
   },
 
   _handleDupe: function _handleDupe(item, dupeId) {
-    // The local dupe has the lower id, so make it the winning id
-    if (dupeId < item.id)
-      [item.id, dupeId] = [dupeId, item.id];
-
-    // Trigger id change from dupe to winning and update the server
+    // Always change the local GUID to the incoming one.
     this._store.changeItemID(dupeId, item.id);
     this._deleteId(dupeId);
     this._tracker.addChangedID(item.id, 0);
+    if (item.parentid) {
+      this._tracker.addChangedID(item.parentid, 0);
+    }
   }
 };
 
 function BookmarksStore(name) {
   Store.call(this, name);
 
   // Explicitly nullify our references to our cached services so we don't leak
   Svc.Obs.add("places-shutdown", function() {
@@ -317,35 +316,25 @@ BookmarksStore.prototype = {
     return this.__ts;
   },
 
 
   itemExists: function BStore_itemExists(id) {
     return this.idForGUID(id) > 0;
   },
 
-  // Hash of old GUIDs to the new renamed GUIDs
-  aliases: {},
-
   applyIncoming: function BStore_applyIncoming(record) {
     // For special folders we're only interested in child ordering.
     if ((record.id in kSpecialIds) && record.children) {
       this._log.debug("Processing special node: " + record.id);
       // Reorder children later
       this._childrenToOrder[record.id] = record.children;
       return;
     }
 
-    // Convert GUID fields to the aliased GUID if necessary
-    ["id", "parentid"].forEach(function(field) {
-      let alias = this.aliases[record[field]];
-      if (alias != null)
-        record[field] = alias;
-    }, this);
-
     // Preprocess the record before doing the normal apply
     switch (record.type) {
       case "query": {
         // Convert the query uri if necessary
         if (record.bmkUri == null || record.folderName == null)
           break;
 
         // Tag something so that the tag exists
@@ -600,21 +589,16 @@ BookmarksStore.prototype = {
         Utils.anno(itemId, CHILDREN_ANNO, val.join(","));
         break;
       }
     }
   },
 
   _orderChildren: function _orderChildren() {
     for (let [guid, children] in Iterator(this._childrenToOrder)) {
-      // Convert children GUIDs to aliases if they exist.
-      children = children.map(function(guid) {
-        return this.aliases[guid] || guid;
-      }, this);
-
       // Reorder children according to the GUID list. Gracefully deal
       // with missing items, e.g. locally deleted.
       let delta = 0;
       for (let idx = 0; idx < children.length; idx++) {
         let itemid = this.idForGUID(children[idx]);
         if (itemid == -1) {
           delta += 1;
           this._log.trace("Could not locate record " + children[idx]);
@@ -664,30 +648,23 @@ BookmarksStore.prototype = {
     return stmt;
   },
 
   _removeAllChildrenAnnos: function _removeAllChildrenAnnos() {
     Utils.queryAsync(this._removeAllChildrenAnnosStm);
   },
 
   changeItemID: function BStore_changeItemID(oldID, newID) {
-    // Remember the GUID change for incoming records
-    this.aliases[oldID] = newID;
-
-    // Update any existing annotation references
-    this._findAnnoItems(PARENT_ANNO, oldID).forEach(function(itemId) {
-      Utils.anno(itemId, PARENT_ANNO, newID);
-    }, this);
+    this._log.debug("Changing GUID " + oldID + " to " + newID);
 
     // Make sure there's an item to change GUIDs
     let itemId = this.idForGUID(oldID);
     if (itemId <= 0)
       return;
 
-    this._log.debug("Changing GUID " + oldID + " to " + newID);
     this._setGUID(itemId, newID);
 
     // Update parent
     let parentid = this._bms.getFolderIdForItem(itemId);
     this._updateChildrenAnno(parentid);
   },
 
   _getNode: function BStore__getNode(folder) {
@@ -888,18 +865,17 @@ BookmarksStore.prototype = {
     // Ensure annotation name exists
     Utils.queryAsync(this._addGUIDAnnotationNameStm);
 
     let stmt = this._checkGUIDItemAnnotationStm;
     stmt.params.item_id = id;
     let result = Utils.queryAsync(stmt, ["item_id", "name_id", "anno_id",
                                          "anno_date"])[0];
     if (!result) {
-      let log = Log4Moz.repository.getLogger("Engine.Bookmarks");
-      log.warn("Couldn't annotate bookmark id " + id);
+      this._log.warn("Couldn't annotate bookmark id " + id);
       return guid;
     }
 
     stmt = this._addItemAnnotationStm;
     if (result.anno_id) {
       stmt.params.id = result.anno_id;
       stmt.params.date_added = result.anno_date;
     } else {
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -840,16 +840,19 @@ WeaveSvc.prototype = {
     this.logout();
     // Reset all engines.
     this.resetClient();
     // Reset Weave prefs.
     this._ignorePrefObserver = true;
     Svc.Prefs.resetBranch("");
     this._ignorePrefObserver = false;
     
+    // Clear keys.
+    CollectionKeys.clear();
+    
     Svc.Prefs.set("lastversion", WEAVE_VERSION);
     // Find weave logins and remove them.
     this.password = "";
     this.passphrase = "";
     Svc.Login.findLogins({}, PWDMGR_HOST, "", "").map(function(login) {
       Svc.Login.removeLogin(login);
     });
     Svc.Obs.notify("weave:service:start-over");
@@ -1524,16 +1527,17 @@ WeaveSvc.prototype = {
     }
     else {
       this.syncInterval = hasMobile ? MULTI_MOBILE_SYNC : MULTI_DESKTOP_SYNC;
       this.syncThreshold = hasMobile ? MULTI_MOBILE_THRESHOLD : MULTI_DESKTOP_THRESHOLD;
     }
   },
 
   _updateEnabledEngines: function _updateEnabledEngines() {
+    this._log.info("Updating enabled engines: " + this.numClients + " clients.");
     let meta = Records.get(this.metaURL);
     if (meta.isNew || !meta.payload.engines)
       return;
     
     // If we're the only client, and no engines are marked as enabled,
     // thumb our noses at the server data: it can't be right.
     // Belt-and-suspenders approach to Bug 615926.
     if ((this.numClients <= 1) &&
--- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
+++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
@@ -1,49 +1,98 @@
+Cu.import("resource://services-sync/stores.js");
 Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/base_records/crypto.js");
 Cu.import("resource://services-sync/base_records/wbo.js");
 
 Svc.DefaultPrefs.set("registerEngines", "");
 Cu.import("resource://services-sync/service.js");
 
 initTestLogging();
 
+function QuietStore() {
+  Store.call("Quiet");
+}
+QuietStore.prototype = {
+  getAllIDs: function getAllIDs() {
+    return [];
+  }
+}
+
 function SteamEngine() {
   SyncEngine.call(this, "Steam");
 }
 SteamEngine.prototype = {
   __proto__: SyncEngine.prototype,
   // We're not interested in engine sync but what the service does.
+  _storeObj: QuietStore,
+  
   _sync: function _sync() {
     this._syncStartup();
   }
 };
 Engines.register(SteamEngine);
 
 function StirlingEngine() {
   SyncEngine.call(this, "Stirling");
 }
 StirlingEngine.prototype = {
   __proto__: SteamEngine.prototype,
   // This engine's enabled state is the same as the SteamEngine's.
   get prefName() "steam"
 };
 Engines.register(StirlingEngine);
 
+let collections = {};
 
+function update_collection(coll) {
+  let timestamp = Date.now() / 1000;
+  collections[coll] = timestamp;
+}
+
+function with_updated_collection(coll, f) {
+  return function(request, response) {
+    if (request.method != "GET")
+      update_collection(coll);
+    f.call(this, request, response);
+  };
+}
+
+function info_collections(request, response) {
+  let body = "Error.";
+  switch(request.method) {
+    case "GET":
+      body = JSON.stringify(collections);
+      break;
+    default:
+      throw "Non-GET on info_collections.";
+  }
+      
+  response.setHeader('X-Weave-Timestamp', ''+Date.now()/1000, false);
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.bodyOutputStream.write(body, body.length);
+}
+  
 function sync_httpd_setup(handlers) {
-  let collections = {};
-  handlers["/1.0/johndoe/storage/crypto/keys"] = new ServerWBO().handler(),
-  handlers["/1.0/johndoe/info/collections"]
-      = (new ServerWBO("collections", collections)).handler(),
-  handlers["/1.0/johndoe/storage/clients"]
-      = (new ServerCollection()).handler();
+  
+  collections = {};
+    
+  handlers["/1.0/johndoe/info/collections"] = info_collections;
+  
+  let cr = new ServerWBO("keys");
+  handlers["/1.0/johndoe/storage/crypto/keys"] =
+    with_updated_collection("crypto", cr.handler());
+  
+  let cl = new ServerCollection();
+  handlers["/1.0/johndoe/storage/clients"] =
+    with_updated_collection("clients", cl.handler());
+  
   return httpd_setup(handlers);
 }
 
 function setUp() {
   Service.username = "johndoe";
   Service.password = "ilovejane";
   Service.passphrase = "sekrit";
   Service.clusterURL = "http://localhost:8080/";
@@ -187,16 +236,62 @@ function test_enabledRemotely() {
     _("Meta record still present.");
     do_check_eq(metaWBO.data.engines.steam.syncID, engine.syncID);
   } finally {
     server.stop(do_test_finished);
     Service.startOver();
   }
 }
 
+function test_disabledRemotelyTwoClients() {
+  _("Test: Engine is enabled locally and disabled on a remote client... with two clients.");
+  Service.syncID = "abcdefghij";
+  let engine = Engines.get("steam");
+  let metaWBO = new ServerWBO("global", {syncID: Service.syncID,
+                                         storageVersion: STORAGE_VERSION,
+                                         engines: {}});
+  let server = sync_httpd_setup({
+    "/1.0/johndoe/storage/meta/global":
+    with_updated_collection("meta", metaWBO.handler()),
+      
+    "/1.0/johndoe/storage/steam":
+    with_updated_collection("steam", new ServerWBO("steam", {}).handler())
+  });
+  do_test_pending();
+  setUp();
+
+  try {
+    _("Enable engine locally.");
+    Service._ignorePrefObserver = true;
+    engine.enabled = true;
+    Service._ignorePrefObserver = false;
+
+    _("Sync.");
+    Weave.Service.login();
+    Weave.Service.sync();
+
+    _("Disable engine by deleting from meta/global.");
+    let d = metaWBO.data; 
+    delete d.engines["steam"];
+    metaWBO.payload = JSON.stringify(d);
+    metaWBO.modified = Date.now() / 1000;
+    
+    _("Add a second client and verify that the local pref is changed.");
+    Clients._store._remoteClients["foobar"] = {name: "foobar", type: "desktop"};
+    Weave.Service.sync();
+    
+    _("Engine is disabled.");
+    do_check_false(engine.enabled);
+    
+  } finally {
+    server.stop(do_test_finished);
+    Service.startOver();
+  }
+}
+
 function test_disabledRemotely() {
   _("Test: Engine is enabled locally and disabled on a remote client");
   Service.syncID = "abcdefghij";
   let engine = Engines.get("steam");
   let metaWBO = new ServerWBO("global", {syncID: Service.syncID,
                                          storageVersion: STORAGE_VERSION,
                                          engines: {}});
   let server = sync_httpd_setup({
@@ -214,18 +309,16 @@ function test_disabledRemotely() {
 
     _("Sync.");
     Weave.Service.login();
     Weave.Service.sync();
 
     _("Engine is not disabled: only one client.");
     do_check_true(engine.enabled);
     
-    // TODO: add a second client and verify that the local pref is changed.
-
   } finally {
     server.stop(do_test_finished);
     Service.startOver();
   }
 }
 
 function test_dependentEnginesEnabledLocally() {
   _("Test: Engine is disabled on remote clients and enabled locally");
@@ -322,11 +415,12 @@ function run_test() {
   if (DISABLE_TESTS_BUG_604565)
     return;
 
   test_newAccount();
   test_enabledLocally();
   test_disabledLocally();
   test_enabledRemotely();
   test_disabledRemotely();
+  test_disabledRemotelyTwoClients();
   test_dependentEnginesEnabledLocally();
   test_dependentEnginesDisabledLocally();
 }
--- a/services/sync/tests/unit/test_service_wipeClient.js
+++ b/services/sync/tests/unit/test_service_wipeClient.js
@@ -1,8 +1,9 @@
+Cu.import("resource://services-sync/base_records/crypto.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/util.js");
 
 Svc.DefaultPrefs.set("registerEngines", "");
 Cu.import("resource://services-sync/service.js");
 
 
 function CanDecryptEngine() {
@@ -57,11 +58,19 @@ function test_withEngineList() {
     do_check_false(Engines.get("cannotdecrypt").wasWiped);
   } finally {
     Engines.get("candecrypt").wasWiped = false;
     Engines.get("cannotdecrypt").wasWiped = false;
     Service.startOver();
   }
 }
 
+function test_startOver_clears_keys() {
+  CollectionKeys.generateNewKeys();
+  do_check_true(!!CollectionKeys.keyForCollection());
+  Service.startOver();
+  do_check_false(!!CollectionKeys.keyForCollection());
+}
+
 function run_test() {
   test_withEngineList();
+  test_startOver_clears_keys();
 }