- Prevent a couple cases of erroneous full syncs due to deleted local items

- On sync conflicts, display only one alert about auto-merged objects per object type, and log the rest to the Error Console
This commit is contained in:
Dan Stillman 2011-05-10 21:43:19 +00:00
parent 44b3b9bd10
commit 12044afe3b

View File

@ -324,6 +324,26 @@ Zotero.Sync.ObjectKeySet.prototype.hasLibraryKey = function (type, libraryID, ke
}
Zotero.Sync.ObjectKeySet.prototype.getKeys = function (type, libraryID) {
var Types = Zotero.Sync.syncObjects[type].plural;
var types = Types.toLowerCase();
if (!libraryID) {
libraryID = 0;
}
if (!this[types] || !this[types][libraryID]) {
return [];
}
var keys = [];
for (var key in this[types][libraryID]) {
keys.push(key);
}
return keys;
}
Zotero.Sync.ObjectKeySet.prototype.removeLibraryKeyPairs = function (type, keyPairs) {
var Types = Zotero.Sync.syncObjects[type].plural;
var types = Types.toLowerCase();
@ -2348,6 +2368,14 @@ Zotero.Sync.Server.Session.prototype.objectInDeleted = function (obj) {
}
/**
* Returns array of keys of deleted objects in specified library
*/
Zotero.Sync.Server.Session.prototype.getDeleted = function (type, libraryID) {
return this.uploadKeys.deleted.getKeys(type, libraryID);
}
Zotero.Sync.Server.Session.prototype.removeFromUpdated = function (objs) {
this._removeFromKeySet('updated', objs);
}
@ -2545,6 +2573,9 @@ Zotero.Sync.Server.Data = new function() {
var toDelete = [];
var toReconcile = [];
// Display a warning once for each object type
syncSession.suppressWarnings = false;
//
// Handle modified objects
//
@ -2560,6 +2591,7 @@ Zotero.Sync.Server.Data = new function() {
var isNewObject;
var localDelete = false;
var skipCR = false;
var deletedItemKeys = null;
// Get local object with same library and key
var obj = Zotero[Types].getByLibraryAndKey(libraryID, key);
@ -2687,7 +2719,7 @@ Zotero.Sync.Server.Data = new function() {
break;
case 'tag':
var changed = _mergeTag(obj, remoteObj);
var changed = _mergeTag(obj, remoteObj, syncSession);
if (!changed) {
syncSession.removeFromUpdated(obj);
}
@ -2737,10 +2769,21 @@ Zotero.Sync.Server.Data = new function() {
case 'tag':
case 'collection':
syncSession.removeFromDeleted(fakeObj);
var msg = _generateAutoChangeMessage(
var msg = _generateAutoChangeLogMessage(
type, null, xmlNode.@name.toString()
);
alert(msg);
Zotero.log(msg, 'warning');
if (!syncSession.suppressWarnings) {
var msg = _generateAutoChangeAlertMessage(
types, null, xmlNode.@name.toString()
);
alert(msg);
syncSession.suppressWarnings = true;
}
deletedItemKeys = syncSession.getDeleted('item', libraryID);
break;
default:
@ -2764,7 +2807,7 @@ Zotero.Sync.Server.Data = new function() {
//
// If we skipped CR above, we already have an object to use
if (!skipCR) {
obj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode, obj, null, defaultLibraryID);
obj = Zotero.Sync.Server.Data['xmlTo' + Type](xmlNode, obj, false, defaultLibraryID, deletedItemKeys);
}
if (isNewObject && type == 'tag') {
@ -2905,6 +2948,8 @@ Zotero.Sync.Server.Data = new function() {
if (xml.deleted.length() && xml.deleted[types].length()) {
Zotero.debug("Processing remotely deleted " + types);
syncSession.suppressWarnings = false;
for each(var xmlNode in xml.deleted[types][type]) {
var libraryID = _libID(xmlNode.@libraryID.toString());
var key = xmlNode.@key.toString();
@ -2939,10 +2984,18 @@ Zotero.Sync.Server.Data = new function() {
case 'tag':
case 'collection':
var msg = _generateAutoChangeMessage(
var msg = _generateAutoChangeLogMessage(
type, obj.name, null
);
alert(msg);
Zotero.log(msg, 'warning');
if (!syncSession.suppressWarnings) {
var msg = _generateAutoChangeAlertMessage(
types, obj.name, null
);
alert(msg);
syncSession.suppressWarnings = true;
}
continue;
default:
@ -3282,12 +3335,17 @@ Zotero.Sync.Server.Data = new function() {
}
if (diff[0].fields.name) {
var msg = _generateAutoChangeLogMessage(
'collection', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
);
Zotero.log(msg, 'warning');
if (!syncSession.suppressWarnings) {
var msg = _generateAutoChangeMessage(
'collection', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
var msg = _generateAutoChangeAlertMessage(
'collections', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
);
// TODO: log rather than alert
alert(msg);
syncSession.suppressWarnings = true;
}
}
@ -3309,12 +3367,14 @@ Zotero.Sync.Server.Data = new function() {
localObj.childItems = diff[1].childItems;
}
var msg = _generateCollectionItemMergeLogMessage(
targetObj.name,
diff[0].childItems.concat(diff[1].childItems)
);
Zotero.log('warning');
if (!syncSession.suppressWarnings) {
var msg = _generateCollectionItemMergeMessage(
targetObj.name,
diff[0].childItems.concat(diff[1].childItems)
);
// TODO: log rather than alert
alert(msg);
}
}
@ -3323,7 +3383,7 @@ Zotero.Sync.Server.Data = new function() {
}
function _mergeTag(localObj, remoteObj) {
function _mergeTag(localObj, remoteObj, syncSession) {
var diff = localObj.diff(remoteObj, false, true);
if (!diff) {
return false;
@ -3348,12 +3408,19 @@ Zotero.Sync.Server.Data = new function() {
var otherDiff = diff[0];
}
// TODO: log old name
if (targetDiff.fields.name) {
var msg = _generateAutoChangeMessage(
var msg = _generateAutoChangeLogMessage(
'tag', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
);
alert(msg);
Zotero.log(msg, 'warning');
if (!syncSession.suppressWarnings) {
var msg = _generateAutoChangeAlertMessage(
'tags', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
);
alert(msg);
syncSession.suppressWarnings = true;
}
}
// Add linked items in the other object to the target one
@ -3363,15 +3430,18 @@ Zotero.Sync.Server.Data = new function() {
var linkedItems = targetObj.getLinkedItems(true);
targetObj.linkedItems = linkedItems.concat(otherDiff.linkedItems);
/*
var msg = _generateTagItemMergeMessage(
var msg = _generateTagItemMergeLogMessage(
targetObj.name,
otherDiff.linkedItems,
remoteIsTarget
);
// TODO: log rather than alert
alert(msg);
*/
Zotero.log(msg, 'warning');
if (!syncSession.suppressWarnings) {
var msg = _generateTagItemMergeAlertMessage();
alert(msg);
syncSession.suppressWarnings = true;
}
}
targetObj.save();
@ -3379,13 +3449,45 @@ Zotero.Sync.Server.Data = new function() {
}
/**
* @param {String} itemTypes
* @param {String} localName
* @param {String} remoteName
* @param {Boolean} [remoteMoreRecent=false]
*/
function _generateAutoChangeAlertMessage(itemTypes, localName, remoteName, remoteMoreRecent) {
if (localName === null) {
var localDelete = true;
}
else if (remoteName === null) {
var remoteDelete = true;
}
// TODO: localize
var msg = "One or more locally deleted Zotero " + itemTypes + " have been "
+ "modified remotely since the last sync. ";
if (localDelete) {
msg += "The remote versions have been kept.";
}
else if (remoteDelete) {
msg += "The local versions have been kept.";
}
else {
msg += "The most recent versions have been kept.";
}
msg += "\n\nView the " + (Zotero.isStandalone ? "" : "Firefox ")
+ "Error Console for the full list of such changes.";
return msg;
}
/**
* @param {String} itemType
* @param {String} localName
* @param {String} remoteName
* @param {Boolean} [remoteMoreRecent=false]
*/
function _generateAutoChangeMessage(itemType, localName, remoteName, remoteMoreRecent) {
function _generateAutoChangeLogMessage(itemType, localName, remoteName, remoteMoreRecent) {
if (localName === null) {
// TODO: localize
localName = "[deleted]";
@ -3397,7 +3499,7 @@ Zotero.Sync.Server.Data = new function() {
}
// TODO: localize
var msg = "A " + itemType + " has changed both locally and "
var msg = "A Zotero " + itemType + " has changed both locally and "
+ "remotely since the last sync:";
msg += "\n\n";
msg += "Local version: " + localName + "\n";
@ -3417,16 +3519,26 @@ Zotero.Sync.Server.Data = new function() {
}
function _generateCollectionItemMergeAlertMessage() {
// TODO: localize
var msg = "One or more Zotero items have been added to and/or removed "
+ "from the same collection on multiple computers since the last sync.\n\n"
+ "View the " + (Zotero.isStandalone ? "" : "Firefox ")
+ "Error Console for the full list of such changes.";
return msg;
}
/**
* @param {String} collectionName
* @param {Integer[]} addedItemIDs
*/
function _generateCollectionItemMergeMessage(collectionName, addedItemIDs) {
function _generateCollectionItemMergeLogMessage(collectionName, addedItemIDs) {
// TODO: localize
var introMsg = "Items in the collection '" + collectionName + "' have been "
+ "added and/or removed in multiple locations."
var introMsg = "Zotero items in the collection '" + collectionName + "' have been "
+ "added and/or removed on multiple computers since the last sync. "
introMsg += " The following items have been added to the collection:";
introMsg += "The following items have been added to the collection:";
var itemText = [];
var max = addedItemIDs.length;
for (var i=0; i<max; i++) {
@ -3449,17 +3561,27 @@ Zotero.Sync.Server.Data = new function() {
}
function _generateTagItemMergeAlertMessage() {
// TODO: localize
var msg = "One or more Zotero tags have been added to and/or removed from "
+ "items on multiple computers since the last sync. "
+ "The different sets of tags have been combined.\n\n"
+ "View the " + (Zotero.isStandalone ? "" : "Firefox ")
+ "Error Console for the full list of such changes.";
return msg;
}
/**
* @param {String} tagName
* @param {Integer[]} addedItemIDs
* @param {Boolean} remoteIsTarget
*/
function _generateTagItemMergeMessage(tagName, addedItemIDs, remoteIsTarget) {
function _generateTagItemMergeLogMessage(tagName, addedItemIDs, remoteIsTarget) {
// TODO: localize
var introMsg = "The tag '" + tagName + "' has been "
+ "added to and/or removed from items in multiple locations."
var introMsg = "The Zotero tag '" + tagName + "' has been added to and/or "
+ "removed from items on multiple computers since the last sync. "
introMsg += " ";
if (remoteIsTarget) {
introMsg += "It has been added to the following remote items:";
}
@ -3905,9 +4027,11 @@ Zotero.Sync.Server.Data = new function() {
*
* @param object xmlCollection E4X XML node with collection data
* @param object item (Optional) Existing Zotero.Collection to update
* @param bool skipPrimary (Optional) Ignore passed primary fields (except itemTypeID)
* @param bool skipPrimary (Optional) Ignore passed primary fields (except itemTypeID)
* @param integer defaultLibraryID (Optional)
* @param array deletedItems (Optional) An array of keys that have been deleted in this sync session
*/
function xmlToCollection(xmlCollection, collection, skipPrimary, defaultLibraryID) {
function xmlToCollection(xmlCollection, collection, skipPrimary, defaultLibraryID, deletedItemKeys) {
if (!collection) {
collection = new Zotero.Collection;
}
@ -3945,6 +4069,18 @@ Zotero.Sync.Server.Data = new function() {
for each(var key in childItems) {
var childItem = Zotero.Items.getByLibraryAndKey(collection.libraryID, key);
if (!childItem) {
// Ignore items that were deleted in this sync session
//
// This can happen if a collection and its items are deleted
// locally but are in conflict with the server, and the local
// item deletes are selected in CR. Then, when the deleted
// collection is automatically restored, the items no
// longer exist.
if (deletedItemKeys && deletedItemKeys.indexOf(key) != -1) {
Zotero.debug("Ignoring deleted collection item '" + key + "'");
continue;
}
var msg = "Missing child item " + key + " for collection "
+ collection.libraryID + "/" + collection.key
+ " in Zotero.Sync.Server.Data.xmlToCollection()";
@ -4200,9 +4336,9 @@ Zotero.Sync.Server.Data = new function() {
*
* @param object xmlTag E4X XML node with tag data
* @param object tag (Optional) Existing Zotero.Tag to update
* @param bool skipPrimary (Optional) Ignore passed primary fields
* @param bool skipPrimary (Optional) Ignore passed primary fields
*/
function xmlToTag(xmlTag, tag, skipPrimary, defaultLibraryID) {
function xmlToTag(xmlTag, tag, skipPrimary, defaultLibraryID, deletedItemKeys) {
if (!tag) {
tag = new Zotero.Tag;
}
@ -4227,6 +4363,12 @@ Zotero.Sync.Server.Data = new function() {
for each(var key in keys) {
var item = Zotero.Items.getByLibraryAndKey(tag.libraryID, key);
if (!item) {
// See note in xmlToCollection()
if (deletedItemKeys && deletedItemKeys.indexOf(key) != -1) {
Zotero.debug("Ignoring deleted linked item '" + key + "'");
continue;
}
var msg = "Linked item " + key + " doesn't exist in Zotero.Sync.Server.Data.xmlToTag()";
var e = new Zotero.Error(msg, "MISSING_OBJECT");
throw (e);