remove sync cores, they are no longer used; remove sharing code from bookmarks (we can bring it back when we need it again, needs work); have the engine give hints to the store about when to wrap all items and cache them (to make subsequent calls to wrap one item fast); move serializeItem/getAllIDs into the store

This commit is contained in:
Dan Mills 2008-12-08 09:53:32 -08:00
Родитель 55df62217e
Коммит e11a3250a5
3 изменённых файлов: 60 добавлений и 795 удалений

Просмотреть файл

@ -52,7 +52,6 @@ Cu.import("resource://weave/resource.js");
Cu.import("resource://weave/clientData.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");
@ -136,19 +135,13 @@ Engine.prototype = {
get score() this._tracker.score,
// _core, _store, and tracker need to be overridden in subclasses
// _store, and tracker need to be overridden in subclasses
get _store() {
let store = new Store();
this.__defineGetter__("_store", function() store);
return store;
},
get _core() {
let core = new SyncCore(this._store);
this.__defineGetter__("_core", function() core);
return core;
},
get _tracker() {
let tracker = new Tracker();
this.__defineGetter__("_tracker", function() tracker);
@ -294,15 +287,15 @@ SyncEngine.prototype = {
},
// Create a new record starting from an ID
// Calls _serializeItem to get the actual item, but sometimes needs to be
// Calls _store.wrapItem to get the actual item, but sometimes needs to be
// overridden anyway (to alter parentid or other properties outside the payload)
_createRecord: function SyncEngine__newCryptoWrapper(id, encrypt) {
_createRecord: function SyncEngine__createRecord(id, encrypt) {
let self = yield;
let record = new CryptoWrapper();
record.uri = this.engineURL + id;
record.encryption = this.cryptoMetaURL;
record.cleartext = yield this._serializeItem.async(this, self.cb, id);
record.cleartext = this._store.wrapItem(id);
if (record.cleartext && record.cleartext.parentid)
record.parentid = record.cleartext.parentid;
@ -313,18 +306,6 @@ SyncEngine.prototype = {
self.done(record);
},
// Serialize an item. This will become the encrypted field in the payload of
// a record. Needs to be overridden in a subclass
_serializeItem: function SyncEngine__serializeItem(id) {
let self = yield;
self.done({});
},
_getAllIDs: function SyncEngine__getAllIDs() {
let self = yield;
self.done({});
},
// Check if a record is "like" another one, even though the IDs are different,
// in that case, we'll change the ID of the local item to match
// Probably needs to be overridden in a subclass, to change which criteria
@ -406,19 +387,27 @@ SyncEngine.prototype = {
this._tracker.clearChangedIDs();
// now add all current ones
let all = yield this._getAllIDs.async(this, self.cb);
let all = this._store.getAllIDs();
for (let id in all) {
this._tracker.changedIDs[id] = true;
}
}
// generate queue from changed items list
// XXX should have a heuristic like this, but then we need to be able to
// serialize each item by itself, something our stores can't currently do
//if (this._tracker.changedIDs.length >= 30)
this._store.cacheItemsHint();
// NOTE we want changed items -> outgoing -> server to be as atomic as
// possible, so we clear the changed IDs after we upload the changed records
// NOTE2 don't encrypt, we'll do that before uploading instead
for (let id in this._tracker.changedIDs) {
this.outgoing.push(yield this._createRecord.async(this, self.cb, id, false));
}
this._store.clearItemCacheHint();
},
// Generate outgoing records

Просмотреть файл

@ -59,658 +59,14 @@ 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");
Cu.import("resource://weave/xmpp/xmppClient.js");
Cu.import("resource://weave/notifications.js");
Cu.import("resource://weave/sharing.js");
Cu.import("resource://weave/resource.js");
Function.prototype.async = Async.sugar;
function BookmarksSharingManager(engine) {
this._init(engine);
}
BookmarksSharingManager.prototype = {
__annoSvc: null,
get _annoSvc() {
if (!this.__anoSvc)
this.__annoSvc = Cc["@mozilla.org/browser/annotation-service;1"].
getService(Ci.nsIAnnotationService);
return this.__annoSvc;
},
__bms: null,
get _bms() {
if (!this.__bms)
this.__bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
return this.__bms;
},
__myUsername: null,
get _myUsername() {
if (!this.__myUsername)
this.__myUsername = ID.get('WeaveID').username;
return this.__myUsername;
},
_init: function SharingManager__init(engine) {
this._engine = engine;
this._log = Log4Moz.repository.getLogger("Bookmark Share");
if ( Utils.prefs.getBoolPref( "xmpp.enabled" ) ) {
this._log.info( "Starting XMPP client for bookmark engine..." );
this._startXmppClient.async(this);
}
},
_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,
so read them from the weave identity: */
let clientName = this._myUsername;
let clientPassword = ID.get('WeaveID').password;
let transport = new HTTPPollingTransport( serverUrl, false, 15000 );
let auth = new PlainAuthenticator();
/* 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 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.)
or "accept" (sharee has accepted our offer to share our dir.)
or "decline" (sharee has declined our offer to share our dir.)
Folder name is the human-readable name of the bookmark folder
being shared (it can contain spaces). serverpath is the path
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" ) {
bmkSharing._incomingShareOffer(from, serverPath, folderName);
} else if ( commandWord == "stop" ) {
bmkSharing._log.info("User " + user + " withdrew " + folderName);
bmkSharing._stopIncomingShare(user, 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 BmkSharing__incomingShareOffer(user,
serverPath,
folderName) {
/* Called when we receive an offer from another user to share a
folder. Set up a notification telling our user about the share offer
and allowing them to accept or reject it.
*/
this._log.info("User " + user + " offered to share folder " + folderName);
let bmkSharing = this;
let acceptButton = new NotificationButton(
"Accept Share",
"a",
function() {
// This is what happens when they click the Accept button:
bmkSharing._log.info("Accepted bookmark share from " + user);
bmkSharing._createIncomingShare(user, serverPath, folderName);
bmkSharing.updateAllIncomingShares();
return false;
}
);
let rejectButton = new NotificationButton(
"No Thanks",
"n",
function() {return false;}
);
let title = "Bookmark Share Offer From " + user;
let description ="Weave user " + user +
" is offering to share a bookmark folder called " + folderName +
" with you. Do you want to accept it?";
let notification = new Notification(title,
description,
null,
Notifications.PRIORITY_INFO,
[acceptButton, rejectButton]
);
Notifications.add(notification);
},
_sendXmppNotification: function BmkSharing__sendXmpp(recipient, cmd, path, name) {
// Send an xmpp message to the share-ee
if ( this._xmppClient ) {
if ( this._xmppClient._connectionStatus == this._xmppClient.CONNECTED ) {
let msgText = "share " + path + " " + name;
this._log.debug( "Sending XMPP message: " + msgText );
this._xmppClient.sendMessage( recipient, msgText );
} else {
this._log.warn( "No XMPP connection for share notification." );
}
}
},
_share: function BmkSharing__share( folderId, 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. */
let folderName = this._bms.getItemTitle(folderId);
// Create the outgoing share folder on the server
this._createOutgoingShare.async( this, self.cb,
folderId, folderName, username );
let serverPath = yield;
this._updateOutgoingShare.async( this, self.cb, folderId );
yield;
/* Set the annotation on the folder so we know
it's an outgoing share: */
this._annoSvc.setItemAnnotation(folderId,
OUTGOING_SHARED_ANNO,
username,
0,
this._annoSvc.EXPIRE_NEVER);
/* LONGTERM TODO: in the future when we allow sharing one folder
with many people, the value of the annotation can be a whole list
of usernames instead of just one. */
// The serverPath is relative; prepend it with /user/myusername/ to make
// it absolute.
let abspath = "/user/" + this._myUsername + "/" + serverPath;
this._sendXmppNotification( username, "share", abspath, folderName);
this._log.info("Shared " + folderName +" with " + username);
ret = true;
self.done( ret );
},
_stopSharing: function BmkSharing__stopSharing( folderId, username ) {
let self = yield;
let folderName = this._bms.getItemTitle(folderId);
let serverPath = "";
if (this._annoSvc.itemHasAnnotation(folderId, SERVER_PATH_ANNO)){
serverPath = this._annoSvc.getItemAnnotation(folderId, SERVER_PATH_ANNO);
} else {
this._log.warn("The folder you are de-sharing has no path annotation.");
}
/* 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.
*/
// Stop the outgoing share:
this._stopOutgoingShare.async(this, self.cb, folderId);
yield;
// Send message to the share-ee, so they can stop their incoming share
let abspath = "/user/" + this._myUsername + "/" + serverPath;
this._sendXmppNotification( username, "stop", abspath, folderName );
this._log.info("Stopped sharing " + folderName + "with " + username);
self.done( true );
},
/* FIXME! Gets all shares, not just the new ones. Doesn't impact
functionality because _incomingShareOffer does not create
duplicates, but annoys the user by showing notification of ALL
shares on EVERY sync :(
*/
getNewShares: function BmkSharing_getNewShares(onComplete) {
this._getNewShares.async(this, onComplete);
},
_getNewShares: function BmkSharing__getNewShares() {
let self = yield;
// FIXME
// let sharingApi = new Sharing.Api( DAV );
let result = yield sharingApi.getShares(self.cb);
this._log.info("Got Shares: " + result);
let shares = result.split(',');
if (shares.length > 1) {
this._log.info('Found shares');
for (var i = 0; i < shares.length - 1; i++) {
let share = shares[i].split(':');
let name = share[0];
let user = share[1];
let path = share[2];
this._incomingShareOffer(user, '/user/' + user + '/' + path, name);
}
}
},
updateAllIncomingShares: function BmkSharing_updateAllIncoming(onComplete) {
this._updateAllIncomingShares.async(this, onComplete);
},
_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._engine._store.findIncomingShares();
for (let i = 0; i < mounts.length; i++) {
try {
this._log.trace("Update incoming share from " + mounts[i].serverPath);
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 BmkSharing_updateAllOutgoing(onComplete) {
this._updateAllOutgoingShares.async(this, onComplete);
},
_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();
},
_createKeyChain: function BmkSharing__createKeychain(serverPath,
myUserName,
username){
/* Creates a new keychain file on the server, inside the directory given
* by serverPath. The keychain file contains keys for both me (the
* current user) and for the friend specified by username (which must
* be a valid weave user id.)
*/
let self = yield;
// Create a new symmetric key, to be used only for encrypting this share.
// XXX HACK. Seems like the engine shouldn't have to be doing any of this, or
// should use its own identity here.
let tmpIdentity = {
realm : "temp ID",
bulkKey : null,
bulkIV : null
};
Crypto.randomKeyGen.async(Crypto, self.cb, tmpIdentity);
yield;
let bulkKey = tmpIdentity.bulkKey;
let bulkIV = tmpIdentity.bulkIV;
/* Get public keys for me and the user I'm sharing with.
Each user's public key is stored in /user/username/public/pubkey. */
let idRSA = ID.get('WeaveCryptoID');
let userPubKeyFile = new Resource("/user/" + username + "/public/pubkey");
userPubKeyFile.pushFilter( new JsonFilter() );
// The above can get a 401 if .htaccess file isnot set to give public
//access to the "public" directory. It can also get a 502?
userPubKeyFile.get(self.cb);
let userPubKey = yield;
userPubKey = userPubKey.pubkey;
/* Create the keyring, containing the sym key encrypted with each
of our public keys: */
Crypto.wrapKey.async(Crypto, self.cb, bulkKey, {realm : "tmpWrapID",
pubkey: idRSA.pubkey} );
let encryptedForMe = yield;
Crypto.wrapKey.async(Crypto, self.cb, bulkKey, {realm : "tmpWrapID",
pubkey: userPubKey} );
let encryptedForYou = yield;
let keys = {
ring : { },
bulkIV : bulkIV
};
keys.ring[myUserName] = encryptedForMe;
keys.ring[username] = encryptedForYou;
let keyringFile = new Resource( serverPath + "/" + KEYRING_FILE_NAME );
keyringFile.pushFilter(new JsonFilter());
keyringFile.put( self.cb, keys);
yield;
self.done();
},
_createOutgoingShare: function BmkSharing__createOutgoing(folderId,
folderName,
username) {
/* To be called asynchronously. FolderId is the integer id of the
bookmark folder to be shared and folderName is a string of its
title. 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().)
Returns a string which is the path on the server to the new share
directory, or false if it failed.*/
let self = yield;
this._log.debug("Turning folder " + folderName + " into outgoing share"
+ " with " + username);
/* Generate a new GUID to use as the new directory name on the server
in which we'll store the shared data. */
let folderGuid = Utils.makeGUID();
/* Create the directory on the server if it does not exist already. */
let serverPath = "share/" + folderGuid;
// FIXME
// let ret = yield DAV.MKCOL(serverPath, self.cb);
if (!ret) {
this._log.error("Can't create remote folder for outgoing share.");
self.done(false);
}
// TODO more error handling
/* Store the path to the server directory in an annotation on the shared
bookmark folder, so we can easily get back to it later. */
this._annoSvc.setItemAnnotation(folderId,
SERVER_PATH_ANNO,
serverPath,
0,
this._annoSvc.EXPIRE_NEVER);
let encryptionTurnedOn = true;
if (encryptionTurnedOn) {
yield this._createKeyChain.async(this, self.cb, serverPath,
this._myUsername, username);
}
// Call Atul's js api for setting htaccess:
// FIXME
// let sharingApi = new Sharing.Api( DAV );
let result = yield sharingApi.shareWithUsers( serverPath,
[username], folderName,
self.cb );
this._log.info(result.errorText);
// return the server path:
self.done( serverPath );
},
_updateOutgoingShare: function BmkSharing__updateOutgoing(folderId) {
/* 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;
// The folder should have an annotation specifying the server path to
// the directory:
if (!this._annoSvc.itemHasAnnotation(folderId, SERVER_PATH_ANNO)) {
this._log.warn("Outgoing share is invalid and can't be synced.");
return;
}
let serverPath = this._annoSvc.getItemAnnotation(folderId,
SERVER_PATH_ANNO);
// TODO the above can throw an exception if the expected anotation isn't
// there.
// From that directory, get the keyring file, and from it, the symmetric
// key that we'll use to encrypt.
let keyringFile = new Resource(serverPath + "/" + KEYRING_FILE_NAME);
keyringFile.pushFilter(new JsonFilter());
keyringFile.get(self.cb);
let keys = yield;
// 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[this._myUsername], idRSA);
let bulkIV = keys.bulkIV;
// Get the json-wrapped contents of everything in the folder:
let wrapMount = this._engine._store._wrapMountOutgoing(folderId);
let jsonService = Components.classes["@mozilla.org/dom/json;1"]
.createInstance(Components.interfaces.nsIJSON);
let json = jsonService.encode( wrapMount );
// 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,
bulkIV : bulkIV
};
Crypto.encryptData.async( Crypto, self.cb, json, tmpIdentity );
let cyphertext = yield;
yield bmkFile.put( self.cb, cyphertext );
self.done();
},
_stopOutgoingShare: function BmkSharing__stopOutgoingShare(folderId) {
/* 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. */
let self = yield;
if (this._annoSvc.itemHasAnnotation(folderId, SERVER_PATH_ANNO)){
let serverPath = this._annoSvc.getItemAnnotation( folderId,
SERVER_PATH_ANNO );
// Delete the share from the server:
// TODO handle error that can happen if these resources do not exist.
let keyringFile = new Resource(serverPath + "/" + KEYRING_FILE_NAME);
keyringFile.delete(self.cb);
yield;
let bmkFile = new Resource(serverPath + "/" + SHARED_BOOKMARK_FILE_NAME);
keyringFile.delete(self.cb);
yield;
// TODO this leaves the folder itself in place... is there a way to
// get rid of that, say through DAV?
}
// Remove the annotations from the local folder:
this._annoSvc.removeItemAnnotation(folderId, SERVER_PATH_ANNO);
this._annoSvc.removeItemAnnotation(folderId, OUTGOING_SHARED_ANNO);
self.done();
},
_createIncomingShare: function BmkSharing__createShare(user,
serverPath,
title) {
/* Creates a new folder in the bookmark menu for the incoming share.
user is the weave id of the user who is offering the folder to us;
serverPath is the path on the server where the shared data is located,
and title is the human-readable title to use for the new folder.
It is safe to call this again for a folder that already exist: this
function will exit without doing anything. It won't create a duplicate.
*/
/* Get the toolbar "Shared Folders" folder (identified by its annotation).
If it doesn't already exist, create it: */
dump( "I'm in _createIncomingShare. user= " + user + "path = " +
serverPath + ", title= " + title + "\n" );
let root;
let a = this._annoSvc.getItemsWithAnnotation(INCOMING_SHARE_ROOT_ANNO,
{});
if (a.length == 1)
root = a[0];
if (!root) {
root = this._bms.createFolder(this._bms.toolbarFolder,
INCOMING_SHARE_ROOT_NAME,
this._bms.DEFAULT_INDEX);
this._annoSvc.setItemAnnotation(root,
INCOMING_SHARE_ROOT_ANNO,
true,
0,
this._annoSvc.EXPIRE_NEVER);
}
/* Inside "Shared Folders", create a new folder annotated with
the originating user and the directory path specified by the incoming
share offer. Unless a folder with these exact annotations already
exists, in which case do nothing. */
let itemExists = false;
a = this._annoSvc.getItemsWithAnnotation(INCOMING_SHARED_ANNO, {});
for (let i = 0; i < a.length; i++) {
let creator = this._annoSvc.getItemAnnotation(a[i], INCOMING_SHARED_ANNO);
let path = this._annoSvc.getItemAnnotation(a[i], SERVER_PATH_ANNO);
if ( creator == user && path == serverPath ) {
itemExists = true;
break;
}
}
if (!itemExists) {
let newId = this._bms.createFolder(root, title, this._bms.DEFAULT_INDEX);
// Keep track of who shared this folder with us...
this._annoSvc.setItemAnnotation(newId,
INCOMING_SHARED_ANNO,
user,
0,
this._annoSvc.EXPIRE_NEVER);
// and what the path to the shared data on the server is...
this._annoSvc.setItemAnnotation(newId,
SERVER_PATH_ANNO,
serverPath,
0,
this._annoSvc.EXPIRE_NEVER);
}
},
_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 */
// TODO error handling (see what Resource can throw or return...)
/* TODO tons of symmetry between this and _updateOutgoingShare, can
probably factor the symkey decryption stuff into a common helper
function. */
let self = yield;
let user = mountData.userid;
// The folder has an annotation specifying the server path to the
// directory:
let serverPath = mountData.serverPath;
// From that directory, get the keyring file, and from it, the symmetric
// key that we'll use to decrypt.
this._log.trace("UpdateIncomingShare: getting keyring file.");
let keyringFile = new Resource(serverPath + "/" + KEYRING_FILE_NAME);
keyringFile.pushFilter(new JsonFilter());
let keys = yield keyringFile.get(self.cb);
// Unwrap (decrypt) the key with the user's private key.
this._log.trace("UpdateIncomingShare: decrypting sym key.");
let idRSA = ID.get('WeaveCryptoID');
let bulkKey = yield Crypto.unwrapKey.async(Crypto, self.cb,
keys.ring[this._myUsername], idRSA);
let bulkIV = keys.bulkIV;
// Decrypt the contents of the bookmark file with the symmetric key:
this._log.trace("UpdateIncomingShare: getting encrypted bookmark file.");
let bmkFile = new Resource(serverPath + "/" + SHARED_BOOKMARK_FILE_NAME);
let cyphertext = yield bmkFile.get(self.cb);
let tmpIdentity = {
realm : "temp ID",
bulkKey : bulkKey,
bulkIV : bulkIV
};
this._log.trace("UpdateIncomingShare: Decrypting.");
Crypto.decryptData.async( Crypto, self.cb, cyphertext, tmpIdentity );
let json = yield;
// decrypting that gets us JSON, turn it into an object:
this._log.trace("UpdateIncomingShare: De-JSON-izing.");
let jsonService = Components.classes["@mozilla.org/dom/json;1"]
.createInstance(Components.interfaces.nsIJSON);
let serverContents = jsonService.decode( json );
// prune tree / get what we want
this._log.trace("UpdateIncomingShare: Pruning.");
for (let guid in serverContents) {
if (serverContents[guid].type != "bookmark")
delete serverContents[guid];
else
serverContents[guid].parentid = mountData.rootGUID;
}
/* Wipe old local contents of the folder, starting from the node: */
this._log.trace("Wiping local contents of incoming share...");
this._bms.removeFolderChildren( mountData.node );
/* Create diff FROM current contents (i.e. nothing) TO the incoming
* data from serverContents. Then apply the diff. */
this._log.trace("Got bookmarks from " + user + ", comparing with local copy");
this._engine._core.detectUpdates(self.cb, {}, serverContents);
let diff = yield;
/* LONGTERM TODO: The createCommands that are executed in applyCommands
* will fail badly if the GUID of the incoming item collides with a
* GUID of a bookmark already in my store. (This happened to me a lot
* during testing, obviously, since I was sharing bookmarks with myself).
* Need to think about the right way to handle this. */
// FIXME: should make sure all GUIDs here live under the mountpoint
this._log.trace("Applying changes to folder from " + user);
this._engine._store.applyCommands.async(this._engine._store, self.cb, diff);
yield;
this._log.trace("Shared folder from " + user + " successfully synced!");
},
_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 a = this._annoSvc.getItemsWithAnnotation(OUTGOING_SHARED_ANNO, {});
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 ) {
this._bms.removeFolder( a[i]);
}
}
}
}
function BookmarksEngine(pbeId) {
this._init(pbeId);
}
@ -729,33 +85,12 @@ BookmarksEngine.prototype = {
return store;
},
get _core() {
let core = new BookmarksSyncCore();
this.__defineGetter__("_core", function() core);
return core;
},
get _tracker() {
let tracker = new BookmarksTracker();
this.__defineGetter__("_tracker", function() tracker);
return tracker;
},
_getAllIDs: function BmkEngine__getAllIDs() {
let self = yield;
let all = this._store.wrap(); // FIXME: using store is an inefficient hack...
delete all["unfiled"];
delete all["toolbar"];
delete all["menu"];
self.done(all);
},
_serializeItem: function BmkEngine__serializeItem(id) {
let self = yield;
let all = this._store.wrap(); // FIXME OMG SO INEFFICIENT
self.done(all[id]);
},
_recordLike: function SyncEngine__recordLike(a, b) {
if (a.parentid != b.parentid)
return false;
@ -794,94 +129,6 @@ BookmarksEngine.prototype = {
// and updating incoming/outgoing shared folders after syncing
};
function BookmarksSyncCore(store) {
this._store = store;
this._init();
}
BookmarksSyncCore.prototype = {
__proto__: SyncCore.prototype,
_logName: "BMSync",
_store: null,
_getEdits: function BSC__getEdits(a, b) {
// NOTE: we do not increment ret.numProps, as that would cause
// edit commands to always get generated
let ret = SyncCore.prototype._getEdits.call(this, a, b);
ret.props.type = a.type;
return ret;
},
// compares properties
// returns true if the property is not set in either object
// returns true if the property is set and equal in both objects
// returns false otherwise
_comp: function BSC__comp(a, b, prop) {
return (!a.data[prop] && !b.data[prop]) ||
(a.data[prop] && b.data[prop] && (a.data[prop] == b.data[prop]));
},
_commandLike: function BSC__commandLike(a, b) {
// Check that neither command is null, that their actions, types,
// and parents are the same, and that they don't have the same
// GUID.
// * Items with the same GUID do not qualify for 'likeness' because
// we already consider them to be the same object, and therefore
// we need to process any edits.
// * Remove or edit commands don't qualify for likeness either,
// since remove or edit commands with different GUIDs are
// guaranteed to refer to two different items
// * The parent GUID check works because reconcile() fixes up the
// parent GUIDs as it runs, and the command list is sorted by
// depth
if (!a || !b ||
a.action != b.action ||
a.action != "create" ||
a.data.type != b.data.type ||
a.data.parentid != b.data.parentid ||
a.GUID == b.GUID)
return false;
// Bookmarks and folders are allowed to be in a different index as long as
// they are in the same folder. Separators must be at
// the same index to qualify for 'likeness'.
switch (a.data.type) {
case "bookmark":
if (this._comp(a, b, 'URI') &&
this._comp(a, b, 'title'))
return true;
return false;
case "query":
if (this._comp(a, b, 'URI') &&
this._comp(a, b, 'title'))
return true;
return false;
case "microsummary":
if (this._comp(a, b, 'URI') &&
this._comp(a, b, 'generatorURI'))
return true;
return false;
case "folder":
if (this._comp(a, b, 'title'))
return true;
return false;
case "livemark":
if (this._comp(a, b, 'title') &&
this._comp(a, b, 'siteURI') &&
this._comp(a, b, 'feedURI'))
return true;
return false;
case "separator":
if (this._comp(a, b, 'index'))
return true;
return false;
default:
let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
this._log.error("commandLike: Unknown item type: " + json.encode(a));
return false;
}
}
};
function BookmarksStore() {
this._init();
}
@ -1403,6 +650,25 @@ BookmarksStore.prototype = {
return items;
},
// FIXME: the fast path here is not so bad, specially since the engine always
// gives the cache hint atm. but wrapping all items to return just one
// (the slow path) is pretty bad
wrapItem: function BStore_wrapItem(id) {
if (this._itemCache)
return this._itemCache[id];
let all = this.wrap();
return all[id];
},
// XXX need a better way to query Places for all GUIDs
getAllIDs: function BStore_getAllIDs() {
let all = this.wrap();
delete all["unfiled"];
delete all["toolbar"];
delete all["menu"];
return all;
},
wipe: function BStore_wipe() {
this._bms.removeFolderChildren(this._bms.bookmarksMenuFolder);
this._bms.removeFolderChildren(this._bms.toolbarFolder);

Просмотреть файл

@ -59,8 +59,7 @@ function Store() {
this._init();
}
Store.prototype = {
_logName: "Store",
_yieldDuringApply: true,
_logName: "BaseClass",
// set this property in child object's wrap()!
_lookup: null,
@ -82,19 +81,21 @@ Store.prototype = {
},
_init: function Store__init() {
this._log = Log4Moz.repository.getLogger("Service." + this._logName);
this._log = Log4Moz.repository.getLogger("Store." + this._logName);
},
applyCommands: function Store_applyCommands(commandList) {
_applyCommands: function Store__applyCommands(commandList) {
let self = yield;
for (var i = 0; i < commandList.length; i++) {
if (this._yieldDuringApply) {
if (i % 5) {
Utils.makeTimerForCall(self.cb);
yield; // Yield to main loop
}
var command = commandList[i];
this._log.trace("Processing command: " + this._json.encode(command));
if (this._log.level <= Log4Moz.Level.Trace)
this._log.trace("Processing command: " + this._json.encode(command));
switch (command["action"]) {
case "create":
this._createCommand(command);
@ -110,7 +111,9 @@ Store.prototype = {
break;
}
}
self.done();
},
applyCommands: function Store_applyCommands(onComplete, commandList) {
this._applyCommands.async(this, onComplete, commandList);
},
// override only if neccessary
@ -121,23 +124,30 @@ Store.prototype = {
return false;
},
cacheItemsHint: function Store_cacheItemsHint() {
this._itemCache = this.wrap();
},
clearItemCacheHint: function Store_clearItemCacheHint() {
this._itemCache = null;
},
// override these in derived objects
// wrap MUST save the wrapped store in the _lookup property!
wrap: function Store_wrap() {
throw "wrap needs to be subclassed";
throw "override wrap in a subclass";
},
wrapItem: function Store_wrapItem() {
throw "override wrapItem in a subclass";
},
getAllIDs: function Store_getAllIDs() {
throw "override getAllIDs in a subclass";
},
wipe: function Store_wipe() {
throw "wipe needs to be subclassed";
},
_resetGUIDs: function Store__resetGUIDs() {
let self = yield;
// default to do nothing
},
resetGUIDs: function Store_resetGUIDs(onComplete) {
this._resetGUIDs.async(this, onComplete);
throw "override wipe in a subclass";
}
};