Merged
authorjonathandicarlo@jonathan-dicarlos-macbook-pro.local
Thu, 29 May 2008 11:18:16 -0700
changeset 44491 7e0928fd9666f7ce5a615560b02667671adba875
parent 44490 c1e37e6846b70f9f3a9b198b7c5245a09257769c (current diff)
parent 44487 ac5493e8f25691c22082c4f3ea4c75aeebc838aa (diff)
child 44492 d11284c8ad1c3b30a4d7ad8e31b6efb2c563c7a9
push idunknown
push userunknown
push dateunknown
Merged
--- a/services/sync/modules/dav.js
+++ b/services/sync/modules/dav.js
@@ -321,17 +321,17 @@ DAVCollection.prototype = {
 
   // Locking
 
   _getActiveLock: function DC__getActiveLock() {
     let self = yield;
     let ret = null;
 
     this._log.debug("Getting active lock token");
-    this.PROPFIND("",
+    this.PROPFIND("lock",
                   "<?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) {
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -42,18 +42,20 @@ 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/dav.js");
+Cu.import("resource://weave/remote.js");
 Cu.import("resource://weave/identity.js");
 Cu.import("resource://weave/stores.js");
 Cu.import("resource://weave/syncCores.js");
 Cu.import("resource://weave/trackers.js");
 Cu.import("resource://weave/async.js");
 
 Function.prototype.async = Async.sugar;
 
@@ -83,31 +85,32 @@ EngineManagerSvc.prototype = {
     if (val instanceof Engine)
       name = val.name;
     delete this._engines[name];
   }
 };
 
 function Engine() {}
 Engine.prototype = {
+  _notify: Wrap.notify,
+
   // "default-engine";
   get name() { throw "name property must be overridden in subclasses"; },
 
   // "DefaultEngine";
   get logName() { throw "logName property must be overridden in subclasses"; },
 
   // "user-data/default-engine/";
   get serverPrefix() { throw "serverPrefix property must be overridden in subclasses"; },
 
-  // These can be overridden in subclasses, but don't need to be (assuming
-  // serverPrefix is not shared with anything else)
-  get statusFile() { return this.serverPrefix + "status.json"; },
-  get keysFile() { return this.serverPrefix + "keys.json"; },
-  get snapshotFile() { return this.serverPrefix + "snapshot.json"; },
-  get deltasFile() { return this.serverPrefix + "deltas.json"; },
+  get _remote() {
+    if (!this.__remote)
+      this.__remote = new RemoteStore(this.serverPrefix);
+    return this.__remote;
+  },
 
   get enabled() {
     return Utils.prefs.getBoolPref("engine." + this.name);
   },
 
   __os: null,
   get _os() {
     if (!this.__os)
@@ -190,21 +193,19 @@ Engine.prototype = {
   },
 
   _getSymKey: function Engine__getSymKey() {
     let self = yield;
 
     if ("none" == Utils.prefs.getCharPref("encryption"))
       return;
 
-    DAV.GET(this.keysFile, self.cb);
-    let keysResp = yield;
-    Utils.ensureStatus(keysResp.status,
-                       "Could not get keys file.", [[200,300]]);
-    let keys = this._json.decode(keysResp.responseText);
+    this._remote.keys.get(self.cb);
+    yield;
+    let keys = this._json.decode(this._remote.keys.data);
 
     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.ring[this._engineId.userHash], this._pbeId);
     let symkey = yield;
     this._engineId.setTempPassword(symkey);
@@ -221,75 +222,34 @@ Engine.prototype = {
   _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", "");
-
-      // try to delete all 3, check status after
-      DAV.DELETE(this.statusFile, self.cb);
-      let statusResp = yield;
-      DAV.DELETE(this.snapshotFile, self.cb);
-      let snapshotResp = yield;
-      DAV.DELETE(this.deltasFile, self.cb);
-      let deltasResp = 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);
+    this._log.debug("Resetting server data");
+    this._remote.status.delete(self.cb);
+    yield;
+    this._remote.keys.delete(self.cb);
+    yield;
+    this._remote.snapshot.delete(self.cb);
+    yield;
+    this._remote.deltas.delete(self.cb);
+    yield;
+    this._log.debug("Server files deleted");
   },
 
   _resetClient: function Engine__resetClient() {
     let self = yield;
-    let done = false;
-
-    try {
-      this._log.debug("Resetting client state");
-      this._os.notifyObservers(null, this._osPrefix + "reset-client:start", "");
-
-      this._snapshot.wipe();
-      this._store.wipe();
-      done = true;
-
-    } catch (e) {
-      throw e;
-
-    } finally {
-      if (done) {
-        this._log.debug("Client reset completed successfully");
-        this._os.notifyObservers(null, this._osPrefix + "reset-client:success", "");
-      } else {
-        this._log.debug("Client reset failed");
-        this._os.notifyObservers(null, this._osPrefix + "reset-client:error", "");
-      }
-      self.done(done);
-    }
+    this._log.debug("Resetting client state");
+    this._snapshot.wipe();
+    this._store.wipe();
+    this._log.debug("Client reset completed successfully");
   },
 
   //       original
   //         / \
   //      A /   \ B
   //       /     \
   // client --C-> server
   //       \     /
@@ -460,43 +420,34 @@ Engine.prototype = {
         if (!status)
           this._log.error("Could not upload files to server"); // eep?
 
       } 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;
+        this._remote.deltas.put(self.cb, data);
+        yield;
 
         let c = 0;
         for (GUID in this._snapshot.data)
           c++;
 
-        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;
+        this._remote.status.put(self.cb, 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}));
 
-        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("Successfully updated deltas and status on server");
+        this._snapshot.save();
       }
     }
 
     this._log.info("Sync complete");
     self.done(true);
   },
 
   /* Get the deltas/combined updates from the server
@@ -520,186 +471,173 @@ Engine.prototype = {
    *   deltas:
    *     all of the individual deltas on the server
    *   updates:
    *     the relevant deltas (from our snapshot version to current),
    *     combined into a single set.
    */
   _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");
