Merged changes and resolved conflicts between my cookie stuff and r282.
authorjono@jono-gibbon-laptop
Tue, 01 Apr 2008 14:46:29 -0700
changeset 44410 3f4d2284b0f180b4c9c575a22c34548e27536a0b
parent 44409 1564929af459f80ed65c5655bc45726bb0d1a46a (current diff)
parent 44407 0f1884ed1e7993c54ad3e090bc4de295686e8a16 (diff)
child 44411 9186b02a5cfb087758e3862ca2356d107b79b089
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
Merged changes and resolved conflicts between my cookie stuff and r282.
services/sync/locales/en-US/preferences.dtd
services/sync/modules/constants.js
services/sync/modules/engines.js
services/sync/modules/service.js
services/sync/modules/stores.js
services/sync/modules/syncCores.js
services/sync/modules/weave.js
services/sync/services-sync.js
--- a/services/sync/locales/en-US/log.dtd
+++ b/services/sync/locales/en-US/log.dtd
@@ -1,4 +1,5 @@
 <!ENTITY dialog.title         "Mozilla Weave Sync - Activity Log">
 <!ENTITY saveAsButton.label   "Save As...">
+<!ENTITY clearButton.label    "Clear Logs">
 <!ENTITY briefLogTab.label    "Brief Log">
 <!ENTITY verboseLogTab.label  "Verbose Log">
--- a/services/sync/locales/en-US/preferences.dtd
+++ b/services/sync/locales/en-US/preferences.dtd
@@ -16,17 +16,17 @@
 <!ENTITY accountSettings.label              "Settings">
 
 <!ENTITY backUpCheckbox.label               "Back up and synchronize my data to a Weave server">
 <!ENTITY encryptOnServerCheckbox.label      "Encrypt my data on the server">
 <!ENTITY syncNowButton.label                "Sync Now">
 <!ENTITY syncItemsList.label                "Synchronize these items:">
 <!ENTITY bookmarksCheckbox.label            "Bookmarks">
 <!ENTITY historyCheckbox.label              "Browsing History">
-<!ENTITY cookiesCheckbox.label              "Cookies (Not recommended!)">
+<!ENTITY cookiesCheckbox.label              "Cookies (Not recommended)">
 
 <!ENTITY addonsGroupbox.description         "This is where you will find, install, and manage Weave add-ons.">
 
 <!ENTITY serverSettingsCaption.label        "Server Settings">
 <!ENTITY serverLocation.label               "Server Location:">
 <!ENTITY encryption.label                   "Encryption:">
 <!ENTITY teaAlgorithmItem.label             "Placeholder Algorithm">
 <!ENTITY noEncryptionItem.label             "No encryption">
--- a/services/sync/locales/en-US/preferences.properties
+++ b/services/sync/locales/en-US/preferences.properties
@@ -1,2 +1,6 @@
 # %S is the username of the signed in user
 signedIn.description = Signed in as %S
+reset.server.warning.title = Warning
+reset.server.warning = This will delete all data on the server.\n\nYou *must* restart this and any other instances of Firefox you may have running (on any computer).
+reset.client.warning.title = Warning
+reset.client.warning = This will delete all bookmarks and history data.  Are you sure you want to do this?
new file mode 100644
--- /dev/null
+++ b/services/sync/locales/en-US/share.dtd
@@ -0,0 +1,6 @@
+<!ENTITY dialog.title        "Weave Sharing">
+<!ENTITY close.button.label  "Close">
+<!ENTITY share.button.label  "Share">
+<!ENTITY description.top     "Enter a the ID of the user you wish to share your bookmarks with:">
+<!ENTITY username.label      "Weave ID (Email):">
+<!ENTITY status.waiting      "Waiting...">
new file mode 100644
--- /dev/null
+++ b/services/sync/locales/en-US/share.properties
@@ -0,0 +1,3 @@
+status.ok = Weave sharing complete!
+status.error = Error.  Please check the Weave ID and try again.
+status.working = Working...
--- a/services/sync/locales/en-US/sync.dtd
+++ b/services/sync/locales/en-US/sync.dtd
@@ -1,6 +1,9 @@
 <!ENTITY syncMenu.label       "Weave">
 <!ENTITY logInItem.label      "Sign In...">
 <!ENTITY logOutItem.label     "Sign Out">
 <!ENTITY syncNowItem.label    "Sync Now">
+<!ENTITY shareItem.label      "Share Bookmarks...">
 <!ENTITY openPrefsItem.label  "Weave Preferences...">
 <!ENTITY openLogItem.label    "Activity Log...">
+<!ENTITY status.offline.label "Offline">
+
--- a/services/sync/locales/en-US/sync.properties
+++ b/services/sync/locales/en-US/sync.properties
@@ -1,2 +1,6 @@
 # %S is the date and time at which the last sync successfully completed
 lastSync.label = Last Update: %S
