Add Notifier.queue()

Notifier.trigger() needs to be async, since if it actually runs it waits for
promises returned from observers. But the vast majority of trigger() calls are
in transactions where they just queue and can therefore be synchronous. This
replaces all such calls with Notifier.queue().

This should fix a race condition that was causing the emptyTrash() test to fail
intermittently.
This commit is contained in:
Dan Stillman 2015-05-29 05:03:05 -04:00
parent f3a6b41c1c
commit 5a2ec43de1
11 changed files with 126 additions and 88 deletions

View File

@ -341,6 +341,7 @@ var Zotero_File_Interface = new function() {
Zotero.UnresponsiveScriptIndicator.enable(); Zotero.UnresponsiveScriptIndicator.enable();
if(importCollection) { if(importCollection) {
// TODO: yield or change to .queue()
Zotero.Notifier.trigger('refresh', 'collection', importCollection.id); Zotero.Notifier.trigger('refresh', 'collection', importCollection.id);
} }
if (!worked) { if (!worked) {

View File

@ -292,7 +292,7 @@ Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env)
)); ));
} }
if (!isNew) { if (!isNew) {
Zotero.Notifier.trigger('move', 'collection', this.id); Zotero.Notifier.queue('move', 'collection', this.id);
} }
env.parentIDs = parentIDs; env.parentIDs = parentIDs;
} }
@ -307,10 +307,10 @@ Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* (
if (!env.options.skipNotifier) { if (!env.options.skipNotifier) {
if (env.isNew) { if (env.isNew) {
Zotero.Notifier.trigger('add', 'collection', this.id, env.notifierData); Zotero.Notifier.queue('add', 'collection', this.id, env.notifierData);
} }
else { else {
Zotero.Notifier.trigger('modify', 'collection', this.id, env.notifierData); Zotero.Notifier.queue('modify', 'collection', this.id, env.notifierData);
} }
} }
@ -611,7 +611,7 @@ Zotero.Collection.prototype._eraseData = Zotero.Promise.coroutine(function* (env
//return Zotero.Collections.reloadAll(); //return Zotero.Collections.reloadAll();
if (!env.skipNotifier) { if (!env.skipNotifier) {
Zotero.Notifier.trigger('delete', 'collection', collections, notifierData); Zotero.Notifier.queue('delete', 'collection', collections, notifierData);
} }
}); });

View File

@ -245,10 +245,10 @@ Zotero.Group.prototype.save = Zotero.Promise.coroutine(function* () {
yield this.load(); yield this.load();
Zotero.Groups.register(this) Zotero.Groups.register(this)
}.bind(this))); }.bind(this)));
Zotero.Notifier.trigger('add', 'group', this.id); Zotero.Notifier.queue('add', 'group', this.id);
} }
else { else {
Zotero.Notifier.trigger('modify', 'group', this.id); Zotero.Notifier.queue('modify', 'group', this.id);
} }
}.bind(this)); }.bind(this));
}); });
@ -293,7 +293,7 @@ Zotero.Group.prototype.erase = Zotero.Promise.coroutine(function* () {
yield Zotero.DB.queryAsync(sql, this.libraryID) yield Zotero.DB.queryAsync(sql, this.libraryID)
Zotero.Groups.unregister(this.id); Zotero.Groups.unregister(this.id);
Zotero.Notifier.trigger('delete', 'group', this.id, notifierData); Zotero.Notifier.queue('delete', 'group', this.id, notifierData);
}.bind(this)); }.bind(this));
yield Zotero.purgeDataObjects(); yield Zotero.purgeDataObjects();

View File