-    DAV.GET(this.statusFile, self.cb);
-    let resp = yield;
-    let status = resp.status;
-
-    switch (status) {
-    case 200: {
+    try {
+      this._log.debug("Getting status file from server");
+      this._remote.status.get(self.cb);
+      yield;
       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 > ENGINE_STORAGE_FORMAT_VERSION) {
-        this._log.error("Server uses storage format v" + status.formatVersion +
-                  ", 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.");
-        this._log.debug("Remote: " + status.GUID);
-        this._log.debug("Local: " + this._snapshot.GUID);
-        this._store.resetGUIDs();
-        this._snapshot.data = {};
-        this._snapshot.version = -1;
-        this._snapshot.GUID = status.GUID;
-      }
-
-      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");
-        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.info("Downloading server deltas");
-        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;
-        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");
-        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);
-        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");
-        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);
-        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;
-      }
-
-      try {
-        for (var i = 0; i < deltas.length; i++) {
-          snap.applyCommands.async(snap, self.cb, deltas[i]);
-          yield;
-        }
-      } catch (e) {
-        this._log.error("Error applying remote deltas to saved snapshot");
-        this._log.error("Clearing local snapshot; next sync will merge");
-        this._log.debug("Exception: " + Utils.exceptionStr(e));
-        this._log.trace("Stack:\n" + Utils.stackTrace(e));
-        this._snapshot.wipe();
-        throw e;
-      }
-
-      ret.status = 0;
-      ret.formatVersion = status.formatVersion;
-      ret.maxVersion = status.maxVersion;
-      ret.snapVersion = status.snapVersion;
-      ret.snapEncryption = status.snapEncryption;
-      ret.deltasEncryption = status.deltasEncryption;
-      ret.snapshot = snap.data;
-      ret.deltas = allDeltas;
-      this._core.detectUpdates(self.cb, this._snapshot.data, snap.data);
-      ret.updates = yield;
-    } break;
-
-    case 404: {
+    } catch (e if e.message.status == 404) {
       this._log.info("Server has no status file, Initial upload to server");
 
       this._snapshot.data = this._store.wrap();
       this._snapshot.version = 0;
       this._snapshot.GUID = null; // in case there are other snapshots out there
 
       this._fullUpload.async(this, self.cb);
       let uploadStatus = yield;
       if (!uploadStatus)
-        break;
+        throw "Initial upload failed";
 
       this._log.info("Initial upload to server successful");
       this._snapshot.save();
 
-      ret.status = 0;
-      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;
+      self.done({status: 0,
+                 formatVersion: ENGINE_STORAGE_FORMAT_VERSION,
+                 maxVersion: this._snapshot.version,
+                 snapVersion: this._snapshot.version,
+                 snapEncryption: Crypto.defaultAlgorithm,
+                 deltasEncryption: Crypto.defaultAlgorithm,
+                 snapshot: Utils.deepCopy(this._snapshot.data),
+                 deltas: [],
+                 updates: []});
+      return;
+    }
+
+    let ret = {status: -1,
+               formatVersion: null, maxVersion: null, snapVersion: null,
+               snapEncryption: null, deltasEncryption: null,
+               snapshot: null, deltas: null, updates: null};
+    let status = this._json.decode(this._remote.status.data);
+    let deltas, allDeltas;
+    let snap = new SnapshotStore();
+
+    // Bail out if the server has a newer format version than we can parse
+    if (status.formatVersion > ENGINE_STORAGE_FORMAT_VERSION) {
+      this._log.error("Server uses storage format v" + status.formatVersion +
+                      ", this client understands up to v" + ENGINE_STORAGE_FORMAT_VERSION);
+      throw "Incompatible server format for engine data";
+    }
+
+    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.");
+      this._log.debug("Remote: " + status.GUID);
+      this._log.debug("Local: " + this._snapshot.GUID);
+      this._store.resetGUIDs();
+      this._snapshot.data = {};
+      this._snapshot.version = -1;
+      this._snapshot.GUID = status.GUID;
+    }
+
+    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._remote.snapshot.get(self.cb);
+      yield;
+      Crypto.PBEdecrypt.async(Crypto, self.cb,
+                              this._remote.snapshot.data,
+      			      this._engineId,
+      			      status.snapEncryption);
+      let data = yield;
+      snap.data = this._json.decode(data);
 
