commit 3872e646ac7fded1137824daa09e00c4ad82dd75
parent 7935d01a1cb5c8caa494e532fdc14e569fac75f6
Author: Dan Stillman <dstillman@zotero.org>
Date: Fri, 15 Sep 2017 20:20:41 -0400
Speed up emptying trash
Shows a progress meter, which allows for larger chunks and fewer
refreshes, avoids unnecessary updating of parent items that are
being deleted anyway, and skip re-sorting of modified items in the
trash.
Closes #1292, Emptying trash is slow
Diffstat:
7 files changed, 101 insertions(+), 53 deletions(-)
diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js
@@ -1160,7 +1160,7 @@ Zotero.DataObject.prototype.updateSynced = Zotero.Promise.coroutine(function* (s
*/
Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* (options = {}) {
if (!options || typeof options != 'object') {
- throw new Error("'options' must be an object");
+ throw new Error("'options' must be an object (" + typeof options + ")");
}
var env = {
diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js
@@ -909,6 +909,7 @@ Zotero.DataObjects.prototype.getPrimaryDataSQLPart = function (part) {
*
* @param {Integer|Integer[]} ids - Object ids
* @param {Object} [options] - See Zotero.DataObject.prototype.erase
+ * @param {Function} [options.onProgress] - f(progress, progressMax)
* @return {Promise}
*/
Zotero.DataObjects.prototype.erase = Zotero.Promise.coroutine(function* (ids, options = {}) {
@@ -920,6 +921,9 @@ Zotero.DataObjects.prototype.erase = Zotero.Promise.coroutine(function* (ids, op
continue;
}
yield obj.erase(options);
+ if (options.onProgress) {
+ options.onProgress(i + 1, ids.length);
+ }
}
this.unload(ids);
}.bind(this));
diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
@@ -3972,13 +3972,13 @@ Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
? (yield this.ObjectsClass.getByLibraryAndKeyAsync(this.libraryID, parentItem))
: null;
- if (parentItem) {
+ if (parentItem && !env.options.skipParentRefresh) {
Zotero.Notifier.queue('refresh', 'item', parentItem.id);
}
// // Delete associated attachment files
if (this.isAttachment()) {
- let linkMode = this.getAttachmentLinkMode();
+ let linkMode = this.attachmentLinkMode;
// If link only, nothing to delete
if (linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
try {
@@ -4005,7 +4005,9 @@ Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
for (let i=0; i<toDelete.length; i++) {
let obj = yield this.ObjectsClass.getAsync(toDelete[i]);
// Copy all options other than 'tx', which would cause a deadlock
- let options = {};
+ let options = {
+ skipParentRefresh: true
+ };
Object.assign(options, env.options);
delete options.tx;
yield obj.erase(options);
@@ -4029,7 +4031,7 @@ Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.DB.queryAsync('DELETE FROM items WHERE itemID=?', this.id);
- if (parentItem) {
+ if (parentItem && !env.options.skipParentRefresh) {
yield parentItem.reload(['primaryData', 'childItems'], true);
parentItem.clearBestAttachmentState();
}
diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js
@@ -869,32 +869,56 @@ Zotero.Items = function() {
/**
* @param {Integer} libraryID - Library to delete from
- * @param {Integer} [days] - Only delete items deleted more than this many days ago
- * @param {Integer} [limit]
+ * @param {Object} [options]
+ * @param {Function} [options.onProgress] - fn(progress, progressMax)
+ * @param {Integer} [options.days] - Only delete items deleted more than this many days ago
*/
- this.emptyTrash = Zotero.Promise.coroutine(function* (libraryID, days, limit) {
+ this.emptyTrash = async function (libraryID, options = {}) {
+ if (typeof arguments[1] == 'number') {
+ Zotero.warn("Zotero.Items.emptyTrash() has changed -- update your code");
+ options.days = arguments[1];
+ }
+
if (!libraryID) {
throw new Error("Library ID not provided");
}
var t = new Date();
- var deletedIDs = [];
-
- deletedIDs = yield this.getDeleted(libraryID, true, days);
- if (deletedIDs.length) {
- yield Zotero.Utilities.Internal.forEachChunkAsync(deletedIDs, 50, Zotero.Promise.coroutine(function* (chunk) {
- yield this.erase(chunk);
- yield Zotero.Notifier.trigger('refresh', 'trash', libraryID);
- }.bind(this)));
- }
-
- if (deletedIDs.length) {
- Zotero.debug("Emptied " + deletedIDs.length + " item(s) from trash in " + (new Date() - t) + " ms");
+ var deleted = await this.getDeleted(libraryID, false, options.days);
+ var processed = 0;
+ if (deleted.length) {
+ let toDelete = {
+ top: [],
+ child: []
+ };
+ deleted.forEach((item) => {
+ item.isTopLevelItem() ? toDelete.top.push(item.id) : toDelete.child.push(item.id)
+ });
+
+ // Show progress meter during deletions
+ let eraseOptions = options.onProgress
+ ? {
+ onProgress: function (progress, progressMax) {
+ options.onProgress(processed + progress, deleted.length);
+ }
+ }
+ : undefined;
+ for (let x of ['top', 'child']) {
+ await Zotero.Utilities.Internal.forEachChunkAsync(
+ toDelete[x],
+ 1000,
+ async function (chunk) {
+ await this.erase(chunk, eraseOptions);
+ processed += chunk.length;
+ }.bind(this)
+ );
+ }
+ Zotero.debug("Emptied " + deleted.length + " item(s) from trash in " + (new Date() - t) + " ms");
}
- return deletedIDs.length;
- });
+ return deleted.length;
+ };
/**
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -702,7 +702,11 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
yield this.refresh(skipExpandMatchParents);
refreshed = true;
madeChanges = true;
- sort = true;
+ // Don't bother re-sorting in trash, since it's probably just a modification of a parent
+ // item that's about to be deleted
+ if (!collectionTreeRow.isTrash()) {
+ sort = true;
+ }
}
else if (collectionTreeRow.isFeed()) {
diff --git a/chrome/content/zotero/xpcom/sync/syncEventListeners.js b/chrome/content/zotero/xpcom/sync/syncEventListeners.js
@@ -39,41 +39,30 @@ Zotero.Sync.EventListeners.ChangeListener = new function () {
return;
}
- var syncSQL = "REPLACE INTO syncDeleteLog (syncObjectTypeID, libraryID, key) "
- + "VALUES (?, ?, ?)";
- var storageSQL = "REPLACE INTO storageDeleteLog (libraryID, key) VALUES (?, ?)";
+ var syncSQL = "REPLACE INTO syncDeleteLog (syncObjectTypeID, libraryID, key) VALUES ";
+ var storageSQL = "REPLACE INTO storageDeleteLog (libraryID, key) VALUES ";
var storageForLibrary = {};
return Zotero.Utilities.Internal.forEachChunkAsync(
ids,
100,
- function (chunk) {
- return Zotero.DB.executeTransaction(function* () {
- for (let id of chunk) {
- if (extraData[id] && extraData[id].skipDeleteLog) {
- continue;
- }
-
+ async function (chunk) {
+ var syncSets = [];
+ var storageSets = [];
+ chunk
+ .filter(id => !extraData[id] || !extraData[id].skipDeleteLog)
+ .forEach(id => {
if (type == 'setting') {
var [libraryID, key] = id.split("/");
}
else {
var { libraryID, key } = extraData[id];
}
-
if (!key) {
throw new Error("Key not provided in notifier object");
}
-
- yield Zotero.DB.queryAsync(
- syncSQL,
- [
- syncObjectTypeID,
- libraryID,
- key
- ]
- );
+ syncSets.push(syncObjectTypeID, libraryID, key);
if (type == 'item') {
if (storageForLibrary[libraryID] === undefined) {
@@ -81,17 +70,28 @@ Zotero.Sync.EventListeners.ChangeListener = new function () {
Zotero.Sync.Storage.Local.getModeForLibrary(libraryID) == 'webdav';
}
if (storageForLibrary[libraryID] && extraData[id].storageDeleteLog) {
- yield Zotero.DB.queryAsync(
- storageSQL,
- [
- libraryID,
- key
- ]
- );
+ storageSets.push(libraryID, key);
}
}
- }
- });
+ });
+
+ if (storageSets.length) {
+ return Zotero.DB.executeTransaction(function* () {
+ yield Zotero.DB.queryAsync(
+ syncSQL + Array(syncSets.length / 3).fill('(?, ?, ?)').join(', '),
+ syncSets
+ );
+ yield Zotero.DB.queryAsync(
+ storageSQL + Array(storageSets.length / 3).fill('(?, ?)').join(', '),
+ storageSets
+ );
+ });
+ }
+ else if (syncSets.length) {
+ await Zotero.DB.queryAsync(
+ syncSQL + Array(syncSets.length / 3).fill('(?, ?, ?)').join(', '), syncSets
+ );
+ }
}
);
});
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -2042,7 +2042,21 @@ var ZoteroPane = new function()
+ Zotero.getString('general.actionCannotBeUndone')
);
if (result) {
- let deleted = yield Zotero.Items.emptyTrash(libraryID);
+ Zotero.showZoteroPaneProgressMeter(null, true);
+ try {
+ let deleted = yield Zotero.Items.emptyTrash(
+ libraryID,
+ {
+ onProgress: (progress, progressMax) => {
+ var percentage = Math.round((progress / progressMax) * 100);
+ Zotero.updateZoteroPaneProgressMeter(percentage);
+ }
+ }
+ );
+ }
+ finally {
+ Zotero.hideZoteroPaneOverlays();
+ }
yield Zotero.purgeDataObjects();
}
});