Asynchronous generator helpers rework + PKI work
* Async helpers are in a module of their own now
* Async routines have simpler semantics now. onComplete handlers are taken care of by the helpers. Exceptions are bubbled up across nested asynchronous generators
* Stack traces are automatically logged for unhandled exceptions
* Async generators are now allowed to 'bottom out' (StopIteration is ignored) - this is configurable.
* RSA key generation fixes
* On login we now create an RSA keypair, encrypt the private one with PBE, and upload them to the server
* Log files are now limited to 2MB (down from 5)
--- a/services/sync/modules/crypto.js
+++ b/services/sync/modules/crypto.js
@@ -40,18 +40,19 @@ 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 = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
function WeaveCrypto() {
this._init();
}
WeaveCrypto.prototype = {
_logName: "Crypto",
__os: null,
@@ -205,41 +206,41 @@ WeaveCrypto.prototype = {
// 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.close();
try {
this._openssl("pkcs8", "-in", "privkey.pem", "-out", "enckey.pem",
- "-topk8", "-v2", algorithm, "-pass", "file:pass");
+ "-topk8", "-v2", algorithm, "-passout", "file:pass");
} catch (e) {
throw e;
} finally {
passFile.remove(false);
privKeyF.remove(false);
}
let [cryptedKeyFIS] = Utils.open(cryptedKeyF, "<");
let cryptedKey = Utils.readStream(cryptedKeyFIS);
cryptedKeyFIS.close();
- cryptedKey.remove(false);
+ cryptedKeyF.remove(false);
let [pubKeyFIS] = Utils.open(pubKeyF, "<");
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, pubkey) {
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);
@@ -256,17 +257,17 @@ WeaveCrypto.prototype = {
let output = Utils.readStream(outpusFIS);
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) {
+ _opensslRSAdecrypt: function Crypto__opensslRSAdecrypt(input, privkey, password) {
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(privkey);
@@ -333,136 +334,132 @@ WeaveCrypto.prototype = {
} break;
default:
this._log.warn("Unknown encryption preference changed - ignoring");
}
},
// Crypto
- PBEencrypt: function Crypto_PBEencrypt(onComplete, data, identity, algorithm) {
- let [self, cont] = yield;
- let listener = new Utils.EventListener(cont);
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ PBEencrypt: function Crypto_PBEencrypt(data, identity, algorithm) {
+ let self = yield;
let ret;
- try {
- if (!algorithm)
- algorithm = this.defaultAlgorithm;
+ if (!algorithm)
+ algorithm = this.defaultAlgorithm;
- if (algorithm != "none")
- this._log.debug("Encrypting data");
+ if (algorithm != "none")
+ this._log.debug("Encrypting data");
- switch (algorithm) {
- case "none":
- ret = data;
- break;
+ switch (algorithm) {
+ case "none":
+ ret = data;
+ break;
- case "XXXTEA": // Weave 0.1.12.10 and below had this typo
- case "XXTEA": {
- let gen = this._xxtea.encrypt(data, identity.password);
- ret = gen.next();
+ case "XXXTEA": // Weave 0.1.12.10 and below had this typo
+ case "XXTEA": {
+ let gen = this._xxtea.encrypt(data, identity.password);
+ ret = gen.next();
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ try {
while (typeof(ret) == "object") {
- timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+ timer.initWithCallback(self.listener, 0, timer.TYPE_ONE_SHOT);
yield; // Yield to main loop
ret = gen.next();
}
gen.close();
- } break;
-
- case "aes-128-cbc":
- case "aes-192-cbc":
- case "aes-256-cbc":
- case "bf-cbc":
- case "des-ede3-cbc":
- ret = this._opensslPBE("-e", algorithm, data, identity.password);
- break;
-
- default:
- throw "Unknown encryption algorithm: " + algorithm;
+ } finally {
+ timer = null;
}
-
- if (algorithm != "none")
- this._log.debug("Done encrypting data");
-
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ } break;
- } finally {
- timer = null;
- Utils.generatorDone(this, self, onComplete, ret);
- yield; // onComplete is responsible for closing the generator
+ case "aes-128-cbc":
+ case "aes-192-cbc":
+ case "aes-256-cbc":
+ case "bf-cbc":
+ case "des-ede3-cbc":
+ ret = this._opensslPBE("-e", algorithm, data, identity.password);
+ break;
+
+ default:
+ throw "Unknown encryption algorithm: " + algorithm;
}
- this._log.warn("generator not properly closed");
+
+ if (algorithm != "none")
+ this._log.debug("Done encrypting data");
+
+ self.done(ret);
},
- PBEdecrypt: function Crypto_PBEdecrypt(onComplete, data, identity, algorithm) {
- let [self, cont] = yield;
- let listener = new Utils.EventListener(cont);
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ PBEdecrypt: function Crypto_PBEdecrypt(data, identity, algorithm) {
+ let self = yield;
let ret;
- try {
- if (!algorithm)
- algorithm = this.defaultAlgorithm;
+ if (!algorithm)
+ algorithm = this.defaultAlgorithm;
- if (algorithm != "none")
- this._log.debug("Decrypting data");
+ if (algorithm != "none")
+ this._log.debug("Decrypting data");
- switch (algorithm) {
- case "none":
- ret = data;
- break;
+ switch (algorithm) {
+ case "none":
+ ret = data;
+ break;
- case "XXXTEA": // Weave 0.1.12.10 and below had this typo
- case "XXTEA": {
- let gen = this._xxtea.decrypt(data, identity.password);
- ret = gen.next();
+ case "XXXTEA": // Weave 0.1.12.10 and below had this typo
+ case "XXTEA": {
+ let gen = this._xxtea.decrypt(data, identity.password);
+ ret = gen.next();
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ try {
while (typeof(ret) == "object") {
- timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
+ timer.initWithCallback(self.listener, 0, timer.TYPE_ONE_SHOT);
yield; // Yield to main loop
ret = gen.next();
}
gen.close();
- } break;
-
- case "aes-128-cbc":
- case "aes-192-cbc":
- case "aes-256-cbc":
- case "bf-cbc":
- case "des-ede3-cbc":
- ret = this._opensslPBE("-d", algorithm, data, identity.password);
- break;
-
- default:
- throw "Unknown encryption algorithm: " + algorithm;
+ } finally {
+ timer = null;
}
-
- if (algorithm != "none")
- this._log.debug("Done decrypting data");
-
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ } break;
- } finally {
- timer = null;
- Utils.generatorDone(this, self, onComplete, ret);
- yield; // onComplete is responsible for closing the generator
+ case "aes-128-cbc":
+ case "aes-192-cbc":
+ case "aes-256-cbc":
+ case "bf-cbc":
+ case "des-ede3-cbc":
+ ret = this._opensslPBE("-d", algorithm, data, identity.password);
+ break;
+
+ default:
+ throw "Unknown encryption algorithm: " + algorithm;
}
- this._log.warn("generator not properly closed");
+
+ if (algorithm != "none")
+ this._log.debug("Done decrypting data");
+
+ self.done(ret);
},
PBEkeygen: function Crypto_PBEkeygen() {
- return this._opensslRand();
+ let self = yield;
+ let ret = this._opensslRand();
+ self.done(ret);
},
RSAkeygen: function Crypto_RSAkeygen(password) {
- return this._opensslRSAKeyGen(password);
+ let self = yield;
+ let ret = this._opensslRSAKeyGen(password);
+ self.done(ret);
},
RSAencrypt: function Crypto_RSAencrypt(data, key) {
- return this._opensslRSAEncrypt(data, key);
+ let self = yield;
+ let ret = this._opensslRSAencrypt(data, key);
+ self.done(ret);
},
RSAdecrypt: function Crypto_RSAdecrypt(data, key, password) {
- return this._opensslRSADecrypt(data, key, password);
+ let self = yield;
+ let ret = this._opensslRSAdecrypt(data, key, password);
+ self.done(ret);
}
};
--- a/services/sync/modules/dav.js
+++ b/services/sync/modules/dav.js
@@ -39,18 +39,19 @@ const EXPORTED_SYMBOLS = ['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");
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
/*
* DAV object
* Abstracts the raw DAV commands
*/
function DAVCollection(baseURL) {
this._baseURL = baseURL;
@@ -75,124 +76,116 @@ DAVCollection.prototype = {
this._baseURL = value;
},
_loggedIn: false,
get loggedIn() {
return this._loggedIn;
},
- _makeRequest: function DC__makeRequest(onComplete, op, path, headers, data) {
- let [self, cont] = yield;
+ _makeRequest: function DC__makeRequest(op, path, headers, data) {
+ let self = yield;
let ret;
- try {
- this._log.debug("Creating " + op + " request for " + this._baseURL + path);
+ this._log.debug("Creating " + op + " request for " + this._baseURL + path);
+
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
+ request = request.QueryInterface(Ci.nsIDOMEventTarget);
- let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
- request = request.QueryInterface(Ci.nsIDOMEventTarget);
-
- request.addEventListener("load", new Utils.EventListener(cont, "load"), false);
- request.addEventListener("error", new Utils.EventListener(cont, "error"), false);
- request = request.QueryInterface(Ci.nsIXMLHttpRequest);
- request.open(op, this._baseURL + path, true);
+ 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;
+ // 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)");
- else
- this._log.debug("HTTP Header " + key + ": " + headers[key]);
- request.setRequestHeader(key, headers[key]);
- }
-
- this._authProvider._authFailed = false;
- request.channel.notificationCallbacks = this._authProvider;
+ let key;
+ for (key in headers) {
+ if (key == 'Authorization')
+ this._log.debug("HTTP Header " + key + ": ***** (suppressed)");
+ else
+ this._log.debug("HTTP Header " + key + ": " + headers[key]);
+ request.setRequestHeader(key, headers[key]);
+ }
- request.send(data);
- let event = yield;
- ret = event.target;
+ this._authProvider._authFailed = false;
+ request.channel.notificationCallbacks = this._authProvider;
- if (this._authProvider._authFailed)
- this._log.warn("_makeRequest: authentication failed");
- if (ret.status < 200 || ret.status >= 300)
- this._log.warn("_makeRequest: got status " + ret.status);
+ request.send(data);
+ let event = yield;
+ ret = event.target;
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ if (this._authProvider._authFailed)
+ this._log.warn("_makeRequest: authentication failed");
+ if (ret.status < 200 || ret.status >= 300)
+ this._log.warn("_makeRequest: got status " + ret.status);
- } finally {
- Utils.generatorDone(this, self, onComplete, ret);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ self.done(ret);
},
get _defaultHeaders() {
return {'Authorization': this._auth? this._auth : '',
'Content-type': 'text/plain',
'If': this._token?
"<" + this._baseURL + "> (<" + this._token + ">)" : ''};
},
// mkdir -p
- _mkcol: function DC__mkcol(path, onComplete) {
- let [self, cont] = yield;
- let ret;
+ _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, cont, "GET", path2 + "/", this._defaultHeaders);
- 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 +
- " (got status " + ret.status + ")");
- } else {
- this._log.debug("Creating path: " + path2);
- let gen = this._makeRequest.async(this, cont, "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 + "/";
+
+ // 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 +
+ " (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("Exception caught: " + (e.message? e.message : e));
+ this._log.error("Could not create directory on server");
+ this._log.error("Exception caught: " + (e.message? e.message : e) +
+ " - " + (e.location? e.location : ""));
+ ok = false;
+ }
- } finally {
- Utils.generatorDone(this, self, onComplete, ret);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ self.done(ok);
},
GET: function DC_GET(path, onComplete) {
return this._makeRequest.async(this, onComplete, "GET", path,
this._defaultHeaders);
},
PUT: function DC_PUT(path, data, onComplete) {
@@ -201,17 +194,17 @@ DAVCollection.prototype = {
},
DELETE: function DC_DELETE(path, onComplete) {
return this._makeRequest.async(this, onComplete, "DELETE", path,
this._defaultHeaders);
},
MKCOL: function DC_MKCOL(path, onComplete) {
- return this._mkcol.async(this, path, onComplete);
+ return this._mkcol.async(this, onComplete, path);
},
PROPFIND: function DC_PROPFIND(path, data, onComplete) {
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);
@@ -228,221 +221,190 @@ DAVCollection.prototype = {
UNLOCK: function DC_UNLOCK(path, onComplete) {
let headers = {'Lock-Token': '<' + this._token + '>'};
headers.__proto__ = this._defaultHeaders;
return this._makeRequest.async(this, onComplete, "UNLOCK", path, headers);
},
// Login / Logout
- login: function DC_login(onComplete, username, password) {
- let [self, cont] = yield;
+ login: function DC_login(username, password) {
+ let self = yield;
- try {
- if (this._loggedIn) {
- this._log.debug("Login requested, but already logged in");
- return;
- }
-
- this._log.info("Logging in");
+ if (this._loggedIn) {
+ this._log.debug("Login requested, but already logged in");
+ self.done(true);
+ yield;
+ }
+
+ this._log.info("Logging in");
- let URI = Utils.makeURI(this._baseURL);
- this._auth = "Basic " + btoa(username + ":" + password);
+ let URI = Utils.makeURI(this._baseURL);
+ this._auth = "Basic " + btoa(username + ":" + password);
- // Make a call to make sure it's working
- this.GET("", cont);
- let resp = yield;
-
- if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
- return;
-
- this._loggedIn = true;
+ // Make a call to make sure it's working
+ this.GET("", self.cb);
+ let resp = yield;
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ if (this._authProvider._authFailed ||
+ resp.status < 200 || resp.status >= 300) {
+ self.done(false);
+ yield;
+ }
- } finally {
- Utils.generatorDone(this, self, onComplete, this._loggedIn);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ this._loggedIn = true;
+
+ self.done(true);
},
logout: function DC_logout() {
this._log.debug("Logging out (forgetting auth header)");
this._loggedIn = false;
this.__auth = null;
},
// Locking
- _getActiveLock: function DC__getActiveLock(onComplete) {
- let [self, cont] = yield;
+ _getActiveLock: function DC__getActiveLock() {
+ let self = yield;
let ret = null;
- try {
- this._log.info("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>", cont);
- let resp = yield;
-
- if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
- return;
+ this._log.info("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;
- let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
- let token = tokens.iterateNext();
- ret = token.textContent;
-
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ if (this._authProvider._authFailed ||
+ resp.status < 200 || resp.status >= 300) {
+ self.done(false);
+ yield;
+ }
- } finally {
- if (ret)
- this._log.debug("Found an active lock token");
- else
- this._log.debug("No active lock token found");
- Utils.generatorDone(this, self, onComplete, ret);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
+ let token = tokens.iterateNext();
+ ret = token.textContent;
+
+ if (ret)
+ this._log.debug("Found an active lock token");
+ else
+ this._log.debug("No active lock token found");
+ self.done(ret);
},
- lock: function DC_lock(onComplete) {
- let [self, cont] = yield;
+ lock: function DC_lock() {
+ let self = yield;
this._token = null;
- try {
- this._log.info("Acquiring lock");
+ this._log.info("Acquiring lock");
- if (this._token) {
- this._log.debug("Lock called, but we already hold a token");
- return;
- }
+ if (this._token) {
+ this._log.debug("Lock called, but we already hold a token");
+ self.done(this._token);
+ yield;
+ }
- this.LOCK("",
- "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
- "<D:lockinfo xmlns:D=\"DAV:\">\n" +
- " <D:locktype><D:write/></D:locktype>\n" +
- " <D:lockscope><D:exclusive/></D:lockscope>\n" +
- "</D:lockinfo>", cont);
- let resp = yield;
+ this.LOCK("",
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+ "<D:lockinfo xmlns:D=\"DAV:\">\n" +
+ " <D:locktype><D:write/></D:locktype>\n" +
+ " <D:lockscope><D:exclusive/></D:lockscope>\n" +
+ "</D:lockinfo>", self.cb);
+ let resp = yield;
- if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
- return;
-
- let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
- let token = tokens.iterateNext();
- if (token)
- this._token = token.textContent;
+ if (this._authProvider._authFailed ||
+ resp.status < 200 || resp.status >= 300) {
+ self.done(this._token);
+ yield;
+ }
- } catch (e){
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ let tokens = Utils.xpath(resp.responseXML, '//D:locktoken/D:href');
+ let token = tokens.iterateNext();
+ if (token)
+ this._token = token.textContent;
- } finally {
- if (this._token)
- this._log.info("Lock acquired");
- else
- this._log.warn("Could not acquire lock");
- Utils.generatorDone(this, self, onComplete, this._token);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ if (this._token)
+ this._log.debug("Lock acquired");
+ else
+ this._log.warn("Could not acquire lock");
+
+ self.done(this._token);
},
- unlock: function DC_unlock(onComplete) {
- let [self, cont] = yield;
- try {
- this._log.info("Releasing lock");
+ unlock: function DC_unlock() {
+ let self = yield;
+
+ this._log.info("Releasing lock");
- if (this._token === null) {
- this._log.debug("Unlock called, but we don't hold a token right now");
- return;
- }
+ if (this._token === null) {
+ this._log.debug("Unlock called, but we don't hold a token right now");
+ self.done(true);
+ yield;
+ }
- this.UNLOCK("", cont);
- let resp = yield;
-
- if (this._authProvider._authFailed || resp.status < 200 || resp.status >= 300)
- return;
+ this.UNLOCK("", self.cb);
+ let resp = yield;
- this._token = null;
-
- } catch (e){
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ if (this._authProvider._authFailed ||
+ resp.status < 200 || resp.status >= 300) {
+ self.done(false);
+ yield;
+ }
- } finally {
- if (this._token) {
- this._log.info("Could not release lock");
- Utils.generatorDone(this, self, onComplete, false);
- } else {
- this._log.info("Lock released (or we didn't have one)");
- Utils.generatorDone(this, self, onComplete, true);
- }
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ this._token = null;
+
+ if (this._token)
+ this._log.info("Could not release lock");
+ else
+ this._log.info("Lock released (or we didn't have one)");
+
+ self.done(!this._token);
},
- forceUnlock: function DC_forceUnlock(onComplete) {
- let [self, cont] = yield;
+ forceUnlock: function DC_forceUnlock() {
+ let self = yield;
let unlocked = true;
- try {
- this._log.info("Forcibly releasing any server locks");
+ this._log.info("Forcibly releasing any server locks");
- this._getActiveLock.async(this, cont);
- this._token = yield;
-
- if (!this._token) {
- this._log.info("No server lock found");
- return;
- }
+ this._getActiveLock.async(this, self.cb);
+ this._token = yield;
- this._log.info("Server lock found, unlocking");
- this.unlock.async(this, cont);
- unlocked = yield;
-
- } catch (e){
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ if (!this._token) {
+ this._log.info("No server lock found");
+ self.done(true);
+ yield;
+ }
- } finally {
- if (unlocked)
- this._log.debug("Lock released");
- else
- this._log.debug("No lock released");
- Utils.generatorDone(this, self, onComplete, unlocked);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ this._log.info("Server lock found, unlocking");
+ this.unlock.async(this, self.cb);
+ unlocked = yield;
+
+ if (unlocked)
+ this._log.debug("Lock released");
+ else
+ this._log.debug("No lock released");
+ self.done(unlocked);
},
- stealLock: function DC_stealLock(onComplete) {
- let [self, cont] = yield;
+ stealLock: function DC_stealLock() {
+ let self = yield;
let stolen = null;
- try {
- this.forceUnlock.async(this, cont);
- let unlocked = yield;
-
- if (unlocked) {
- this.lock.async(this, cont);
- stolen = yield;
- }
+ this.forceUnlock.async(this, self.cb);
+ let unlocked = yield;
- } catch (e){
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ if (unlocked) {
+ this.lock.async(this, self.cb);
+ stolen = yield;
+ }
- } finally {
- Utils.generatorDone(this, self, onComplete, stolen);
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ self.done(stolen);
}
};
/*
* Auth provider object
* Taken from nsMicrosummaryService.js and massaged slightly
*/
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -43,18 +43,19 @@ 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/stores.js");
Cu.import("resource://weave/syncCores.js");
+Cu.import("resource://weave/async.js");
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
let Crypto = new WeaveCrypto();
function Engine(davCollection, cryptoId) {
//this._init(davCollection, cryptoId);
}
Engine.prototype = {
// "default-engine";
get name() { throw "name property must be overridden in subclasses"; },
@@ -63,16 +64,17 @@ Engine.prototype = {
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"; },
__os: null,
get _os() {
if (!this.__os)
this.__os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
@@ -115,111 +117,120 @@ Engine.prototype = {
_init: function Engine__init(davCollection, cryptoId) {
this._dav = davCollection;
this._cryptoId = cryptoId;
this._log = Log4Moz.Service.getLogger("Service." + this.logName);
this._osPrefix = "weave:" + this.name + ":";
this._snapshot.load();
},
+ _getSymKey: function Engine__getCryptoId(cryptoId) {
+ let self = yield;
+ let done = false;
+
+ this._dav.GET(this.keysFile, self.cb);
+ let keysResp = yield;
+ Utils.ensureStatus(keysResp.status,
+ "Could not get keys file.", [[200,300]]);
+ let keys = keysResp.responseText;
+
+ if (!keys[this._userHash]) {
+ this._log.error("Keyring does not contain a key for this user");
+ return;
+ }
+
+ //Crypto.RSAdecrypt.async(Crypto, self.cb,
+ // keys[this._userHash],
+
+ self.done(done);
+ yield;
+ this._log.warn("_getSymKey generator not properly closed");
+ },
+
_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(onComplete) {
- let [self, cont] = yield;
+ _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, cont);
+ this._dav.lock.async(this._dav, self.cb);
let locked = yield;
- if (locked)
- this._log.debug("Lock acquired");
- else {
- this._log.warn("Could not acquire lock, aborting server reset");
- return;
- }
+ if (!locked)
+ throw "Could not acquire lock, aborting server reset";
// try to delete all 3, check status after
- this._dav.DELETE(this.statusFile, cont);
+ this._dav.DELETE(this.statusFile, self.cb);
let statusResp = yield;
- this._dav.DELETE(this.snapshotFile, cont);
+ this._dav.DELETE(this.snapshotFile, self.cb);
let snapshotResp = yield;
- this._dav.DELETE(this.deltasFile, cont);
+ this._dav.DELETE(this.deltasFile, self.cb);
let deltasResp = yield;
- this._dav.unlock.async(this._dav, cont);
+ this._dav.unlock.async(this._dav, self.cb);
let unlocked = yield;
- Utils.checkStatus(statusResp.status,
- "Could not delete status file.", [[200,300],404]);
- Utils.checkStatus(snapshotResp.status,
- "Could not delete snapshot file.", [[200,300],404]);
- Utils.checkStatus(deltasResp.status,
- "Could not delete deltas file.", [[200,300],404]);
+ 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) {
- if (e != 'checkStatus failed')
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ this._log.error("Could not delete server files");
+ this._os.notifyObservers(null, this._osPrefix + "reset-server:error", "");
+ throw e;
+ }
- } finally {
- if (done) {
- this._log.debug("Server reset completed successfully");
- this._os.notifyObservers(null, this._osPrefix + "reset-server:success", "");
- } else {
- this._log.debug("Server reset failed");
- this._os.notifyObservers(null, this._osPrefix + "reset-server:error", "");
- }
- Utils.generatorDone(this, self, onComplete, done)
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ self.done(done)
},
- _resetClient: function Engine__resetClient(onComplete) {
- let [self, cont] = yield;
+ _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) {
- this._log.error("Exception caught: " + (e.message? e.message : 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", "");
}
- Utils.generatorDone(this, self, onComplete, done);
- yield; // onComplete is responsible for closing the generator
+ self.done(done);
}
- this._log.warn("generator not properly closed");
},
// original
// / \
// A / \ B
// / \
// client --C-> server
// \ /
@@ -240,41 +251,42 @@ Engine.prototype = {
// 1.2) Generate single delta from snapshot -> current server status ("B")
// 2) Generate local deltas from snapshot -> current client status ("A")
// 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(onComplete) {
- let [self, cont] = yield;
+ _sync: function BmkEngine__sync() {
+ let self = yield;
let synced = false, locked = null;
try {
this._log.info("Beginning sync");
this._os.notifyObservers(null, this._osPrefix + "sync:start", "");
- this._dav.lock.async(this._dav, cont);
+ this._dav.lock.async(this._dav, self.cb);
locked = yield;
if (locked)
this._log.info("Lock acquired");
else {
this._log.warn("Could not acquire lock, aborting sync");
return;
}
// Before we get started, make sure we have a remote directory to play in
- this._dav.MKCOL(this.serverPrefix, cont);
+ this._dav.MKCOL(this.serverPrefix, self.cb);
let ret = yield;
- Utils.checkStatus(ret.status, "Could not create remote folder.");
+ if (!ret)
+ throw "Could not create remote folder";
// 1) Fetch server deltas
- this._getServerData.async(this, cont);
+ this._getServerData.async(this, self.cb);
let server = yield;
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);
if (server.status != 0) {
@@ -282,34 +294,34 @@ Engine.prototype = {
"or initial upload failed. Aborting sync.");
return;
}
// 2) Generate local deltas from snapshot -> current client status
let localJson = new SnapshotStore();
localJson.data = this._store.wrap();
- this._core.detectUpdates(cont, this._snapshot.data, localJson.data);
+ this._core.detectUpdates(self.cb, this._snapshot.data, localJson.data);
let localUpdates = yield;
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));
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.
this._log.info("Reconciling client/server updates");
- this._core.reconcile(cont, localUpdates, 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);
@@ -346,17 +358,17 @@ Engine.prototype = {
// 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();
- this._core.detectUpdates(cont, this._snapshot.data, newSnapshot);
+ 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);
@@ -367,17 +379,17 @@ Engine.prototype = {
// 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
newSnapshot = this._store.wrap();
- this._core.detectUpdates(cont, server.snapshot, newSnapshot);
+ 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)");
@@ -390,42 +402,42 @@ Engine.prototype = {
this._snapshot.data = newSnapshot;
this._snapshot.version = ++server.maxVersion;
server.deltas.push(serverDelta);
if (server.formatVersion != STORAGE_FORMAT_VERSION ||
this._encryptionChanged) {
- this._fullUpload.async(this, cont);
+ 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, cont,
+ Crypto.PBEencrypt.async(Crypto, self.cb,
this._serializeCommands(server.deltas),
this._cryptoId);
let data = yield;
- this._dav.PUT(this.deltasFile, data, cont);
+ this._dav.PUT(this.deltasFile, data, self.cb);
let deltasPut = yield;
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}), cont);
+ 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
@@ -434,34 +446,32 @@ Engine.prototype = {
}
}
}
this._log.info("Sync complete");
synced = true;
} catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ throw e;
} finally {
let ok = false;
if (locked) {
- this._dav.unlock.async(this._dav, cont);
+ this._dav.unlock.async(this._dav, self.cb);
ok = yield;
}
if (ok && synced) {
this._os.notifyObservers(null, this._osPrefix + "sync:success", "");
- Utils.generatorDone(this, self, onComplete, true);
+ self.done(true);
} else {
this._os.notifyObservers(null, this._osPrefix + "sync:error", "");
- Utils.generatorDone(this, self, onComplete, false);
+ self.done(false);
}
- yield; // onComplete is responsible for closing the generator
}
- this._log.warn("generator not properly closed");
},
/* Get the deltas/combined updates from the server
* Returns:
* status:
* -1: error
* 0: ok
* These fields may be null when status is -1:
@@ -478,230 +488,215 @@ Engine.prototype = {
* snapshot:
* full snapshot of the latest server version (deltas applied)
* 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(onComplete) {
- let [self, cont] = yield;
+ _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};
- try {
- this._log.debug("Getting status file from server");
- this._dav.GET(this.statusFile, cont);
- 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) {
- this._log.error("Server uses storage format v" + status.formatVersion +
- ", this client understands up to v" + STORAGE_FORMAT_VERSION);
- Utils.generatorDone(this, self, onComplete, ret)
- return;
- }
+ this._log.debug("Getting status file from server");
+ this._dav.GET(this.statusFile, self.cb);
+ let resp = yield;
+ let status = resp.status;
- 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) {
- 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, cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not download snapshot.");
- Crypto.PBEdecrypt.async(Crypto, cont,
- resp.responseText,
- this._cryptoId,
- status.snapEncryption);
- let data = yield;
- snap.data = this._json.decode(data);
+ switch (status) {
+ case 200: {
+ this._log.info("Got status file from server");
- this._log.info("Downloading server deltas");
- this._dav.GET(this.deltasFile, cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not download deltas.");
- Crypto.PBEdecrypt.async(Crypto, cont,
- resp.responseText,
- this._cryptoId,
- 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) {
- snap.data = Utils.deepCopy(this._snapshot.data);
-
- this._log.info("Downloading server deltas");
- this._dav.GET(this.deltasFile, cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not download deltas.");
- Crypto.PBEdecrypt.async(Crypto, cont,
- resp.responseText,
- this._cryptoId,
- 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) {
- snap.data = Utils.deepCopy(this._snapshot.data);
+ let status = this._json.decode(resp.responseText);
+ let deltas, allDeltas;
+ let snap = new SnapshotStore();
- // FIXME: could optimize this case by caching deltas file
- this._log.info("Downloading server deltas");
- this._dav.GET(this.deltasFile, cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not download deltas.");
- Crypto.PBEdecrypt.async(Crypto, cont,
- resp.responseText,
- this._cryptoId,
- 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");
- return;
- }
-
- for (var i = 0; i < deltas.length; i++) {
- snap.applyCommands(deltas[i]);
- }
-
- 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(cont, this._snapshot.data, snap.data);
- ret.updates = yield;
- } break;
-
- case 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, cont);
- let uploadStatus = yield;
- if (!uploadStatus)
- return;
-
- this._log.info("Initial upload to server successful");
- this._snapshot.save();
-
- ret.status = 0;
- ret.formatVersion = 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;
-
- default:
- this._log.error("Could not get status file: unknown HTTP status code " +
- status);
+ // Bail out if the server has a newer format version than we can parse
+ if (status.formatVersion > STORAGE_FORMAT_VERSION) {
+ this._log.error("Server uses storage format v" + status.formatVersion +
+ ", this client understands up to v" + STORAGE_FORMAT_VERSION);
break;
}
- } catch (e) {
- if (e != 'checkStatus failed' &&
- e != 'decrypt failed')
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ 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._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,
+ status.snapEncryption);
+ let data = yield;
+ snap.data = this._json.decode(data);
+
+ this._log.info("Downloading server deltas");
+ this._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,
+ 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);
+ resp = yield;
+ Utils.ensureStatus(resp.status, "Could not download deltas.");
+ Crypto.PBEdecrypt.async(Crypto, self.cb,
+ resp.responseText,
+ this._cryptoId,
+ 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);
- } finally {
- Utils.generatorDone(this, self, onComplete, ret)
- yield; // onComplete is responsible for closing the generator
+ // FIXME: could optimize this case by caching deltas file
+ this._log.info("Downloading server deltas");
+ this._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,
+ 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]);
+ }
+
+ 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: {
+ 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;
+
+ this._log.info("Initial upload to server successful");
+ this._snapshot.save();
+
+ ret.status = 0;
+ ret.formatVersion = 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;
+
+ default:
+ this._log.error("Could not get status file: unknown HTTP status code " +
+ status);
+ break;
}
- this._log.warn("generator not properly closed");
+
+ self.done(ret)
},
- _fullUpload: function Engine__fullUpload(onComplete) {
- let [self, cont] = yield;
+ _fullUpload: function Engine__fullUpload() {
+ let self = yield;
let ret = false;
- try {
- Crypto.PBEencrypt.async(Crypto, cont,
- this._snapshot.serialize(),
- this._cryptoId);
- let data = yield;
- this._dav.PUT(this.snapshotFile, data, cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not upload snapshot.");
+ let gen = Crypto.PBEencrypt.async(Crypto, self.cb,
+ this._snapshot.serialize(),
+ this._cryptoId);
+ let data = yield;
+ if (gen.failed) throw "Encryption failed.";
+
+ this._dav.PUT(this.snapshotFile, data, self.cb);
+ resp = yield;
+ Utils.ensureStatus(resp.status, "Could not upload snapshot.");
- this._dav.PUT(this.deltasFile, "[]", cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not upload deltas.");
-
- let c = 0;
- for (GUID in this._snapshot.data)
- c++;
+ this._dav.PUT(this.deltasFile, "[]", self.cb);
+ resp = yield;
+ Utils.ensureStatus(resp.status, "Could not upload deltas.");
- this._dav.PUT(this.statusFile,
- this._json.encode(
- {GUID: this._snapshot.GUID,
- formatVersion: STORAGE_FORMAT_VERSION,
- snapVersion: this._snapshot.version,
- maxVersion: this._snapshot.version,
- snapEncryption: Crypto.defaultAlgorithm,
- deltasEncryption: "none",
- itemCount: c}), cont);
- resp = yield;
- Utils.checkStatus(resp.status, "Could not upload status file.");
+ let c = 0;
+ for (GUID in this._snapshot.data)
+ c++;
- this._log.info("Full upload to server successful");
- ret = true;
+ this._dav.PUT(this.statusFile,
+ this._json.encode(
+ {GUID: this._snapshot.GUID,
+ formatVersion: 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.");
- } catch (e) {
- if (e != 'checkStatus failed')
- this._log.error("Exception caught: " + (e.message? e.message : e));
-
- } finally {
- Utils.generatorDone(this, self, onComplete, ret)
- yield; // onComplete is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ this._log.info("Full upload to server successful");
+ ret = true;
+ self.done(ret)
},
sync: function Engine_sync(onComplete) {
return this._sync.async(this, onComplete);
},
resetServer: function Engine_resetServer(onComplete) {
return this._resetServer.async(this, onComplete);
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -61,16 +61,20 @@ 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; },
+
_password: null,
get password() {
if (this._password === null)
return findPassword(this.realm, this.username);
return this._password;
},
set password(value) {
setPassword(this.realm, this.username, value);
--- a/services/sync/modules/log4moz.js
+++ b/services/sync/modules/log4moz.js
@@ -473,17 +473,17 @@ Log4MozService.prototype = {
},
newFileAppender: function LogSvc_newAppender(kind, file, formatter) {
switch (kind) {
case "file":
return new FileAppender(file, formatter);
case "rotating":
// FIXME: hardcoded constants
- return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 5, 0);
+ return new RotatingFileAppender(file, formatter, ONE_MEGABYTE * 2, 0);
default:
dump("log4moz: unknown appender kind: " + kind);
return;
}
},
newFormatter: function LogSvc_newFormatter(kind) {
switch (kind) {
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -44,19 +44,20 @@ 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/engines.js");
Cu.import("resource://weave/dav.js");
Cu.import("resource://weave/identity.js");
-
+Cu.import("resource://weave/async.js");
-Function.prototype.async = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
+let Crypto = new WeaveCrypto();
/*
* Service singleton
* Main entry point into Weave's sync framework
*/
function WeaveSyncService() { this._init(); }
WeaveSyncService.prototype = {
@@ -298,182 +299,184 @@ WeaveSyncService.prototype = {
},
_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(onComplete) {
- let [self, cont] = yield;
- let success = false;
+ _login: function WeaveSync__login() {
+ let self = yield;
try {
this._log.debug("Logging in");
this._os.notifyObservers(null, "weave:service-login:start", "");
- if (!this.username) {
- this._log.warn("No username set, login failed");
- return;
- }
- if (!this.password) {
- this._log.warn("No password given or found in password manager");
- return;
- }
+ 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, cont, this.username, this.password);
- success = yield;
+ this._dav.login.async(this._dav, self.cb, this.username, this.password);
+ let success = yield;
// 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, cont);
+ this._dav.MKCOL("user/" + this.userPath, self.cb);
let ret = yield;
+ if (!ret)
+ throw "Could not create user directory. Got status: " + ret.status;
+
+ 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.";
+ }
+
+ this._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.key = keyResp.responseText;
- if (ret.status == 201) {
- this._log.debug("Successfully created user directory. Re-attempting login.");
- this._dav.baseURL = serverURL + "user/" + this.userPath + "/";
- this._dav.login.async(this._dav, cont, this.username, this.password);
- success = yield;
- } else {
- this._log.debug("Could not create user directory. Got status: " + ret.status);
- }
+ } else {
+ // 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";
+
+ 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");
+
+ 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.error("Exception caught: " + e.message);
-
- } finally {
- this._passphrase = null;
- if (success) {
- //this._log.debug("Login successful"); // chrome prints this too, hm
- this._os.notifyObservers(null, "weave:service-login:success", "");
- } else {
- //this._log.debug("Login error");
- this._os.notifyObservers(null, "weave:service-login:error", "");
- }
- Utils.generatorDone(this, self, onComplete, success);
- yield; // onComplete is responsible for closing the generator
+ this._log.warn(Async.exceptionStr(self, e));
+ this._log.trace(e.trace);
+ this._os.notifyObservers(null, "weave:service-login:error", "");
+ self.done(false);
}
- this._log.warn("generator not properly closed");
},
- _resetLock: function WeaveSync__resetLock(onComplete) {
- let [self, cont] = yield;
+ _resetLock: function WeaveSync__resetLock() {
+ let self = yield;
let success = false;
try {
this._log.debug("Resetting server lock");
this._os.notifyObservers(null, "weave:server-lock-reset:start", "");
- this._dav.forceUnlock.async(this._dav, cont);
+ this._dav.forceUnlock.async(this._dav, self.cb);
success = yield;
} catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ throw e;
} 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", "");
}
- Utils.generatorDone(this, self, onComplete, success);
- yield; // generatorDone is responsible for closing the generator
+ self.done(success);
}
- this._log.warn("generator not properly closed");
},
_sync: function WeaveSync__sync() {
- let [self, cont] = yield;
+ 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(cont);
+ this._bmkEngine.sync(self.cb);
yield;
}
if (this._prefs.getBoolPref("history")) {
- this._histEngine.sync(cont);
+ this._histEngine.sync(self.cb);
yield;
}
success = true;
this._unlock();
} catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ throw e;
} finally {
if (success)
this._os.notifyObservers(null, "weave:service:sync:success", "");
else
this._os.notifyObservers(null, "weave:service:sync:error", "");
- Utils.generatorDone(this, self);
- yield; // generatorDone is responsible for closing the generator
+ self.done();
}
- this._log.warn("generator not properly closed");
},
_resetServer: function WeaveSync__resetServer() {
- let [self, cont] = yield;
+ let self = yield;
- try {
- if (!this._lock())
- return;
-
- this._bmkEngine.resetServer(cont);
- this._histEngine.resetServer(cont);
+ if (!this._lock())
+ return;
- this._unlock();
-
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ this._bmkEngine.resetServer(self.cb);
+ this._histEngine.resetServer(self.cb);
- } finally {
- Utils.generatorDone(this, self);
- yield; // generatorDone is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ this._unlock();
+ self.done();
},
_resetClient: function WeaveSync__resetClient() {
- let [self, cont] = yield;
+ let self = yield;
- try {
- if (!this._lock())
- return;
-
- this._bmkEngine.resetClient(cont);
- this._histEngine.resetClient(cont);
+ if (!this._lock())
+ return;
- this._unlock();
-
- } catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ this._bmkEngine.resetClient(self.cb);
+ this._histEngine.resetClient(self.cb);
- } finally {
- Utils.generatorDone(this, self);
- yield; // generatorDone is responsible for closing the generator
- }
- this._log.warn("generator not properly closed");
+ this._unlock();
+ self.done();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]),
// nsIObserver
observe: function WeaveSync__observe(subject, topic, data) {
if (topic != "nsPref:changed")
--- a/services/sync/modules/syncCores.js
+++ b/services/sync/modules/syncCores.js
@@ -40,18 +40,19 @@ 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 = Utils.generatorAsync;
+Function.prototype.async = Async.sugar;
/*
* SyncCore objects
* Sync cores deal with diff creation and conflict resolution.
* Tree data structures where all nodes have GUIDs only need to be
* subclassed for each data type to implement commandLike and
* itemExists.
*/
@@ -84,19 +85,19 @@ SyncCore.prototype = {
_nodeParentsInt: function SC__nodeParentsInt(GUID, tree, parents) {
if (!tree[GUID] || !tree[GUID].parentGUID)
return parents;
parents.push(tree[GUID].parentGUID);
return this._nodeParentsInt(tree[GUID].parentGUID, tree, parents);
},
- _detectUpdates: function SC__detectUpdates(onComplete, a, b) {
- let [self, cont] = yield;
- let listener = new Utils.EventListener(cont);
+ _detectUpdates: function SC__detectUpdates(a, b) {
+ let self = yield;
+ let listener = new Utils.EventListener(self.cb);
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
let cmds = [];
try {
for (let GUID in a) {
timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
@@ -111,16 +112,17 @@ SyncCore.prototype = {
depth: parents.length, parents: parents,
data: edits.props});
} else {
let parents = this._nodeParents(GUID, a); // ???
cmds.push({action: "remove", GUID: GUID,
depth: parents.length, parents: parents});
}
}
+
for (let GUID in b) {
timer.initWithCallback(listener, 0, timer.TYPE_ONE_SHOT);
yield; // Yield to main loop
if (GUID in a)
continue;
let parents = this._nodeParents(GUID, b);
@@ -136,24 +138,22 @@ SyncCore.prototype = {
if (a.index > b.index)
return -1;
if (a.index < b.index)
return 1;
return 0; // should never happen, but not a big deal if it does
});
} catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ throw e;
} finally {
timer = null;
- Utils.generatorDone(this, self, onComplete, cmds);
- yield; // onComplete is responsible for closing the generator
+ self.done(cmds);
}
- this._log.warn("generator not properly closed");
},
_commandLike: function SC__commandLike(a, b) {
this._log.error("commandLike needs to be subclassed");
// Check that neither command is null, and verify that the GUIDs
// are different (otherwise we need to check for edits)
if (!a || !b || a.GUID == b.GUID)
@@ -211,19 +211,19 @@ SyncCore.prototype = {
}
},
_itemExists: function SC__itemExists(GUID) {
this._log.error("itemExists needs to be subclassed");
return false;
},
- _reconcile: function SC__reconcile(onComplete, listA, listB) {
- let [self, cont] = yield;
- let listener = new Utils.EventListener(cont);
+ _reconcile: function SC__reconcile(listA, listB) {
+ let self = yield;
+ let listener = new Utils.EventListener(self.cb);
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");
@@ -292,24 +292,23 @@ SyncCore.prototype = {
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};
} catch (e) {
- this._log.error("Exception caught: " + (e.message? e.message : e));
+ this._log.error("Exception caught: " + (e.message? e.message : e) +
+ " - " + (e.location? e.location : "_reconcile"));
} finally {
timer = null;
- Utils.generatorDone(this, self, onComplete, ret);
- yield; // onComplete is responsible for closing the generator
+ self.done(ret);
}
- this._log.warn("generator not properly closed");
},
// Public methods
detectUpdates: function SC_detectUpdates(onComplete, a, b) {
return this._detectUpdates.async(this, onComplete, a, b);
},
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -108,31 +108,53 @@ let Utils = {
ret = {};
for (let key in thing)
ret[key] = Utils.deepCopy(thing[key]);
}
return ret;
},
+ exceptionStr: function Weave_exceptionStr(e) {
+ let message = e.message? e.message : e;
+ let location = e.location? " (" + e.location + ")" : "";
+ return message + location;
+ },
+
+ stackTrace: function Weave_stackTrace(stackFrame, str) {
+ if (stackFrame.caller)
+ str = Utils.stackTrace(stackFrame.caller, str);
+
+ if (!str)
+ str = "";
+ str += stackFrame + "\n";
+
+ return str;
+ },
+
checkStatus: function Weave_checkStatus(code, msg, ranges) {
if (!ranges)
ranges = [[200,300]];
for (let i = 0; i < ranges.length; i++) {
rng = ranges[i];
if (typeof(rng) == "object" && code >= rng[0] && code < rng[1])
- return;
+ return true;
else if (typeof(rng) == "integer" && code == rng)
- return;
+ return true;
}
let log = Log4Moz.Service.getLogger("Service.Util");
log.error(msg + " Error code: " + code);
- throw 'checkStatus failed';
+ return false;
+ },
+
+ ensureStatus: function Weave_ensureStatus(args) {
+ if (!Utils.checkStatus.apply(Utils, arguments))
+ throw 'checkStatus failed';
},
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);
@@ -142,83 +164,16 @@ let Utils = {
let root = xmlDoc.ownerDocument == null ?
xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement
let nsResolver = xmlDoc.createNSResolver(root);
return xmlDoc.evaluate(xpathString, xmlDoc, nsResolver,
Ci.nsIDOMXPathResult.ANY_TYPE, null);
},
- bind2: function Weave_bind2(object, method) {
- return function innerBind() { return method.apply(object, arguments); }
- },
-
- // Meant to be used like this in code that imports this file:
- //
- // Function.prototype.async = generatorAsync;
- //
- // So that you can do:
- //
- // gen = fooGen.async(...);
- // ret = yield;
- //
- // where fooGen is a generator function, and gen is the running generator.
- // ret is whatever the generator 'returns' via generatorDone().
-
- generatorAsync: function Weave_generatorAsync(self, extra_args) {
- try {
- let args = Array.prototype.slice.call(arguments, 1);
- let gen = this.apply(self, args);
- gen.next(); // must initialize before sending
- gen.send([gen, function(data) {Utils.continueGenerator(gen, data);}]);
- return gen;
- } catch (e) {
- if (e instanceof StopIteration) {
- dump("async warning: generator stopped unexpectedly");
- return null;
- } else {
- dump("Exception caught: " + e.message);
- }
- }
- },
-
- continueGenerator: function Weave_continueGenerator(generator, data) {
- try { generator.send(data); }
- catch (e) {
- if (e instanceof StopIteration)
- dump("continueGenerator warning: generator stopped unexpectedly");
- else
- dump("Exception caught: " + e.message);
- }
- },
-
- // generators created using Function.async can't simply call the
- // 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 clean.
- generatorDone: function Weave_generatorDone(object, generator, callback, retval) {
- if (object._timer)
- throw "Called generatorDone when there is a timer already set."
-
- let cb = Utils.bind2(object, function(event) {
- generator.close();
- generator = null;
- object._timer = null;
- if (callback)
- callback(retval);
- });
-
- object._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- object._timer.initWithCallback(new Utils.EventListener(cb),
- 0, object._timer.TYPE_ONE_SHOT);
- },
-
runCmd: function Weave_runCmd() {
var binary;
var args = [];
for (let i = 0; i < arguments.length; ++i) {
args.push(arguments[i]);
}
@@ -313,16 +268,20 @@ let Utils = {
readStream: function Weave_readStream(is) {
let ret = "", str = {};
while (is.readString(4096, str) != 0) {
ret += str.value;
}
return ret;
},
+ bind2: function Async_bind2(object, method) {
+ return function innerBind() { return method.apply(object, arguments); }
+ },
+
/*
* Event listener object
* Used to handle XMLHttpRequest and nsITimer callbacks
*/
EventListener: function Weave_EventListener(handler, eventName) {
this._handler = handler;
this._eventName = eventName;
@@ -336,12 +295,12 @@ Utils.EventListener.prototype = {
// DOM event listener
handleEvent: function EL_handleEvent(event) {
this._log.trace("Handling event " + this._eventName);
this._handler(event);
},
// nsITimerCallback
notify: function EL_notify(timer) {
- this._log.trace("Timer fired");
+ //this._log.trace("Timer fired");
this._handler(timer);
}
}