--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -47,82 +47,52 @@ const OUTGOING_SHARED_ANNO = "weave/shar
const SERVER_PATH_ANNO = "weave/shared-server-path";
// Standard names for shared files on the server
const KEYRING_FILE_NAME = "keyring";
const SHARED_BOOKMARK_FILE_NAME = "shared_bookmarks";
// Information for the folder that contains all incoming shares
const INCOMING_SHARE_ROOT_ANNO = "weave/mounted-shares-folder";
const INCOMING_SHARE_ROOT_NAME = "Shared Folders";
-
Cu.import("resource://weave/log4moz.js");
Cu.import("resource://weave/dav.js");
Cu.import("resource://weave/util.js");
Cu.import("resource://weave/crypto.js");
Cu.import("resource://weave/async.js");
Cu.import("resource://weave/engines.js");
Cu.import("resource://weave/syncCores.js");
Cu.import("resource://weave/stores.js");
Cu.import("resource://weave/trackers.js");
Cu.import("resource://weave/identity.js");
-
-/* LONGTERM TODO: when we start working on the ability to share other types
-of data besides bookmarks, the xmppClient instance should be moved to hang
-off of Weave.Service instead of hanging off the BookmarksEngine. But for
-now this is the easiest place to deal with it. */
Cu.import("resource://weave/xmpp/xmppClient.js");
Function.prototype.async = Async.sugar;
-function BookmarksEngine(pbeId) {
- this._init(pbeId);
+function BookmarksSharingManager(engine) {
+ this._init(engine);
}
-BookmarksEngine.prototype = {
- get name() { return "bookmarks"; },
- get logName() { return "BmkEngine"; },
- get serverPrefix() { return "user-data/bookmarks/"; },
-
- __core: null,
- get _core() {
- if (!this.__core)
- this.__core = new BookmarksSyncCore();
- return this.__core;
- },
-
- __store: null,
- get _store() {
- if (!this.__store)
- this.__store = new BookmarksStore();
- return this.__store;
- },
-
- __tracker: null,
- get _tracker() {
- if (!this.__tracker)
- this.__tracker = new BookmarksTracker();
- return this.__tracker;
- },
-
+BookmarksSharingManager.prototype = {
__annoSvc: null,
get _annoSvc() {
if (!this.__anoSvc)
this.__annoSvc = Cc["@mozilla.org/browser/annotation-service;1"].
getService(Ci.nsIAnnotationService);
return this.__annoSvc;
},
- _init: function BmkEngine__init( pbeId ) {
- this.__proto__.__proto__._init.call( this, pbeId );
+ _init: function SharingManager__init(engine) {
+ this._engine = engine;
+ this._log = Log4Moz.Service.getLogger("Bookmark Share");
if ( Utils.prefs.getBoolPref( "xmpp.enabled" ) ) {
dump( "Starting XMPP client for bookmark engine..." );
this._startXmppClient.async(this);
}
},
- _startXmppClient: function BmkEngine__startXmppClient() {
+ _startXmppClient: function BmkSharing__startXmppClient() {
// To be called asynchronously.
let self = yield;
// Get serverUrl and realm of the jabber server from preferences:
let serverUrl = Utils.prefs.getCharPref( "xmpp.server.url" );
let realm = Utils.prefs.getCharPref( "xmpp.server.realm" );
/* Username/password for XMPP are the same; as the ones for Weave,
@@ -135,17 +105,17 @@ BookmarksEngine.prototype = {
/* LONGTERM TODO would prefer to use MD5Authenticator instead,
once we get it working, but since we are connecting over SSL, the
Plain auth is probably fine for now. */
this._xmppClient = new XmppClient( clientName,
realm,
clientPassword,
transport,
auth );
- let bmkEngine = this;
+ let bmkSharing = this;
let messageHandler = {
handle: function ( messageText, from ) {
/* The callback function for incoming xmpp messages.
We expect message text to be in the form of:
<command> <serverpath> <foldername>.
Where command is one of:
"share" (sender offers to share directory dir with us)
or "stop" (sender has stopped sharing directory dir with us.)
@@ -157,77 +127,63 @@ BookmarksEngine.prototype = {
on the server to the directory where the data is stored:
only the machine seese this, and it can't have spaces.
*/
let words = messageText.split(" ");
let commandWord = words[0];
let serverPath = words[1];
let directoryName = words.slice(2).join(" ");
if ( commandWord == "share" ) {
- bmkEngine._incomingShareOffer(from, serverPath, folderName);
+ bmkSharing._incomingShareOffer(from, serverPath, folderName);
} else if ( commandWord == "stop" ) {
- bmkEngine._incomingShareWithdrawn(from, serverPath, folderName);
+ bmkSharing._incomingShareWithdrawn(from, serverPath, folderName);
}
}
};
this._xmppClient.registerMessageHandler( messageHandler );
this._xmppClient.connect( realm, self.cb );
yield;
if ( this._xmppClient._connectionStatus == this._xmppClient.FAILED ) {
this._log.warn( "Weave can't log in to xmpp server: xmpp disabled." );
} else if ( this._xmppClient._connectionStatus == this._xmppClient.CONNECTED ) {
this._log.info( "Weave logged into xmpp OK." );
}
self.done();
},
- _incomingShareOffer: function BmkEngine__incomingShareOffer(user,
+ _incomingShareOffer: function BmkSharing__incomingShareOffer(user,
serverPath,
folderName) {
/* Called when we receive an offer from another user to share a
folder.
TODO what should happen is that we add a notification to the queue
telling that the incoming share has been offered; when the offer
is accepted we will call createIncomingShare and then
updateIncomingShare.
But since we don't have notification in place yet, I'm going to skip
right ahead to creating the incoming share.
*/
this._log.info("User " + user + " offered to share folder " + folderName);
- this._createIncomingShare( user, serverPath, folderName );
+ this._createIncomingShare(user, serverPath, folderName);
},
- _incomingShareWithdrawn: function BmkEngine__incomingShareStop(user,
+ _incomingShareWithdrawn: function BmkSharing__incomingShareStop(user,
serverPath,
folderName) {
/* Called when we receive a message telling us that a user who has
already shared a directory with us has chosen to stop sharing
the directory.
*/
this._log.info("User " + user + " stopped sharing folder " + folderName);
this._stopIncomingShare(user, serverPath, folderName);
},
-
- _sync: function BmkEngine__sync() {
- /* After syncing, also call syncMounts to get the
- incoming shared bookmark folder contents. */
- let self = yield;
- this.__proto__.__proto__._sync.async(this, self.cb );
- yield;
- this.updateAllOutgoingShares(self.cb);
- yield;
- this.updateAllIncomingShares(self.cb);
- yield;
- self.done();
- },
-
- _share: function BmkEngine__share( selectedFolder, username ) {
+ _share: function BmkSharing__share( selectedFolder, username ) {
// Return true if success, false if failure.
let ret = false;
let self = yield;
/* TODO What should the behavior be if i'm already sharing it with user
A and I ask to share it with user B? (This should be prevented by
the UI. */
@@ -261,17 +217,17 @@ BookmarksEngine.prototype = {
with many people, the value of the annotation can be a whole list
of usernames instead of just one. */
this._log.info("Shared " + folderName +" with " + username);
ret = true;
self.done( ret );
},
- _stopSharing: function BmkEngine__stopSharing( selectedFolder, username ) {
+ _stopSharing: function BmkSharing__stopSharing( selectedFolder, username ) {
let self = yield;
let folderName = selectedFolder.getAttribute( "label" );
let serverPath = this._annoSvc.getItemAnnotation(folderNode,
SERVER_PATH_ANNO);
/* LONGTERM TODO: when we move to being able to share one folder with
* multiple people, this needs to be modified so we can stop sharing with
* one person but keep sharing with others.
@@ -291,59 +247,59 @@ BookmarksEngine.prototype = {
this._log.warn( "No XMPP connection for share notification." );
}
}
this._log.info("Stopped sharing " + folderName + "with " + username);
self.done( true );
},
- updateAllIncomingShares: function BmkEngine_updateAllIncoming(onComplete) {
+ updateAllIncomingShares: function BmkSharing_updateAllIncoming(onComplete) {
this._updateAllIncomingShares.async(this, onComplete);
},
- _updateAllIncomingShares: function BmkEngine__updateAllIncoming() {
+ _updateAllIncomingShares: function BmkSharing__updateAllIncoming() {
/* For every bookmark folder in my tree that has the annotation
marking it as an incoming shared folder, pull down its latest
contents from its owner's account on the server. (This is
a one-way data transfer because I can't modify bookmarks that
are owned by someone else but shared to me; any changes I make
to the folder contents are simply wiped out by the latest
server contents.) */
let self = yield;
- let mounts = this._store.findIncomingShares();
+ let mounts = this._engine._store.findIncomingShares();
for (let i = 0; i < mounts.length; i++) {
try {
this._updateIncomingShare.async(this, self.cb, mounts[i]);
yield;
} catch (e) {
this._log.warn("Could not sync shared folder from " + mounts[i].userid);
this._log.trace(Utils.stackTrace(e));
}
}
},
- updateAllOutgoingShares: function BmkEngine_updateAllOutgoing(onComplete) {
+ updateAllOutgoingShares: function BmkSharing_updateAllOutgoing(onComplete) {
this._updateAllOutgoingShares.async(this, onComplete);
},
- _updateAllOutgoingShares: function BmkEngine__updateAllOutgoing() {
+ _updateAllOutgoingShares: function BmkSharing__updateAllOutgoing() {
let self = yield;
let shares = this._annoSvc.getItemsWithAnnotation(OUTGOING_SHARED_ANNO,
{});
for ( let i=0; i < shares.length; i++ ) {
/* TODO only update the shares that have changed. Perhaps we can
do this by checking whether there's a corresponding entry in the
diff produced by the latest sync. */
this._updateOutgoingShare.async(this, self.cb, shares[i]);
yield;
}
self.done();
},
- _createOutgoingShare: function BmkEngine__createOutgoing(folder, username) {
+ _createOutgoingShare: function BmkSharing__createOutgoing(folder, username) {
/* To be called asynchronously. Folder is a node indicating the bookmark
folder that is being shared; username is a string indicating the user
that it is to be shared with. This function creates the directory and
keyring on the server in which the shared data will be put, but it
doesn't actually put the bookmark data there (that's done in
_updateOutgoingShare().) */
let self = yield;
let myUserName = ID.get('WeaveID').username;
@@ -401,29 +357,30 @@ BookmarksEngine.prototype = {
let keys = {
ring : { },
bulkIV : bulkIV
};
keys.ring[myUserName] = encryptedForMe;
keys.ring[username] = encryptedForYou;
let keyringFile = new Resource( serverPath + "/" + KEYRING_FILE_NAME );
- keyringFile.put( self.cb, this._json.encode( keys ) );
+ let jsonService = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+ keyringFile.put( self.cb, jsonService.encode( keys ) );
yield;
// Call Atul's js api for setting htaccess:
let sharingApi = new Sharing.Api( DAV );
sharingApi.shareWithUsers( serverPath, [username], self.cb );
let result = yield;
// return the server path:
self.done( serverPath );
},
- _updateOutgoingShare: function BmkEngine__updateOutgoing(folderNode) {
+ _updateOutgoingShare: function BmkSharing__updateOutgoing(folderNode) {
/* Puts all the bookmark data from the specified bookmark folder,
encrypted, onto the shared directory on the server (replacing
anything that was already there).
To be called asynchronously.
TODO: error handling*/
let self = yield;
let myUserName = ID.get('WeaveID').username;
// The folder has an annotation specifying the server path to the
@@ -440,17 +397,17 @@ BookmarksEngine.prototype = {
// Unwrap (decrypt) the key with the user's private key.
let idRSA = ID.get('WeaveCryptoID');
let bulkKey = yield Crypto.unwrapKey.async(Crypto, self.cb,
keys.ring[myUserName], idRSA);
let bulkIV = keys.bulkIV;
// Get the json-wrapped contents of everything in the folder:
- let json = this._store._wrapMount( folderNode, myUserName );
+ let json = this._engine._store._wrapMount( folderNode, myUserName );
/* TODO what does wrapMount do with this username? Should I be passing
in my own or that of the person I share with? */
// Encrypt it with the symkey and put it into the shared-bookmark file.
let bmkFile = new Resource(serverPath + "/" + SHARED_BOOKMARK_FILE_NAME);
let tmpIdentity = {
realm : "temp ID",
bulkKey : bulkKey,
@@ -458,21 +415,20 @@ BookmarksEngine.prototype = {
};
Crypto.encryptData.async( Crypto, self.cb, json, tmpIdentity );
let cyphertext = yield;
bmkFile.put( self.cb, cyphertext );
yield;
self.done();
},
- _stopOutgoingShare: function BmkEngine__stopOutgoingShare(folderNode) {
+ _stopOutgoingShare: function BmkSharing__stopOutgoingShare(folderNode) {
/* Stops sharing the specified folder. Deletes its data from the
server, deletes the annotations that mark it as shared, and sends
a message to the shar-ee to let them know it's been withdrawn. */
- // TODO: currently not called from anywhere.
let self = yield;
let serverPath = this._annoSvc.getItemAnnotation( folderNode,
SERVER_PATH_ANNO );
let username = this._annoSvc.getItemAnnotation( folderNode,
OUTGOING_SHARED_ANNO );
// Delete the share from the server:
let keyringFile = new Resource(serverPath + "/" + KEYRING_FILE_NAME);
@@ -557,17 +513,17 @@ BookmarksEngine.prototype = {
this._annoSvc.setItemAnnotation(newId,
SERVER_PATH_ANNO,
serverPath,
0,
this._annoSvc.EXPIRE_NEVER);
}
},
- _updateIncomingShare: function BmkEngine__updateIncomingShare(mountData) {
+ _updateIncomingShare: function BmkSharing__updateIncomingShare(mountData) {
/* Pull down bookmarks from the server for a single incoming
shared folder, obliterating whatever was in that folder before.
mountData is an object that's expected to have member data:
userid: weave id of the user sharing the folder with us,
rootGUID: guid in our bookmark store of the share mount point,
node: the bookmark menu node for the share mount point folder,
snapshot: the json-wrapped current contents of the share. */
@@ -604,28 +560,28 @@ BookmarksEngine.prototype = {
delete json[guid];
else
json[guid].parentGUID = mountData.rootGUID;
}
/* Create diff between the json from server and the current contents;
then apply the diff. */
this._log.trace("Got bookmarks from " + user + ", comparing with local copy");
- this._core.detectUpdates(self.cb, mountData.snapshot, snap.data);
+ this._engine._core.detectUpdates(self.cb, mountData.snapshot, snap.data);
let diff = yield;
// FIXME: should make sure all GUIDs here live under the mountpoint
this._log.trace("Applying changes to folder from " + user);
- this._store.applyCommands.async(this._store, self.cb, diff);
+ this._engine._store.applyCommands.async(this._engine._store, self.cb, diff);
yield;
this._log.trace("Shared folder from " + user + " successfully synced!");
},
- _stopIncomingShare: function BmkEngine__stopIncomingShare(user,
+ _stopIncomingShare: function BmkSharing__stopIncomingShare(user,
serverPath,
folderName)
{
/* Delete the incoming share folder. Since the update of incoming folders
* is triggered when the engine spots a folder with a certain annotation on
* it, just getting rid of this folder is all we need to do.
*/
let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
@@ -635,16 +591,69 @@ BookmarksEngine.prototype = {
for (let i = 0; i < a.length; i++) {
let creator = this._annoSvc.getItemAnnotation(a[i], OUTGOING_SHARED_ANNO);
let path = this._annoSvc.getItemAnnotation(a[i], SERVER_PATH_ANNO);
if ( creator == user && path == serverPath ) {
bms.removeFolder( a[i]);
}
}
}
+}
+
+
+
+function BookmarksEngine(pbeId) {
+ this._init(pbeId);
+}
+BookmarksEngine.prototype = {
+ get name() { return "bookmarks"; },
+ get logName() { return "BmkEngine"; },
+ get serverPrefix() { return "user-data/bookmarks/"; },
+
+ __core: null,
+ get _core() {
+ if (!this.__core)
+ this.__core = new BookmarksSyncCore();
+ return this.__core;
+ },
+
+ __store: null,
+ get _store() {
+ if (!this.__store)
+ this.__store = new BookmarksStore();
+ return this.__store;
+ },
+
+ __tracker: null,
+ get _tracker() {
+ if (!this.__tracker)
+ this.__tracker = new BookmarksTracker();
+ return this.__tracker;
+ },
+
+ __sharing: null,
+ get _sharing() {
+ if (!this.__sharing)
+ this.__sharing = new BookmarksSharingManager(this);
+ return this.__sharing;
+ },
+
+ _sync: function BmkEngine__sync() {
+ /* After syncing the regular bookmark folder contents,
+ * also update both the incoming and outgoing shared folders. */
+ let self = yield;
+ this.__proto__.__proto__._sync.async(this, self.cb );
+ yield;
+ this._sharing.updateAllOutgoingShares(self.cb);
+ yield;
+ this._sharing.updateAllIncomingShares(self.cb);
+ yield;
+ self.done();
+ }
+
};
BookmarksEngine.prototype.__proto__ = new Engine();
function BookmarksSyncCore() {
this._init();
}
BookmarksSyncCore.prototype = {
_logName: "BMSync",