- Add automatic merging of collection and tag metadata and associated items, with warning alerts (eventually to be converted to logged notifications)
- Switch to using only keys for deleted items - Fix various tag-related problems - Probably other things
This commit is contained in:
parent
104d39bfa6
commit
c7b2c84869
6 changed files with 389 additions and 213 deletions
|
@ -398,7 +398,7 @@ Zotero.Collection.prototype.save = function () {
|
||||||
currentIDs = [];
|
currentIDs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._previousData.childCollections) {
|
if (this._previousData) {
|
||||||
for each(var id in this._previousData.childCollections) {
|
for each(var id in this._previousData.childCollections) {
|
||||||
if (currentIDs.indexOf(id) == -1) {
|
if (currentIDs.indexOf(id) == -1) {
|
||||||
removed.push(id);
|
removed.push(id);
|
||||||
|
@ -406,7 +406,7 @@ Zotero.Collection.prototype.save = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for each(var id in currentIDs) {
|
for each(var id in currentIDs) {
|
||||||
if (this._previousData.childCollections &&
|
if (this._previousData &&
|
||||||
this._previousData.childCollections.indexOf(id) != -1) {
|
this._previousData.childCollections.indexOf(id) != -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -442,7 +442,7 @@ Zotero.Collection.prototype.save = function () {
|
||||||
currentIDs = [];
|
currentIDs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._previousData.childItems) {
|
if (this._previousData) {
|
||||||
for each(var id in this._previousData.childItems) {
|
for each(var id in this._previousData.childItems) {
|
||||||
if (currentIDs.indexOf(id) == -1) {
|
if (currentIDs.indexOf(id) == -1) {
|
||||||
removed.push(id);
|
removed.push(id);
|
||||||
|
@ -450,7 +450,7 @@ Zotero.Collection.prototype.save = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for each(var id in currentIDs) {
|
for each(var id in currentIDs) {
|
||||||
if (this._previousData.childItems &&
|
if (this._previousData &&
|
||||||
this._previousData.childItems.indexOf(id) != -1) {
|
this._previousData.childItems.indexOf(id) != -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -801,6 +801,8 @@ Zotero.Collection.prototype.toArray = function() {
|
||||||
|
|
||||||
|
|
||||||
Zotero.Collection.prototype.serialize = function(nested) {
|
Zotero.Collection.prototype.serialize = function(nested) {
|
||||||
|
var childCollections = this.getChildCollections(true);
|
||||||
|
var childItems = this.getChildItems(true);
|
||||||
var obj = {
|
var obj = {
|
||||||
primary: {
|
primary: {
|
||||||
collectionID: this.id,
|
collectionID: this.id,
|
||||||
|
@ -811,8 +813,8 @@ Zotero.Collection.prototype.serialize = function(nested) {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
parent: this.parent,
|
parent: this.parent,
|
||||||
},
|
},
|
||||||
childCollections: this.getChildCollections(true),
|
childCollections: childCollections ? childCollections : [],
|
||||||
childItems: this.getChildItems(true),
|
childItems: childItems ? childItems : [],
|
||||||
descendents: this.id ? this.getDescendents(nested) : []
|
descendents: this.id ? this.getDescendents(nested) : []
|
||||||
};
|
};
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
@ -143,8 +143,8 @@ Zotero.Tag.prototype.loadFromRow = function (row) {
|
||||||
/**
|
/**
|
||||||
* Returns items linked to this tag
|
* Returns items linked to this tag
|
||||||
*
|
*
|
||||||
* @param bool asIDs Return as itemIDs
|
* @param {Boolean} asIDs Return as itemIDs
|
||||||
* @return array Array of Zotero.Item instances or itemIDs,
|
* @return {Array} Array of Zotero.Item instances or itemIDs,
|
||||||
* or FALSE if none
|
* or FALSE if none
|
||||||
*/
|
*/
|
||||||
Zotero.Tag.prototype.getLinkedItems = function (asIDs) {
|
Zotero.Tag.prototype.getLinkedItems = function (asIDs) {
|
||||||
|
@ -211,7 +211,7 @@ Zotero.Tag.prototype.removeItem = function (itemID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Zotero.Tag.prototype.save = function () {
|
Zotero.Tag.prototype.save = function (full) {
|
||||||
// Default to manual tag
|
// Default to manual tag
|
||||||
if (!this.type) {
|
if (!this.type) {
|
||||||
this.type = 0;
|
this.type = 0;
|
||||||
|
@ -307,7 +307,7 @@ Zotero.Tag.prototype.save = function () {
|
||||||
|
|
||||||
|
|
||||||
// Linked items
|
// Linked items
|
||||||
if (this._changed.linkedItems) {
|
if (full || this._changed.linkedItems) {
|
||||||
var removed = [];
|
var removed = [];
|
||||||
var newids = [];
|
var newids = [];
|
||||||
var currentIDs = this.getLinkedItems(true);
|
var currentIDs = this.getLinkedItems(true);
|
||||||
|
@ -315,20 +315,32 @@ Zotero.Tag.prototype.save = function () {
|
||||||
currentIDs = [];
|
currentIDs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the database for comparison instead of relying on the cache
|
||||||
|
// This is necessary for a syncing edge case (described in sync.js).
|
||||||
|
if (full) {
|
||||||
|
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
|
||||||
|
var dbItemIDs = Zotero.DB.columnQuery(sql, tagID);
|
||||||
|
if (dbItemIDs) {
|
||||||
|
removed = Zotero.Utilities.prototype.arrayDiff(currentIDs, dbItemIDs);
|
||||||
|
newids = Zotero.Utilities.prototype.arrayDiff(dbItemIDs, currentIDs);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newids = currentIDs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
if (this._previousData.linkedItems) {
|
if (this._previousData.linkedItems) {
|
||||||
for each(var id in this._previousData.linkedItems) {
|
removed = Zotero.Utilities.prototype.arrayDiff(
|
||||||
if (currentIDs.indexOf(id) == -1) {
|
currentIDs, this._previousData.linkedItems
|
||||||
removed.push(id);
|
);
|
||||||
|
newids = Zotero.Utilities.prototype.arrayDiff(
|
||||||
|
this._previousData.linkedItems, currentIDs
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
newids = currentIDs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for each(var id in currentIDs) {
|
|
||||||
if (this._previousData.linkedItems &&
|
|
||||||
this._previousData.linkedItems.indexOf(id) != -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
newids.push(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removed.length) {
|
if (removed.length) {
|
||||||
var sql = "DELETE FROM itemTags WHERE tagID=? "
|
var sql = "DELETE FROM itemTags WHERE tagID=? "
|
||||||
|
@ -414,8 +426,17 @@ Zotero.Tag.prototype.diff = function (tag, includeMatches, ignoreOnlyDateModifie
|
||||||
var d2 = Zotero.Utilities.prototype.arrayDiff(
|
var d2 = Zotero.Utilities.prototype.arrayDiff(
|
||||||
otherData.linkedItems, thisData.linkedItems
|
otherData.linkedItems, thisData.linkedItems
|
||||||
);
|
);
|
||||||
numDiffs += d1.length;
|
numDiffs += d1.length + d2.length;
|
||||||
numDiffs += d2.length;
|
|
||||||
|
if (d1.length || d2.length) {
|
||||||
|
numDiffs += d1.length + d2.length;
|
||||||
|
diff[0].linkedItems = d1;
|
||||||
|
diff[1].linkedItems = d2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
diff[0].linkedItems = [];
|
||||||
|
diff[1].linkedItems = [];
|
||||||
|
}
|
||||||
|
|
||||||
// DEBUG: ignoreOnlyDateModified wouldn't work if includeMatches was set?
|
// DEBUG: ignoreOnlyDateModified wouldn't work if includeMatches was set?
|
||||||
if (numDiffs == 0 ||
|
if (numDiffs == 0 ||
|
||||||
|
@ -429,6 +450,8 @@ Zotero.Tag.prototype.diff = function (tag, includeMatches, ignoreOnlyDateModifie
|
||||||
|
|
||||||
|
|
||||||
Zotero.Tag.prototype.serialize = function () {
|
Zotero.Tag.prototype.serialize = function () {
|
||||||
|
var linkedItems = this.getLinkedItems(true);
|
||||||
|
|
||||||
var obj = {
|
var obj = {
|
||||||
primary: {
|
primary: {
|
||||||
tagID: this.id,
|
tagID: this.id,
|
||||||
|
@ -439,7 +462,7 @@ Zotero.Tag.prototype.serialize = function () {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
},
|
},
|
||||||
linkedItems: this.getLinkedItems(true),
|
linkedItems: linkedItems ? linkedItems : []
|
||||||
};
|
};
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2144,7 +2144,15 @@ Zotero.Schema = new function(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// // 1.5 Sync Preview 3.6
|
||||||
|
if (i==47) {
|
||||||
|
Zotero.DB.query("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
|
||||||
|
Zotero.DB.query("DROP INDEX syncDeleteLog_timestamp");
|
||||||
|
Zotero.DB.query("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n key TEXT NOT NULL UNIQUE,\n timestamp INT NOT NULL,\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n);");
|
||||||
|
Zotero.DB.query("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);");
|
||||||
|
Zotero.DB.query("INSERT INTO syncDeleteLog SELECT syncObjectTypeID, key, timestamp FROM syncDeleteLogOld");
|
||||||
|
Zotero.DB.query("DROP TABLE syncDeleteLogOld");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateDBVersion('userdata', toVersion);
|
_updateDBVersion('userdata', toVersion);
|
||||||
|
|
|
@ -136,10 +136,10 @@ Zotero.Sync = new function() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param object lastSyncDate JS Date object
|
* @param object lastSyncDate JS Date object
|
||||||
* @return mixed Returns object with deleted ids
|
* @return mixed
|
||||||
* {
|
* {
|
||||||
* items: [ { id: 123, key: ABCD1234 }, ... ]
|
* items: [ 'ABCD1234', 'BCDE2345', ... ]
|
||||||
* creators: [ { id: 123, key: ABCD1234 }, ... ],
|
* creators: [ 'ABCD1234', 'BCDE2345', ... ],
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
* or FALSE if none or -1 if last sync time is before start of log
|
* or FALSE if none or -1 if last sync time is before start of log
|
||||||
|
@ -162,7 +162,7 @@ Zotero.Sync = new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var param = false;
|
var param = false;
|
||||||
var sql = "SELECT syncObjectTypeID, objectID, key FROM syncDeleteLog";
|
var sql = "SELECT syncObjectTypeID, key FROM syncDeleteLog";
|
||||||
if (lastSyncDate) {
|
if (lastSyncDate) {
|
||||||
param = Zotero.Date.toUnixTimestamp(lastSyncDate);
|
param = Zotero.Date.toUnixTimestamp(lastSyncDate);
|
||||||
sql += " WHERE timestamp>?";
|
sql += " WHERE timestamp>?";
|
||||||
|
@ -174,20 +174,17 @@ Zotero.Sync = new function() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var deletedIDs = {};
|
var deletedKeys = {};
|
||||||
for each(var syncObject in this.syncObjects) {
|
for each(var syncObject in this.syncObjects) {
|
||||||
deletedIDs[syncObject.plural.toLowerCase()] = [];
|
deletedKeys[syncObject.plural.toLowerCase()] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for each(var row in rows) {
|
for each(var row in rows) {
|
||||||
var type = this.getObjectTypeName(row.syncObjectTypeID);
|
var type = this.getObjectTypeName(row.syncObjectTypeID);
|
||||||
type = this.syncObjects[type].plural.toLowerCase()
|
type = this.syncObjects[type].plural.toLowerCase()
|
||||||
deletedIDs[type].push({
|
deletedKeys[type].push(row.key);
|
||||||
id: row.objectID,
|
|
||||||
key: row.key
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return deletedIDs;
|
return deletedKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -297,7 +294,7 @@ Zotero.Sync.EventListener = new function () {
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
if (event == 'delete') {
|
if (event == 'delete') {
|
||||||
var sql = "INSERT INTO syncDeleteLog VALUES (?, ?, ?, ?)";
|
var sql = "INSERT INTO syncDeleteLog VALUES (?, ?, ?)";
|
||||||
var syncStatement = Zotero.DB.getStatement(sql);
|
var syncStatement = Zotero.DB.getStatement(sql);
|
||||||
|
|
||||||
if (isItem && Zotero.Sync.Storage.active) {
|
if (isItem && Zotero.Sync.Storage.active) {
|
||||||
|
@ -326,9 +323,8 @@ Zotero.Sync.EventListener = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
syncStatement.bindInt32Parameter(0, objectTypeID);
|
syncStatement.bindInt32Parameter(0, objectTypeID);
|
||||||
syncStatement.bindInt32Parameter(1, ids[i]);
|
syncStatement.bindStringParameter(1, key);
|
||||||
syncStatement.bindStringParameter(2, key);
|
syncStatement.bindInt32Parameter(2, ts);
|
||||||
syncStatement.bindInt32Parameter(3, ts);
|
|
||||||
|
|
||||||
if (storageEnabled &&
|
if (storageEnabled &&
|
||||||
oldItem.primary.itemType == 'attachment' &&
|
oldItem.primary.itemType == 'attachment' &&
|
||||||
|
@ -652,7 +648,7 @@ Zotero.Sync.Server = new function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.nextLocalSyncDate = false;
|
this.nextLocalSyncDate = false;
|
||||||
this.apiVersion = 2;
|
this.apiVersion = 3;
|
||||||
|
|
||||||
default xml namespace = '';
|
default xml namespace = '';
|
||||||
|
|
||||||
|
@ -715,7 +711,7 @@ Zotero.Sync.Server = new function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Zotero.debug('Got session ID ' + _sessionID + ' from server');
|
//Zotero.debug('Got session ID ' + _sessionID + ' from server');
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
|
@ -1318,10 +1314,10 @@ Zotero.BufferedInputListener.prototype = {
|
||||||
* },
|
* },
|
||||||
* deleted: {
|
* deleted: {
|
||||||
* items: [
|
* items: [
|
||||||
* { id: 1234, key: ABCDEFGHIJKMNPQRSTUVWXYZ23456789 }, ...
|
* 'ABCD1234', 'BCDE2345', ...
|
||||||
* ],
|
* ],
|
||||||
* creators: [
|
* creators: [
|
||||||
* { id: 1234, key: ABCDEFGHIJKMNPQRSTUVWXYZ23456789 }, ...
|
* 'ABCD1234', 'BCDE2345', ...
|
||||||
* ]
|
* ]
|
||||||
* }
|
* }
|
||||||
* };
|
* };
|
||||||
|
@ -1370,26 +1366,21 @@ Zotero.Sync.Server.Session.prototype.removeFromUpdated = function (syncObjectTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Zotero.Sync.Server.Session.prototype.addToDeleted = function (syncObjectTypeName, id, key) {
|
Zotero.Sync.Server.Session.prototype.addToDeleted = function (syncObjectTypeName, key) {
|
||||||
var pluralType = Zotero.Sync.syncObjects[syncObjectTypeName].plural.toLowerCase();
|
var pluralType = Zotero.Sync.syncObjects[syncObjectTypeName].plural.toLowerCase();
|
||||||
var deleted = this.uploadIDs.deleted[pluralType];
|
var deleted = this.uploadIDs.deleted[pluralType];
|
||||||
|
if (deleted.indexOf(key) != -1) {
|
||||||
// DEBUG: inefficient
|
|
||||||
for each(var pair in deleted) {
|
|
||||||
if (pair.id == id) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
deleted.push(key);
|
||||||
deleted.push({ id: id, key: key});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Zotero.Sync.Server.Session.prototype.removeFromDeleted = function (syncObjectTypeName, id, key) {
|
Zotero.Sync.Server.Session.prototype.removeFromDeleted = function (syncObjectTypeName, key) {
|
||||||
var pluralType = Zotero.Sync.syncObjects[syncObjectTypeName].plural.toLowerCase();
|
var pluralType = Zotero.Sync.syncObjects[syncObjectTypeName].plural.toLowerCase();
|
||||||
var deleted = this.uploadIDs.deleted[pluralType];
|
var deleted = this.uploadIDs.deleted[pluralType];
|
||||||
|
|
||||||
for (var i=0; i<deleted.length; i++) {
|
for (var i=0; i<deleted.length; i++) {
|
||||||
if (deleted[i].id == id && deleted[i].key == key) {
|
if (deleted[i] == key) {
|
||||||
deleted.splice(i, 1);
|
deleted.splice(i, 1);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
|
@ -1488,16 +1479,16 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
* Pull out collections from delete queue in XML
|
* Pull out collections from delete queue in XML
|
||||||
*
|
*
|
||||||
* @param {XML} xml
|
* @param {XML} xml
|
||||||
* @return {Integer[]} Array of collection ids
|
* @return {String[]} Array of collection keys
|
||||||
*/
|
*/
|
||||||
function _getDeletedCollections(xml) {
|
function _getDeletedCollectionKeys(xml) {
|
||||||
var ids = [];
|
var keys = [];
|
||||||
if (xml.deleted && xml.deleted.collections) {
|
if (xml.deleted && xml.deleted.collections) {
|
||||||
for each(var xmlNode in xml.deleted.collections.collection) {
|
for each(var xmlNode in xml.deleted.collections.collection) {
|
||||||
ids.push(parseInt(xmlNode.@id));
|
keys.push(xmlNode.@key.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ids;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1510,7 +1501,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
Zotero.DB.beginTransaction();
|
Zotero.DB.beginTransaction();
|
||||||
|
|
||||||
xml = _preprocessUpdatedXML(xml);
|
xml = _preprocessUpdatedXML(xml);
|
||||||
var deletedCollections = _getDeletedCollections(xml);
|
var deletedCollectionKeys = _getDeletedCollectionKeys(xml);
|
||||||
|
|
||||||
var remoteCreatorStore = {};
|
var remoteCreatorStore = {};
|
||||||
var relatedItemsStore = {};
|
var relatedItemsStore = {};
|
||||||
|
@ -1644,85 +1635,18 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'collection':
|
case 'collection':
|
||||||
var diff = obj.diff(remoteObj, false, true);
|
var changed = _mergeCollection(obj, remoteObj);
|
||||||
if (diff) {
|
if (!changed) {
|
||||||
var fieldsChanged = false;
|
|
||||||
for (var field in diff[0].primary) {
|
|
||||||
if (field != 'dateModified') {
|
|
||||||
fieldsChanged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var field in diff[0].fields) {
|
|
||||||
fieldsChanged = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldsChanged) {
|
|
||||||
// Check for collection hierarchy change
|
|
||||||
if (diff[0].childCollections.length) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
if (diff[1].childCollections.length) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
// Check for item membership change
|
|
||||||
if (diff[0].childItems.length) {
|
|
||||||
var childItems = remoteObj.getChildItems(true);
|
|
||||||
remoteObj.childItems = childItems.concat(diff[0].childItems);
|
|
||||||
}
|
|
||||||
if (diff[1].childItems.length) {
|
|
||||||
var childItems = obj.getChildItems(true);
|
|
||||||
obj.childItems = childItems.concat(diff[1].childItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: log
|
|
||||||
|
|
||||||
// TEMP: uncomment once supported
|
|
||||||
//reconcile = true;
|
|
||||||
}
|
|
||||||
// No CR necessary
|
|
||||||
else {
|
|
||||||
var save = false;
|
|
||||||
// Check for child collections in the remote object
|
|
||||||
// that aren't in the local one
|
|
||||||
if (diff[1].childCollections.length) {
|
|
||||||
// TODO: log
|
|
||||||
// TODO: add
|
|
||||||
save = true;
|
|
||||||
}
|
|
||||||
// Check for items in the remote object
|
|
||||||
// that aren't in the local one
|
|
||||||
if (diff[1].childItems.length) {
|
|
||||||
var childItems = obj.getChildItems(true);
|
|
||||||
obj.childItems = childItems.concat(diff[1].childItems);
|
|
||||||
|
|
||||||
var msg = _logCollectionItemMerge(obj.name, diff[1].childItems);
|
|
||||||
// TODO: log rather than alert
|
|
||||||
alert(msg);
|
|
||||||
|
|
||||||
save = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (save) {
|
|
||||||
obj.save();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
syncSession.removeFromUpdated(type, obj.id);
|
syncSession.removeFromUpdated(type, obj.id);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
break;
|
continue;
|
||||||
|
|
||||||
case 'tag':
|
case 'tag':
|
||||||
var diff = obj.diff(remoteObj, false, true);
|
var changed = _mergeTag(obj, remoteObj);
|
||||||
if (!diff) {
|
if (!changed) {
|
||||||
syncSession.removeFromUpdated(type, obj.id);
|
syncSession.removeFromUpdated(type, obj.id);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reconcile) {
|
if (!reconcile) {
|
||||||
|
@ -1775,13 +1699,6 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
syncSession.uploadIDs.updated[types][index] = newID;
|
syncSession.uploadIDs.updated[types][index] = newID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update id in local deletions array
|
|
||||||
for (var i in syncSession.uploadIDs.deleted[types]) {
|
|
||||||
if (syncSession.uploadIDs.deleted[types][i].id == oldID) {
|
|
||||||
syncSession.uploadIDs.deleted[types][i] = newID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add items linked to creators to updated array,
|
// Add items linked to creators to updated array,
|
||||||
// since their timestamps will be set to the
|
// since their timestamps will be set to the
|
||||||
// transaction timestamp
|
// transaction timestamp
|
||||||
|
@ -1808,21 +1725,35 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
else {
|
else {
|
||||||
isNewObject = true;
|
isNewObject = true;
|
||||||
|
|
||||||
|
Zotero.debug(syncSession.uploadIDs.deleted);
|
||||||
|
|
||||||
// Check if object has been deleted locally
|
// Check if object has been deleted locally
|
||||||
for each(var pair in syncSession.uploadIDs.deleted[types]) {
|
for each(var key in syncSession.uploadIDs.deleted[types]) {
|
||||||
if (pair.id != parseInt(xmlNode.@id) ||
|
if (key != xmlNode.@key.toString()) {
|
||||||
pair.key != xmlNode.@key.toString()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: non-merged items
|
// TODO: non-merged items
|
||||||
|
|
||||||
if (type != 'item') {
|
switch (type) {
|
||||||
|
case 'item':
|
||||||
|
localDelete = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Auto-restore locally deleted tags that have
|
||||||
|
// changed remotely
|
||||||
|
case 'tag':
|
||||||
|
syncSession.removeFromDeleted(type, key);
|
||||||
|
var msg = _generateAutoChangeMessage(
|
||||||
|
type, null, xmlNode.@name.toString()
|
||||||
|
);
|
||||||
|
alert(msg);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
alert('Delete reconciliation unimplemented for ' + types);
|
alert('Delete reconciliation unimplemented for ' + types);
|
||||||
throw ('Delete reconciliation unimplemented for ' + types);
|
throw ('Delete reconciliation unimplemented for ' + types);
|
||||||
}
|
}
|
||||||
|
|
||||||
localDelete = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If key already exists on a different item, change local key
|
// If key already exists on a different item, change local key
|
||||||
|
@ -1858,13 +1789,19 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
? parseInt(xmlNode.@type) : 0;
|
? parseInt(xmlNode.@type) : 0;
|
||||||
var linkedItems = _deleteConflictingTag(syncSession, tagName, tagType);
|
var linkedItems = _deleteConflictingTag(syncSession, tagName, tagType);
|
||||||
if (linkedItems) {
|
if (linkedItems) {
|
||||||
obj.dateModified = Zotero.DB.transactionDateTime;
|
var mod = false;
|
||||||
for each(var id in linkedItems) {
|
for each(var id in linkedItems) {
|
||||||
obj.addItem(id);
|
var added = obj.addItem(id);
|
||||||
|
if (added) {
|
||||||
|
mod = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (mod) {
|
||||||
|
obj.dateModified = Zotero.DB.transactionDateTime;
|
||||||
syncSession.addToUpdated('tag', parseInt(xmlNode.@id));
|
syncSession.addToUpdated('tag', parseInt(xmlNode.@id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (localDelete) {
|
if (localDelete) {
|
||||||
// TODO: order reconcile by parent/child?
|
// TODO: order reconcile by parent/child?
|
||||||
|
@ -1889,11 +1826,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
if (obj.isRegularItem()) {
|
if (obj.isRegularItem()) {
|
||||||
var creators = obj.getCreators();
|
var creators = obj.getCreators();
|
||||||
for each(var creator in creators) {
|
for each(var creator in creators) {
|
||||||
syncSession.removeFromDeleted(
|
syncSession.removeFromDeleted('creator', creator.ref.key);
|
||||||
'creator',
|
|
||||||
creator.ref.id,
|
|
||||||
creator.ref.key
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (obj.isAttachment() &&
|
else if (obj.isAttachment() &&
|
||||||
|
@ -1925,10 +1858,14 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
Zotero.debug("Processing remotely deleted " + types);
|
Zotero.debug("Processing remotely deleted " + types);
|
||||||
|
|
||||||
for each(var xmlNode in xml.deleted[types][type]) {
|
for each(var xmlNode in xml.deleted[types][type]) {
|
||||||
var id = parseInt(xmlNode.@id);
|
var key = xmlNode.@key.toString();
|
||||||
var obj = Zotero[Types].get(id);
|
var obj = Zotero[Types].getByKey(key);
|
||||||
// Object can't be found
|
// Object can't be found
|
||||||
if (!obj || obj.key != xmlNode.@key) {
|
if (!obj) {
|
||||||
|
// Since it's already deleted remotely, don't include
|
||||||
|
// the object in the deleted array if something else
|
||||||
|
// caused its deletion during the sync
|
||||||
|
syncSession.removeFromDeleted(type, xmlNode.@key.toString());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1940,7 +1877,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
// Local object hasn't been modified -- delete
|
// Local object hasn't been modified -- delete
|
||||||
else {
|
else {
|
||||||
toDelete.push(id);
|
toDelete.push(obj.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1995,7 +1932,20 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for each(var obj in toSave) {
|
for each(var obj in toSave) {
|
||||||
obj.save();
|
// Use a special saving mode for tags to avoid an issue that
|
||||||
|
// occurs if a tag has changed names remotely but another tag
|
||||||
|
// conflicts with the local version after the first tag has been
|
||||||
|
// updated in memory, causing a deletion of the local tag.
|
||||||
|
// Using the normal save mode, when the first remote tag then
|
||||||
|
// goes to save, the linked items aren't saved, since as far
|
||||||
|
// as the in-memory object is concerned, they haven't changed,
|
||||||
|
// even though they've been deleted from the DB.
|
||||||
|
//
|
||||||
|
// To replicate, add an item, add a tag, sync both sides,
|
||||||
|
// rename the tag, add a new one with the old name, and sync.
|
||||||
|
var full = type == 'tag';
|
||||||
|
|
||||||
|
obj.save(full);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add back related items (which now exist)
|
// Add back related items (which now exist)
|
||||||
|
@ -2012,7 +1962,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
// Add back subcollections
|
// Add back subcollections
|
||||||
else if (type == 'collection') {
|
else if (type == 'collection') {
|
||||||
for each(var collection in collections) {
|
for each(var collection in collections) {
|
||||||
if (collection.childCollections) {
|
if (collection.childCollections.length) {
|
||||||
collection.obj.childCollections = collection.childCollections;
|
collection.obj.childCollections = collection.childCollections;
|
||||||
collection.obj.save();
|
collection.obj.save();
|
||||||
}
|
}
|
||||||
|
@ -2042,8 +1992,8 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
// collections so that any deleted items within them don't
|
// collections so that any deleted items within them don't
|
||||||
// update them, which would trigger erroneous conflicts
|
// update them, which would trigger erroneous conflicts
|
||||||
var collections = [];
|
var collections = [];
|
||||||
for each(var colID in deletedCollections) {
|
for each(var colKey in deletedCollectionKeys) {
|
||||||
var col = Zotero.Collections.get(colID);
|
var col = Zotero.Collections.getByKey(colKey);
|
||||||
col.lockDateModified();
|
col.lockDateModified();
|
||||||
collections.push(col);
|
collections.push(col);
|
||||||
}
|
}
|
||||||
|
@ -2154,10 +2104,9 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
|
|
||||||
Zotero.debug('Processing locally deleted ' + types);
|
Zotero.debug('Processing locally deleted ' + types);
|
||||||
|
|
||||||
for each(var obj in ids.deleted[types]) {
|
for each(var key in ids.deleted[types]) {
|
||||||
var deletexml = new XML('<' + type + '/>');
|
var deletexml = new XML('<' + type + '/>');
|
||||||
deletexml.@id = obj.id;
|
deletexml.@key = key;
|
||||||
deletexml.@key = obj.key;
|
|
||||||
xml.deleted[types][type] += deletexml;
|
xml.deleted[types][type] += deletexml;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2171,6 +2120,215 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _mergeCollection(localObj, remoteObj) {
|
||||||
|
var diff = localObj.diff(remoteObj, false, true);
|
||||||
|
if (!diff) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.debug("COLLECTION HAS CHANGED");
|
||||||
|
Zotero.debug(diff);
|
||||||
|
|
||||||
|
// Local is newer
|
||||||
|
if (diff[0].primary.dateModified >
|
||||||
|
diff[1].primary.dateModified) {
|
||||||
|
var remoteIsTarget = false;
|
||||||
|
var targetObj = localObj;
|
||||||
|
var targetDiff = diff[0];
|
||||||
|
var otherDiff = diff[1];
|
||||||
|
}
|
||||||
|
// Remote is newer
|
||||||
|
else {
|
||||||
|
var remoteIsTarget = true;
|
||||||
|
var targetObj = remoteObj;
|
||||||
|
var targetDiff = diff[1];
|
||||||
|
var otherDiff = diff[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetDiff.fields.name) {
|
||||||
|
var msg = _generateAutoChangeMessage(
|
||||||
|
'collection', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
|
||||||
|
);
|
||||||
|
// TODO: log rather than alert
|
||||||
|
alert(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for child collections in the other object
|
||||||
|
// that aren't in the target one
|
||||||
|
if (otherDiff.childCollections.length) {
|
||||||
|
// TODO: log
|
||||||
|
// TODO: add
|
||||||
|
throw ("Collection hierarchy conflict resolution is unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add items in other object to target one
|
||||||
|
if (otherDiff.childItems.length) {
|
||||||
|
var childItems = targetObj.getChildItems(true);
|
||||||
|
targetObj.childItems = childItems.concat(otherDiff.childItems);
|
||||||
|
|
||||||
|
var msg = _generateCollectionItemMergeMessage(
|
||||||
|
targetObj.name,
|
||||||
|
otherDiff.childItems,
|
||||||
|
remoteIsTarget
|
||||||
|
);
|
||||||
|
// TODO: log rather than alert
|
||||||
|
alert(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetObj.save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _mergeTag(localObj, remoteObj) {
|
||||||
|
var diff = localObj.diff(remoteObj, false, true);
|
||||||
|
if (!diff) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Zotero.debug("TAG HAS CHANGED");
|
||||||
|
Zotero.debug(diff);
|
||||||
|
|
||||||
|
// Local is newer
|
||||||
|
if (diff[0].primary.dateModified >
|
||||||
|
diff[1].primary.dateModified) {
|
||||||
|
var remoteIsTarget = false;
|
||||||
|
var targetObj = localObj;
|
||||||
|
var targetDiff = diff[0];
|
||||||
|
var otherDiff = diff[1];
|
||||||
|
}
|
||||||
|
// Remote is newer
|
||||||
|
else {
|
||||||
|
var remoteIsTarget = true;
|
||||||
|
var targetObj = remoteObj;
|
||||||
|
var targetDiff = diff[1];
|
||||||
|
var otherDiff = diff[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: log old name
|
||||||
|
if (targetDiff.fields.name) {
|
||||||
|
var msg = _generateAutoChangeMessage(
|
||||||
|
'tag', diff[0].fields.name, diff[1].fields.name, remoteIsTarget
|
||||||
|
);
|
||||||
|
alert(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add linked items in the other object to the target one
|
||||||
|
if (otherDiff.linkedItems.length) {
|
||||||
|
// need to handle changed items
|
||||||
|
|
||||||
|
var linkedItems = targetObj.getLinkedItems(true);
|
||||||
|
targetObj.linkedItems = linkedItems.concat(otherDiff.linkedItems);
|
||||||
|
|
||||||
|
var msg = _generateTagItemMergeMessage(
|
||||||
|
targetObj.name,
|
||||||
|
otherDiff.linkedItems,
|
||||||
|
remoteIsTarget
|
||||||
|
);
|
||||||
|
// TODO: log rather than alert
|
||||||
|
alert(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetObj.save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} itemType
|
||||||
|
* @param {String} localName
|
||||||
|
* @param {String} remoteName
|
||||||
|
* @param {Boolean} [remoteMoreRecent=false]
|
||||||
|
*/
|
||||||
|
function _generateAutoChangeMessage(itemType, localName, remoteName, remoteMoreRecent) {
|
||||||
|
if (localName === null) {
|
||||||
|
// TODO: localize
|
||||||
|
localName = "[deleted]";
|
||||||
|
var localDelete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: localize
|
||||||
|
var msg = "A " + itemType + " has changed both locally and "
|
||||||
|
+ "remotely since the last sync:";
|
||||||
|
msg += "\n\n";
|
||||||
|
msg += "Local version: " + localName + "\n";
|
||||||
|
msg += "Remote version: " + remoteName + "\n";
|
||||||
|
msg += "\n";
|
||||||
|
if (localDelete) {
|
||||||
|
msg += "The remote version has been kept.";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var moreRecent = remoteMoreRecent ? remoteName : localName;
|
||||||
|
msg += "The most recent version, '" + moreRecent + "', has been kept.";
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} collectionName
|
||||||
|
* @param {Integer[]} addedItemIDs
|
||||||
|
* @param {Boolean} remoteIsTarget
|
||||||
|
*/
|
||||||
|
function _generateCollectionItemMergeMessage(collectionName, addedItemIDs, remoteIsTarget) {
|
||||||
|
// TODO: localize
|
||||||
|
var introMsg = "Items in the collection '" + collectionName + "' have been "
|
||||||
|
+ "added and/or removed in multiple locations."
|
||||||
|
|
||||||
|
introMsg += " ";
|
||||||
|
if (remoteIsTarget) {
|
||||||
|
introMsg += "The following items have been added to the remote collection:";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
introMsg += "The following items have been added to the local collection:";
|
||||||
|
}
|
||||||
|
var itemText = [];
|
||||||
|
for each(var id in addedItemIDs) {
|
||||||
|
var item = Zotero.Items.get(id);
|
||||||
|
var title = item.getField('title');
|
||||||
|
var text = " - " + title;
|
||||||
|
var firstCreator = item.getField('firstCreator');
|
||||||
|
if (firstCreator) {
|
||||||
|
text += " (" + firstCreator + ")";
|
||||||
|
}
|
||||||
|
itemText.push(text);
|
||||||
|
}
|
||||||
|
return introMsg + "\n\n" + itemText.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} tagName
|
||||||
|
* @param {Integer[]} addedItemIDs
|
||||||
|
* @param {Boolean} remoteIsTarget
|
||||||
|
*/
|
||||||
|
function _generateTagItemMergeMessage(tagName, addedItemIDs, remoteIsTarget) {
|
||||||
|
// TODO: localize
|
||||||
|
var introMsg = "The tag '" + tagName + "' has been "
|
||||||
|
+ "added to and/or removed from items in multiple locations."
|
||||||
|
|
||||||
|
introMsg += " ";
|
||||||
|
if (remoteIsTarget) {
|
||||||
|
introMsg += "It has been added to the following remote items:";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
introMsg += "It has been added to the following local items:";
|
||||||
|
}
|
||||||
|
var itemText = [];
|
||||||
|
for each(var id in addedItemIDs) {
|
||||||
|
var item = Zotero.Items.get(id);
|
||||||
|
var title = item.getField('title');
|
||||||
|
var text = " - " + title;
|
||||||
|
var firstCreator = item.getField('firstCreator');
|
||||||
|
if (firstCreator) {
|
||||||
|
text += " (" + firstCreator + ")";
|
||||||
|
}
|
||||||
|
itemText.push(text);
|
||||||
|
}
|
||||||
|
return introMsg + "\n\n" + itemText.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a conflict resolution window and return the results
|
* Open a conflict resolution window and return the results
|
||||||
*
|
*
|
||||||
|
@ -2223,7 +2381,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
delete relatedItems[obj.id];
|
delete relatedItems[obj.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
syncSession.addToDeleted(type, obj.id, obj.left.key);
|
syncSession.addToDeleted(type, obj.left.key);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2236,7 +2394,7 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
// Item had been deleted locally, so remove from
|
// Item had been deleted locally, so remove from
|
||||||
// deleted array
|
// deleted array
|
||||||
if (obj.left == 'deleted') {
|
if (obj.left == 'deleted') {
|
||||||
syncSession.removeFromDeleted(type, obj.id, obj.ref.key);
|
syncSession.removeFromDeleted(type, obj.ref.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: only upload if the local item was chosen
|
// TODO: only upload if the local item was chosen
|
||||||
|
@ -2583,26 +2741,6 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function _logCollectionItemMerge(collectionName, remoteItemIDs) {
|
|
||||||
// TODO: localize
|
|
||||||
var introMsg = "Items in the collection '" + collectionName + "' have been "
|
|
||||||
+ "added and/or removed in multiple locations. The following remote "
|
|
||||||
+ "items have been added to the local collection:";
|
|
||||||
var itemText = [];
|
|
||||||
for each(var id in remoteItemIDs) {
|
|
||||||
var item = Zotero.Items.get(id);
|
|
||||||
var title = item.getField('title');
|
|
||||||
var text = " - " + title;
|
|
||||||
var firstCreator = item.getField('firstCreator');
|
|
||||||
if (firstCreator) {
|
|
||||||
text += " (" + firstCreator + ")";
|
|
||||||
}
|
|
||||||
itemText.push(text);
|
|
||||||
}
|
|
||||||
return introMsg + "\n\n" + itemText.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a Zotero.Creator object to an E4X <creator> object
|
* Converts a Zotero.Creator object to an E4X <creator> object
|
||||||
*/
|
*/
|
||||||
|
@ -2871,16 +3009,16 @@ Zotero.Sync.Server.Data = new function() {
|
||||||
function _deleteConflictingTag(syncSession, name, type) {
|
function _deleteConflictingTag(syncSession, name, type) {
|
||||||
var tagID = Zotero.Tags.getID(name, type);
|
var tagID = Zotero.Tags.getID(name, type);
|
||||||
if (tagID) {
|
if (tagID) {
|
||||||
|
Zotero.debug("Deleting conflicting local tag " + tagID);
|
||||||
var tag = Zotero.Tags.get(tagID);
|
var tag = Zotero.Tags.get(tagID);
|
||||||
var linkedItems = tag.getLinkedItems(true);
|
var linkedItems = tag.getLinkedItems(true);
|
||||||
Zotero.Tags.erase(tagID);
|
Zotero.Tags.erase(tagID);
|
||||||
// DEBUG: should purge() be called by Tags.erase()
|
|
||||||
Zotero.Tags.purge();
|
Zotero.Tags.purge();
|
||||||
|
|
||||||
syncSession.removeFromUpdated('tag', tagID);
|
syncSession.removeFromUpdated('tag', tagID);
|
||||||
syncSession.addToDeleted('tag', tagID, tag.key);
|
//syncSession.addToDeleted('tag', tag.key);
|
||||||
|
|
||||||
return linkedItems;
|
return linkedItems ? linkedItems : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -292,27 +292,33 @@ Zotero.Utilities.prototype.isInt = function(x) {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares an array with another (comparator) and returns an array with
|
* Compares an array with another and returns an array with
|
||||||
* the values from comparator that don't exist in vector
|
* the values from array2 that don't exist in array1
|
||||||
*
|
*
|
||||||
* Code by Carlos R. L. Rodrigues
|
* @param {Array} array1 Array that will be checked
|
||||||
* From http://jsfromhell.com/array/diff [rev. #1]
|
* @param {Array} array2 Array that will be compared
|
||||||
*
|
* @param {Boolean} useIndex If true, return an array containing just
|
||||||
* @param {Array} v Array that will be checked
|
|
||||||
* @param {Array} c Array that will be compared
|
|
||||||
* @param {Boolean} useIndex If true, returns an array containing just
|
|
||||||
* the index of the comparator's elements;
|
* the index of the comparator's elements;
|
||||||
* otherwise returns the values
|
* otherwise return the values
|
||||||
*/
|
*/
|
||||||
Zotero.Utilities.prototype.arrayDiff = function(v, c, m) {
|
Zotero.Utilities.prototype.arrayDiff = function(array1, array2, useIndex) {
|
||||||
var d = [], e = -1, h, i, j, k;
|
if (array1.constructor.name != 'Array') {
|
||||||
for(i = c.length, k = v.length; i--;){
|
throw ("array1 is not an array in Zotero.Utilities.arrayDiff() (" + array1 + ")");
|
||||||
for(j = k; j && (h = c[i] !== v[--j]););
|
}
|
||||||
h && (d[++e] = m ? i : c[i]);
|
if (array2.constructor.name != 'Array') {
|
||||||
|
throw ("array2 is not an array in Zotero.Utilities.arrayDiff() (" + array2 + ")");
|
||||||
}
|
}
|
||||||
return d;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
var val, pos, vals = [];
|
||||||
|
for (var i=0; i<array2.length; i++) {
|
||||||
|
val = array2[i];
|
||||||
|
pos = array1.indexOf(val);
|
||||||
|
if (pos == -1) {
|
||||||
|
vals.push(useIndex ? pos : val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- 46
|
-- 47
|
||||||
|
|
||||||
-- This file creates tables containing user-specific data -- any changes made
|
-- This file creates tables containing user-specific data -- any changes made
|
||||||
-- here must be mirrored in transition steps in schema.js::_migrateSchema()
|
-- here must be mirrored in transition steps in schema.js::_migrateSchema()
|
||||||
|
@ -198,7 +198,6 @@ CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID);
|
||||||
|
|
||||||
CREATE TABLE syncDeleteLog (
|
CREATE TABLE syncDeleteLog (
|
||||||
syncObjectTypeID INT NOT NULL,
|
syncObjectTypeID INT NOT NULL,
|
||||||
objectID INT NOT NULL,
|
|
||||||
key TEXT NOT NULL UNIQUE,
|
key TEXT NOT NULL UNIQUE,
|
||||||
timestamp INT NOT NULL,
|
timestamp INT NOT NULL,
|
||||||
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
|
FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
|
||||||
|
|
Loading…
Add table
Reference in a new issue