Expose a _handleDupe on engines and provide a custom one for bookmarks that tracks GUID changes so that it can keep an alias mapping to fix incoming item properties (id, parent, predecessor). Move out _reparentOrphans so that it is triggered on update and not just create because folders can change ids to the right parent.

This commit is contained in:
Edward Lee 2009-09-08 23:33:15 -07:00
Родитель e95c1122bc
Коммит 57b041bbe1
2 изменённых файлов: 81 добавлений и 39 удалений

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

@ -386,6 +386,22 @@ SyncEngine.prototype = {
this._delete.ids.push(id); this._delete.ids.push(id);
}, },
_handleDupe: function _handleDupe(item, dupeId) {
// The local dupe is the lower id, so pretend the incoming is for it
if (dupeId < item.id) {
this._deleteId(item.id);
item.id = dupeId;
this._tracker.changedIDs[dupeId] = true;
}
// The incoming item has the lower id, so change the dupe to it
else {
this._store.changeItemID(dupeId, item.id);
this._deleteId(dupeId);
}
this._store.cache.clear(); // because parentid refs will be wrong
},
// Reconciliation has three steps: // Reconciliation has three steps:
// 1) Check for the same item (same ID) on both the incoming and outgoing // 1) Check for the same item (same ID) on both the incoming and outgoing
// queues. This means the same item was modified on this profile and // queues. This means the same item was modified on this profile and
@ -425,20 +441,8 @@ SyncEngine.prototype = {
// Step 3: Check for similar items // Step 3: Check for similar items
this._log.trace("Reconcile step 3"); this._log.trace("Reconcile step 3");
let dupeId = this._findDupe(item); let dupeId = this._findDupe(item);
if (dupeId) { if (dupeId)
// Stick with the canonical lower id, so convert the dupe to incoming this._handleDupe(item, dupeId);
if (item.id < dupeId) {
this._store.changeItemID(dupeId, item.id);
this._deleteId(dupeId);
}
// The local dupe is the lower id, so pretend the incoming is for it
else {
this._deleteId(item.id);
item.id = dupeId;
}
this._store.cache.clear(); // because parentid refs will be wrong
}
// Apply the incoming item (now that the dupe is the right id) // Apply the incoming item (now that the dupe is the right id)
return true; return true;

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

@ -137,6 +137,17 @@ BookmarksEngine.prototype = {
} }
// TODO for bookmarks, check if it exists and find guid // TODO for bookmarks, check if it exists and find guid
// for everything else (folders, separators) look for parent/pred? // for everything else (folders, separators) look for parent/pred?
},
_handleDupe: function _handleDupe(item, dupeId) {
// The local dupe has the lower id, so make it the winning id
if (dupeId < item.id)
[item.id, dupeId] = [dupeId, item.id];
// Trigger id change from dupe to winning and update the server
this._store.changeItemID(dupeId, item.id);
this._deleteId(dupeId);
this._tracker.changedIDs[item.id] = true;
} }
}; };
@ -199,6 +210,9 @@ BookmarksStore.prototype = {
return idForGUID(id) > 0; return idForGUID(id) > 0;
}, },
// Hash of old GUIDs to the new renamed GUIDs
aliases: {},
applyIncoming: function BStore_applyIncoming(record) { applyIncoming: function BStore_applyIncoming(record) {
// Ignore (accidental?) root changes // Ignore (accidental?) root changes
if (record.id in kSpecialIds) { if (record.id in kSpecialIds) {
@ -206,6 +220,13 @@ BookmarksStore.prototype = {
return; return;
} }
// Convert GUID fields to the aliased GUID if necessary
["id", "parentid", "predecessorid"].forEach(function(field) {
let alias = this.aliases[record[field]];
if (alias != null)
record[field] = alias;
}, this);
// Preprocess the record before doing the normal apply // Preprocess the record before doing the normal apply
switch (record.type) { switch (record.type) {
case "query": { case "query": {
@ -280,6 +301,10 @@ BookmarksStore.prototype = {
// Do some post-processing if we have an item // Do some post-processing if we have an item
let itemId = idForGUID(record.id); let itemId = idForGUID(record.id);
if (itemId > 0) { if (itemId > 0) {
// Move any children that is looking for this folder as a parent
if (record.type == "folder")
this._reparentOrphans(itemId);
// Create an annotation to remember that it needs a parent // Create an annotation to remember that it needs a parent
// XXX Work around Bug 510628 by prepending parenT // XXX Work around Bug 510628 by prepending parenT
if (record._orphan) if (record._orphan)
@ -310,6 +335,30 @@ BookmarksStore.prototype = {
Utils.anno(id, anno) == val); Utils.anno(id, anno) == val);
}, },
/**
* For the provided parent item, attach its children to it
*/
_reparentOrphans: function _reparentOrphans(parentId) {
// Find orphans and reunite with this folder parent
let parentGUID = GUIDForId(parentId);
let orphans = this._findAnnoItems(PARENT_ANNO, parentGUID);
this._log.debug("Reparenting orphans " + orphans + " to " + parentId);
orphans.forEach(function(orphan) {
// Append the orphan under the parent unless it's supposed to be first
let insertPos = Svc.Bookmark.DEFAULT_INDEX;
if (!Svc.Annos.itemHasAnnotation(orphan, PREDECESSOR_ANNO))
insertPos = 0;
// Move the orphan to the parent and drop the missing parent annotation
Svc.Bookmark.moveItem(orphan, parentId, insertPos);
Svc.Annos.removeItemAnnotation(orphan, PARENT_ANNO);
});
// Fix up the ordering of the now-parented items
orphans.forEach(this._attachFollowers, this);
},
/** /**
* Move an item and all of its followers to a new position until reaching an * Move an item and all of its followers to a new position until reaching an
* item that shouldn't be moved * item that shouldn't be moved
@ -430,25 +479,6 @@ BookmarksStore.prototype = {
this._log.trace("Setting GUID of new item " + newId + " to " + record.id); this._log.trace("Setting GUID of new item " + newId + " to " + record.id);
this._setGUID(newId, record.id); this._setGUID(newId, record.id);
// Find orphans and reunite with this new folder parent
if (record.type == "folder") {
let orphans = this._findAnnoItems(PARENT_ANNO, record.id);
this._log.debug("Reparenting orphans " + orphans + " to " + record.title);
orphans.map(function(orphan) {
// Append the orphan under the parent unless it's supposed to be first
let insertPos = Svc.Bookmark.DEFAULT_INDEX;
if (!Svc.Annos.itemHasAnnotation(orphan, PREDECESSOR_ANNO))
insertPos = 0;
// Move the orphan to the parent and drop the missing parent annotation
Svc.Bookmark.moveItem(orphan, newId, insertPos);
Svc.Annos.removeItemAnnotation(orphan, PARENT_ANNO);
// Return the now-parented orphan so it can attach its followers
return orphan;
}).forEach(this._attachFollowers, this);
}
}, },
remove: function BStore_remove(record) { remove: function BStore_remove(record) {
@ -551,14 +581,22 @@ BookmarksStore.prototype = {
}, },
changeItemID: function BStore_changeItemID(oldID, newID) { changeItemID: function BStore_changeItemID(oldID, newID) {
// Remember the GUID change for incoming records and avoid invalid refs
this.aliases[oldID] = newID;
this.cache.clear();
// Update any existing annotation references
this._findAnnoItems(PARENT_ANNO, oldID).forEach(function(itemId) {
Utils.anno(itemId, PARENT_ANNO, "T" + newID);
}, this);
this._findAnnoItems(PREDECESSOR_ANNO, oldID).forEach(function(itemId) {
Utils.anno(itemId, PREDECESSOR_ANNO, "R" + newID);
}, this);
// Make sure there's an item to change GUIDs
let itemId = idForGUID(oldID); let itemId = idForGUID(oldID);
if (itemId == null) // toplevel folder if (itemId <= 0)
return; return;
if (itemId <= 0) {
this._log.warn("Can't change GUID " + oldID + " to " +
newID + ": Item does not exist");
return;
}
this._log.debug("Changing GUID " + oldID + " to " + newID); this._log.debug("Changing GUID " + oldID + " to " + newID);
this._setGUID(itemId, newID); this._setGUID(itemId, newID);