@ -1231,7 +1231,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
} }
if (!env.options.skipNotifier) { if (!env.options.skipNotifier) {
Zotero.Notifier.trigger('add', 'item', itemID, env.notifierData); Zotero.Notifier.queue('add', 'item', itemID, env.notifierData);
} }
} }
else { else {
@ -1240,7 +1240,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.DB.queryAsync(sql, env.sqlValues); yield Zotero.DB.queryAsync(sql, env.sqlValues);
if (!env.options.skipNotifier) { if (!env.options.skipNotifier) {
Zotero.Notifier.trigger('modify', 'item', itemID, env.notifierData); Zotero.Notifier.queue('modify', 'item', itemID, env.notifierData);
} }
} }
@ -1349,7 +1349,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
let newParentItemNotifierData = {}; let newParentItemNotifierData = {};
//newParentItemNotifierData[newParentItem.id] = {}; //newParentItemNotifierData[newParentItem.id] = {};
Zotero.Notifier.trigger('modify', 'item', parentItemID, newParentItemNotifierData); Zotero.Notifier.queue('modify', 'item', parentItemID, newParentItemNotifierData);
switch (Zotero.ItemTypes.getName(itemTypeID)) { switch (Zotero.ItemTypes.getName(itemTypeID)) {
case 'note': case 'note':
@ -1373,7 +1373,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
let newParentItemNotifierData = {}; let newParentItemNotifierData = {};
//newParentItemNotifierData[newParentItem.id] = {}; //newParentItemNotifierData[newParentItem.id] = {};
Zotero.Notifier.trigger('modify', 'item', parentItemID, newParentItemNotifierData); Zotero.Notifier.queue('modify', 'item', parentItemID, newParentItemNotifierData);
} }
let oldParentKey = this._previousData.parentKey; let oldParentKey = this._previousData.parentKey;
@ -1383,7 +1383,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
if (oldParentItemID) { if (oldParentItemID) {
let oldParentItemNotifierData = {}; let oldParentItemNotifierData = {};
//oldParentItemNotifierData[oldParentItemID] = {}; //oldParentItemNotifierData[oldParentItemID] = {};
Zotero.Notifier.trigger('modify', 'item', oldParentItemID, oldParentItemNotifierData); Zotero.Notifier.queue('modify', 'item', oldParentItemID, oldParentItemNotifierData);
} }
else { else {
Zotero.debug("Old source item " + oldParentKey Zotero.debug("Old source item " + oldParentKey
@ -1406,7 +1406,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
yield this.loadCollections(); yield this.loadCollections();
this.removeFromCollection(changedCollections[i]); this.removeFromCollection(changedCollections[i]);
Zotero.Notifier.trigger( Zotero.Notifier.queue(
'remove', 'remove',
'collection-item', 'collection-item',
changedCollections[i] + '-' + this.id changedCollections[i] + '-' + this.id
@ -1472,9 +1472,9 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.DB.queryAsync(sql, itemID); yield Zotero.DB.queryAsync(sql, itemID);
// Refresh trash // Refresh trash
Zotero.Notifier.trigger('refresh', 'trash', this.libraryID); Zotero.Notifier.queue('refresh', 'trash', this.libraryID);
if (this._deleted) { if (this._deleted) {
Zotero.Notifier.trigger('trash', 'item', this.id); Zotero.Notifier.queue('trash', 'item', this.id);
} }
if (parentItemID) { if (parentItemID) {
@ -1594,7 +1594,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
// "OR REPLACE" allows changing type // "OR REPLACE" allows changing type
let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)"; let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]); yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + tag.tag); Zotero.Notifier.queue('add', 'item-tag', this.id + '-' + tag.tag);
} }
if (toRemove.length) { if (toRemove.length) {
@ -1604,7 +1604,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
let tagID = Zotero.Tags.getID(this.libraryID, tag.tag); let tagID = Zotero.Tags.getID(this.libraryID, tag.tag);
let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?"; let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?";
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]); yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + tag.tag); Zotero.Notifier.queue('remove', 'item-tag', this.id + '-' + tag.tag);
} }
Zotero.Prefs.set('purge.tags', true); Zotero.Prefs.set('purge.tags', true);
} }
@ -1634,7 +1634,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.DB.queryAsync(sql, [collectionID, this.id, orderIndex]); yield Zotero.DB.queryAsync(sql, [collectionID, this.id, orderIndex]);
yield this.ContainerObjectsClass.refreshChildItems(collectionID); yield this.ContainerObjectsClass.refreshChildItems(collectionID);
Zotero.Notifier.trigger('add', 'collection-item', collectionID + '-' + this.id); Zotero.Notifier.queue('add', 'collection-item', collectionID + '-' + this.id);
} }
if (toRemove.length) { if (toRemove.length) {
@ -1646,7 +1646,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
for (let i=0; i<toRemove.length; i++) { for (let i=0; i<toRemove.length; i++) {
let collectionID = toRemove[i]; let collectionID = toRemove[i];
yield this.ContainerObjectsClass.refreshChildItems(collectionID); yield this.ContainerObjectsClass.refreshChildItems(collectionID);
Zotero.Notifier.trigger('remove', 'collection-item', collectionID + '-' + this.id); Zotero.Notifier.queue('remove', 'collection-item', collectionID + '-' + this.id);
} }
} }
} }
@ -1714,7 +1714,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
} }
} }
Zotero.Notifier.trigger('modify', 'item', removed.concat(newids)); Zotero.Notifier.queue('modify', 'item', removed.concat(newids));
} }
// Update child item counts and contents // Update child item counts and contents
@ -3977,7 +3977,7 @@ Zotero.Item.prototype._erasePreCommit = Zotero.Promise.coroutine(function* (env)
this.ObjectsClass.unload(this.id); this.ObjectsClass.unload(this.id);
if (!env.skipNotifier) { if (!env.skipNotifier) {
Zotero.Notifier.trigger('delete', 'item', this.id, env.deletedItemNotifierData); Zotero.Notifier.queue('delete', 'item', this.id, env.deletedItemNotifierData);
} }
Zotero.Prefs.set('purge.items', true); Zotero.Prefs.set('purge.items', true);

View File

@ -485,7 +485,7 @@ Zotero.Items = function() {
let item = yield this.getAsync(id); let item = yield this.getAsync(id);
if (!item) { if (!item) {
Zotero.debug('Item ' + id + ' does not exist in Items.trash()!', 1); Zotero.debug('Item ' + id + ' does not exist in Items.trash()!', 1);
Zotero.Notifier.trigger('delete', 'item', id); Zotero.Notifier.queue('delete', 'item', id);
continue; continue;
} }
item.deleted = true; item.deleted = true;
@ -515,7 +515,7 @@ Zotero.Items = function() {
if (deletedIDs.length) { if (deletedIDs.length) {
yield Zotero.Utilities.Internal.forEachChunkAsync(deletedIDs, 50, function* (chunk) { yield Zotero.Utilities.Internal.forEachChunkAsync(deletedIDs, 50, function* (chunk) {
yield this.erase(chunk); yield this.erase(chunk);
Zotero.Notifier.trigger('refresh', 'trash', libraryID); yield Zotero.Notifier.trigger('refresh', 'trash', libraryID);
}.bind(this)); }.bind(this));
} }

View File

@ -274,7 +274,7 @@ Zotero.Tags = new function() {
tag: oldName tag: oldName
} }
}; };
Zotero.Notifier.trigger( Zotero.Notifier.queue(
'modify', 'modify',
'item-tag', 'item-tag',
oldItemIDs.map(function (itemID) itemID + '-' + newName), oldItemIDs.map(function (itemID) itemID + '-' + newName),
@ -425,7 +425,7 @@ Zotero.Tags = new function() {
sql = "DROP TABLE tagDelete"; sql = "DROP TABLE tagDelete";
yield Zotero.DB.queryAsync(sql); yield Zotero.DB.queryAsync(sql);
Zotero.Notifier.trigger('delete', 'tag', toDelete, notifierData); Zotero.Notifier.queue('delete', 'tag', toDelete, notifierData);
Zotero.Prefs.set('purge.tags', false); Zotero.Prefs.set('purge.tags', false);
}); });
@ -610,7 +610,7 @@ Zotero.Tags = new function() {
}; };
if (affectedItems.length) { if (affectedItems.length) {
Zotero.Notifier.trigger('redraw', 'item', affectedItems, { column: 'title' }); yield Zotero.Notifier.trigger('redraw', 'item', affectedItems, { column: 'title' });
} }
} }
}); });

