Closes #431, Add Notifier trigger hook to DB.commitTransaction() and queue notifications during transactions
unlock = Zotero.Notifier.begin(lock) Zotero.Notifier.commit(unlock) Zotero.Notifier.reset() Zotero.DB.addCallback(type, callback) begin(), commit() and reset() are added to beginTransaction(), commitTransaction() and rollbackTransaction(), respectively, on startup, so notifications are now automatically queued during DB transactions -- this has the potential to make complex operations dramatically faster begin() can also be called manually -- pass true to indicate that intermediate commit()'s (e.g. called from commitTransaction()) shouldn't run the event queue, and pass the value returned to the matching commit() call. (The return value of commit() will be true if it is the first begin() to request a lock and false otherwise -- this allows multiple begin(true) calls to be nested without the nested ones triggering notification. This does make trigger() order a bit less predictable, but I'm ordering events and types (e.g. calling modify events after add and before delete) in an attempt to avoid problems. We'll see if this works or we need a more sophisticated ordering/grouping scheme.
This commit is contained in:
parent
4bf390341a
commit
b7b5ff2933
|
@ -25,6 +25,7 @@ Zotero.DB = new function(){
|
|||
var _connection;
|
||||
var _transactionRollback;
|
||||
var _transactionNestingLevel = 0;
|
||||
var _callbacks = { begin: [], commit: [], rollback: [] };
|
||||
|
||||
this.query = query;
|
||||
this.valueQuery = valueQuery;
|
||||
|
@ -36,6 +37,8 @@ Zotero.DB = new function(){
|
|||
this.beginTransaction = beginTransaction;
|
||||
this.commitTransaction = commitTransaction;
|
||||
this.rollbackTransaction = rollbackTransaction;
|
||||
this.addCallback = addCallback;
|
||||
this.removeCallback = removeCallback;
|
||||
this.transactionInProgress = transactionInProgress;
|
||||
this.commitAllTransactions = commitAllTransactions;
|
||||
this.tableExists = tableExists;
|
||||
|
@ -288,6 +291,13 @@ Zotero.DB = new function(){
|
|||
else {
|
||||
Zotero.debug('Beginning DB transaction', 5);
|
||||
db.beginTransaction();
|
||||
|
||||
// Run callbacks
|
||||
for (var i=0; i<_callbacks.begin.length; i++) {
|
||||
if (_callbacks.begin[i]) {
|
||||
_callbacks.begin[i]();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,11 +317,18 @@ Zotero.DB = new function(){
|
|||
Zotero.debug('Committing transaction',5);
|
||||
try {
|
||||
db.commitTransaction();
|
||||
|
||||
// Run callbacks
|
||||
for (var i=0; i<_callbacks.commit.length; i++) {
|
||||
if (_callbacks.commit[i]) {
|
||||
_callbacks.commit[i]();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
var dberr = (db.lastErrorString!='not an error')
|
||||
? ' [ERROR: ' + db.lastErrorString + ']' : '';
|
||||
throw(e + ' [QUERY: ' + sql + ']' + dberr);
|
||||
throw(e + dberr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -335,6 +352,13 @@ Zotero.DB = new function(){
|
|||
_transactionRollback = false;
|
||||
try {
|
||||
db.rollbackTransaction();
|
||||
|
||||
// Run callbacks
|
||||
for (var i=0; i<_callbacks.rollback.length; i++) {
|
||||
if (_callbacks.rollback[i]) {
|
||||
_callbacks.rollback[i]();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
var dberr = (db.lastErrorString!='not an error')
|
||||
|
@ -345,6 +369,38 @@ Zotero.DB = new function(){
|
|||
}
|
||||
|
||||
|
||||
function addCallback(type, cb) {
|
||||
switch (type) {
|
||||
case 'begin':
|
||||
case 'commit':
|
||||
case 'rollback':
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid callback type '" + type + "' in DB.addCallback()");
|
||||
}
|
||||
|
||||
var id = _callbacks[type].length;
|
||||
_callbacks[type][id] = cb;
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
function removeCallback(type, id) {
|
||||
switch (type) {
|
||||
case 'begin':
|
||||
case 'commit':
|
||||
case 'rollback':
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ("Invalid callback type '" + type + "' in DB.removeCallback()");
|
||||
}
|
||||
|
||||
delete _callbacks[type][id];
|
||||
}
|
||||
|
||||
|
||||
function transactionInProgress(){
|
||||
var db = _getDBConnection();
|
||||
return db.transactionInProgress;
|
||||
|
|
|
@ -24,6 +24,9 @@ Zotero.Notifier = new function(){
|
|||
var _observers = new Zotero.Hash();
|
||||
var _disabled = false;
|
||||
var _types = ['collection', 'search', 'item'];
|
||||
var _inTransaction;
|
||||
var _locked = false;
|
||||
var _queue = [];
|
||||
|
||||
this.registerObserver = registerObserver;
|
||||
this.unregisterObserver = unregisterObserver;
|
||||
|
@ -32,10 +35,14 @@ Zotero.Notifier = new function(){
|
|||
this.unregisterCollectionObserver = unregisterCollectionObserver;
|
||||
this.unregisterItemObserver = unregisterItemObserver;
|
||||
this.trigger = trigger;
|
||||
this.begin = begin;
|
||||
this.commit = commit;
|
||||
this.reset = reset;
|
||||
this.disable = disable;
|
||||
this.enable = enable;
|
||||
this.isEnabled = isEnabled;
|
||||
|
||||
|
||||
function registerObserver(ref, types){
|
||||
if (types){
|
||||
types = Zotero.flattenArguments(types);
|
||||
|
@ -108,8 +115,10 @@ Zotero.Notifier = new function(){
|
|||
* ids - single id or array of ids
|
||||
*
|
||||
* c = collection, s = search, i = item
|
||||
*
|
||||
* New events and types should be added to the order arrays in commit()
|
||||
**/
|
||||
function trigger(event, type, ids){
|
||||
function trigger(event, type, ids, force){
|
||||
if (_disabled){
|
||||
return false;
|
||||
}
|
||||
|
@ -120,9 +129,23 @@ Zotero.Notifier = new function(){
|
|||
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
|
||||
Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', "
|
||||
+ '[' + ids.join() + ']' + ") called "
|
||||
+ "[observers: " + _observers.length + "]");
|
||||
var queue = _inTransaction && !force;
|
||||
|
||||
Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', " + '[' + ids.join() + '])'
|
||||
+ (queue ? " queued" : " called " + "[observers: " + _observers.length + "]"));
|
||||
|
||||
if (queue) {
|
||||
if (!_queue[type]) {
|
||||
_queue[type] = [];
|
||||
}
|
||||
if (!_queue[type][event]) {
|
||||
_queue[type][event] = [];
|
||||
}
|
||||
|
||||
_queue[type][event] = _queue[type][event].concat(ids);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i in _observers.items){
|
||||
Zotero.debug("Calling notify() on observer with hash '" + i + "'", 4);
|
||||
|
@ -136,6 +159,105 @@ Zotero.Notifier = new function(){
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Begin queueing event notifications (i.e. don't notify the observers)
|
||||
*
|
||||
* _lock_ will prevent subsequent commits from running the queue until commit() is called
|
||||
* with the _unlock_ being true
|
||||
*
|
||||
* Note: Be sure the matching commit() gets called (e.g. in a finally{...} block) or
|
||||
* notifications will break until Firefox is restarted or commit(true)/reset() is called manually
|
||||
*/
|
||||
function begin(lock) {
|
||||
if (lock && !_locked) {
|
||||
_locked = true;
|
||||
var unlock = true;
|
||||
}
|
||||
else {
|
||||
var unlock = false;
|
||||
}
|
||||
|
||||
if (_inTransaction) {
|
||||
//Zotero.debug("Notifier queue already open", 4);
|
||||
}
|
||||
else {
|
||||
Zotero.debug("Beginning Notifier event queue");
|
||||
_inTransaction = true;
|
||||
}
|
||||
|
||||
return unlock;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send notifications for ids in the event queue
|
||||
*
|
||||
* If the queue is locked, notifications will only run if _unlock_ is true
|
||||
*/
|
||||
function commit(unlock) {
|
||||
// If there's a lock on the event queue and _unlock_ isn't given, don't commit
|
||||
if ((unlock == undefined && _locked) || (unlock != undefined && !unlock)) {
|
||||
//Zotero.debug("Keeping Notifier event queue open", 4);
|
||||
return;
|
||||
}
|
||||
|
||||
var runQueue = [];
|
||||
|
||||
function sorter(a, b) {
|
||||
return order.indexOf(a) - order.indexOf(b);
|
||||
}
|
||||
var order = ['collection', 'search', 'items'];
|
||||
_queue.sort();
|
||||
|
||||
var order = ['add', 'modify', 'remove', 'move', 'delete'];
|
||||
var totals = '';
|
||||
for (var type in _queue) {
|
||||
if (!runQueue[type]) {
|
||||
runQueue[type] = [];
|
||||
}
|
||||
|
||||
_queue[type].sort();
|
||||
|
||||
for (var event in _queue[type]) {
|
||||
runQueue[type][event] = [];
|
||||
|
||||
// Remove redundant ids
|
||||
for each(var id in _queue[type][event]) {
|
||||
if (runQueue[type][event].indexOf(id) == -1) {
|
||||
runQueue[type][event].push(id);
|
||||
}
|
||||
}
|
||||
|
||||
totals += ' [' + event + '-' + type + ': ' + runQueue[type][event].length + ']';
|
||||
}
|
||||
}
|
||||
|
||||
if (totals) {
|
||||
Zotero.debug("Committing Notifier event queue" + totals);
|
||||
|
||||
for (var type in runQueue) {
|
||||
for (var event in runQueue[type]) {
|
||||
if (runQueue[type][event].length) {
|
||||
trigger(event, type, runQueue[type][event], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Reset the event queue
|
||||
*/
|
||||
function reset() {
|
||||
Zotero.debug("Resetting Notifier event queue");
|
||||
_locked = false;
|
||||
_queue = [];
|
||||
_inTransaction = false;
|
||||
}
|
||||
|
||||
|
||||
function disable(){
|
||||
Zotero.debug('Disabling Notifier notifications');
|
||||
_disabled = true;
|
||||
|
|
|
@ -122,6 +122,11 @@ var Zotero = new function(){
|
|||
.getService(Components.interfaces.nsIStringBundleService);
|
||||
_localizedStringBundle = stringBundleService.createBundle(src, appLocale);
|
||||
|
||||
// Add notifier queue callbacks to the DB layer
|
||||
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
|
||||
Zotero.DB.addCallback('commit', Zotero.Notifier.commit);
|
||||
Zotero.DB.addCallback('rollback', Zotero.Notifier.reset);
|
||||
|
||||
// Trigger updating of schema and scrapers
|
||||
Zotero.Schema.updateSchema();
|
||||
Zotero.Schema.updateScrapersRemote();
|
||||
|
|
Loading…
Reference in New Issue
Block a user