-    default:
-      this._log.error("Could not get status file: unknown HTTP status code " +
-                      status);
-      break;
+      this._log.info("Downloading server deltas");
+      this._remote.deltas.get(self.cb);
+      yield;
+      Crypto.PBEdecrypt.async(Crypto, self.cb,
+                              this._remote.deltas.data,
+      			      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._remote.deltas.get(self.cb);
+      yield;
+      Crypto.PBEdecrypt.async(Crypto, self.cb,
+                              this._remote.deltas.data,
+      			      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._remote.deltas.get(self.cb);
+      yield;
+      Crypto.PBEdecrypt.async(Crypto, self.cb,
+                              this._remote.deltas.data,
+      			      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");
+      throw "Server snapshot is older than local snapshot";
     }
 
+    try {
+      for (var i = 0; i < deltas.length; i++) {
+        snap.applyCommands.async(snap, self.cb, deltas[i]);
+        yield;
+      }
+    } catch (e) {
+      this._log.error("Error applying remote deltas to saved snapshot");
+      this._log.error("Clearing local snapshot; next sync will merge");
+      this._log.debug("Exception: " + Utils.exceptionStr(e));
+      this._log.trace("Stack:\n" + Utils.stackTrace(e));
+      this._snapshot.wipe();
+      throw e;
+    }
+
+    ret.status = 0;
+    ret.formatVersion = status.formatVersion;
+    ret.maxVersion = status.maxVersion;
+    ret.snapVersion = status.snapVersion;
+    ret.snapEncryption = status.snapEncryption;
+    ret.deltasEncryption = status.deltasEncryption;
+    ret.snapshot = snap.data;
+    ret.deltas = allDeltas;
+    this._core.detectUpdates(self.cb, this._snapshot.data, snap.data);
+    ret.updates = yield;
+
     self.done(ret)
   },
 
   _fullUpload: function Engine__fullUpload() {
     let self = yield;
     let ret = false;
 
     Crypto.PBEkeygen.async(Crypto, self.cb);
@@ -715,48 +653,42 @@ Engine.prototype = {
       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.");
-
+    this._remote.keys.put(self.cb, this._json.encode(keys));
+    yield;
     Crypto.PBEencrypt.async(Crypto, self.cb,
                             this._snapshot.serialize(),
       		            this._engineId);
     let data = yield;
 
-    DAV.PUT(this.snapshotFile, data, self.cb);
-    resp = yield;
-    Utils.ensureStatus(resp.status, "Could not upload snapshot.");
-
-    DAV.PUT(this.deltasFile, "[]", self.cb);
-    resp = yield;
-    Utils.ensureStatus(resp.status, "Could not upload deltas.");
+    this._remote.snapshot.put(self.cb, data);
+    yield;
+    this._remote.deltas.put(self.cb, "[]");
+    yield;
 
     let c = 0;
     for (GUID in this._snapshot.data)
       c++;
 
-    DAV.PUT(this.statusFile,
-                  this._json.encode(
-                    {GUID: this._snapshot.GUID,
-                     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._remote.status.put(self.cb,
+                            this._json.encode(
+                              {GUID: this._snapshot.GUID,
+                               formatVersion: ENGINE_STORAGE_FORMAT_VERSION,
+                               snapVersion: this._snapshot.version,
+                               maxVersion: this._snapshot.version,
+                               snapEncryption: Crypto.defaultAlgorithm,
+                               deltasEncryption: "none",
+                               itemCount: c}));
+    yield;
 
     this._log.info("Full upload to server successful");
     ret = true;
     self.done(ret);
   },
 
   _share: function Engine__share(username) {
     let self = yield;
@@ -847,21 +779,21 @@ Engine.prototype = {
     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);
+    this._notify("reset-server", this._resetServer).async(this, onComplete);
   },
 
   resetClient: function Engine_resetClient(onComplete) {
-    return this._resetClient.async(this, onComplete);
+    this._notify("reset-client", this._resetClient).async(this, onComplete);
   }
 };
 
 function BookmarksEngine(pbeId) {
   this._init(pbeId);
 }
 BookmarksEngine.prototype = {
   get name() { return "bookmarks"; },
new file mode 100644
--- /dev/null
+++ b/services/sync/modules/remote.js
@@ -0,0 +1,231 @@
+/* ***** 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 = ['Resource', 'RemoteStore'];
+
+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");
+Cu.import("resource://weave/dav.js");
+Cu.import("resource://weave/stores.js");
+
+Function.prototype.async = Async.sugar;
+
+
+function RequestException(resource, action, request) {
+  this._resource = resource;
+  this._action = action;
+  this._request = request;
+}
+RequestException.prototype = {
+  get resource() { return this._resource; },
+  get action() { return this._action; },
+  get request() { return this._request; },
+  get status() { return this._request.status; },
+  toString: function ReqEx_toString() {
+    return "Could not " + this._action + " resource " + this._resource.path +
+      " (" + this._request.status + ")";
+  }
+};
+
+function Resource(path) {
+  this._identity = null; // unused
+  this._dav = null; // unused
+  this._path = path;
+  this._data = null;
+  this._downloaded = false;
+  this._dirty = false;
+}
+Resource.prototype = {
+  get identity() { return this._identity; },
+  set identity(value) { this._identity = value; },
+
+  get dav() { return this._dav; },
+  set dav(value) { this._dav = value; },
+
+  get path() { return this._path; },
+  set path(value) {
+    this._dirty = true;
+    this._path = value;
+  },
+
+  get data() { return this._data; },
+  set data(value) {
+    this._dirty = true;
+    this._data = value;
+  },
+
+  get downloaded() { return this._downloaded; },
+  get dirty() { return this._dirty; },
+
+  _sync: function Res__sync() {
+    let self = yield;
+    let ret;
+
+    // If we've set the locally stored value, upload it.  If we
+    // haven't, and we haven't yet downloaded this resource, then get
+    // it.  Otherwise do nothing (don't try to get it every time)
+
+    if (this.dirty) {
+      this.put(self.cb, this.data);
+      ret = yield;
+
+    } else if (!this.downloaded) {
+      this.get(self.cb);
+      ret = yield;
+    }
+
+    self.done(ret);
+  },
+  sync: function Res_sync(onComplete) {
+    this._sync.async(this, onComplete);
+  },
+
+  _request: function Res__request(action, data) {
+    let self = yield;
+    let listener, timer;
+    let iter = 0;
+    let ret;
+
+    while (true) {
+      switch (action) {
+      case "GET":
+        DAV.GET(this.path, self.cb);
+        break;
+      case "PUT":
+        DAV.PUT(this.path, data, self.cb);
+        break;
+      case "DELETE":
+        DAV.DELETE(this.path, self.cb);
+        break;
+      default:
+        throw "Unknown request action for Resource";
+      }
+      ret = yield;
+
+      if (action == "DELETE" &&
+          Utils.checkStatus(ret.status, null, [[200,300],404])) {
+        this._dirty = false;
+        this._data = null;
+        break;
+
+      } else if (Utils.checkStatus(ret.status)) {
+        this._dirty = false;
+        if (action == "GET")
+          this._data = ret.responseText;
+        else if (action == "PUT")
+          this._data = data;
+        break;
+
+      } else if (action == "GET" && ret.status == 404) {
+        throw new RequestException(this, action, ret);
+
+      } else if (iter >= 10) {
+        // iter too big? bail
+        throw new RequestException(this, action, ret);
+
+      } else {
+        // wait for a bit and try again
+        if (!timer) {
+          listener = new Utils.EventListener(self.cb);
+          timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+        }
+        timer.initWithCallback(listener, iter * 100, timer.TYPE_ONE_SHOT);
+        yield;
+        iter++;
+      }
+    }
+
+    self.done(ret);
+  },
+
+  get: function Res_get(onComplete) {
+    this._request.async(this, onComplete, "GET");
+  },
+
+  put: function Res_put(onComplete, data) {
+    this._request.async(this, onComplete, "PUT", data);
+  },
+
+  delete: function Res_delete(onComplete) {
+    this._request.async(this, onComplete, "DELETE");
+  }
+};
+
+function JsonResource(path) {
+
+}
+JsonResource.prototype = {
+  __proto__: new Resource(),
+
+  _get: function(onComplete) {
+    let self = yield;
+    this.__proto__.get(onComplete);
+      yield;
+  },
+  get: function JRes_get(onComplete) {
+    foo.async();
+  }
+};
+
+function RemoteStore(serverPrefix) {
+  this._prefix = serverPrefix;
+  this._status = new Resource(serverPrefix + "status.json");
+  this._keys = new Resource(serverPrefix + "keys.json");
+  this._snapshot = new Resource(serverPrefix + "snapshot.json");
+  this._deltas = new Resource(serverPrefix + "deltas.json");
+}
+RemoteStore.prototype = {
+  get status() {
+    return this._status;
+  },
+  get keys() {
+    return this._keys;
+  },
+  get snapshot() {
+    return this._snapshot;
+  },
+  get deltas() {
+    return this._deltas;
+  }
+};