+status.idle = Idle
+status.active = Working...
+status.offline = Offline
+status.error = Error
--- a/services/sync/modules/async.js
+++ b/services/sync/modules/async.js
@@ -69,16 +69,18 @@ AsyncException.prototype = {
   
   toString: function AsyncException_toString() {
     return this.message;
   }
 };
 
 function Generator(object, method, onComplete, args) {
   this._log = Log4Moz.Service.getLogger("Async.Generator");
+  this._log.level =
+    Log4Moz.Level[Utils.prefs.getCharPref("log.logger.async")];
   this._object = object;
   this._method = method;
   this.onComplete = onComplete;
   this._args = args;
 
   let frame = Components.stack.caller;
   if (frame.name == "Async_run")
     frame = frame.caller;
@@ -93,17 +95,17 @@ Generator.prototype = {
 
   // set to true to error when generators to bottom out/return.
   // you will then have to ensure that all generators yield as the
   // last thing they do, and never call 'return'
   get errorOnStop() { return this._errorOnStop; },
   set errorOnStop(value) { this._errorOnStop = value; },
 
   get cb() {
-    let cb = Utils.bind2(this, function(data) { this.cont(data); });
+    let self = this, cb = function(data) { self.cont(data); };
     cb.parentGenerator = this;
     return cb;
   },
   get listener() { return new Utils.EventListener(this.cb); },
 
   get _object() { return this.__object; },
   set _object(value) {
     if (typeof value != "object")
@@ -116,88 +118,91 @@ Generator.prototype = {
     if (typeof value != "function")
       throw "expected type 'function', got type '" + typeof(value) + "'";
     this.__method = value;
   },
 
   get onComplete() {
     if (this._onComplete)
       return this._onComplete;
-    return function() { this._log.trace("Generator " + this.name + " has no onComplete"); };
+    return function() {
+      //this._log.trace("Generator " + this.name + " has no onComplete");
+    };
   },
   set onComplete(value) {
     if (value && typeof value != "function")
       throw "expected type 'function', got type '" + typeof(value) + "'";
     this._onComplete = value;
   },
 
   get trace() {
     return Utils.stackTrace(this._initFrame) +
       "JS frame :: unknown (async) :: " + this.name;
   },
 
   _handleException: function AsyncGen__handleException(e) {
     if (e instanceof StopIteration) {
       if (this.errorOnStop) {
-        this._log.error("Generator stopped unexpectedly");
+        this._log.error("[" + this.name + "] Generator stopped unexpectedly");
         this._log.trace("Stack trace:\n" + this.trace);
         this._exception = "Generator stopped unexpectedly"; // don't propagate StopIteration
       }
 
     } else if (this.onComplete.parentGenerator instanceof Generator) {
-      //this._log.trace("Saving exception and stack trace");
+      this._log.trace("[" + this.name + "] Saving exception and stack trace");
+      this._log.trace(Async.exceptionStr(this, e));
 
-      switch (typeof e) {
-      case "string":
+      if (e instanceof AsyncException)
+        e.trace = this.trace + e.trace? "\n" + e.trace : "";
+      else
         e = new AsyncException(this, e);
-        break;
-      case "object":
-        if (e.trace) // means we're re-throwing up the stack
-          e.trace = this.trace + "\n" + e.trace;
-        else
-          e.trace = this.trace;
-        break;
-      default:
-        this._log.debug("Unknown exception type: " + typeof(e));
-        break;
-      }
 
       this._exception = e;
 
     } else {
       this._log.error(Async.exceptionStr(this, e));
-      this._log.trace("Stack trace:\n" + this.trace +
+      this._log.debug("Stack trace:\n" + this.trace +
                       (e.trace? "\n" + e.trace : ""));
     }
 
     // continue execution of caller.
     // in the case of StopIteration we could return an error right
     // away, but instead it's easiest/best to let the caller handle
     // the error after a yield / in a callback.
-    this.done();
+    if (!this._timer) {
+      this._log.trace("[" + this.name + "] running done() from _handleException()");
+      this.done();
+    }
   },
 
   run: function AsyncGen_run() {
     try {
       this._generator = this._method.apply(this._object, this._args);
       this.generator.next(); // must initialize before sending
       this.generator.send(this);
     } catch (e) {
-      this._handleException(e);
+      if (!(e instanceof StopIteration) || !this._timer)
+        this._handleException(e);
     }
   },
 
   cont: function AsyncGen_cont(data) {
     try { this.generator.send(data); }
-    catch (e) { this._handleException(e); }
+    catch (e) {
+      if (!(e instanceof StopIteration) || !this._timer)
+        this._handleException(e);
+    }
   },
 
   throw: function AsyncGen_throw(exception) {
     try { this.generator.throw(exception); }
-    catch (e) { this._handleException(e); }
+    catch (e) {
+      if (!(e instanceof StopIteration) || !this._timer)
+        this._handleException(e);
+    }
   },
 
   // async generators can't simply call a callback with the return
   // value, since that would cause the calling function to end up
   // running (after the yield) from inside the generator.  Instead,
   // generators can call this method which sets up a timer to call the
   // callback from a timer (and cleans up the timer to avoid leaks).
   // It also closes generators after the timeout, to keep things
@@ -208,25 +213,32 @@ Generator.prototype = {
     let self = this;
     let cb = function() { self._done(retval); };
     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     this._timer.initWithCallback(new Utils.EventListener(cb),
                                  0, this._timer.TYPE_ONE_SHOT);
   },
 
   _done: function AsyncGen__done(retval) {
-    this._generator.close();
+    if (!this._generator) {
+      this._log.error("Async method '" + this.name + "' is missing a 'yield' call " +
+                      "(or called done() after being finalized)");
+      this._log.trace("Initial stack trace:\n" + this.trace);
+    } else {
+      this._generator.close();
+    }
     this._generator = null;
     this._timer = null;
 
     if (this._exception) {
-      this._log.trace("Propagating exception to parent generator");
+      this._log.trace("[" + this.name + "] Propagating exception to parent generator");
       this.onComplete.parentGenerator.throw(this._exception);
     } else {
       try {
+        this._log.trace("[" + this.name + "] Running onComplete()");
         this.onComplete(retval);
       } catch (e) {
         this._log.error("Exception caught from onComplete handler of " +
                         this.name + " generator");
         this._log.error("Exception: " + Utils.exceptionStr(e));
         this._log.trace("Current stack trace:\n" + Utils.stackTrace(Components.stack));
         this._log.trace("Initial stack trace:\n" + this.trace);
       }
@@ -238,17 +250,17 @@ Async = {
 
   // Use:
   // let gen = Async.run(this, this.fooGen, ...);
   // let ret = yield;
   //
   // where fooGen is a generator function, and gen is a Generator instance
   // ret is whatever the generator 'returns' via Generator.done().
 
-  run: function Async_run(object, method, onComplete, args) {
+  run: function Async_run(object, method, onComplete /* , arg1, arg2, ... */) {
     let args = Array.prototype.slice.call(arguments, 3);
     let gen = new Generator(object, method, onComplete, args);
     gen.run();
     return gen;
   },
 
   // Syntactic sugar for run().  Meant to be used like this in code
   // that imports this file:
@@ -258,17 +270,17 @@ Async = {
   // So that you can do:
   //
   // let gen = fooGen.async(...);
   // let ret = yield;
   //
   // Note that 'this' refers to the method being called, not the
   // Async object.
 
-  sugar: function Async_sugar(object, onComplete, extra_args) {
+  sugar: function Async_sugar(object, onComplete  /* , arg1, arg2, ... */) {
     let args = Array.prototype.slice.call(arguments, 1);
     args.unshift(object, this);
     Async.run.apply(Async, args);
   },
 
   exceptionStr: function Async_exceptionStr(gen, ex) {
     return "Exception caught in " + gen.name + ": " + Utils.exceptionStr(ex);
   }
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -30,24 +30,26 @@
  * 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 ***** */
 
 const EXPORTED_SYMBOLS = ['WEAVE_VERSION', 'STORAGE_FORMAT_VERSION',
+                          'ENGINE_STORAGE_FORMAT_VERSION',
 			  'PREFS_BRANCH',
 			  'MODE_RDONLY', 'MODE_WRONLY',
 			  'MODE_CREATE', 'MODE_APPEND', 'MODE_TRUNCATE',
 			  'PERMS_FILE', 'PERMS_PASSFILE', 'PERMS_DIRECTORY',
 			  'ONE_BYTE', 'ONE_KILOBYTE', 'ONE_MEGABYTE'];
 
-const WEAVE_VERSION = "0.1.20";
+const WEAVE_VERSION = "0.1.27";
 const STORAGE_FORMAT_VERSION = 2;
+const ENGINE_STORAGE_FORMAT_VERSION = 2;
 
 const PREFS_BRANCH = "extensions.weave.";
 
 const MODE_RDONLY   = 0x01;
 const MODE_WRONLY   = 0x02;
 const MODE_CREATE   = 0x08;
 const MODE_APPEND   = 0x10;
 const MODE_TRUNCATE = 0x20;
--- a/services/sync/modules/crypto.js
+++ b/services/sync/modules/crypto.js
@@ -29,35 +29,37 @@
  * 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 ***** */
 
-const EXPORTED_SYMBOLS = ['WeaveCrypto'];
+const EXPORTED_SYMBOLS = ['Crypto'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/async.js");
 
 Function.prototype.async = Async.sugar;
 
-function WeaveCrypto() {
+Utils.lazy(this, 'Crypto', CryptoSvc);
+
+function CryptoSvc() {
   this._init();
 }
-WeaveCrypto.prototype = {
+CryptoSvc.prototype = {
   _logName: "Crypto",
 
   __os: null,
   get _os() {
     if (!this.__os)
       this.__os = Cc["@mozilla.org/observer-service;1"]
         .getService(Ci.nsIObserverService);
     return this.__os;
@@ -83,16 +85,18 @@ WeaveCrypto.prototype = {
       .getService(Ci.nsIPrefBranch);
     let cur = branch.getCharPref("extensions.weave.encryption");
     if (value != cur)
       branch.setCharPref("extensions.weave.encryption", value);
   },
 
   _init: function Crypto__init() {
     this._log = Log4Moz.Service.getLogger("Service." + this._logName);
+    this._log.level =
+      Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.crypto")];
     let branch = Cc["@mozilla.org/preferences-service;1"]
       .getService(Ci.nsIPrefBranch2);
     branch.addObserver("extensions.weave.encryption", this, false);
   },
 
   _openssl: function Crypto__openssl() {
     let extMgr = Components.classes["@mozilla.org/extensions/manager;1"]
       .getService(Components.interfaces.nsIExtensionManager);
@@ -158,34 +162,34 @@ WeaveCrypto.prototype = {
     outputFile.remove(false);
 
     return ret;
   },
 
   // generates a random string that can be used as a passphrase
   _opensslRand: function Crypto__opensslRand(length) {
     if (!length)
-      length = 256;
+      length = 128;
 
     let outputFile = Utils.getTmp("output");
     if (outputFile.exists())
       outputFile.remove(false);
 
     this._openssl("rand", "-base64", "-out", "output", length);
 
     let [outputFIS] = Utils.open(outputFile, "<");
     let ret = Utils.readStream(outputFIS);
     outputFIS.close();
     outputFile.remove(false);
 
     return ret;
   },
 
   // generates an rsa public/private key pair, with the private key encrypted
-  _opensslRSAKeyGen: function Crypto__opensslRSAKeyGen(password, algorithm, bits) {
+  _opensslRSAKeyGen: function Crypto__opensslRSAKeyGen(identity, algorithm, bits) {
     if (!algorithm)
       algorithm = "aes-256-cbc";
     if (!bits)
       bits = "2048";
 
     let privKeyF = Utils.getTmp("privkey.pem");
     if (privKeyF.exists())
       privKeyF.remove(false);
@@ -201,17 +205,17 @@ WeaveCrypto.prototype = {
 
     let cryptedKeyF = Utils.getTmp("enckey.pem");
     if (cryptedKeyF.exists())
       cryptedKeyF.remove(false);
 
     // nsIProcess doesn't support stdin, so we write a file instead
     let passFile = Utils.getTmp("pass");
     let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
-    passFOS.writeString(password);
+    passFOS.writeString(identity.password);
     passFOS.close();
 
     try {
       this._openssl("pkcs8", "-in", "privkey.pem", "-out", "enckey.pem",
                     "-topk8", "-v2", algorithm, "-passout", "file:pass");
 
     } catch (e) {
       throw e;
@@ -230,77 +234,133 @@ WeaveCrypto.prototype = {
     let pubKey = Utils.readStream(pubKeyFIS);
     pubKeyFIS.close();
     pubKeyF.remove(false);
 
     return [cryptedKey, pubKey];
   },
 
   // returns 'input' encrypted with the 'pubkey' public RSA key
-  _opensslRSAencrypt: function Crypto__opensslRSAencrypt(input, pubkey) {
+  _opensslRSAencrypt: function Crypto__opensslRSAencrypt(input, identity) {
+    let inputFile = Utils.getTmp("input");
+    let [inputFOS] = Utils.open(inputFile, ">");
+    inputFOS.writeString(input);
+    inputFOS.close();
+
+    let keyFile = Utils.getTmp("key");
+    let [keyFOS] = Utils.open(keyFile, ">");
+    keyFOS.writeString(identity.pubkey);
+    keyFOS.close();
+
+    let tmpFile = Utils.getTmp("tmp-output");
+    if (tmpFile.exists())
+      tmpFile.remove(false);
+
+    let outputFile = Utils.getTmp("output");
+    if (outputFile.exists())
+      outputFile.remove(false);
+
+    this._openssl("rsautl", "-encrypt", "-pubin", "-inkey", "key",
+                  "-in", "input", "-out", "tmp-output");
+    this._openssl("base64", "-in", "tmp-output", "-out", "output");
+
+    let [outputFIS] = Utils.open(outputFile, "<");
+    let output = Utils.readStream(outputFIS);
+    outputFIS.close();
+    outputFile.remove(false);
+
+    return output;
+  },
+
+  // returns 'input' decrypted with the 'privkey' private RSA key and password
+  _opensslRSAdecrypt: function Crypto__opensslRSAdecrypt(input, identity) {
     let inputFile = Utils.getTmp("input");
     let [inputFOS] = Utils.open(inputFile, ">");
     inputFOS.writeString(input);
     inputFOS.close();
 
     let keyFile = Utils.getTmp("key");
     let [keyFOS] = Utils.open(keyFile, ">");
-    keyFOS.writeString(pubkey);
+    keyFOS.writeString(identity.privkey);
     keyFOS.close();
 
+    let tmpKeyFile = Utils.getTmp("tmp-key");
+    if (tmpKeyFile.exists())
+      tmpKeyFile.remove(false);
+
+    let tmpFile = Utils.getTmp("tmp-output");
+    if (tmpFile.exists())
+      tmpFile.remove(false);
+
     let outputFile = Utils.getTmp("output");
     if (outputFile.exists())
       outputFile.remove(false);
 
-    this._openssl("rsautl", "-encrypt", "-pubin", "-inkey", "key",
-                  "-in", "input", "-out", "output");
+    // nsIProcess doesn't support stdin, so we write a file instead
+    let passFile = Utils.getTmp("pass");
+    let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
+    passFOS.writeString(identity.password);
+    passFOS.close();
+
+    try {
+      this._openssl("base64", "-d", "-in", "input", "-out", "tmp-output");
+      // FIXME: this is because openssl.exe (in windows only) doesn't
+      // seem to support -passin for rsautl, but it works for rsa.
+      this._openssl("rsa", "-in", "key", "-out", "tmp-key", "-passin", "file:pass");
+      this._openssl("rsautl", "-decrypt", "-inkey", "tmp-key",
+                    "-in", "tmp-output", "-out", "output");
+
+    } catch(e) {
+      throw e;
+
+    } finally {
+      passFile.remove(false);
+      tmpKeyFile.remove(false);
+      tmpFile.remove(false);
+      keyFile.remove(false);
+    }
 
     let [outputFIS] = Utils.open(outputFile, "<");
-    let output = Utils.readStream(outpusFIS);
+    let output = Utils.readStream(outputFIS);
     outputFIS.close();
     outputFile.remove(false);
 
     return output;
   },
 
-  // returns 'input' decrypted with the 'privkey' private RSA key and password
-  _opensslRSAdecrypt: function Crypto__opensslRSAdecrypt(input, privkey, password) {
-    let inputFile = Utils.getTmp("input");
-    let [inputFOS] = Utils.open(inputFile, ">");
-    inputFOS.writeString(input);
-    inputFOS.close();
-
+  // returns the public key from the private key
+  _opensslRSAkeydecrypt: function Crypto__opensslRSAkeydecrypt(identity) {
     let keyFile = Utils.getTmp("key");
     let [keyFOS] = Utils.open(keyFile, ">");
-    keyFOS.writeString(privkey);
+    keyFOS.writeString(identity.privkey);
     keyFOS.close();
 
     let outputFile = Utils.getTmp("output");
     if (outputFile.exists())
       outputFile.remove(false);
 
     // nsIProcess doesn't support stdin, so we write a file instead
     let passFile = Utils.getTmp("pass");
     let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
-    passFOS.writeString(password);
+    passFOS.writeString(identity.password);
     passFOS.close();
 
     try {
-      this._openssl("rsautl", "-decrypt", "-inkey", "key", "-pass",
-                    "file:pass", "-in", "input", "-out", "output");
+      this._openssl("rsa", "-in", "key", "-pubout", "-out", "output",
+                    "-passin", "file:pass");
 
     } catch(e) {
       throw e;
 
     } finally {
       passFile.remove(false);
     }
 
     let [outputFIS] = Utils.open(outputFile, "<");
-    let output = Utils.readStream(outpusFIS);
+    let output = Utils.readStream(outputFIS);
     outputFIS.close();
     outputFile.remove(false);
 
     return output;
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]),
 
@@ -440,26 +500,32 @@ WeaveCrypto.prototype = {
   },
 
   PBEkeygen: function Crypto_PBEkeygen() {
     let self = yield;
     let ret = this._opensslRand();
     self.done(ret);
   },
 
-  RSAkeygen: function Crypto_RSAkeygen(password) {
+  RSAkeygen: function Crypto_RSAkeygen(identity) {
     let self = yield;
-    let ret = this._opensslRSAKeyGen(password);
+    let ret = this._opensslRSAKeyGen(identity);
+    self.done(ret);
+  },
+
+  RSAencrypt: function Crypto_RSAencrypt(data, identity) {
+    let self = yield;
+    let ret = this._opensslRSAencrypt(data, identity);
     self.done(ret);
   },
 
-  RSAencrypt: function Crypto_RSAencrypt(data, key) {
+  RSAdecrypt: function Crypto_RSAdecrypt(data, identity) {
     let self = yield;
-    let ret = this._opensslRSAencrypt(data, key);
+    let ret = this._opensslRSAdecrypt(data, identity);
     self.done(ret);
   },
 
-  RSAdecrypt: function Crypto_RSAdecrypt(data, key, password) {
+  RSAkeydecrypt: function Crypto_RSAkeydecrypt(identity) {
     let self = yield;
-    let ret = this._opensslRSAdecrypt(data, key, password);
+    let ret = this._opensslRSAkeydecrypt(identity);
     self.done(ret);
   }
 };
--- a/services/sync/modules/dav.js
+++ b/services/sync/modules/dav.js
@@ -29,91 +29,111 @@
  * 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 ***** */
 
-const EXPORTED_SYMBOLS = ['DAVCollection'];
+const EXPORTED_SYMBOLS = ['DAV', 'DAVCollection'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/async.js");
+Cu.import("resource://weave/constants.js");
 
 Function.prototype.async = Async.sugar;
 
+Utils.lazy(this, 'DAV', DAVCollection);
+
 /*
  * DAV object
  * Abstracts the raw DAV commands
  */
 
 function DAVCollection(baseURL) {
   this._baseURL = baseURL;
   this._authProvider = new DummyAuthProvider();
   this._log = Log4Moz.Service.getLogger("Service.DAV");
+  this._log.level =
+    Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.dav")];
 }
 DAVCollection.prototype = {
+
   __dp: null,
   get _dp() {
     if (!this.__dp)
       this.__dp = Cc["@mozilla.org/xmlextras/domparser;1"].
         createInstance(Ci.nsIDOMParser);
     return this.__dp;
   },
 
   _auth: null,
 
   get baseURL() {
     return this._baseURL;
   },
   set baseURL(value) {
+    if (value[value.length-1] != '/')
+      value = value + '/';
     this._baseURL = value;
   },
 
   _loggedIn: false,
   get loggedIn() {
     return this._loggedIn;
   },
 
+  get locked() {
+    return !this._lockAllowed || this._token != null;
+  },
+
+  _lockAllowed: true,
+  get allowLock() {
+    return this._lockAllowed;
+  },
+  set allowLock(value) {
+    this._lockAllowed = value;
+  },
+
   _makeRequest: function DC__makeRequest(op, path, headers, data) {
     let self = yield;
     let ret;
 
-    this._log.debug("Creating " + op + " request for " + this._baseURL + path);
+    this._log.debug(op + " request for " + path);
 
     let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
     request = request.QueryInterface(Ci.nsIDOMEventTarget);
-  
+
     request.addEventListener("load", new Utils.EventListener(self.cb, "load"), false);
     request.addEventListener("error", new Utils.EventListener(self.cb, "error"), false);
     request = request.QueryInterface(Ci.nsIXMLHttpRequest);
     request.open(op, this._baseURL + path, true);
 
 
     // Force cache validation
     let channel = request.channel;
     channel = channel.QueryInterface(Ci.nsIRequest);
     let loadFlags = channel.loadFlags;
     loadFlags |= Ci.nsIRequest.VALIDATE_ALWAYS;
     channel.loadFlags = loadFlags;
 
     let key;
     for (key in headers) {
       if (key == 'Authorization')
-        this._log.debug("HTTP Header " + key + ": ***** (suppressed)");
+        this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
       else
-        this._log.debug("HTTP Header " + key + ": " + headers[key]);
+        this._log.trace("HTTP Header " + key + ": " + headers[key]);
       request.setRequestHeader(key, headers[key]);
     }
 
     this._authProvider._authFailed = false;
     request.channel.notificationCallbacks = this._authProvider;
 
     request.send(data);
     let event = yield;
@@ -137,43 +157,43 @@ DAVCollection.prototype = {
   // mkdir -p
   _mkcol: function DC__mkcol(path) {
     let self = yield;
     let ok = true;
 
     try {
       let components = path.split('/');
       let path2 = '';
-  
+
       for (let i = 0; i < components.length; i++) {
-  
+
         // trailing slashes will cause an empty path component at the end
         if (components[i] == '')
           break;
-  
+
         path2 = path2 + components[i];
-  
+
         // check if it exists first
         this._makeRequest.async(this, self.cb, "GET", path2 + "/", this._defaultHeaders);
         let ret = yield;
-        if (!(ret.status == 404 || ret.status == 500)) { // FIXME: 500 is a services.m.c oddity
-          this._log.debug("Skipping creation of path " + path2 +
+        if (ret.status != 404) {
+          this._log.trace("Skipping creation of path " + path2 +
         		  " (got status " + ret.status + ")");
         } else {
           this._log.debug("Creating path: " + path2);
           this._makeRequest.async(this, self.cb, "MKCOL", path2,
         			  this._defaultHeaders);
           ret = yield;
-  
+
           if (ret.status != 201) {
             this._log.debug(ret.responseText);
             throw 'request failed: ' + ret.status;
           }
         }
-  
+
         // add slash *after* the request, trailing slashes cause a 412!
         path2 = path2 + "/";
       }
 
     } catch (e) {
       this._log.error("Could not create directory on server");
       this._log.error("Exception caught: " + (e.message? e.message : e) +
                       " - " + (e.location? e.location : ""));
@@ -206,16 +226,19 @@ DAVCollection.prototype = {
     let headers = {'Content-type': 'text/xml; charset="utf-8"',
                    'Depth': '0'};
     headers.__proto__ = this._defaultHeaders;
     return this._makeRequest.async(this, onComplete, "PROPFIND", path,
                                    headers, data);
   },
 
   LOCK: function DC_LOCK(path, data, onComplete) {
+    if (!this._lockAllowed)
+      throw "Cannot acquire lock (internal lock)";
+
     let headers = {'Content-type': 'text/xml; charset="utf-8"',
                    'Depth': 'infinity',
                    'Timeout': 'Second-600'};
     headers.__proto__ = this._defaultHeaders;
     return this._makeRequest.async(this, onComplete, "LOCK", path, headers, data);
   },
 
   UNLOCK: function DC_UNLOCK(path, onComplete) {
@@ -259,18 +282,18 @@ DAVCollection.prototype = {
   login: function DC_login(username, password) {
     let self = yield;
 
     if (this._loggedIn) {
       this._log.debug("Login requested, but already logged in");
       self.done(true);
       yield;
     }
- 
-    this._log.info("Logging in");
+
+    this._log.debug("Logging in");
 
     let URI = Utils.makeURI(this._baseURL);
     this._auth = "Basic " + btoa(username + ":" + password);
 
     // Make a call to make sure it's working
     this.GET("", self.cb);
     let resp = yield;
 
@@ -281,57 +304,58 @@ DAVCollection.prototype = {
     }
 
     this._loggedIn = true;
 
     self.done(true);
   },
 
   logout: function DC_logout() {
-    this._log.debug("Logging out (forgetting auth header)");
+    this._log.trace("Logging out (forgetting auth header)");
     this._loggedIn = false;
     this.__auth = null;
   },
 
   // Locking
 
   _getActiveLock: function DC__getActiveLock() {
     let self = yield;
     let ret = null;
 
-    this._log.info("Getting active lock token");
+    this._log.debug("Getting active lock token");
     this.PROPFIND("",
                   "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
                   "<D:propfind xmlns:D='DAV:'>" +
                   "  <D:prop><D:lockdiscovery/></D:prop>" +
                   "</D:propfind>", self.cb);
     let resp = yield;
 
     if (this._authProvider._authFailed ||
         resp.status < 200 || resp.status >= 300) {
       self.done(false);
       yield;
     }
 
     let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
     let token = tokens.iterateNext();
-    ret = token.textContent;
+    if (token)
+      ret = token.textContent;
 
     if (ret)
-      this._log.debug("Found an active lock token");
+      this._log.trace("Found an active lock token");
     else
-      this._log.debug("No active lock token found");
+      this._log.trace("No active lock token found");
     self.done(ret);
   },
 
   lock: function DC_lock() {
     let self = yield;
     this._token = null;
 
-    this._log.info("Acquiring lock");
+    this._log.trace("Acquiring lock");
 
     if (this._token) {
       this._log.debug("Lock called, but we already hold a token");
       self.done(this._token);
       yield;
     }
 
     this.LOCK("",
@@ -349,29 +373,29 @@ DAVCollection.prototype = {
     }
 
     let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
     let token = tokens.iterateNext();
     if (token)
       this._token = token.textContent;
 
     if (this._token)
-      this._log.debug("Lock acquired");
+      this._log.trace("Lock acquired");
     else
       this._log.warn("Could not acquire lock");
 
     self.done(this._token);
   },
 
   unlock: function DC_unlock() {
     let self = yield;
 
-    this._log.info("Releasing lock");
+    this._log.trace("Releasing lock");
 
-    if (this._token === null) {
+    if (!this.locked) {
       this._log.debug("Unlock called, but we don't hold a token right now");
       self.done(true);
       yield;
     }
 
     this.UNLOCK("", self.cb);
     let resp = yield;
 
@@ -379,46 +403,46 @@ DAVCollection.prototype = {
         resp.status < 200 || resp.status >= 300) {
       self.done(false);
       yield;
     }
 
     this._token = null;
 
     if (this._token)
-      this._log.info("Could not release lock");
+      this._log.trace("Could not release lock");
     else
-      this._log.info("Lock released (or we didn't have one)");
+      this._log.trace("Lock released (or we didn't have one)");
 
     self.done(!this._token);
   },
 
   forceUnlock: function DC_forceUnlock() {
     let self = yield;
     let unlocked = true;
 
-    this._log.info("Forcibly releasing any server locks");
+    this._log.debug("Forcibly releasing any server locks");
 
     this._getActiveLock.async(this, self.cb);
     this._token = yield;
 
     if (!this._token) {
-      this._log.info("No server lock found");
+      this._log.debug("No server lock found");
       self.done(true);
       yield;
     }
 
-    this._log.info("Server lock found, unlocking");
+    this._log.trace("Server lock found, unlocking");
     this.unlock.async(this, self.cb);
     unlocked = yield;
 
     if (unlocked)
-      this._log.debug("Lock released");
+      this._log.trace("Lock released");
     else
-      this._log.debug("No lock released");
+      this._log.trace("No lock released");
     self.done(unlocked);
   },
 
   stealLock: function DC_stealLock() {
     let self = yield;
     let stolen = null;
 
     this.forceUnlock.async(this, self.cb);
@@ -438,17 +462,17 @@ DAVCollection.prototype = {
  * Auth provider object
  * Taken from nsMicrosummaryService.js and massaged slightly
  */
 
 function DummyAuthProvider() {}
 DummyAuthProvider.prototype = {
   // Implement notification callback interfaces so we can suppress UI
   // and abort loads for bad SSL certs and HTTP authorization requests.
-  
+
   // Interfaces this component implements.
   interfaces: [Ci.nsIBadCertListener,
                Ci.nsIAuthPromptProvider,
                Ci.nsIAuthPrompt,
                Ci.nsIPrompt,
                Ci.nsIProgressEventSink,
                Ci.nsIInterfaceRequestor,
                Ci.nsISupports],
@@ -474,42 +498,42 @@ DummyAuthProvider.prototype = {
     case Ci.nsIPrompt:
       return this.prompt;
     default:
       return this;
     }
   },
 
   // nsIInterfaceRequestor
-  
+
   getInterface: function DAP_getInterface(iid) {
     return this.QueryInterface(iid);
   },
 
   // nsIBadCertListener
 
   // Suppress UI and abort secure loads from servers with bad SSL certificates.
-  
+
   confirmUnknownIssuer: function DAP_confirmUnknownIssuer(socketInfo, cert, certAddType) {
     return false;
   },
 
   confirmMismatchDomain: function DAP_confirmMismatchDomain(socketInfo, targetURL, cert) {
     return false;
   },
 
   confirmCertExpired: function DAP_confirmCertExpired(socketInfo, cert) {
     return false;
   },
 
   notifyCrlNextupdate: function DAP_notifyCrlNextupdate(socketInfo, targetURL, cert) {
   },
 
   // nsIAuthPromptProvider
-  
+
   getAuthPrompt: function(aPromptReason, aIID) {
     this._authFailed = true;
     throw Cr.NS_ERROR_NOT_AVAILABLE;
   },
 
   // HTTP always requests nsIAuthPromptProvider first, so it never needs
   // nsIAuthPrompt, but not all channels use nsIAuthPromptProvider, so we
   // implement nsIAuthPrompt too.
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -41,25 +41,26 @@ const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
 Cu.import("resource://weave/crypto.js");
+Cu.import("resource://weave/dav.js");
+Cu.import("resource://weave/identity.js");
 Cu.import("resource://weave/stores.js");
 Cu.import("resource://weave/syncCores.js");
 Cu.import("resource://weave/async.js");
 
 Function.prototype.async = Async.sugar;
-let Crypto = new WeaveCrypto();
 
-function Engine(davCollection, cryptoId) {
-  //this._init(davCollection, cryptoId);
+function Engine(davCollection, pbeId) {
+  //this._init(davCollection, pbeId);
 }
 Engine.prototype = {
   // "default-engine";
   get name() { throw "name property must be overridden in subclasses"; },
 
   // "DefaultEngine";
   get logName() { throw "logName property must be overridden in subclasses"; },
 
@@ -109,94 +110,86 @@ Engine.prototype = {
     if (!this.__snapshot)
       this.__snapshot = new SnapshotStore(this.name);
     return this.__snapshot;
   },
   set _snapshot(value) {
     this.__snapshot = value;
   },
 
-  _init: function Engine__init(davCollection, cryptoId) {
-    this._dav = davCollection;
-    this._cryptoId = cryptoId;
+  _init: function Engine__init(davCollection, pbeId) {
+    this._pbeId = pbeId;
+    this._engineId = new Identity(pbeId.realm + " - " + this.logName,
+                                  pbeId.username);
     this._log = Log4Moz.Service.getLogger("Service." + this.logName);
+    this._log.level =
+      Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.engine")];
     this._osPrefix = "weave:" + this.name + ":";
     this._snapshot.load();
   },
 
-  _getSymKey: function Engine__getCryptoId(cryptoId) {
+  _getSymKey: function Engine__getSymKey() {
     let self = yield;
-    let done = false;
 
-    this._dav.GET(this.keysFile, self.cb);
+    DAV.GET(this.keysFile, self.cb);
     let keysResp = yield;
     Utils.ensureStatus(keysResp.status,
                        "Could not get keys file.", [[200,300]]);
-    let keys = keysResp.responseText;
+    let keys = this._json.decode(keysResp.responseText);
 
-    if (!keys[this._userHash]) {
-      this._log.error("Keyring does not contain a key for this user");
-      return;
-    }
+    if (!keys || !keys.ring || !keys.ring[this._engineId.userHash])
+      throw "Keyring does not contain a key for this user";
 
-    //Crypto.RSAdecrypt.async(Crypto, self.cb,
-    //                        keys[this._userHash],
-                              
-    self.done(done);
-    yield;
-    this._log.warn("_getSymKey generator not properly closed");
+    Crypto.RSAdecrypt.async(Crypto, self.cb,
+                            keys.ring[this._engineId.userHash], this._pbeId);
+    let symkey = yield;
+    this._engineId.setTempPassword(symkey);
+
+    self.done(true);
   },
 
   _serializeCommands: function Engine__serializeCommands(commands) {
     let json = this._json.encode(commands);
     //json = json.replace(/ {action/g, "\n {action");
     return json;
   },
-  
+
   _serializeConflicts: function Engine__serializeConflicts(conflicts) {
     let json = this._json.encode(conflicts);
     //json = json.replace(/ {action/g, "\n {action");
     return json;
   },
 
   _resetServer: function Engine__resetServer() {
     let self = yield;
     let done = false;
 
     try {
       this._log.debug("Resetting server data");
       this._os.notifyObservers(null, this._osPrefix + "reset-server:start", "");
 
-      this._dav.lock.async(this._dav, self.cb);
-      let locked = yield;
-      if (!locked)
-        throw "Could not acquire lock, aborting server reset";
-
       // try to delete all 3, check status after
-      this._dav.DELETE(this.statusFile, self.cb);
+      DAV.DELETE(this.statusFile, self.cb);
       let statusResp = yield;
-      this._dav.DELETE(this.snapshotFile, self.cb);
+      DAV.DELETE(this.snapshotFile, self.cb);
       let snapshotResp = yield;
-      this._dav.DELETE(this.deltasFile, self.cb);
+      DAV.DELETE(this.deltasFile, self.cb);
       let deltasResp = yield;
 
-      this._dav.unlock.async(this._dav, self.cb);
-      let unlocked = yield;
-
       Utils.ensureStatus(statusResp.status,
                          "Could not delete status file.", [[200,300],404]);
       Utils.ensureStatus(snapshotResp.status,
                          "Could not delete snapshot file.", [[200,300],404]);
       Utils.ensureStatus(deltasResp.status,
                          "Could not delete deltas file.", [[200,300],404]);
 
       this._log.debug("Server files deleted");
       done = true;
       this._os.notifyObservers(null, this._osPrefix + "reset-server:success", "");
-        
+
     } catch (e) {
       this._log.error("Could not delete server files");
       this._os.notifyObservers(null, this._osPrefix + "reset-server:error", "");
       throw e;
     }
 
     self.done(done)
   },
@@ -253,225 +246,196 @@ Engine.prototype = {
   // 3) Reconcile client/server deltas and generate new deltas for them.
   //    Reconciliation won't generate C directly, we will simply diff
   //    server->final after step 3.1.
   // 3.1) Apply local delta with server changes ("D")
   // 3.2) Append server delta to the delta file and upload ("C")
 
   _sync: function BmkEngine__sync() {
     let self = yield;
-    let synced = false, locked = null;
+
+    this._log.info("Beginning sync");
 
-    try {
-      this._log.info("Beginning sync");
-      this._os.notifyObservers(null, this._osPrefix + "sync:start", "");
-
-      this._dav.lock.async(this._dav, self.cb);
-      locked = yield;
+    // Before we get started, make sure we have a remote directory to play in
+    DAV.MKCOL(this.serverPrefix, self.cb);
+    let ret = yield;
+    if (!ret)
+      throw "Could not create remote folder";
 
-      if (locked)
-        this._log.info("Lock acquired");
-      else {
-        this._log.warn("Could not acquire lock, aborting sync");
-        return;
-      }
+    // 1) Fetch server deltas
+    this._getServerData.async(this, self.cb);
+    let server = yield;
 
-      // Before we get started, make sure we have a remote directory to play in
-      this._dav.MKCOL(this.serverPrefix, self.cb);
-      let ret = yield;
-      if (!ret)
-        throw "Could not create remote folder";
+    this._log.info("Local snapshot version: " + this._snapshot.version);
+    this._log.info("Server status: " + server.status);
+    this._log.info("Server maxVersion: " + server.maxVersion);
+    this._log.info("Server snapVersion: " + server.snapVersion);
 
-      // 1) Fetch server deltas
-      this._getServerData.async(this, self.cb);
-      let server = yield;
+    if (server.status != 0) {
+      this._log.fatal("Sync error: could not get server status, " +
+                      "or initial upload failed.  Aborting sync.");
+      return;
+    }
 
-      this._log.info("Local snapshot version: " + this._snapshot.version);
-      this._log.info("Server status: " + server.status);
-      this._log.info("Server maxVersion: " + server.maxVersion);
-      this._log.info("Server snapVersion: " + server.snapVersion);
+    // 2) Generate local deltas from snapshot -> current client status
 
-      if (server.status != 0) {
-        this._log.fatal("Sync error: could not get server status, " +
-                        "or initial upload failed.  Aborting sync.");
-        return;
-      }
+    let localJson = new SnapshotStore();
+    localJson.data = this._store.wrap();
+    this._core.detectUpdates(self.cb, this._snapshot.data, localJson.data);
+    let localUpdates = yield;
 
-      // 2) Generate local deltas from snapshot -> current client status
+    this._log.trace("local json:\n" + localJson.serialize());
+    this._log.trace("Local updates: " + this._serializeCommands(localUpdates));
+    this._log.trace("Server updates: " + this._serializeCommands(server.updates));
 
-      let localJson = new SnapshotStore();
-      localJson.data = this._store.wrap();
-      this._core.detectUpdates(self.cb, this._snapshot.data, localJson.data);
-      let localUpdates = yield;
+    if (server.updates.length == 0 && localUpdates.length == 0) {
+      this._snapshot.version = server.maxVersion;
+      this._log.info("Sync complete: no changes needed on client or server");
+      self.done(true);
+      return;
+    }
 
-      this._log.trace("local json:\n" + localJson.serialize());
-      this._log.trace("Local updates: " + this._serializeCommands(localUpdates));
-      this._log.trace("Server updates: " + this._serializeCommands(server.updates));
+    // 3) Reconcile client/server deltas and generate new deltas for them.
+
+    this._log.info("Reconciling client/server updates");
+    this._core.reconcile(self.cb, localUpdates, server.updates);
+    ret = yield;
 
-      if (server.updates.length == 0 && localUpdates.length == 0) {
-        this._snapshot.version = server.maxVersion;
-        this._log.info("Sync complete (1): no changes needed on client or server");
-        synced = true;
-        return;
-      }
-	  
-      // 3) Reconcile client/server deltas and generate new deltas for them.
+    let clientChanges = ret.propagations[0];
+    let serverChanges = ret.propagations[1];
+    let clientConflicts = ret.conflicts[0];
+    let serverConflicts = ret.conflicts[1];
 
-      this._log.info("Reconciling client/server updates");
-      this._core.reconcile(self.cb, localUpdates, server.updates);
-      ret = yield;
-
-      let clientChanges = ret.propagations[0];
-      let serverChanges = ret.propagations[1];
-      let clientConflicts = ret.conflicts[0];
-      let serverConflicts = ret.conflicts[1];
+    this._log.info("Changes for client: " + clientChanges.length);
+    this._log.info("Predicted changes for server: " + serverChanges.length);
+    this._log.info("Client conflicts: " + clientConflicts.length);
+    this._log.info("Server conflicts: " + serverConflicts.length);
+    this._log.trace("Changes for client: " + this._serializeCommands(clientChanges));
+    this._log.trace("Predicted changes for server: " + this._serializeCommands(serverChanges));
+    this._log.trace("Client conflicts: " + this._serializeConflicts(clientConflicts));
+    this._log.trace("Server conflicts: " + this._serializeConflicts(serverConflicts));
 
-      this._log.info("Changes for client: " + clientChanges.length);
-      this._log.info("Predicted changes for server: " + serverChanges.length);
-      this._log.info("Client conflicts: " + clientConflicts.length);
-      this._log.info("Server conflicts: " + serverConflicts.length);
-      this._log.trace("Changes for client: " + this._serializeCommands(clientChanges));
-      this._log.trace("Predicted changes for server: " + this._serializeCommands(serverChanges));
-      this._log.trace("Client conflicts: " + this._serializeConflicts(clientConflicts));
-      this._log.trace("Server conflicts: " + this._serializeConflicts(serverConflicts));
+    if (!(clientChanges.length || serverChanges.length ||
+          clientConflicts.length || serverConflicts.length)) {
+      this._log.info("Sync complete: no changes needed on client or server");
+      this._snapshot.data = localJson.data;
+      this._snapshot.version = server.maxVersion;
+      this._snapshot.save();
+      self.done(true);
+      return;
+    }
 
-      if (!(clientChanges.length || serverChanges.length ||
-            clientConflicts.length || serverConflicts.length)) {
-        this._log.info("Sync complete (2): no changes needed on client or server");
-        this._snapshot.data = localJson.data;
-        this._snapshot.version = server.maxVersion;
-        this._snapshot.save();
-        synced = true;
-        return;
-      }
+    if (clientConflicts.length || serverConflicts.length) {
+      this._log.warn("Conflicts found!  Discarding server changes");
+    }
 
-      if (clientConflicts.length || serverConflicts.length) {
-        this._log.warn("Conflicts found!  Discarding server changes");
-      }
+    let savedSnap = Utils.deepCopy(this._snapshot.data);
+    let savedVersion = this._snapshot.version;
+    let newSnapshot;
 
-      let savedSnap = Utils.deepCopy(this._snapshot.data);
-      let savedVersion = this._snapshot.version;
-      let newSnapshot;
+    // 3.1) Apply server changes to local store
+    if (clientChanges.length) {
+      this._log.info("Applying changes locally");
+      // Note that we need to need to apply client changes to the
+      // current tree, not the saved snapshot
 
-      // 3.1) Apply server changes to local store
-      if (clientChanges.length) {
-        this._log.info("Applying changes locally");
-        // Note that we need to need to apply client changes to the
-        // current tree, not the saved snapshot
-
-	localJson.applyCommands(clientChanges);
-        this._snapshot.data = localJson.data;
-        this._snapshot.version = server.maxVersion;
-        this._store.applyCommands(clientChanges);
-        newSnapshot = this._store.wrap();
+      localJson.applyCommands.async(localJson, self.cb, clientChanges);
+      yield;
+      this._snapshot.data = localJson.data;
+      this._snapshot.version = server.maxVersion;
+      this._store.applyCommands.async(this._store, self.cb, clientChanges);
+      yield;
+      newSnapshot = this._store.wrap();
 
-        this._core.detectUpdates(self.cb, this._snapshot.data, newSnapshot);
-        let diff = yield;
-        if (diff.length != 0) {
-          this._log.warn("Commands did not apply correctly");
-          this._log.debug("Diff from snapshot+commands -> " +
-                          "new snapshot after commands:\n" +
-                          this._serializeCommands(diff));
-          // FIXME: do we really want to revert the snapshot here?
-          this._snapshot.data = Utils.deepCopy(savedSnap);
-          this._snapshot.version = savedVersion;
-        }
-        this._snapshot.save();
+      this._core.detectUpdates(self.cb, this._snapshot.data, newSnapshot);
+      let diff = yield;
+      if (diff.length != 0) {
+        this._log.warn("Commands did not apply correctly");
+        this._log.debug("Diff from snapshot+commands -> " +
+                        "new snapshot after commands:\n" +
+                        this._serializeCommands(diff));
+        // FIXME: do we really want to revert the snapshot here?
+        this._snapshot.data = Utils.deepCopy(savedSnap);
+        this._snapshot.version = savedVersion;
       }
+      this._snapshot.save();
+    }
 
-      // 3.2) Append server delta to the delta file and upload
+    // 3.2) Append server delta to the delta file and upload
 
-      // Generate a new diff, from the current server snapshot to the
-      // current client snapshot.  In the case where there are no
-      // conflicts, it should be the same as what the resolver returned
+    // Generate a new diff, from the current server snapshot to the
+    // current client snapshot.  In the case where there are no
+    // conflicts, it should be the same as what the resolver returned
 
-      newSnapshot = this._store.wrap();
-      this._core.detectUpdates(self.cb, server.snapshot, newSnapshot);
-      let serverDelta = yield;
+    newSnapshot = this._store.wrap();
+    this._core.detectUpdates(self.cb, server.snapshot, newSnapshot);
+    let serverDelta = yield;
 
-      // Log an error if not the same
-      if (!(serverConflicts.length ||
-            Utils.deepEquals(serverChanges, serverDelta)))
-        this._log.warn("Predicted server changes differ from " +
-                       "actual server->client diff (can be ignored in many cases)");
+    // Log an error if not the same
+    if (!(serverConflicts.length ||
+          Utils.deepEquals(serverChanges, serverDelta)))
+      this._log.warn("Predicted server changes differ from " +
+                     "actual server->client diff (can be ignored in many cases)");
 
-      this._log.info("Actual changes for server: " + serverDelta.length);
-      this._log.debug("Actual changes for server: " +
-                      this._serializeCommands(serverDelta));
+    this._log.info("Actual changes for server: " + serverDelta.length);
+    this._log.debug("Actual changes for server: " +
+                    this._serializeCommands(serverDelta));
 
-      if (serverDelta.length) {
-        this._log.info("Uploading changes to server");
+    if (serverDelta.length) {
+      this._log.info("Uploading changes to server");
 
-        this._snapshot.data = newSnapshot;
-        this._snapshot.version = ++server.maxVersion;
+      this._snapshot.data = newSnapshot;
+      this._snapshot.version = ++server.maxVersion;
 
-        server.deltas.push(serverDelta);
+      server.deltas.push(serverDelta);
 
-        if (server.formatVersion != STORAGE_FORMAT_VERSION ||
-            this._encryptionChanged) {
-          this._fullUpload.async(this, self.cb);
-          let status = yield;
-          if (!status)
-            this._log.error("Could not upload files to server"); // eep?
+      if (server.formatVersion != ENGINE_STORAGE_FORMAT_VERSION ||
+          this._encryptionChanged) {
+        this._fullUpload.async(this, self.cb);
+        let status = yield;
+        if (!status)
+          this._log.error("Could not upload files to server"); // eep?
 
-        } else {
-	  Crypto.PBEencrypt.async(Crypto, self.cb,
-                                  this._serializeCommands(server.deltas),
-				  this._cryptoId);
-          let data = yield;
-          this._dav.PUT(this.deltasFile, data, self.cb);
-          let deltasPut = yield;
+      } else {
+        Crypto.PBEencrypt.async(Crypto, self.cb,
+                                this._serializeCommands(server.deltas),
+      			  this._engineId);
+        let data = yield;
+        DAV.PUT(this.deltasFile, data, self.cb);
+        let deltasPut = yield;
 
-          let c = 0;
-          for (GUID in this._snapshot.data)
-            c++;
+        let c = 0;
+        for (GUID in this._snapshot.data)
+          c++;
 
-          this._dav.PUT(this.statusFile,
-                        this._json.encode(
-                          {GUID: this._snapshot.GUID,
-                           formatVersion: STORAGE_FORMAT_VERSION,
-                           snapVersion: server.snapVersion,
-                           maxVersion: this._snapshot.version,
-                           snapEncryption: server.snapEncryption,
-                           deltasEncryption: Crypto.defaultAlgorithm,
-                           itemCount: c}), self.cb);
-          let statusPut = yield;
+        DAV.PUT(this.statusFile,
+                      this._json.encode(
+                        {GUID: this._snapshot.GUID,
+                         formatVersion: ENGINE_STORAGE_FORMAT_VERSION,
+                         snapVersion: server.snapVersion,
+                         maxVersion: this._snapshot.version,
+                         snapEncryption: server.snapEncryption,
+                         deltasEncryption: Crypto.defaultAlgorithm,
+                         itemCount: c}), self.cb);
+        let statusPut = yield;
 
-          if (deltasPut.status >= 200 && deltasPut.status < 300 &&
-              statusPut.status >= 200 && statusPut.status < 300) {
-            this._log.info("Successfully updated deltas and status on server");
-            this._snapshot.save();
-          } else {
-            // FIXME: revert snapshot here? - can't, we already applied
-            // updates locally! - need to save and retry
-            this._log.error("Could not update deltas on server");
-          }
+        if (deltasPut.status >= 200 && deltasPut.status < 300 &&
+            statusPut.status >= 200 && statusPut.status < 300) {
+          this._log.info("Successfully updated deltas and status on server");
+          this._snapshot.save();
+        } else {
+          // FIXME: revert snapshot here? - can't, we already applied
+          // updates locally! - need to save and retry
+          this._log.error("Could not update deltas on server");
         }
       }
-
-      this._log.info("Sync complete");
-      synced = true;
-
-    } catch (e) {
-      throw e;
+    }
 
-    } finally {
-      let ok = false;
-      if (locked) {
-        this._dav.unlock.async(this._dav, self.cb);
-        ok = yield;
-      }
-      if (ok && synced) {
-        this._os.notifyObservers(null, this._osPrefix + "sync:success", "");
-        self.done(true);
-      } else {
-        this._os.notifyObservers(null, this._osPrefix + "sync:error", "");
-        self.done(false);
-      }
-    }
+    this._log.info("Sync complete");
+    self.done(true);
   },
 
   /* Get the deltas/combined updates from the server
    * Returns:
    *   status:
    *     -1: error
    *      0: ok
    * These fields may be null when status is -1:
@@ -496,35 +460,38 @@ Engine.prototype = {
   _getServerData: function BmkEngine__getServerData() {
     let self = yield;
     let ret = {status: -1,
                formatVersion: null, maxVersion: null, snapVersion: null,
                snapEncryption: null, deltasEncryption: null,
                snapshot: null, deltas: null, updates: null};
 
     this._log.debug("Getting status file from server");
-    this._dav.GET(this.statusFile, self.cb);
+    DAV.GET(this.statusFile, self.cb);
     let resp = yield;
     let status = resp.status;
 
     switch (status) {
     case 200: {
       this._log.info("Got status file from server");
 
       let status = this._json.decode(resp.responseText);
       let deltas, allDeltas;
       let snap = new SnapshotStore();
 
       // Bail out if the server has a newer format version than we can parse
-      if (status.formatVersion > STORAGE_FORMAT_VERSION) {
+      if (status.formatVersion > ENGINE_STORAGE_FORMAT_VERSION) {
         this._log.error("Server uses storage format v" + status.formatVersion +
-                  ", this client understands up to v" + STORAGE_FORMAT_VERSION);
+                  ", this client understands up to v" + ENGINE_STORAGE_FORMAT_VERSION);
         break;
       }
 
+      this._getSymKey.async(this, self.cb);
+      yield;
+
       if (status.formatVersion == 0) {
         ret.snapEncryption = status.snapEncryption = "none";
         ret.deltasEncryption = status.deltasEncryption = "none";
       }
 
       if (status.GUID != this._snapshot.GUID) {
         this._log.info("Remote/local sync GUIDs do not match.  " +
                     "Forcing initial sync.");
@@ -538,79 +505,80 @@ Engine.prototype = {
 
       if (this._snapshot.version < status.snapVersion) {
         this._log.trace("Local snapshot version < server snapVersion");
 
         if (this._snapshot.version >= 0)
           this._log.info("Local snapshot is out of date");
 
         this._log.info("Downloading server snapshot");
-        this._dav.GET(this.snapshotFile, self.cb);
+        DAV.GET(this.snapshotFile, self.cb);
         resp = yield;
         Utils.ensureStatus(resp.status, "Could not download snapshot.");
         Crypto.PBEdecrypt.async(Crypto, self.cb,
                                 resp.responseText,
-      			  this._cryptoId,
+      			  this._engineId,
       			  status.snapEncryption);
         let data = yield;
         snap.data = this._json.decode(data);
 
         this._log.info("Downloading server deltas");
-        this._dav.GET(this.deltasFile, self.cb);
+        DAV.GET(this.deltasFile, self.cb);
         resp = yield;
         Utils.ensureStatus(resp.status, "Could not download deltas.");
         Crypto.PBEdecrypt.async(Crypto, self.cb,
                                 resp.responseText,
-      			  this._cryptoId,
+      			  this._engineId,
       			  status.deltasEncryption);
         data = yield;
         allDeltas = this._json.decode(data);
         deltas = this._json.decode(data);
 
       } else if (this._snapshot.version >= status.snapVersion &&
                  this._snapshot.version < status.maxVersion) {
         this._log.trace("Server snapVersion <= local snapshot version < server maxVersion");
         snap.data = Utils.deepCopy(this._snapshot.data);
 
         this._log.info("Downloading server deltas");
-        this._dav.GET(this.deltasFile, self.cb);
+        DAV.GET(this.deltasFile, self.cb);
         resp = yield;
         Utils.ensureStatus(resp.status, "Could not download deltas.");
         Crypto.PBEdecrypt.async(Crypto, self.cb,
                                 resp.responseText,
-      			  this._cryptoId,
+      			  this._engineId,
       			  status.deltasEncryption);
         let data = yield;
         allDeltas = this._json.decode(data);
         deltas = allDeltas.slice(this._snapshot.version - status.snapVersion);
 
       } else if (this._snapshot.version == status.maxVersion) {
         this._log.trace("Local snapshot version == server maxVersion");
         snap.data = Utils.deepCopy(this._snapshot.data);
 
         // FIXME: could optimize this case by caching deltas file
         this._log.info("Downloading server deltas");
-        this._dav.GET(this.deltasFile, self.cb);
+        DAV.GET(this.deltasFile, self.cb);
         resp = yield;
         Utils.ensureStatus(resp.status, "Could not download deltas.");
         Crypto.PBEdecrypt.async(Crypto, self.cb,
                                 resp.responseText,
-      			  this._cryptoId,
+      			  this._engineId,
       			  status.deltasEncryption);
         let data = yield;
         allDeltas = this._json.decode(data);
         deltas = [];
 
       } else { // this._snapshot.version > status.maxVersion
         this._log.error("Server snapshot is older than local snapshot");
         break;
       }
 
       for (var i = 0; i < deltas.length; i++) {
-        snap.applyCommands(deltas[i]);
+        snap.applyCommands.async(snap, self.cb, deltas[i]);
+        yield;
       }
 
       ret.status = 0;
       ret.formatVersion = status.formatVersion;
       ret.maxVersion = status.maxVersion;
       ret.snapVersion = status.snapVersion;
       ret.snapEncryption = status.snapEncryption;
       ret.deltasEncryption = status.deltasEncryption;
@@ -631,17 +599,17 @@ Engine.prototype = {
       let uploadStatus = yield;
       if (!uploadStatus)
         break;
 
       this._log.info("Initial upload to server successful");
       this._snapshot.save();
 
       ret.status = 0;
-      ret.formatVersion = STORAGE_FORMAT_VERSION;
+      ret.formatVersion = ENGINE_STORAGE_FORMAT_VERSION;
       ret.maxVersion = this._snapshot.version;
       ret.snapVersion = this._snapshot.version;
       ret.snapEncryption = Crypto.defaultAlgorithm;
       ret.deltasEncryption = Crypto.defaultAlgorithm;
       ret.snapshot = Utils.deepCopy(this._snapshot.data);
       ret.deltas = [];
       ret.updates = [];
     } break;
@@ -654,65 +622,172 @@ Engine.prototype = {
 
     self.done(ret)
   },
 
   _fullUpload: function Engine__fullUpload() {
     let self = yield;
     let ret = false;
 
+    Crypto.PBEkeygen.async(Crypto, self.cb);
+    let symkey = yield;
+    this._engineId.setTempPassword(symkey);
+    if (!this._engineId.password)
+      throw "Could not generate a symmetric encryption key";
+
+    Crypto.RSAencrypt.async(Crypto, self.cb,
+                            this._engineId.password, this._pbeId);
+    let enckey = yield;
+    if (!enckey)
+      throw "Could not encrypt symmetric encryption key";
+
+    let keys = {ring: {}};
+    keys.ring[this._engineId.userHash] = enckey;
+    DAV.PUT(this.keysFile, this._json.encode(keys), self.cb);
+    let resp = yield;
+    Utils.ensureStatus(resp.status, "Could not upload keyring file.");
+
     Crypto.PBEencrypt.async(Crypto, self.cb,
                             this._snapshot.serialize(),
-      		            this._cryptoId);
+      		            this._engineId);
     let data = yield;
-      
-    this._dav.PUT(this.snapshotFile, data, self.cb);
+
+    DAV.PUT(this.snapshotFile, data, self.cb);
     resp = yield;
     Utils.ensureStatus(resp.status, "Could not upload snapshot.");
 
-    this._dav.PUT(this.deltasFile, "[]", self.cb);
+    DAV.PUT(this.deltasFile, "[]", self.cb);
     resp = yield;
     Utils.ensureStatus(resp.status, "Could not upload deltas.");
 
     let c = 0;
     for (GUID in this._snapshot.data)
       c++;
 
-    this._dav.PUT(this.statusFile,
+    DAV.PUT(this.statusFile,
                   this._json.encode(
                     {GUID: this._snapshot.GUID,
-                     formatVersion: STORAGE_FORMAT_VERSION,
+                     formatVersion: ENGINE_STORAGE_FORMAT_VERSION,
                      snapVersion: this._snapshot.version,
                      maxVersion: this._snapshot.version,
                      snapEncryption: Crypto.defaultAlgorithm,
                      deltasEncryption: "none",
                      itemCount: c}), self.cb);
     resp = yield;
     Utils.ensureStatus(resp.status, "Could not upload status file.");
 
     this._log.info("Full upload to server successful");
     ret = true;
     self.done(ret)
   },
 
+  _share: function Engine__share(username) {
+    let self = yield;
+    let base = DAV.baseURL;
+
+    this._log.debug("Sharing bookmarks with " + username);
+
+    this._getSymKey.async(this, self.cb);
+    yield;
+
+    // copied from getSymKey
+    DAV.GET(this.keysFile, self.cb);
+    let ret = yield;
+    Utils.ensureStatus(ret.status, "Could not get keys file.");
+    let keys = this._json.decode(ret.responseText);
+
+    // get the other user's pubkey
+    let hash = Utils.sha1(username);
+    let serverURL = Utils.prefs.getCharPref("serverURL");
+
+    try {
+      DAV.baseURL = serverURL + "user/" + hash + "/";  //FIXME: very ugly!
+      DAV.GET("public/pubkey", self.cb);
+      ret = yield;
+    }
+    catch (e) { throw e; }
+    finally { DAV.baseURL = base; }
+
+    Utils.ensureStatus(ret.status, "Could not get public key for " + username);
+
+    let id = new Identity();
+    id.pubkey = ret.responseText;
+
+    // now encrypt the symkey with their pubkey and upload the new keyring
+    Crypto.RSAencrypt.async(Crypto, self.cb, this._engineId.password, id);
+    let enckey = yield;
+    if (!enckey)
+      throw "Could not encrypt symmetric encryption key";
+
+    keys.ring[hash] = enckey;
+    DAV.PUT(this.keysFile, this._json.encode(keys), self.cb);
+    ret = yield;
+    Utils.ensureStatus(ret.status, "Could not upload keyring file.");
+
+    this._createShare(username, username);
+
+    this._log.debug("All done sharing!");
+
+    self.done(true);
+  },
+
+  // FIXME: EEK bookmarks specific
+  _createShare: function Engine__createShare(id, title) {
+    let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+      getService(Ci.nsINavBookmarksService);
+    let ans = Cc["@mozilla.org/browser/annotation-service;1"].
+      getService(Ci.nsIAnnotationService);
+
+    let root;
+    let a = ans.getItemsWithAnnotation("weave/mounted-shares-folder", {});
+    if (a.length == 1)
+      root = a[0];
+
+    if (!root) {
+      root = bms.createFolder(bms.toolbarFolder, "Shared Folders",
+                              bms.DEFAULT_INDEX);
+      ans.setItemAnnotation(root, "weave/mounted-shares-folder", true, 0,
+                            ans.EXPIRE_NEVER);
+    }
+
+    let item
+    a = ans.getItemsWithAnnotation("weave/mounted-share-id", {});
+    for (let i = 0; i < a.length; i++) {
+      if (ans.getItemAnnotation(a[i], "weave/mounted-share-id") == id) {
+        item = a[i];
+        break;
+      }
+    }
+
+    if (!item) {
+      let newId = bms.createFolder(root, title, bms.DEFAULT_INDEX);
+      ans.setItemAnnotation(newId, "weave/mounted-share-id", id, 0,
+                            ans.EXPIRE_NEVER);
+    }
+  },
+
   sync: function Engine_sync(onComplete) {
     return this._sync.async(this, onComplete);
   },
 
+  share: function Engine_share(onComplete, username) {
+    return this._share.async(this, onComplete, username);
+  },
+
   resetServer: function Engine_resetServer(onComplete) {
     return this._resetServer.async(this, onComplete);
   },
 
   resetClient: function Engine_resetClient(onComplete) {
     return this._resetClient.async(this, onComplete);
   }
 };
 
-function BookmarksEngine(davCollection, cryptoId) {
-  this._init(davCollection, cryptoId);
+function BookmarksEngine(davCollection, pbeId) {
+  this._init(davCollection, pbeId);
 }
 BookmarksEngine.prototype = {
   get name() { return "bookmarks-engine"; },
   get logName() { return "BmkEngine"; },
   get serverPrefix() { return "user-data/bookmarks/"; },
 
   __core: null,
   get _core() {
@@ -721,22 +796,109 @@ BookmarksEngine.prototype = {
     return this.__core;
   },
 
   __store: null,
   get _store() {
     if (!this.__store)
       this.__store = new BookmarksStore();
     return this.__store;
+  },
+
+  syncMounts: function BmkEngine_syncMounts(onComplete) {
+    this._syncMounts.async(this, onComplete);
+  },
+  _syncMounts: function BmkEngine__syncMounts() {
+    let self = yield;
+    let mounts = this._store.findMounts();
+
+    for (i = 0; i < mounts.length; i++) {
+      try {
+        this._syncOneMount.async(this, self.cb, mounts[i]);
+        yield;
+      } catch (e) {
+        this._log.warn("Could not sync shared folder from " + mounts[i].userid);
+        this._log.trace(Utils.stackTrace(e));
+      }
+    }
+  },
+
+  _syncOneMount: function BmkEngine__syncOneMount(mountData) {
+    let self = yield;
+    let user = mountData.userid;
+    let base = DAV.baseURL;
+    let serverURL = Utils.prefs.getCharPref("serverURL");
+    let snap = new SnapshotStore();
+
+    this._log.debug("Syncing shared folder from user " + user);
+
+    try {
+      let hash = Utils.sha1(user);
+      DAV.baseURL = serverURL + "user/" + hash + "/";  //FIXME: very ugly!
+
+      this._getSymKey.async(this, self.cb);
+      yield;
+
+      this._log.trace("Getting status file for " + user);
+      DAV.GET(this.statusFile, self.cb);
+      let resp = yield;
+      Utils.ensureStatus(resp.status, "Could not download status file.");
+      let status = this._json.decode(resp.responseText);
+
+      this._log.trace("Downloading server snapshot for " + user);
+      DAV.GET(this.snapshotFile, self.cb);
+      resp = yield;
+      Utils.ensureStatus(resp.status, "Could not download snapshot.");
+      Crypto.PBEdecrypt.async(Crypto, self.cb, resp.responseText,
+    			        this._engineId, status.snapEncryption);
+      let data = yield;
+      snap.data = this._json.decode(data);
+
+      this._log.trace("Downloading server deltas for " + user);
+      DAV.GET(this.deltasFile, self.cb);
+      resp = yield;
+      Utils.ensureStatus(resp.status, "Could not download deltas.");
+      Crypto.PBEdecrypt.async(Crypto, self.cb, resp.responseText,
+    			        this._engineId, status.deltasEncryption);
+      data = yield;
+      deltas = this._json.decode(data);
+    }
+    catch (e) { throw e; }
+    finally { DAV.baseURL = base; }
+
+    // apply deltas to get current snapshot
+    for (var i = 0; i < deltas.length; i++) {
+      snap.applyCommands.async(snap, self.cb, deltas[i]);
+      yield;
+    }
+
+    // prune tree / get what we want
+    for (let guid in snap.data) {
+      if (snap.data[guid].type != "bookmark")
+        delete snap.data[guid];
+      else
+        snap.data[guid].parentGUID = mountData.rootGUID;
+    }
+
+    this._log.trace("Got bookmarks fror " + user + ", comparing with local copy");
+    this._core.detectUpdates(self.cb, mountData.snapshot, snap.data);
+    let diff = yield;
+
+    // FIXME: should make sure all GUIDs here live under the mountpoint
+    this._log.trace("Applying changes to folder from " + user);
+    this._store.applyCommands.async(this._store, self.cb, diff);
+    yield;
+
+    this._log.trace("Shared folder from " + user + " successfully synced!");
   }
 };
 BookmarksEngine.prototype.__proto__ = new Engine();
 
-function HistoryEngine(davCollection, cryptoId) {
-  this._init(davCollection, cryptoId);
+function HistoryEngine(davCollection, pbeId) {
+  this._init(davCollection, pbeId);
 }
 HistoryEngine.prototype = {
   get name() { return "history-engine"; },
   get logName() { return "HistEngine"; },
   get serverPrefix() { return "user-data/history/"; },
 
   __core: null,
   get _core() {
@@ -749,17 +911,16 @@ HistoryEngine.prototype = {
   get _store() {
     if (!this.__store)
       this.__store = new HistoryStore();
     return this.__store;
   }
 };
 HistoryEngine.prototype.__proto__ = new Engine();
 
-
 // Jono: the following is copy-and-paste code
 function CookieEngine(davCollection, cryptoId) {
   this._init(davCollection, cryptoId);
 }
 CookieEngine.prototype = {
   get name() { return "cookie-engine"; },
   get logName() { return "CookieEngine"; },
   get serverPrefix() { return "user-data/cookies/"; },
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -61,23 +61,30 @@ function Identity(realm, username, passw
 }
 Identity.prototype = {
   get realm() { return this._realm; },
   set realm(value) { this._realm = value; },
 
   get username() { return this._username; },
   set username(value) { this._username = value; },
 
-  _key: null,
-  get key() { return this._key; },
-  set key(value) { this._key = value; },
+  get userHash() { return Utils.sha1(this.username); },
+
+  _privkey: null,
+  get privkey() { return this._privkey; },
+  set privkey(value) { this._privkey = value; },
+
+  // FIXME: get this from the privkey using crypto.js?
+  _pubkey: null,
+  get pubkey() { return this._pubkey; },
+  set pubkey(value) { this._pubkey = value; },
 
   _password: null,
   get password() {
-    if (this._password === null)
+    if (!this._password)
       return findPassword(this.realm, this.username);
     return this._password;
   },
   set password(value) {
     setPassword(this.realm, this.username, value);
   },
 
   setTempPassword: function Id_setTempPassword(value) {
--- a/services/sync/modules/log4moz.js
+++ b/services/sync/modules/log4moz.js
@@ -53,38 +53,46 @@ const MODE_TRUNCATE = 0x20;
 
 const PERMS_FILE      = 0644;
 const PERMS_DIRECTORY = 0755;
 
 const ONE_BYTE = 1;
 const ONE_KILOBYTE = 1024 * ONE_BYTE;
 const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
 
-const Log4Moz = {};
-Log4Moz.Level = {};
-Log4Moz.Level.Fatal  = 70;
-Log4Moz.Level.Error  = 60;
-Log4Moz.Level.Warn   = 50;
-Log4Moz.Level.Info   = 40;
-Log4Moz.Level.Config = 30;
-Log4Moz.Level.Debug  = 20;
-Log4Moz.Level.Trace  = 10;
-Log4Moz.Level.All    = 0;
+let Log4Moz = {
+  Level: {
+    Fatal:  70,
+    Error:  60,
+    Warn:   50,
+    Info:   40,
+    Config: 30,
+    Debug:  20,
+    Trace:  10,
+    All:    0,
+    Desc: {
+      70: "FATAL",
+      60: "ERROR",
+      50: "WARN",
+      40: "INFO",
+      30: "CONFIG",
+      20: "DEBUG",
+      10: "TRACE",
+      0:  "ALL"
+    }
+  },
 
-Log4Moz.Level.Desc = {
-  70: "FATAL",
-  60: "ERROR",
-  50: "WARN",
-  40: "INFO",
-  30: "CONFIG",
-  20: "DEBUG",
-  10: "TRACE",
-  0:  "ALL"
+  get Service() {
+    delete Log4Moz.Service;
+    Log4Moz.Service = new Log4MozService();
+    return Log4Moz.Service;
+  }
 };
 
+
 /*
  * LogMessage
  * Encapsulates a single log event's data
  */
 function LogMessage(loggerName, level, message){
   this.loggerName = loggerName;
   this.message = message;
   this.level = level;
@@ -384,16 +392,21 @@ FileAppender.prototype = {
   doAppend: function FApp_doAppend(message) {
     if (message === null || message.length <= 0)
       return;
     try {
       this._fos().write(message, message.length);
     } catch(e) {
       dump("Error writing file:\n" + e);
     }
+  },
+
+  clear: function FApp_clear() {
+    this.closeStream();
+    this._file.remove(false);
   }
 };
 FileAppender.prototype.__proto__ = new Appender();
 
 /*
  * RotatingFileAppender
  * Similar to FileAppender, but rotates logs when they become too large
  */
@@ -490,10 +503,8 @@ Log4MozService.prototype = {
     case "basic":
       return new BasicFormatter();
     default:
       dump("log4moz: unknown formatter kind: " + kind);
       return;
     }
   }
 };
-
-Log4Moz.Service = new Log4MozService();
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -29,54 +29,74 @@
  * 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 ***** */
 
-const EXPORTED_SYMBOLS = ['WeaveSyncService'];
+const EXPORTED_SYMBOLS = ['Weave'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
+Cu.import("resource://weave/wrap.js");
 Cu.import("resource://weave/crypto.js");
 Cu.import("resource://weave/engines.js");
 Cu.import("resource://weave/dav.js");
 Cu.import("resource://weave/identity.js");
 Cu.import("resource://weave/async.js");
 
 Function.prototype.async = Async.sugar;
-let Crypto = new WeaveCrypto();
+
+// for export
+let Weave = {};
+Cu.import("resource://weave/constants.js", Weave);
+Cu.import("resource://weave/util.js", Weave);
+Cu.import("resource://weave/async.js", Weave);
+Cu.import("resource://weave/crypto.js", Weave);
+Cu.import("resource://weave/identity.js", Weave);
+Cu.import("resource://weave/dav.js", Weave);
+Cu.import("resource://weave/stores.js", Weave);
+Cu.import("resource://weave/syncCores.js", Weave);
+Cu.import("resource://weave/engines.js", Weave);
+Cu.import("resource://weave/service.js", Weave);
+Utils.lazy(Weave, 'Service', WeaveSvc);
 
 /*
  * Service singleton
  * Main entry point into Weave's sync framework
  */
 
-function WeaveSyncService() { this._init(); }
-WeaveSyncService.prototype = {
+function WeaveSvc() {
+  this._initLogs();
+  this._log.info("Weave Sync Service Initializing");
+
+  Utils.prefs.addObserver("", this, false);
 
-  __prefs: null,
-  get _prefs() {
-    if (!this.__prefs) {
-      this.__prefs = Cc["@mozilla.org/preferences-service;1"]
-        .getService(Ci.nsIPrefService);
-      this.__prefs = this.__prefs.getBranch(PREFS_BRANCH);
-      this.__prefs.QueryInterface(Ci.nsIPrefBranch2);
-    }
-    return this.__prefs;
-  },
+  if (!this.enabled) {
+    this._log.info("Weave Sync disabled");
+    return;
+  }
+
+  this._setSchedule(this.schedule);
+}
+WeaveSvc.prototype = {
+
+  _notify: Wrap.notify,
+  _lock: Wrap.lock,
+  _localLock: Wrap.localLock,
+  _osPrefix: "weave:service:",
 
   __os: null,
   get _os() {
     if (!this.__os)
       this.__os = Cc["@mozilla.org/observer-service;1"]
         .getService(Ci.nsIObserverService);
     return this.__os;
   },
@@ -84,48 +104,39 @@ WeaveSyncService.prototype = {
   __dirSvc: null,
   get _dirSvc() {
     if (!this.__dirSvc)
       this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
         getService(Ci.nsIProperties);
     return this.__dirSvc;
   },
 
-  __dav: null,
-  get _dav() {
-    if (!this.__dav)
-      this.__dav = new DAVCollection();
-    return this.__dav;
-  },
-
   // FIXME: engines should be loaded dynamically somehow / need API to register
 
   __bmkEngine: null,
   get _bmkEngine() {
     if (!this.__bmkEngine)
-      this.__bmkEngine = new BookmarksEngine(this._dav, this._cryptoId);
+      this.__bmkEngine = new BookmarksEngine(DAV, this._cryptoId);
     return this.__bmkEngine;
   },
 
   __histEngine: null,
   get _histEngine() {
     if (!this.__histEngine)
-      this.__histEngine = new HistoryEngine(this._dav, this._cryptoId);
+      this.__histEngine = new HistoryEngine(DAV, this._cryptoId);
     return this.__histEngine;
   },
 
   __cookieEngine: null,
   get _cookieEngine() {
     if (!this.__cookieEngine)
-      this.__cookieEngine = new CookieEngine(this._dav, this._cryptoId);
+      this.__cookieEngine = new CookieEngine(DAV, this._cryptoId);
     return this.__cookieEngine;
   },
 
-  // Logger object
-  _log: null,
 
   // Timer object for automagically syncing
   _scheduleTimer: null,
 
   __mozId: null,
   get _mozId() {
     if (this.__mozId === null)
       this.__mozId = new Identity('Mozilla Services Password', this.username);
@@ -136,88 +147,51 @@ WeaveSyncService.prototype = {
   get _cryptoId() {
     if (this.__cryptoId === null)
       this.__cryptoId = new Identity('Mozilla Services Encryption Passphrase',
 				     this.username);
     return this.__cryptoId;
   },
 
   get username() {
-    return this._prefs.getCharPref("username");
+    return Utils.prefs.getCharPref("username");
   },
   set username(value) {
     if (value)
-      this._prefs.setCharPref("username", value);
+      Utils.prefs.setCharPref("username", value);
     else
-      this._prefs.clearUserPref("username");
+      Utils.prefs.clearUserPref("username");
 
     // fixme - need to loop over all Identity objects - needs some rethinking...
     this._mozId.username = value;
     this._cryptoId.username = value;
   },
 
   get password() { return this._mozId.password; },
   set password(value) { this._mozId.password = value; },
 
   get passphrase() { return this._cryptoId.password; },
   set passphrase(value) { this._cryptoId.password = value; },
 
-  get userPath() {
-    this._log.info("Hashing username " + this.username);
-
-    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-      createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-
-    let hasher = Cc["@mozilla.org/security/hash;1"]
-      .createInstance(Ci.nsICryptoHash);
-    hasher.init(hasher.SHA1);
-
-    let data = converter.convertToByteArray(this.username, {});
-    hasher.update(data, data.length);
-    let rawHash = hasher.finish(false);
-
-    // return the two-digit hexadecimal code for a byte
-    function toHexString(charCode) {
-      return ("0" + charCode.toString(16)).slice(-2);
-    }
-
-    let hash = [toHexString(rawHash.charCodeAt(i)) for (i in rawHash)].join("");
-    this._log.debug("Username hashes to " + hash);
-    return hash;
-  },
+  get userPath() { return this._mozId.userHash; },
 
   get currentUser() {
-    if (this._dav.loggedIn)
+    if (DAV.loggedIn)
       return this.username;
     return null;
   },
 
   get enabled() {
-    return this._prefs.getBoolPref("enabled");
+    return Utils.prefs.getBoolPref("enabled");
   },
 
   get schedule() {
     if (!this.enabled)
       return 0; // manual/off
-    return this._prefs.getIntPref("schedule");
-  },
-
-  _init: function WeaveSync__init() {
-    this._initLogs();
-    this._log.info("Weave Sync Service Initializing");
-
-    this._prefs.addObserver("", this, false);
-
-    if (!this.enabled) {
-      this._log.info("Weave Sync disabled");
-      return;
-    }
-
-    this._setSchedule(this.schedule);
+    return Utils.prefs.getIntPref("schedule");
   },
 
   _setSchedule: function Weave__setSchedule(schedule) {
     switch (this.schedule) {
     case 0:
       this._disableSchedule();
       break;
     case 1:
@@ -254,258 +228,161 @@ WeaveSyncService.prototype = {
     if (this.enabled) {
       this._log.info("Running scheduled sync");
       this.sync();
     }
   },
 
   _initLogs: function WeaveSync__initLogs() {
     this._log = Log4Moz.Service.getLogger("Service.Main");
+    this._log.level =
+      Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.main")];
 
     let formatter = Log4Moz.Service.newFormatter("basic");
     let root = Log4Moz.Service.rootLogger;
-    root.level = Log4Moz.Level[this._prefs.getCharPref("log.rootLogger")];
+    root.level = Log4Moz.Level[Utils.prefs.getCharPref("log.rootLogger")];
 
     let capp = Log4Moz.Service.newAppender("console", formatter);
-    capp.level = Log4Moz.Level[this._prefs.getCharPref("log.appender.console")];
+    capp.level = Log4Moz.Level[Utils.prefs.getCharPref("log.appender.console")];
     root.addAppender(capp);
 
     let dapp = Log4Moz.Service.newAppender("dump", formatter);
-    dapp.level = Log4Moz.Level[this._prefs.getCharPref("log.appender.dump")];
+    dapp.level = Log4Moz.Level[Utils.prefs.getCharPref("log.appender.dump")];
     root.addAppender(dapp);
 
     let brief = this._dirSvc.get("ProfD", Ci.nsIFile);
     brief.QueryInterface(Ci.nsILocalFile);
     brief.append("weave");
     brief.append("logs");
     brief.append("brief-log.txt");
     if (!brief.exists())
       brief.create(brief.NORMAL_FILE_TYPE, PERMS_FILE);
 
     let verbose = brief.parent.clone();
     verbose.append("verbose-log.txt");
     if (!verbose.exists())
       verbose.create(verbose.NORMAL_FILE_TYPE, PERMS_FILE);
 
-    let fapp = Log4Moz.Service.newFileAppender("rotating", brief, formatter);
-    fapp.level = Log4Moz.Level[this._prefs.getCharPref("log.appender.briefLog")];
-    root.addAppender(fapp);
-    let vapp = Log4Moz.Service.newFileAppender("rotating", verbose, formatter);
-    vapp.level = Log4Moz.Level[this._prefs.getCharPref("log.appender.debugLog")];
-    root.addAppender(vapp);
+    this._briefApp = Log4Moz.Service.newFileAppender("rotating", brief, formatter);
+    this._briefApp.level = Log4Moz.Level[Utils.prefs.getCharPref("log.appender.briefLog")];
+    root.addAppender(this._briefApp);
+    this._debugApp = Log4Moz.Service.newFileAppender("rotating", verbose, formatter);
+    this._debugApp.level = Log4Moz.Level[Utils.prefs.getCharPref("log.appender.debugLog")];
+    root.addAppender(this._debugApp);
   },
 
-  _lock: function weaveSync__lock() {
-    if (this._locked) {
-      this._log.warn("Service lock failed: already locked");
-      this._os.notifyObservers(null, "weave:service-lock:error", "");
-      return false;
-    }
-    this._locked = true;
-    this._log.debug("Service lock acquired");
-    this._os.notifyObservers(null, "weave:service-lock:success", "");
-    return true;
+  clearLogs: function WeaveSvc_clearLogs() {
+    this._briefApp.clear();
+    this._debugApp.clear();
   },
 
-  _unlock: function WeaveSync__unlock() {
-    this._locked = false;
-    this._log.debug("Service lock released");
-    this._os.notifyObservers(null, "weave:service-unlock:success", "");
-  },
-
-  _login: function WeaveSync__login() {
+  _uploadVersion: function WeaveSync__uploadVersion() {
     let self = yield;
 
-    try {
-      this._log.debug("Logging in");
-      this._os.notifyObservers(null, "weave:service-login:start", "");
-
-      if (!this.username)
-        throw "No username set, login failed";
-      if (!this.password)
-        throw "No password given or found in password manager";
-
-      let serverURL = this._prefs.getCharPref("serverURL");
-      this._dav.baseURL = serverURL + "user/" + this.userPath + "/";
-      this._log.info("Using server URL: " + this._dav.baseURL);
-
-      this._dav.login.async(this._dav, self.cb, this.username, this.password);
-      let success = yield;
+    DAV.MKCOL("meta", self.cb);
+    let ret = yield;
+    if (!ret)
+      throw "Could not create meta information directory";
 
-      // FIXME: we want to limit this to when we get a 404!
-      if (!success) {
-        this._log.debug("Attempting to create user directory");
-
-        this._dav.baseURL = serverURL;
-        this._dav.MKCOL("user/" + this.userPath, self.cb);
-        let ret = yield;
-        if (!ret)
-          throw "Could not create user directory.  Got status: " + ret.status;
+    DAV.PUT("meta/version", STORAGE_FORMAT_VERSION, self.cb);
+    ret = yield;
+    Utils.ensureStatus(ret.status, "Could not upload server version file");
+  },
 
-        this._log.debug("Successfully created user directory.  Re-attempting login.");
-        this._dav.baseURL = serverURL + "user/" + this.userPath + "/";
-        this._dav.login.async(this._dav, self.cb, this.username, this.password);
-        success = yield;
-        if (!success)
-          throw "Created user directory, but login still failed.  Aborting.";
-      }
+  // force a server wipe when the version is lower than ours (or there is none)
+  _versionCheck: function WeaveSync__versionCheck() {
+    let self = yield;
 
-      this._dav.GET("private/privkey", self.cb);
-      let keyResp = yield;
-      Utils.ensureStatus(keyResp.status,
-                         "Could not get private key from server", [[200,300],404]);
+    DAV.GET("meta/version", self.cb);
+    let ret = yield;
 
-      if (keyResp.status != 404) {
-        this._cryptoId.key = keyResp.responseText;
-
-      } else {
-        // FIXME: hack to wipe everyone's server data... needs to be removed at some point
-        this._serverWipe.async(this, self.cb);
-        yield;
-        
-        // generate a new key
-        this._log.debug("Generating new RSA key");
-        Crypto.RSAkeygen.async(Crypto, self.cb, this._cryptoId.password);
-        let [privkey, pubkey] = yield;
-
-        this._cryptoId.key = privkey;
-
-        this._dav.MKCOL("private/", self.cb);
-        ret = yield;
-        if (!ret)
-          throw "Could not create private key directory";
+    if (!Utils.checkStatus(ret.status)) {
+      this._log.info("Server has no version file.  Wiping server data.");
+      this._serverWipe.async(this, self.cb);
+      yield;
+      this._uploadVersion.async(this, self.cb);
+      yield;
 
-        this._dav.MKCOL("public/", self.cb);
-        ret = yield;
-        if (!ret)
-          throw "Could not create public key directory";
-
-        this._dav.PUT("private/privkey", privkey, self.cb);
-        ret = yield;
-        Utils.ensureStatus(ret.status, "Could not upload private key");
+    } else if (ret.responseText < STORAGE_FORMAT_VERSION) {
+      this._log.info("Server version too low.  Wiping server data.");
+      this._serverWipe.async(this, self.cb);
+      yield;
+      this._uploadVersion.async(this, self.cb);
+      yield;
 
-        this._dav.PUT("public/pubkey", pubkey, self.cb);
-        ret = yield;
-        Utils.ensureStatus(ret.status, "Could not upload public key");
-      }
-
-      this._passphrase = null;
-      this._os.notifyObservers(null, "weave:service-login:success", "");
-      self.done(true);
-
-    } catch (e) {
-      this._log.warn(Async.exceptionStr(self, e));
-      this._log.trace(e.trace);
-      this._os.notifyObservers(null, "weave:service-login:error", "");
-      self.done(false);
+    } else if (ret.responseText > STORAGE_FORMAT_VERSION) {
+      // FIXME: should we do something here?
     }
   },
 
-  _resetLock: function WeaveSync__resetLock() {
+  _checkUserDir: function WeaveSvc__checkUserDir() {
     let self = yield;
-    let success = false;
+
+    this._log.trace("Checking user directory exists");
 
-    try {
-      this._log.debug("Resetting server lock");
-      this._os.notifyObservers(null, "weave:server-lock-reset:start", "");
+    let serverURL = Utils.prefs.getCharPref("serverURL");
+    if (serverURL[serverURL.length-1] != '/')
+      serverURL = serverURL + '/';
 
-      this._dav.forceUnlock.async(this._dav, self.cb);
-      success = yield;
+    DAV.baseURL = serverURL;
+    DAV.MKCOL("user/" + this.userPath, self.cb);
+    let ret = yield;
+    if (!ret)
+      throw "Could not create user directory";
 
-    } catch (e) {
-      throw e;
+    DAV.baseURL = serverURL + "user/" + this.userPath + "/";
+    this._log.info("Using server URL: " + DAV.baseURL);
+  },
+
+  _keyCheck: function WeaveSvc__keyCheck() {
+    let self = yield;
 
-    } finally {
-      if (success) {
-        this._log.debug("Server lock reset successful");
-        this._os.notifyObservers(null, "weave:server-lock-reset:success", "");
-      } else {
-        this._log.debug("Server lock reset failed");
-        this._os.notifyObservers(null, "weave:server-lock-reset:error", "");
-      }
-      self.done(success);
+    DAV.GET("private/privkey", self.cb);
+    let keyResp = yield;
+    Utils.ensureStatus(keyResp.status,
+                       "Could not get private key from server", [[200,300],404]);
+
+    if (keyResp.status != 404) {
+      this._cryptoId.privkey = keyResp.responseText;
+      Crypto.RSAkeydecrypt.async(Crypto, self.cb, this._cryptoId);
+      this._cryptoId.pubkey = yield;
+
+    } else {
+      this._generateKeys.async(this, self.cb);
+      yield;
     }
   },
 
-  _sync: function WeaveSync__sync() {
-    let self = yield;
-    let success = false;
-
-    try {
-      if (!this._lock())
-	return;
-
-      this._os.notifyObservers(null, "weave:service:sync:start", "");
-
-      if (this._prefs.getBoolPref("bookmarks")) {
-        this._bmkEngine.sync(self.cb);
-        yield;
-      }
-      if (this._prefs.getBoolPref("history")) {
-        this._histEngine.sync(self.cb);
-        yield;
-      }
-      if (this._prefs.getBoolPref("cookies")) {
-        this._cookieEngine.sync(self.cb);
-        yield;
-      }
-      success = true;
-      this._unlock();
-
-    } catch (e) {
-      throw e;
-
-    } finally {
-      if (success)
-        this._os.notifyObservers(null, "weave:service:sync:success", "");
-      else
-        this._os.notifyObservers(null, "weave:service:sync:error", "");
-      self.done();
-    }
-  },
-
-  _resetServer: function WeaveSync__resetServer() {
+  _generateKeys: function WeaveSync__generateKeys() {
     let self = yield;
 
-    if (!this._lock())
-      throw "Could not acrquire lock";
-
-    this._bmkEngine.resetServer(self.cb);
-    this._histEngine.resetServer(self.cb);
+    this._log.debug("Generating new RSA key");
+    Crypto.RSAkeygen.async(Crypto, self.cb, this._cryptoId);
+    let [privkey, pubkey] = yield;
 
-    this._unlock();
-    self.done();
-  },
+    this._cryptoId.privkey = privkey;
+    this._cryptoId.pubkey = pubkey;
 
-  _resetClient: function WeaveSync__resetClient() {
-    let self = yield;
-
-    if (!this._lock())
-      throw "Could not acrquire lock";
+    DAV.MKCOL("private/", self.cb);
+    let ret = yield;
+    if (!ret)
+      throw "Could not create private key directory";
 
-    this._bmkEngine.resetClient(self.cb);
-    this._histEngine.resetClient(self.cb);
-
-    this._unlock();
-    self.done();
-  },
-
-  _serverWipe: function WeaveSync__serverWipe() {
-    let self = yield;
+    DAV.MKCOL("public/", self.cb);
+    ret = yield;
+    if (!ret)
+      throw "Could not create public key directory";
 
-    this._dav.listFiles.async(this._dav, self.cb);
-    let names = yield;
+    DAV.PUT("private/privkey", privkey, self.cb);
+    ret = yield;
+    Utils.ensureStatus(ret.status, "Could not upload private key");
 
-    for (let i = 0; i < names.length; i++) {
-      this._dav.DELETE(names[i], self.cb);
-      let resp = yield;
-      this._log.debug(resp.status);
-    }
-
-    self.done();
+    DAV.PUT("public/pubkey", pubkey, self.cb);
+    ret = yield;
+    Utils.ensureStatus(ret.status, "Could not upload public key");
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]),
 
   // nsIObserver
 
   observe: function WeaveSync__observe(subject, topic, data) {
     if (topic != "nsPref:changed")
@@ -516,40 +393,166 @@ WeaveSyncService.prototype = {
     case "schedule":
       this._setSchedule(this.schedule);
       break;
     }
   },
 
   // These are global (for all engines)
 
-  login: function WeaveSync_login(password, passphrase) {
-    if (!this._lock())
-      return;
+  login: function WeaveSync_login(onComplete, password, passphrase) {
+    this._localLock(this._notify("login", this._login,
+                                 password, passphrase)).async(this, onComplete);
+  },
+  _login: function WeaveSync__login(password, passphrase) {
+    let self = yield;
+
     // cache password & passphrase
-    // if null, _login() will try to get them from the pw manager
+    // if null, we'll try to get them from the pw manager below
     this._mozId.setTempPassword(password);
     this._cryptoId.setTempPassword(passphrase);
-    let self = this;
-    this._login.async(this, function() {self._unlock()});
+
+    this._log.debug("Logging in");
+
+    if (!this.username)
+      throw "No username set, login failed";
+    if (!this.password)
+      throw "No password given or found in password manager";
+
+    this._checkUserDir.async(this, self.cb);
+    yield;
+
+    DAV.login.async(DAV, self.cb, this.username, this.password);
+    let success = yield;
+    if (!success)
+      throw "Login failed";
+
+    this._versionCheck.async(this, self.cb);
+    yield;
+
+    this._keyCheck.async(this, self.cb);
+    yield;
+
+    self.done(true);
   },
 
   logout: function WeaveSync_logout() {
     this._log.info("Logging out");
-    this._dav.logout();
+    DAV.logout();
     this._mozId.setTempPassword(null); // clear cached password
     this._cryptoId.setTempPassword(null); // and passphrase
-    this._os.notifyObservers(null, "weave:service-logout:success", "");
+    this._os.notifyObservers(null, "weave:service:logout:success", "");
+  },
+
+  resetLock: function WeaveSvc_resetLock(onComplete) {
+    this._notify("reset-server-lock", this._resetLock).async(this, onComplete);
+  },
+  _resetLock: function WeaveSvc__resetLock() {
+    let self = yield;
+    DAV.forceUnlock.async(DAV, self.cb);
+    yield;
   },
 
-  resetLock: function WeaveSync_resetLock() {
-    if (!this._lock())
-      return;
-    let self = this;
-    this._resetLock.async(this, function() {self._unlock()});
+  serverWipe: function WeaveSvc_serverWipe(onComplete) {
+    let cb = function WeaveSvc_serverWipeCb() {
+      let self = yield;
+      this._serverWipe.async(this, self.cb);
+      yield;
+      this.logout();
+      self.done();
+    };
+    this._lock(this._notify("server-wipe", cb)).async(this, onComplete);
+  },
+  _serverWipe: function WeaveSvc__serverWipe() {
+    let self = yield;
+
+    DAV.listFiles.async(DAV, self.cb);
+    let names = yield;
+
+    for (let i = 0; i < names.length; i++) {
+      DAV.DELETE(names[i], self.cb);
+      let resp = yield;
+      this._log.debug(resp.status);
+    }
   },
 
   // These are per-engine
 
-  sync: function WeaveSync_sync() { this._sync.async(this); },
-  resetServer: function WeaveSync_resetServer() { this._resetServer.async(this); },
-  resetClient: function WeaveSync_resetClient() { this._resetClient.async(this); }
+  sync: function WeaveSync_sync(onComplete) {
+    this._lock(this._notify("sync", this._sync)).async(this, onComplete);
+  },
+  _sync: function WeaveSync__sync() {
+    let self = yield;
+
+    if (!DAV.loggedIn)
+      throw "Can't sync: Not logged in";
+
+    this._versionCheck.async(this, self.cb);
+    yield;
+
+    this._keyCheck.async(this, self.cb);
+    yield;
+
+    if (Utils.prefs.getBoolPref("bookmarks")) {
+      this._notify(this._bmkEngine.name + ":sync",
+                   this._syncEngine, this._bmkEngine).async(this, self.cb);
+      yield;
+      this._bmkEngine.syncMounts(self.cb); // FIXME
+      yield;
+    }
+    if (Utils.prefs.getBoolPref("history")) {
+      this._notify(this._histEngine.name + ":sync",
+                   this._syncEngine, this._histEngine).async(this, self.cb);
+      yield;
+    }
+    if (this._prefs.getBoolPref("cookies")) {
+	this._notify(this._cookieEngine.name + ":sync",
+                    this._syncEngine, this._cookieEngine).async(this, self.cb);
+      yield;
+    }
+  },
+  _syncEngine: function WeaveSvc__syncEngine(engine) {
+    let self = yield;
+    engine.sync(self.cb);
+    yield;
+  },
+
+  resetServer: function WeaveSync_resetServer(onComplete) {
+    this._lock(this._notify("reset-server",
+                            this._resetServer)).async(this, onComplete);
+  },
+  _resetServer: function WeaveSync__resetServer() {
+    let self = yield;
+
+    if (!DAV.loggedIn)
+      throw "Can't reset server: Not logged in";
+
+    this._bmkEngine.resetServer(self.cb);
+    yield;
+    this._histEngine.resetServer(self.cb);
+    yield;
+  },
+
+  resetClient: function WeaveSync_resetClient(onComplete) {
+    this._localLock(this._notify("reset-client",
+                                 this._resetClient)).async(this, onComplete);
+  },
+  _resetClient: function WeaveSync__resetClient() {
+    let self = yield;
+    this._bmkEngine.resetClient(self.cb);
+    yield;
+    this._histEngine.resetClient(self.cb);
+    yield;
+  },
+
+  shareBookmarks: function WeaveSync_shareBookmarks(onComplete, username) {
+    this._lock(this._notify("share-bookmarks",
+                            this._shareBookmarks,
+                            username)).async(this, onComplete);
+  },
+  _shareBookmarks: function WeaveSync__shareBookmarks(username) {
+    let self = yield;
+    this._bmkEngine.share(self.cb, username);
+    let ret = yield;
+    self.done(ret);
+  }
+
 };
--- a/services/sync/modules/stores.js
+++ b/services/sync/modules/stores.js
@@ -41,42 +41,57 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://weave/log4moz.js");
 Cu.import("resource://weave/constants.js");
 Cu.import("resource://weave/util.js");
+Cu.import("resource://weave/async.js");
+
+Function.prototype.async = Async.sugar;
 
 /*
  * Data Stores
  * These can wrap, serialize items and apply commands
  */
 
 function Store() {
   this._init();
 }
 Store.prototype = {
   _logName: "Store",
+  _yieldDuringApply: true,
 
   __json: null,
   get _json() {
     if (!this.__json)
       this.__json = Cc["@mozilla.org/dom/json;1"].
         createInstance(Ci.nsIJSON);
     return this.__json;
   },
 
   _init: function Store__init() {
     this._log = Log4Moz.Service.getLogger("Service." + this._logName);
   },
 
   applyCommands: function Store_applyCommands(commandList) {
+    let self = yield, timer, listener;
+
+    if (this._yieldDuringApply) {
+      listener = new Utils.EventListener(self.cb);
+      timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    }
+
     for (var i = 0; i < commandList.length; i++) {
+      if (this._yieldDuringApply) {
+        timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+        yield; // Yield to main loop
+      }
       var command = commandList[i];
       this._log.debug("Processing command: " + this._json.encode(command));
       switch (command["action"]) {
       case "create":
         this._createCommand(command);
         break;
       case "remove":
         this._removeCommand(command);
@@ -84,16 +99,17 @@ Store.prototype = {
       case "edit":
         this._editCommand(command);
         break;
       default:
         this._log.error("unknown action in command: " + command["action"]);
         break;
       }
     }
+    self.done();
   },
 
   // override these in derived objects
   wrap: function Store_wrap() {},
   wipe: function Store_wipe() {},
   resetGUIDs: function Store_resetGUIDs() {}
 };
 
@@ -303,191 +319,157 @@ BookmarksStore.prototype = {
   __ans: null,
   get _ans() {
     if (!this.__ans)
       this.__ans = Cc["@mozilla.org/browser/annotation-service;1"].
                    getService(Ci.nsIAnnotationService);
     return this.__ans;
   },
 
-  _getFolderNodes: function BSS__getFolderNodes(folder) {
-    let query = this._hsvc.getNewQuery();
-    query.setFolders([folder], 1);
-    return this._hsvc.executeQuery(query, this._hsvc.getNewQueryOptions()).root;
-  },
-
-  _wrapNode: function BSS__wrapNode(node) {
-    var items = {};
-    this._wrapNodeInternal(node, items, null, null);
-    return items;
-  },
-
-  _wrapNodeInternal: function BSS__wrapNodeInternal(node, items, parentGUID, index) {
-    let GUID = this._bms.getItemGUID(node.itemId);
-    let item = {parentGUID: parentGUID,
-                index: index};
-
-    if (node.type == node.RESULT_TYPE_FOLDER) {
-      if (this._ls.isLivemark(node.itemId)) {
-        item.type = "livemark";
-        let siteURI = this._ls.getSiteURI(node.itemId);
-        let feedURI = this._ls.getFeedURI(node.itemId);
-        item.siteURI = siteURI? siteURI.spec : "";
-        item.feedURI = feedURI? feedURI.spec : "";
-      } else {
-        item.type = "folder";
-        node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
-        node.containerOpen = true;
-        for (var i = 0; i < node.childCount; i++) {
-          this._wrapNodeInternal(node.getChild(i), items, GUID, i);
-        }
-      }
-      item.title = node.title;
-    } else if (node.type == node.RESULT_TYPE_URI ||
-               node.type == node.RESULT_TYPE_QUERY) {
-      if (this._ms.hasMicrosummary(node.itemId)) {
-        item.type = "microsummary";
-        let micsum = this._ms.getMicrosummary(node.itemId);
-        item.generatorURI = micsum.generator.uri.spec; // breaks local generators
-      } else if (node.type == node.RESULT_TYPE_QUERY) {
-        item.type = "query";
-        item.title = node.title;
-      } else {
-        item.type = "bookmark";
-        item.title = node.title;
-      }
-      item.URI = node.uri;
-      item.tags = this._ts.getTagsForURI(Utils.makeURI(node.uri), {});
-      item.keyword = this._bms.getKeywordForBookmark(node.itemId);
-    } else if (node.type == node.RESULT_TYPE_SEPARATOR) {
-      item.type = "separator";
-    } else {
-      this._log.warn("Warning: unknown item type, cannot serialize: " + node.type);
-      return;
+  _getItemIdForGUID: function BStore__getItemIdForGUID(GUID) {
+    switch (GUID) {
+    case "menu":
+      return this._bms.bookmarksMenuFolder;
+    case "toolbar":
+      return this._bms.toolbarFolder;
+    case "unfiled":
+      return this._bms.unfiledBookmarksFolder;
+    default:
+      return this._bms.getItemIdForGUID(GUID);
     }
-
-    items[GUID] = item;
-  },
-
-  _getWrappedBookmarks: function BSS__getWrappedBookmarks(folder) {
-    return this._wrapNode(this._getFolderNodes(folder));
-  },
-
-  _resetGUIDsInt: function BSS__resetGUIDsInt(node) {
-    if (this._ans.itemHasAnnotation(node.itemId, "placesInternal/GUID"))
-      this._ans.removeItemAnnotation(node.itemId, "placesInternal/GUID");
-
-    if (node.type == node.RESULT_TYPE_FOLDER &&
-        !this._ls.isLivemark(node.itemId)) {
-      node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
-      node.containerOpen = true;
-      for (var i = 0; i < node.childCount; i++) {
-        this._resetGUIDsInt(node.getChild(i));
-      }
-    }
+    return null;
   },
 
   _createCommand: function BStore__createCommand(command) {
     let newId;
-    let parentId = this._bms.getItemIdForGUID(command.data.parentGUID);
+    let parentId = this._getItemIdForGUID(command.data.parentGUID);
 
     if (parentId < 0) {
       this._log.warn("Creating node with unknown parent -> reparenting to root");
       parentId = this._bms.bookmarksMenuFolder;
     }
 
     switch (command.data.type) {
     case "query":
     case "bookmark":
     case "microsummary": {
-      this._log.info(" -> creating bookmark \"" + command.data.title + "\"");
+      this._log.debug(" -> creating bookmark \"" + command.data.title + "\"");
       let URI = Utils.makeURI(command.data.URI);
       newId = this._bms.insertBookmark(parentId,
                                        URI,
                                        command.data.index,
                                        command.data.title);
       this._ts.untagURI(URI, null);
       this._ts.tagURI(URI, command.data.tags);
       this._bms.setKeywordForBookmark(newId, command.data.keyword);
 
       if (command.data.type == "microsummary") {
-        this._log.info("   \-> is a microsummary");
+        this._log.debug("   \-> is a microsummary");
         let genURI = Utils.makeURI(command.data.generatorURI);
         try {
           let micsum = this._ms.createMicrosummary(URI, genURI);
           this._ms.setMicrosummary(newId, micsum);
         }
         catch(ex) { /* ignore "missing local generator" exceptions */ }
       }
     } break;
     case "folder":
-      this._log.info(" -> creating folder \"" + command.data.title + "\"");
+      this._log.debug(" -> creating folder \"" + command.data.title + "\"");
       newId = this._bms.createFolder(parentId,
                                      command.data.title,
                                      command.data.index);
       break;
     case "livemark":
-      this._log.info(" -> creating livemark \"" + command.data.title + "\"");
+      this._log.debug(" -> creating livemark \"" + command.data.title + "\"");
       newId = this._ls.createLivemark(parentId,
                                       command.data.title,
                                       Utils.makeURI(command.data.siteURI),
                                       Utils.makeURI(command.data.feedURI),
                                       command.data.index);
       break;
+    case "mounted-share":
+      this._log.debug(" -> creating share mountpoint \"" + command.data.title + "\"");
+      newId = this._bms.createFolder(parentId,
+                                     command.data.title,
+                                     command.data.index);
+
+      this._ans.setItemAnnotation(newId, "weave/mounted-share-id",
+                                  command.data.mountId, 0, this._ans.EXPIRE_NEVER);
+      break;
     case "separator":
-      this._log.info(" -> creating separator");
+      this._log.debug(" -> creating separator");
       newId = this._bms.insertSeparator(parentId, command.data.index);
       break;
     default:
       this._log.error("_createCommand: Unknown item type: " + command.data.type);
       break;
     }
     if (newId)
       this._bms.setItemGUID(newId, command.GUID);
   },
 
   _removeCommand: function BStore__removeCommand(command) {
+    if (command.GUID == "menu" ||
+        command.GUID == "toolbar" ||
+        command.GUID == "unfiled") {
+      this._log.warn("Attempted to remove root node (" + command.GUID +
+                     ").  Skipping command.");
+      return;
+    }
+
     var itemId = this._bms.getItemIdForGUID(command.GUID);
     if (itemId < 0) {
       this._log.warn("Attempted to remove item " + command.GUID +
                      ", but it does not exist.  Skipping.");
       return;
     }
     var type = this._bms.getItemType(itemId);
 
     switch (type) {
     case this._bms.TYPE_BOOKMARK:
-      this._log.info("  -> removing bookmark " + command.GUID);
+      this._log.debug("  -> removing bookmark " + command.GUID);
       this._bms.removeItem(itemId);
       break;
     case this._bms.TYPE_FOLDER:
-      this._log.info("  -> removing folder " + command.GUID);
+      this._log.debug("  -> removing folder " + command.GUID);
       this._bms.removeFolder(itemId);
       break;
     case this._bms.TYPE_SEPARATOR:
-      this._log.info("  -> removing separator " + command.GUID);
+      this._log.debug("  -> removing separator " + command.GUID);
       this._bms.removeItem(itemId);
       break;
     default:
       this._log.error("removeCommand: Unknown item type: " + type);
       break;
     }
   },
 
   _editCommand: function BStore__editCommand(command) {
+    if (command.GUID == "menu" ||
+        command.GUID == "toolbar" ||
+        command.GUID == "unfiled") {
+      this._log.warn("Attempted to edit root node (" + command.GUID +
+                     ").  Skipping command.");
+      return;
+    }
+
     var itemId = this._bms.getItemIdForGUID(command.GUID);
     if (itemId < 0) {
       this._log.warn("Item for GUID " + command.GUID + " not found.  Skipping.");
       return;
     }
 
     for (let key in command.data) {
       switch (key) {
+      case "type":
+        // all commands have this to help in reconciliation, but it makes
+        // no sense to edit it
+        break;
       case "GUID":
-        var existing = this._bms.getItemIdForGUID(command.data.GUID);
+        var existing = this._getItemIdForGUID(command.data.GUID);
         if (existing < 0)
           this._bms.setItemGUID(itemId, command.data.GUID);
         else
           this._log.warn("Can't change GUID " + command.GUID +
                          " to " + command.data.GUID + ": GUID already exists.");
         break;
       case "title":
         this._bms.setItemTitle(itemId, command.data.title);
@@ -499,21 +481,21 @@ BookmarksStore.prototype = {
         this._bms.moveItem(itemId, this._bms.getFolderIdForItem(itemId),
                            command.data.index);
         break;
       case "parentGUID": {
         let index = -1;
         if (command.data.index && command.data.index >= 0)
           index = command.data.index;
         this._bms.moveItem(
-          itemId, this._bms.getItemIdForGUID(command.data.parentGUID), index);
+          itemId, this._getItemIdForGUID(command.data.parentGUID), index);
       } break;
       case "tags": {
         let tagsURI = this._bms.getBookmarkURI(itemId);
-        this._ts.untagURI(URI, null);
+        this._ts.untagURI(tagsURI, null);
         this._ts.tagURI(tagsURI, command.data.tags);
       } break;
       case "keyword":
         this._bms.setKeywordForBookmark(itemId, command.data.keyword);
         break;
       case "generatorURI": {
         let micsumURI = Utils.makeURI(this._bms.getBookmarkURI(itemId));
         let genURI = Utils.makeURI(command.data.generatorURI);
@@ -528,126 +510,237 @@ BookmarksStore.prototype = {
         break;
       default:
         this._log.warn("Can't change item property: " + key);
         break;
       }
     }
   },
 
-  wrap: function BStore_wrap() {
-    let filed = this._getWrappedBookmarks(this._bms.bookmarksMenuFolder);
-    let toolbar = this._getWrappedBookmarks(this._bms.toolbarFolder);
-    let unfiled = this._getWrappedBookmarks(this._bms.unfiledBookmarksFolder);
+  _getNode: function BSS__getNode(folder) {
+    let query = this._hsvc.getNewQuery();
+    query.setFolders([folder], 1);
+    return this._hsvc.executeQuery(query, this._hsvc.getNewQueryOptions()).root;
+  },
 
-    for (let guid in unfiled) {
-      if (!(guid in filed))
-        filed[guid] = unfiled[guid];
+  __wrap: function BSS___wrap(node, items, parentGUID, index, guidOverride) {
+    let GUID, item;
+
+    // we override the guid for the root items, "menu", "toolbar", etc.
+    if (guidOverride) {
+      GUID = guidOverride;
+      item = {};
+    } else {
+      GUID = this._bms.getItemGUID(node.itemId);
+      item = {parentGUID: parentGUID, index: index};
     }
 
-    for (let guid in toolbar) {
-      if (!(guid in filed))
-        filed[guid] = toolbar[guid];
+    if (node.type == node.RESULT_TYPE_FOLDER) {
+      if (this._ls.isLivemark(node.itemId)) {
+        item.type = "livemark";
+        let siteURI = this._ls.getSiteURI(node.itemId);
+        let feedURI = this._ls.getFeedURI(node.itemId);
+        item.siteURI = siteURI? siteURI.spec : "";
+        item.feedURI = feedURI? feedURI.spec : "";
+
+      } else if (this._ans.itemHasAnnotation(node.itemId,
+                                             "weave/mounted-share-id")) {
+        item.type = "mounted-share";
+        item.title = node.title;
+        item.mountId = this._ans.getItemAnnotation(node.itemId,
+                                                   "weave/mounted-share-id");
+
+      } else {
+        item.type = "folder";
+        node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+        node.containerOpen = true;
+        for (var i = 0; i < node.childCount; i++) {
+          this.__wrap(node.getChild(i), items, GUID, i);
+        }
+      }
+      if (!guidOverride)
+        item.title = node.title; // no titles for root nodes
+
+    } else if (node.type == node.RESULT_TYPE_URI ||
+               node.type == node.RESULT_TYPE_QUERY) {
+      if (this._ms.hasMicrosummary(node.itemId)) {
+        item.type = "microsummary";
+        let micsum = this._ms.getMicrosummary(node.itemId);
+        item.generatorURI = micsum.generator.uri.spec; // breaks local generators
+      } else if (node.type == node.RESULT_TYPE_QUERY) {
+        item.type = "query";
+        item.title = node.title;
+      } else {
+        item.type = "bookmark";
+        item.title = node.title;
+      }
+      item.URI = node.uri;
+      item.tags = this._ts.getTagsForURI(Utils.makeURI(node.uri), {});
+      item.keyword = this._bms.getKeywordForBookmark(node.itemId);
+
+    } else if (node.type == node.RESULT_TYPE_SEPARATOR) {
+      item.type = "separator";
+
+    } else {
+      this._log.warn("Warning: unknown item type, cannot serialize: " + node.type);
+      return;
     }
 
-    return filed; // (combined)
+    items[GUID] = item;
+  },
+
+  // helper
+  _wrap: function BStore__wrap(node, items, rootName) {
+    return this.__wrap(node, items, null, null, rootName);
+  },
+
+  _wrapMount: function BStore__wrapMount(node, id) {
+    if (node.type != node.RESULT_TYPE_FOLDER)
+      throw "Trying to wrap a non-folder mounted share";
+
+    let GUID = this._bms.getItemGUID(node.itemId);
+    let ret = {rootGUID: GUID, userid: id, snapshot: {}};
+
+    node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+    node.containerOpen = true;
+    for (var i = 0; i < node.childCount; i++) {
+      this.__wrap(node.getChild(i), ret.snapshot, GUID, i);
+    }
+
+    // remove any share mountpoints
+    for (let guid in ret.snapshot) {
+      if (ret.snapshot[guid].type == "mounted-share")
+        delete ret.snapshot[guid];
+    }
+
+    return ret;
+  },
+
+  _resetGUIDs: function BSS__resetGUIDs(node) {
+    if (this._ans.itemHasAnnotation(node.itemId, "placesInternal/GUID"))
+      this._ans.removeItemAnnotation(node.itemId, "placesInternal/GUID");
+
+    if (node.type == node.RESULT_TYPE_FOLDER &&
+        !this._ls.isLivemark(node.itemId)) {
+      node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+      node.containerOpen = true;
+      for (var i = 0; i < node.childCount; i++) {
+        this._resetGUIDs(node.getChild(i));
+      }
+    }
+  },
+
+  findMounts: function BStore_findMounts() {
+    let ret = [];
+    let a = this._ans.getItemsWithAnnotation("weave/mounted-share-id", {});
+    for (let i = 0; i < a.length; i++) {
+      let id = this._ans.getItemAnnotation(a[i], "weave/mounted-share-id");
+      ret.push(this._wrapMount(this._getNode(a[i]), id));
+    }
+    return ret;
+  },
+
+  wrap: function BStore_wrap() {
+    var items = {};
+    this._wrap(this._getNode(this._bms.bookmarksMenuFolder), items, "menu");
+    this._wrap(this._getNode(this._bms.toolbarFolder), items, "toolbar");
+    this._wrap(this._getNode(this._bms.unfiledBookmarksFolder), items, "unfiled");
+    return items;
   },
 
   wipe: function BStore_wipe() {
     this._bms.removeFolderChildren(this._bms.bookmarksMenuFolder);
     this._bms.removeFolderChildren(this._bms.toolbarFolder);
     this._bms.removeFolderChildren(this._bms.unfiledBookmarksFolder);
   },
 
   resetGUIDs: function BStore_resetGUIDs() {
-    this._resetGUIDsInt(this._getFolderNodes(this._bms.bookmarksMenuFolder));
-    this._resetGUIDsInt(this._getFolderNodes(this._bms.toolbarFolder));
-    this._resetGUIDsInt(this._getFolderNodes(this._bms.unfiledBookmarksFolder));
+    this._resetGUIDs(this._getNode(this._bms.bookmarksMenuFolder));
+    this._resetGUIDs(this._getNode(this._bms.toolbarFolder));
+    this._resetGUIDs(this._getNode(this._bms.unfiledBookmarksFolder));
   }
 };
 BookmarksStore.prototype.__proto__ = new Store();
 
 function HistoryStore() {
   this._init();
 }
 HistoryStore.prototype = {
   _logName: "HistStore",
 
   __hsvc: null,
   get _hsvc() {
-    if (!this.__hsvc)
+    if (!this.__hsvc) {
       this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
                     getService(Ci.nsINavHistoryService);
+      this.__hsvc.QueryInterface(Ci.nsIGlobalHistory2);
+      this.__hsvc.QueryInterface(Ci.nsIBrowserHistory);
+    }
     return this.__hsvc;
   },
 
-  __browserHist: null,
-  get _browserHist() {
-    if (!this.__browserHist)
-      this.__browserHist = Cc["@mozilla.org/browser/nav-history-service;1"].
-                           getService(Ci.nsIBrowserHistory);
-    return this.__browserHist;
-  },
-
   _createCommand: function HistStore__createCommand(command) {
-    this._log.info("  -> creating history entry: " + command.GUID);
+    this._log.debug("  -> creating history entry: " + command.GUID);
     try {
-      this._browserHist.addPageWithDetails(Utils.makeURI(command.GUID),
-					   command.data.title,
-					   command.data.time);
-      this._hsvc.setPageDetails(Utils.makeURI(command.GUID), command.data.title,
-				command.data.accessCount, false, false);
+      let uri = Utils.makeURI(command.data.URI);
+      this._hsvc.addVisit(uri, command.data.time, null,
+                          this._hsvc.TRANSITION_TYPED, false, null);
+      this._hsvc.setPageTitle(uri, command.data.title);
     } catch (e) {
       this._log.error("Exception caught: " + (e.message? e.message : e));
     }
   },
 
   _removeCommand: function HistStore__removeCommand(command) {
-    this._log.info("  -> NOT removing history entry: " + command.GUID);
-    //this._browserHist.removePage(command.GUID);
+    this._log.trace("  -> NOT removing history entry: " + command.GUID);
+    // we can't remove because we only sync the last 1000 items, not
+    // the whole store.  So we don't know if remove commands were
+    // generated due to the user removing an entry or because it
+    // dropped past the 1000 item mark.
   },
 
   _editCommand: function HistStore__editCommand(command) {
-    this._log.info("  -> FIXME: NOT editing history entry: " + command.GUID);
+    this._log.trace("  -> FIXME: NOT editing history entry: " + command.GUID);
     // FIXME: implement!
   },
 
   _historyRoot: function HistStore__historyRoot() {
     let query = this._hsvc.getNewQuery(),
         options = this._hsvc.getNewQueryOptions();
 
     query.minVisits = 1;
-    options.maxResults = 500;
-    options.sortingMode = query.SORT_BY_LASTMODIFIED_DESCENDING;
+    options.maxResults = 1000;
+    options.resultType = options.RESULTS_AS_VISIT; // FULL_VISIT does not work
+    options.sortingMode = options.SORT_BY_DATE_DESCENDING;
     options.queryType = options.QUERY_TYPE_HISTORY;
 
     let root = this._hsvc.executeQuery(query, options).root;
     root.QueryInterface(Ci.nsINavHistoryQueryResultNode);
     return root;
   },
 
   wrap: function HistStore_wrap() {
     let root = this._historyRoot();
     root.containerOpen = true;
     let items = {};
     for (let i = 0; i < root.childCount; i++) {
       let item = root.getChild(i);
-      items[item.uri] = {parentGUID: '',
+      let guid = item.time + ":" + item.uri
+      items[guid] = {parentGUID: '',
 			 title: item.title,
 			 URI: item.uri,
-			 time: item.time,
-			 accessCount: item.accessCount,
-			 dateAdded: item.dateAdded,
+			 time: item.time
 			};
+      // FIXME: sync transition type - requires FULL_VISITs
     }
     return items;
   },
 
   wipe: function HistStore_wipe() {
-    this._browserHist.removeAllPages();
+    this._hsvc.removeAllPages();
   }
 };
 HistoryStore.prototype.__proto__ = new Store();
 
 
 function CookieStore() {
   this._init();
 }
--- a/services/sync/modules/syncCores.js
+++ b/services/sync/modules/syncCores.js
@@ -174,17 +174,17 @@ SyncCore.prototype = {
 
   // When we change the GUID of a local item (because we detect it as
   // being the same item as a remote one), we need to fix any other
   // local items that have it as their parent
   _fixParents: function SC__fixParents(list, oldGUID, newGUID) {
     for (let i = 0; i < list.length; i++) {
       if (!list[i])
         continue;
-      if (list[i].data.parentGUID == oldGUID)
+      if (list[i].data && list[i].data.parentGUID == oldGUID)
         list[i].data.parentGUID = newGUID;
       for (let j = 0; j < list[i].parents.length; j++) {
         if (list[i].parents[j] == oldGUID)
           list[i].parents[j] = newGUID;
       }
     }
   },
 
@@ -222,93 +222,86 @@ SyncCore.prototype = {
     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     let propagations = [[], []];
     let conflicts = [[], []];
     let ret = {propagations: propagations, conflicts: conflicts};
     this._log.debug("Reconciling " + listA.length +
 		    " against " + listB.length + "commands");
 
-    try {
-      let guidChanges = [];
-      for (let i = 0; i < listA.length; i++) {
-	let a = listA[i];
+    let guidChanges = [];
+    for (let i = 0; i < listA.length; i++) {
+      let a = listA[i];
+      timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+      yield; // Yield to main loop
+
+      //this._log.debug("comparing " + i + ", listB length: " + listB.length);
+
+      let skip = false;
+      listB = listB.filter(function(b) {
+        // fast path for when we already found a matching command
+        if (skip)
+          return true;
+
+        if (Utils.deepEquals(a, b)) {
+          delete listA[i]; // a
+          skip = true;
+          return false; // b
+
+        } else if (this._commandLike(a, b)) {
+          this._fixParents(listA, a.GUID, b.GUID);
+          guidChanges.push({action: "edit",
+      		      GUID: a.GUID,
+      		      data: {GUID: b.GUID}});
+          delete listA[i]; // a
+          skip = true;
+          return false; // b, but we add it back from guidChanges
+        }
+
+        // watch out for create commands with GUIDs that already exist
+        if (b.action == "create" && this._itemExists(b.GUID)) {
+          this._log.error("Remote command has GUID that already exists " +
+                          "locally. Dropping command.");
+          return false; // delete b
+        }
+        return true; // keep b
+      }, this);
+    }
+
+    listA = listA.filter(function(elt) { return elt });
+    listB = guidChanges.concat(listB);
+
+    for (let i = 0; i < listA.length; i++) {
+      for (let j = 0; j < listB.length; j++) {
+
         timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
         yield; // Yield to main loop
 
-	//this._log.debug("comparing " + i + ", listB length: " + listB.length);
-
-	let skip = false;
-	listB = listB.filter(function(b) {
-	  // fast path for when we already found a matching command
-	  if (skip)
-	    return true;
-
-          if (Utils.deepEquals(a, b)) {
-            delete listA[i]; // a
-	    skip = true;
-	    return false; // b
-
-          } else if (this._commandLike(a, b)) {
-            this._fixParents(listA, a.GUID, b.GUID);
-	    guidChanges.push({action: "edit",
-			      GUID: a.GUID,
-			      data: {GUID: b.GUID}});
-            delete listA[i]; // a
-	    skip = true;
-	    return false; // b, but we add it back from guidChanges
-          }
-  
-          // watch out for create commands with GUIDs that already exist
-          if (b.action == "create" && this._itemExists(b.GUID)) {
-            this._log.error("Remote command has GUID that already exists " +
-                            "locally. Dropping command.");
-	    return false; // delete b
-          }
-	  return true; // keep b
-        }, this);
-      }
-  
-      listA = listA.filter(function(elt) { return elt });
-      listB = guidChanges.concat(listB);
-  
-      for (let i = 0; i < listA.length; i++) {
-        for (let j = 0; j < listB.length; j++) {
-
-          timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
-          yield; // Yield to main loop
-  
-          if (this._conflicts(listA[i], listB[j]) ||
-              this._conflicts(listB[j], listA[i])) {
-            if (!conflicts[0].some(
-              function(elt) { return elt.GUID == listA[i].GUID }))
-              conflicts[0].push(listA[i]);
-            if (!conflicts[1].some(
-              function(elt) { return elt.GUID == listB[j].GUID }))
-              conflicts[1].push(listB[j]);
-          }
+        if (this._conflicts(listA[i], listB[j]) ||
+            this._conflicts(listB[j], listA[i])) {
+          if (!conflicts[0].some(
+            function(elt) { return elt.GUID == listA[i].GUID }))
+            conflicts[0].push(listA[i]);
+          if (!conflicts[1].some(
+            function(elt) { return elt.GUID == listB[j].GUID }))
+            conflicts[1].push(listB[j]);
         }
       }
-  
-      this._getPropagations(listA, conflicts[0], propagations[1]);
-  
-      timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
-      yield; // Yield to main loop
-  
-      this._getPropagations(listB, conflicts[1], propagations[0]);
-      ret = {propagations: propagations, conflicts: conflicts};
+    }
+
+    this._getPropagations(listA, conflicts[0], propagations[1]);
 
-    } catch (e) {
-      this._log.error("Exception caught: " + (e.message? e.message : e) +
-                      " - " + (e.location? e.location : "_reconcile"));
+    timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+    yield; // Yield to main loop
 
-    } finally {
-      timer = null;
-      self.done(ret);
-    }
+    this._getPropagations(listB, conflicts[1], propagations[0]);
+    ret = {propagations: propagations, conflicts: conflicts};
+
+    timer = null;
+    self.done(ret);
   },
 
   // Public methods
 
   detectUpdates: function SC_detectUpdates(onComplete, a, b) {
     return this._detectUpdates.async(this, onComplete, a, b);
   },
 
@@ -330,65 +323,86 @@ BookmarksSyncCore.prototype = {
                    getService(Ci.nsINavBookmarksService);
     return this.__bms;
   },
 
   _itemExists: function BSC__itemExists(GUID) {
     return this._bms.getItemIdForGUID(GUID) >= 0;
   },
 
-  _commandLike: function BSC_commandLike(a, b) {
+  _getEdits: function BSC__getEdits(a, b) {
+    // NOTE: we do not increment ret.numProps, as that would cause
+    // edit commands to always get generated
+    let ret = SyncCore.prototype._getEdits.call(this, a, b);
+    ret.props.type = a.type;
+    return ret;
+  },
+
+  // compares properties
+  // returns true if the property is not set in either object
+  // returns true if the property is set and equal in both objects
+  // returns false otherwise
+  _comp: function BSC__comp(a, b, prop) {
+    return (!a.data[prop] && !b.data[prop]) ||
+      (a.data[prop] && b.data[prop] && (a.data[prop] == b.data[prop]));
+  },
+
+  _commandLike: function BSC__commandLike(a, b) {
     // Check that neither command is null, that their actions, types,
     // and parents are the same, and that they don't have the same
     // GUID.
-    // Items with the same GUID do not qualify for 'likeness' because
-    // we already consider them to be the same object, and therefore
-    // we need to process any edits.
-    // The parent GUID check works because reconcile() fixes up the
-    // parent GUIDs as it runs, and the command list is sorted by
-    // depth
+    // * Items with the same GUID do not qualify for 'likeness' because
+    //   we already consider them to be the same object, and therefore
+    //   we need to process any edits.
+    // * Remove or edit commands don't qualify for likeness either,
+    //   since remove or edit commands with different GUIDs are
+    //   guaranteed to refer to two different items
+    // * The parent GUID check works because reconcile() fixes up the
+    //   parent GUIDs as it runs, and the command list is sorted by
+    //   depth
     if (!a || !b ||
-       a.action != b.action ||
-       a.data.type != b.data.type ||
-       a.data.parentGUID != b.data.parentGUID ||
-       a.GUID == b.GUID)
+        a.action != b.action ||
+        a.action != "create" ||
+        a.data.type != b.data.type ||
+        a.data.parentGUID != b.data.parentGUID ||
+        a.GUID == b.GUID)
       return false;
 
     // Bookmarks are allowed to be in a different index as long as
     // they are in the same folder.  Folders and separators must be at
     // the same index to qualify for 'likeness'.
     switch (a.data.type) {
     case "bookmark":
-      if (a.data.URI == b.data.URI &&
-          a.data.title == b.data.title)
+      if (this._comp(a, b, 'URI') &&
+          this._comp(a, b, 'title'))
         return true;
       return false;
     case "query":
-      if (a.data.URI == b.data.URI &&
-          a.data.title == b.data.title)
+      if (this._comp(a, b, 'URI') &&
+          this._comp(a, b, 'title'))
         return true;
       return false;
     case "microsummary":
-      if (a.data.URI == b.data.URI &&
-          a.data.generatorURI == b.data.generatorURI)
+      if (this._comp(a, b, 'URI') &&
+          this._comp(a, b, 'generatorURI'))
         return true;
       return false;
     case "folder":
-      if (a.index == b.index &&
-          a.data.title == b.data.title)
+      if (this._comp(a, b, 'index') &&
+          this._comp(a, b, 'title'))
         return true;
       return false;
     case "livemark":
-      if (a.data.title == b.data.title &&
-          a.data.siteURI == b.data.siteURI &&
-          a.data.feedURI == b.data.feedURI)
+      if (this._comp(a, b, 'title') &&
+          this._comp(a, b, 'siteURI') &&
+          this._comp(a, b, 'feedURI'))
         return true;
       return false;
     case "separator":
-      if (a.index == b.index)
+      if (this._comp(a, b, 'index'))
         return true;
       return false;
     default:
       let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
       this._log.error("commandLike: Unknown item type: " + json.encode(a));
       return false;
     }
   }
@@ -412,18 +426,16 @@ HistorySyncCore.prototype = {
     // the GUID, so the same sites will map to the same item (same
     // GUID), without our intervention.
     return false;
   }
 };
 HistorySyncCore.prototype.__proto__ = new SyncCore();
 
 
-
-
 function CookiesSyncCore() {
   this._init();
 }
 CookiesSyncCore.prototype = {
   _logName: "CookieSync",
 
   __cookieManager: null,
   get _cookieManager() {
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -46,16 +46,27 @@ Cu.import("resource://weave/constants.js
 Cu.import("resource://weave/log4moz.js");
 
 /*
  * Utility functions
  */
 
 let Utils = {
 
+  // lazy load objects from a constructor on first access.  It will
+  // work with the global object ('this' in the global context).
+  lazy: function Weave_lazy(dest, prop, ctr) {
+    let getter = function() {
+      delete dest[prop];
+      dest[prop] = new ctr();
+      return dest[prop];
+    };
+    dest.__defineGetter__(prop, getter);
+  },
+  
   deepEquals: function Weave_deepEquals(a, b) {
     if (!a && !b)
       return true;
     if (!a || !b)
       return false;
 
     // if neither is an object, just use ==
     if (typeof(a) != "object" && typeof(b) != "object")
@@ -138,26 +149,51 @@ let Utils = {
       rng = ranges[i];
       if (typeof(rng) == "object" && code >= rng[0] && code < rng[1])
         return true;
       else if (typeof(rng) == "number" && code == rng) {
         return true;
       }
     }
 
-    let log = Log4Moz.Service.getLogger("Service.Util");
-    log.error(msg + " Error code: " + code);
+    if (msg) {
+      let log = Log4Moz.Service.getLogger("Service.Util");
+      log.error(msg + " Error code: " + code);
+    }
+
     return false;
   },
 
   ensureStatus: function Weave_ensureStatus(args) {
     if (!Utils.checkStatus.apply(Utils, arguments))
       throw 'checkStatus failed';
   },
 
+  sha1: function Weave_sha1(string) {
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+      createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+
+    let hasher = Cc["@mozilla.org/security/hash;1"]
+      .createInstance(Ci.nsICryptoHash);
+    hasher.init(hasher.SHA1);
+
+    let data = converter.convertToByteArray(string, {});
+    hasher.update(data, data.length);
+    let rawHash = hasher.finish(false);
+
+    // return the two-digit hexadecimal code for a byte
+    function toHexString(charCode) {
+      return ("0" + charCode.toString(16)).slice(-2);
+    }
+
+    let hash = [toHexString(rawHash.charCodeAt(i)) for (i in rawHash)].join("");
+    return hash;
+  },
+
   makeURI: function Weave_makeURI(URIString) {
     if (URIString === null || URIString == "")
       return null;
     let ioservice = Cc["@mozilla.org/network/io-service;1"].
       getService(Ci.nsIIOService);
     return ioservice.newURI(URIString, null, null);
   },
 
@@ -273,25 +309,38 @@ let Utils = {
     }
     return ret;
   },
 
   bind2: function Async_bind2(object, method) {
     return function innerBind() { return method.apply(object, arguments); }
   },
 
+  _prefs: null,
+  get prefs() {
+    if (!this.__prefs) {
+      this.__prefs = Cc["@mozilla.org/preferences-service;1"]
+        .getService(Ci.nsIPrefService);
+      this.__prefs = this.__prefs.getBranch(PREFS_BRANCH);
+      this.__prefs.QueryInterface(Ci.nsIPrefBranch2);
+    }
+    return this.__prefs;
+  },
+
   /*
    * Event listener object
    * Used to handle XMLHttpRequest and nsITimer callbacks
    */
 
   EventListener: function Weave_EventListener(handler, eventName) {
     this._handler = handler;
     this._eventName = eventName;
-    this._log = Log4Moz.Service.getLogger("Service.EventHandler");
+    this._log = Log4Moz.Service.getLogger("Async.EventHandler");
+    this._log.level =
+      Log4Moz.Level[Utils.prefs.getCharPref("log.logger.async")];
   }
 };
 
 Utils.EventListener.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsISupports]),
 
   // DOM event listener
   handleEvent: function EL_handleEvent(event) {
deleted file mode 100644
--- a/services/sync/modules/weave.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/* ***** 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 Bookmarks Sync.
- *
- * The Initial Developer of the Original Code is Mozilla.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *  Dan Mills <thunder@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 ***** */
-
-const EXPORTED_SYMBOLS = ['Weave'];
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://weave/service.js");
-
-let Weave = {};
-//Weave.Crypto = new WeaveCrypto();
-Weave.Service = new WeaveSyncService();
new file mode 100644
--- /dev/null
+++ b/services/sync/modules/wrap.js
@@ -0,0 +1,166 @@
+/* ***** 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 Bookmarks Sync.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Dan Mills <thunder@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 ***** */
+
+const EXPORTED_SYMBOLS = ['Wrap'];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://weave/log4moz.js");
+Cu.import("resource://weave/dav.js");
+Cu.import("resource://weave/async.js");
+
+Function.prototype.async = Async.sugar;
+
+/*
+ * Wrapper utility functions
+ *
+ * Not in util.js because that would cause a circular dependency
+ * between util.js and async.js (we include async.js so that our
+ * returned generator functions have the .async sugar defined)
+ */
+
+let Wrap = {
+
+  // NOTE: requires _osPrefix string property in your object
+  // NOTE2: copy this function over to your objects, use like this:
+  //
+  // MyObj.prototype = {
+  //   _notify: Wrap.notify,
+  //   ...
+  //   method: function MyMethod() {
+  //     let self = yield;
+  //     ...
+  //     // doFoo is assumed to be an asynchronous method
+  //     this._notify("foo", this._doFoo, arg1, arg2).async(this, self.cb);
+  //     let ret = yield;
+  //     ...
+  //   }
+  // };
+  notify: function Weave_notify(name, method /* , arg1, arg2, ..., argN */) {
+    let savedName = name;
+    let savedMethod = method;
+    let savedArgs = Array.prototype.slice.call(arguments, 2);
+
+    return function WeaveNotifyWrapper(/* argN+1, argN+2, ... */) {
+      let self = yield;
+      let ret;
+      let args = Array.prototype.slice.call(arguments);
+
+      try {
+        this._os.notifyObservers(null, this._osPrefix + savedName + ":start", "");
+
+        args = savedArgs.concat(args);
+        args.unshift(this, savedMethod, self.cb);
+        Async.run.apply(Async, args);
+        ret = yield;
+
+        this._os.notifyObservers(null, this._osPrefix + savedName + ":success", "");
+
+      } catch (e) {
+        this._os.notifyObservers(null, this._osPrefix + savedName + ":error", "");
+        throw e;
+      }
+
+      self.done(ret);
+    };
+  },
+
+  // NOTE: see notify, this works the same way.  they can be
+  // chained together as well.
+  lock: function WeaveSync_lock(method /* , arg1, arg2, ..., argN */) {
+    let savedMethod = method;
+    let savedArgs = Array.prototype.slice.call(arguments, 1);
+
+    return function WeaveLockWrapper( /* argN+1, argN+2, ... */) {
+      let self = yield;
+      let ret;
+      let args = Array.prototype.slice.call(arguments);
+
+      DAV.lock.async(DAV, self.cb);
+      let locked = yield;
+      if (!locked)
+        throw "Could not acquire lock";
+
+      try {
+        args = savedArgs.concat(args);
+        args.unshift(this, savedMethod, self.cb);
+        Async.run.apply(Async, args);
+        ret = yield;
+
+      } catch (e) {
+        throw e;
+
+      } finally {
+        DAV.unlock.async(DAV, self.cb);
+        yield;
+      }
+
+      self.done(ret);
+    };
+  },
+
+  // NOTE: see notify, this works the same way.  they can be
+  // chained together as well.
+  localLock: function WeaveSync_localLock(method /* , arg1, arg2, ..., argN */) {
+    let savedMethod = method;
+    let savedArgs = Array.prototype.slice.call(arguments, 1);
+
+    return function WeaveLocalLockWrapper(/* argN+1, argN+2, ... */) {
+      let self = yield;
+      let ret;
+      let args = Array.prototype.slice.call(arguments);
+
+      if (DAV.locked)
+        throw "Could not acquire lock";
+      DAV.allowLock = false;
+
+      try {
+        args = savedArgs.concat(args);
+        args.unshift(this, savedMethod, self.cb);
+        Async.run.apply(Async, args);
+        ret = yield;
+      }
+      catch (e) { throw e; }
+      finally { DAV.allowLock = true; }
+
+      self.done(ret);
+    };
+  }
+}
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -1,22 +1,29 @@
 pref("extensions.weave.serverURL", "https://services.mozilla.com/");
 pref("extensions.weave.username", "nobody@mozilla.com");
 
 pref("extensions.weave.encryption", "aes-256-cbc");
 
 pref("extensions.weave.lastversion", "firstrun");
 pref("extensions.weave.lastsync", "0");
 
+pref("extensions.weave.ui.syncnow", true);
+pref("extensions.weave.ui.sharebookmarks", false);
 pref("extensions.weave.rememberpassword", true);
 pref("extensions.weave.autoconnect", true);
 pref("extensions.weave.enabled", true);
 pref("extensions.weave.bookmarks", true);
 pref("extensions.weave.history", true);
 pref("extensions.weave.cookies", false );
 pref("extensions.weave.schedule", 1);
 
-
-pref("extensions.weave.log.rootLogger", "Config");
 pref("extensions.weave.log.appender.console", "Warn");
 pref("extensions.weave.log.appender.dump", "Error");
 pref("extensions.weave.log.appender.briefLog", "Info");
-pref("extensions.weave.log.appender.debugLog", "Config");
+pref("extensions.weave.log.appender.debugLog", "Trace");
+
+pref("extensions.weave.log.rootLogger", "Trace");
+pref("extensions.weave.log.logger.async", "Debug");
+pref("extensions.weave.log.logger.service.crypto", "Debug");
+pref("extensions.weave.log.logger.service.dav", "Debug");
+pref("extensions.weave.log.logger.service.engine", "Debug");
+pref("extensions.weave.log.logger.service.main", "Trace");