View File

@ -424,6 +424,8 @@ Zotero.Fulltext = new function(){
var sql = "REPLACE INTO fulltextContent (itemID, textContent) VALUES (?,?)"; var sql = "REPLACE INTO fulltextContent (itemID, textContent) VALUES (?,?)";
Zotero.DB.query(sql, [itemID, {string:text}]); Zotero.DB.query(sql, [itemID, {string:text}]);
*/ */
Zotero.Notifier.queue('refresh', 'item', itemID);
}.bind(this)); }.bind(this));
// If there's a processor cache file, delete it (whether or not we just used it) // If there's a processor cache file, delete it (whether or not we just used it)
@ -432,8 +434,6 @@ Zotero.Fulltext = new function(){
if (cacheFile.exists()) { if (cacheFile.exists()) {
cacheFile.remove(false); cacheFile.remove(false);
} }
Zotero.Notifier.trigger('refresh', 'item', itemID);
}.bind(this)); }.bind(this));

View File

@ -88,6 +88,7 @@ Zotero.Notifier = new function(){
delete _observers[id]; delete _observers[id];
} }
/** /**
* Trigger a notification to the appropriate observers * Trigger a notification to the appropriate observers
* *
@ -106,37 +107,78 @@ Zotero.Notifier = new function(){
* - New events and types should be added to the order arrays in commit() * - New events and types should be added to the order arrays in commit()
**/ **/
this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) { this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
if (_inTransaction && !force) {
return this.queue(event, type, ids, extraData);
}
if (_disabled){ if (_disabled){
Zotero.debug("Notifications are disabled"); Zotero.debug("Notifications are disabled");
return false; return false;
} }
if (_types && _types.indexOf(type) == -1){ if (_types && _types.indexOf(type) == -1) {
throw ('Invalid type ' + type + ' in Notifier.trigger()'); throw new Error("Invalid type '" + type + "'");
} }
ids = Zotero.flattenArguments(ids); ids = Zotero.flattenArguments(ids);
var queue = _inTransaction && !force; if (Zotero.Debug.enabled) {
_logTrigger(event, type, ids, extraData);
}
var order = _getObserverOrder(type);
for (let id of order) {
Zotero.debug("Calling notify() with " + event + "/" + type
+ " on observer with id '" + id + "'", 4);
if (!_observers[id]) {
Zotero.debug("Observer no longer exists");
continue;
}
// Catch exceptions so all observers get notified even if
// one throws an error
try {
yield Zotero.Promise.resolve(_observers[id].ref.notify(event, type, ids, extraData));
}
catch (e) {
Zotero.debug(e);
Components.utils.reportError(e);
}
}
return true;
});
/**
* Queue an event until the end of the current notifier transaction
*
* Takes the same parameters as trigger()
*
* @throws If a notifier transaction isn't currently open
*/
this.queue = function (event, type, ids, extraData) {
if (_disabled){
Zotero.debug("Notifications are disabled");
return false;
}
if (_types && _types.indexOf(type) == -1) {
throw new Error("Invalid type '" + type + "'");
}
ids = Zotero.flattenArguments(ids);
if (Zotero.Debug.enabled) { if (Zotero.Debug.enabled) {
Zotero.debug("Notifier.trigger(" _logTrigger(event, type, ids, extraData, true);
+ "'" + event + "', "
+ "'" + type + "', "
+ "[" + ids.join() + "]"
+ (extraData ? ", " + extraData : "")
+ ")"
+ (queue
? " queued"
: " called " + "[observers: " + _countObserversForType(type) + "]")
);
if (extraData) {
Zotero.debug(extraData);
} }
if (!_inTransaction) {
throw new Error("Can't queue event outside of a transaction");
} }
// Merge with existing queue // Merge with existing queue
if (queue) {
if (!_queue[type]) { if (!_queue[type]) {
_queue[type] = []; _queue[type] = [];
} }
@ -173,30 +215,24 @@ Zotero.Notifier = new function(){
return true; return true;
} }
var order = _getObserverOrder(type);
for (let id of order) {
Zotero.debug("Calling notify() with " + event + "/" + type
+ " on observer with id '" + id + "'", 4);
if (!_observers[id]) { function _logTrigger(event, type, ids, extraData, queueing) {
Zotero.debug("Observer no longer exists"); Zotero.debug("Notifier.trigger("
continue; + "'" + event + "', "
} + "'" + type + "', "
+ "[" + ids.join() + "]"
// Catch exceptions so all observers get notified even if + (extraData ? ", " + extraData : "")
// one throws an error + ")"
try { + (queueing
yield Zotero.Promise.resolve(_observers[id].ref.notify(event, type, ids, extraData)); ? " queued "
} : " called "
catch (e) { + "[observers: " + _countObserversForType(type) + "]")
Zotero.debug(e); );
Components.utils.reportError(e); if (extraData) {
Zotero.debug(extraData);
} }
} }
return true;
});
/** /**
* Get order of observer by priority, with lower numbers having higher priority. * Get order of observer by priority, with lower numbers having higher priority.

View File

@ -172,10 +172,10 @@ Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
if (env.isNew) { if (env.isNew) {
Zotero.Notifier.trigger('add', 'search', this.id, env.notifierData); Zotero.Notifier.queue('add', 'search', this.id, env.notifierData);
} }
else if (!env.options.skipNotifier) { else if (!env.options.skipNotifier) {
Zotero.Notifier.trigger('modify', 'search', this.id, env.notifierData); Zotero.Notifier.queue('modify', 'search', this.id, env.notifierData);
} }
if (env.isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) { if (env.isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) {
@ -233,7 +233,7 @@ Zotero.Search.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.DB.queryAsync(sql, this.id); yield Zotero.DB.queryAsync(sql, this.id);
if (!env.skipNotifier) { if (!env.skipNotifier) {
Zotero.Notifier.trigger('delete', 'search', this.id, notifierData); Zotero.Notifier.queue('delete', 'search', this.id, notifierData);
} }
}); });

View File

@ -1220,6 +1220,7 @@ Zotero.Sync.Storage = new function () {
var libraryID, key; var libraryID, key;
[libraryID, key] = libraryKey.split("/"); [libraryID, key] = libraryKey.split("/");
var item = Zotero.Items.getByLibraryAndKey(libraryID, key); var item = Zotero.Items.getByLibraryAndKey(libraryID, key);
// TODO: yield or switch to queue
Zotero.Notifier.trigger('redraw', 'item', item.id, { column: "hasAttachment" }); Zotero.Notifier.trigger('redraw', 'item', item.id, { column: "hasAttachment" });
var parent = item.parentItemKey; var parent = item.parentItemKey;

View File

@ -75,7 +75,7 @@ Zotero.SyncedSettings = (function () {
var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?"; var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?";
yield Zotero.DB.queryAsync(sql, [setting, libraryID]); yield Zotero.DB.queryAsync(sql, [setting, libraryID]);
Zotero.Notifier.trigger('delete', 'setting', [id], extraData); yield Zotero.Notifier.trigger('delete', 'setting', [id], extraData);
return true; return true;
} }
@ -100,7 +100,7 @@ Zotero.SyncedSettings = (function () {
+ "(setting, libraryID, value, synced) VALUES (?, ?, ?, ?)"; + "(setting, libraryID, value, synced) VALUES (?, ?, ?, ?)";
yield Zotero.DB.queryAsync(sql, [setting, libraryID, JSON.stringify(value), synced]); yield Zotero.DB.queryAsync(sql, [setting, libraryID, JSON.stringify(value), synced]);
} }
Zotero.Notifier.trigger(event, 'setting', [id], extraData); yield Zotero.Notifier.trigger(event, 'setting', [id], extraData);
return true; return true;
}) })
}; };