From daf4a8fe4db46ed29de2babed79610a2522821df Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Mon, 7 Mar 2016 16:05:51 -0500 Subject: [PATCH] Deasyncification :back: :cry: While trying to get translation and citing working with asynchronously generated data, we realized that drag-and-drop support was going to be...problematic. Firefox only supports synchronous methods for providing drag data (unlike, it seems, the DataTransferItem interface supported by Chrome), which means that we'd need to preload all relevant data on item selection (bounded by export.quickCopy.dragLimit) and keep the translate/cite methods synchronous (or maintain two separate versions). What we're trying instead is doing what I said in #518 we weren't going to do: loading most object data on startup and leaving many more functions synchronous. Essentially, this takes the various load*() methods described in #518, moves them to startup, and makes them operate on entire libraries rather than individual objects. The obvious downside here (other than undoing much of the work of the last many months) is that it increases startup time, potentially quite a lot for larger libraries. On my laptop, with a 3,000-item library, this adds about 3 seconds to startup time. I haven't yet tested with larger libraries. But I'm hoping that we can optimize this further to reduce that delay. Among other things, this is loading data for all libraries, when it should be able to load data only for the library being viewed. But this is also fundamentally just doing some SELECT queries and storing the results, so it really shouldn't need to be that slow (though performance may be bounded a bit here by XPCOM overhead). If we can make this fast enough, it means that third-party plugins should be able to remain much closer to their current designs. (Some things, including saving, will still need to be made asynchronous.) --- chrome/content/zotero/bindings/noteeditor.xml | 61 +- chrome/content/zotero/bindings/relatedbox.xml | 182 +++-- chrome/content/zotero/bindings/tagsbox.xml | 33 +- chrome/content/zotero/duplicatesMerge.js | 6 +- chrome/content/zotero/itemPane.js | 3 - chrome/content/zotero/locateMenu.js | 1 - chrome/content/zotero/recognizePDF.js | 1 - chrome/content/zotero/xpcom/api.js | 1 - chrome/content/zotero/xpcom/attachments.js | 3 +- chrome/content/zotero/xpcom/cite.js | 1 - .../content/zotero/xpcom/collectionTreeRow.js | 1 + .../zotero/xpcom/collectionTreeView.js | 22 +- .../content/zotero/xpcom/data/collection.js | 169 +---- .../content/zotero/xpcom/data/collections.js | 100 ++- chrome/content/zotero/xpcom/data/creators.js | 40 +- .../content/zotero/xpcom/data/dataObject.js | 121 +--- .../content/zotero/xpcom/data/dataObjects.js | 273 +++++++- chrome/content/zotero/xpcom/data/item.js | 417 ++++-------- chrome/content/zotero/xpcom/data/items.js | 636 +++++++++++++----- chrome/content/zotero/xpcom/db.js | 8 +- chrome/content/zotero/xpcom/itemTreeView.js | 163 ++--- chrome/content/zotero/xpcom/search.js | 190 +++--- chrome/content/zotero/xpcom/storage.js | 4 +- .../zotero/xpcom/storage/storageEngine.js | 4 +- .../zotero/xpcom/storage/storageLocal.js | 155 +---- chrome/content/zotero/xpcom/storage/webdav.js | 69 +- chrome/content/zotero/xpcom/storage/zfs.js | 47 +- .../content/zotero/xpcom/sync/syncEngine.js | 2 +- chrome/content/zotero/xpcom/sync/syncLocal.js | 6 +- chrome/content/zotero/xpcom/timeline.js | 1 - .../xpcom/translation/translate_item.js | 8 +- chrome/content/zotero/xpcom/utilities.js | 5 +- .../zotero/xpcom/utilities_internal.js | 72 +- chrome/content/zotero/xpcom/zotero.js | 12 +- chrome/content/zotero/zoteroPane.js | 20 +- components/zotero-protocol-handler.js | 18 +- test/content/support.js | 7 +- test/resource/chai | 2 +- test/resource/mocha | 2 +- test/tests/collectionTest.js | 6 - test/tests/collectionTreeViewTest.js | 8 +- test/tests/creatorsTest.js | 21 + test/tests/dataObjectTest.js | 4 +- test/tests/dataObjectUtilitiesTest.js | 4 +- test/tests/fileInterfaceTest.js | 2 +- test/tests/itemTest.js | 60 +- test/tests/preferences_syncTest.js | 2 +- test/tests/relatedboxTest.js | 6 +- test/tests/searchTest.js | 12 +- test/tests/storageLocalTest.js | 72 +- test/tests/syncEngineTest.js | 16 +- test/tests/syncLocalTest.js | 67 +- test/tests/translateTest.js | 2 +- test/tests/utilitiesTest.js | 26 +- test/tests/webdavTest.js | 38 +- test/tests/zfsTest.js | 41 +- test/tests/zoteroPaneTest.js | 2 +- 57 files changed, 1648 insertions(+), 1607 deletions(-) create mode 100644 test/tests/creatorsTest.js diff --git a/chrome/content/zotero/bindings/noteeditor.xml b/chrome/content/zotero/bindings/noteeditor.xml index d9b17c03e5..37659c63a4 100644 --- a/chrome/content/zotero/bindings/noteeditor.xml +++ b/chrome/content/zotero/bindings/noteeditor.xml @@ -417,48 +417,41 @@ 0) { - var x = this.boxObject.screenX; - var y = this.boxObject.screenY; - this.id('relatedPopup').openPopupAtScreen(x, y, false); - } - else { - this.id('related').add(); - } - }, this); + var relatedList = this.item.relatedItems; + if (relatedList.length > 0) { + var x = this.boxObject.screenX; + var y = this.boxObject.screenY; + this.id('relatedPopup').openPopupAtScreen(x, y, false); + } + else { + this.id('related').add(); + } ]]> diff --git a/chrome/content/zotero/bindings/relatedbox.xml b/chrome/content/zotero/bindings/relatedbox.xml index aa8cc30efe..3989dbbeaf 100644 --- a/chrome/content/zotero/bindings/relatedbox.xml +++ b/chrome/content/zotero/bindings/relatedbox.xml @@ -74,25 +74,20 @@ Zotero.Promise.check(this.item)); - var keys = this.item.relatedItems; - if (keys.length) { - let items = yield Zotero.Items.getAsync(keys) - .tap(() => Zotero.Promise.check(this.item)); - for (let item of items) { - r = r + item.getDisplayTitle() + ", "; - } - r = r.substr(0,r.length-2); + var r = ""; + + if (this.item) { + var keys = this.item.relatedItems; + if (keys.length) { + for (let key of keys) { + let item = Zotero.Items.getByLibraryAndKey(this.item.libraryID, key); + r = r + item.getDisplayTitle() + ", "; } + r = r.substr(0,r.length-2); } - - return r; - }, this); + } + + return r; ]]> @@ -129,89 +124,79 @@ - - Zotero.Promise.check(this.item)); - var relatedKeys = this.item.relatedItems; - for (var i = 0; i < relatedKeys.length; i++) { - let key = relatedKeys[i]; - let relatedItem = - yield Zotero.Items.getByLibraryAndKeyAsync( - this.item.libraryID, key - ) - .tap(() => Zotero.Promise.check(this.item)); - let id = relatedItem.id; - yield relatedItem.loadItemData() - .tap(() => Zotero.Promise.check(this.item)); - let icon = document.createElement("image"); - icon.className = "zotero-box-icon"; - let type = Zotero.ItemTypes.getName(relatedItem.itemTypeID); - if (type=='attachment') - { - switch (relatedItem.attaachmentLinkMode) { - case Zotero.Attachments.LINK_MODE_LINKED_URL: - type += '-web-link'; - break; - - case Zotero.Attachments.LINK_MODE_IMPORTED_URL: - type += '-snapshot'; - break; - - case Zotero.Attachments.LINK_MODE_LINKED_FILE: - type += '-link'; - break; - - case Zotero.Attachments.LINK_MODE_IMPORTED_FILE: - type += '-file'; - break; - } + - + this.updateCount(relatedKeys.length); + } + ]]> Zotero.Promise.check(this.mode)); - var tags = this.item.getTags(); - if (tags) { - for(var i = 0; i < tags.length; i++) - { - r = r + tags[i].tag + ", "; - } - r = r.substr(0,r.length-2); - } - } + var r = ""; - return r; - }, this); + if (this.item) { + var tags = this.item.getTags(); + if (tags) { + for(var i = 0; i < tags.length; i++) + { + r = r + tags[i].tag + ", "; + } + r = r.substr(0,r.length-2); + } + } + + return r; ]]> @@ -211,9 +207,6 @@ return Zotero.spawn(function* () { Zotero.debug('Reloading tags box'); - yield this.item.loadTags() - .tap(() => Zotero.Promise.check(this.mode)); - // Cancel field focusing while we're updating this._reloading = true; diff --git a/chrome/content/zotero/duplicatesMerge.js b/chrome/content/zotero/duplicatesMerge.js index 27bf6ec582..f6682aae73 100644 --- a/chrome/content/zotero/duplicatesMerge.js +++ b/chrome/content/zotero/duplicatesMerge.js @@ -131,9 +131,9 @@ var Zotero_Duplicates_Pane = new function () { // alternative values so that they're still available if the item box // modifies the item Zotero.spawn(function* () { - var diff = yield item.multiDiff(_otherItems, _ignoreFields); + var diff = item.multiDiff(_otherItems, _ignoreFields); if (diff) { - let itemValues = yield item.toJSON(); + let itemValues = item.toJSON(); for (let i in diff) { diff[i].unshift(itemValues[i] !== undefined ? itemValues[i] : ''); } @@ -141,8 +141,6 @@ var Zotero_Duplicates_Pane = new function () { } var newItem = yield item.copy(); - yield newItem.loadItemData(); - yield newItem.loadCreators(); itembox.item = newItem; }); } diff --git a/chrome/content/zotero/itemPane.js b/chrome/content/zotero/itemPane.js index 8c6dd2e1a3..45bfad1324 100644 --- a/chrome/content/zotero/itemPane.js +++ b/chrome/content/zotero/itemPane.js @@ -94,13 +94,11 @@ var ZoteroItemPane = new function() { _notesList.removeChild(_notesList.firstChild); } - yield item.loadChildItems(); let notes = yield Zotero.Items.getAsync(item.getNotes()); if (notes.length) { for (var i = 0; i < notes.length; i++) { let note = notes[i]; let id = notes[i].id; - yield note.loadItemData(); var icon = document.createElement('image'); icon.className = "zotero-box-icon"; @@ -148,7 +146,6 @@ var ZoteroItemPane = new function() { box.mode = 'edit'; } - yield Zotero.Promise.all([item.loadItemData(), item.loadCreators()]); box.item = item; }); diff --git a/chrome/content/zotero/locateMenu.js b/chrome/content/zotero/locateMenu.js index 836d101dff..9d960f443a 100644 --- a/chrome/content/zotero/locateMenu.js +++ b/chrome/content/zotero/locateMenu.js @@ -400,7 +400,6 @@ var Zotero_LocateMenu = new function() { } if(item.isRegularItem()) { - yield item.loadChildItems(); var attachments = item.getAttachments(); if(attachments) { // look through url fields for non-file:/// attachments diff --git a/chrome/content/zotero/recognizePDF.js b/chrome/content/zotero/recognizePDF.js index 316e089e13..4846282aa0 100644 --- a/chrome/content/zotero/recognizePDF.js +++ b/chrome/content/zotero/recognizePDF.js @@ -395,7 +395,6 @@ var Zotero_RecognizePDF = new function() { } // put new item in same collections as the old one - yield item.loadCollections(); let itemCollections = item.getCollections(); for (let i = 0; i < itemCollections.length; i++) { let collection = yield Zotero.Collections.getAsync(itemCollections[i]); diff --git a/chrome/content/zotero/xpcom/api.js b/chrome/content/zotero/xpcom/api.js index 81229692f3..fa5ec440e5 100644 --- a/chrome/content/zotero/xpcom/api.js +++ b/chrome/content/zotero/xpcom/api.js @@ -56,7 +56,6 @@ Zotero.API = { if (!col) { throw new Error('Invalid collection ID or key'); } - yield col.loadChildItems(); results = col.getChildItems(); break; diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 012170d9c7..24ddfedbbe 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -1090,8 +1090,7 @@ Zotero.Attachments = new function(){ Zotero.DB.requireTransaction(); - attachment.loadItemData(); - var newAttachment = yield attachment.clone(libraryID); + var newAttachment = attachment.clone(libraryID); if (attachment.isImportedAttachment()) { // Attachment path isn't copied over by clone() if libraryID is different newAttachment.attachmentPath = attachment.attachmentPath; diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js index c542a99ef5..467164b951 100644 --- a/chrome/content/zotero/xpcom/cite.js +++ b/chrome/content/zotero/xpcom/cite.js @@ -529,7 +529,6 @@ Zotero.Cite.System.prototype = { throw "Zotero.Cite.System.retrieveItem called on non-item "+item; } - throw new Error("Unimplemented"); var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem); // TEMP: citeproc-js currently expects the id property to be the item DB id diff --git a/chrome/content/zotero/xpcom/collectionTreeRow.js b/chrome/content/zotero/xpcom/collectionTreeRow.js index d3a7734758..f54a09045c 100644 --- a/chrome/content/zotero/xpcom/collectionTreeRow.js +++ b/chrome/content/zotero/xpcom/collectionTreeRow.js @@ -295,6 +295,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu // Create the outer (filter) search var s2 = new Zotero.Search(); + if (this.isTrash()) { s2.addCondition('deleted', 'true'); } diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index ced95a6827..fc35fb239a 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -526,7 +526,7 @@ Zotero.CollectionTreeView.prototype._addSortedRow = Zotero.Promise.coroutine(fun ); } else if (objectType == 'search') { - let search = yield Zotero.Searches.getAsync(id); + let search = Zotero.Searches.get(id); let libraryID = search.libraryID; let startRow = this._rowMap['L' + libraryID]; @@ -545,7 +545,6 @@ Zotero.CollectionTreeView.prototype._addSortedRow = Zotero.Promise.coroutine(fun var inSearches = false; for (let i = startRow; i < this.rowCount; i++) { let treeRow = this.getRow(i); - Zotero.debug(treeRow.id); beforeRow = i; // If we've reached something other than collections, stop @@ -1504,10 +1503,6 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine } if (dataType == 'zotero/item') { - if (treeRow.isCollection()) { - yield treeRow.ref.loadChildItems(); - } - var ids = data; var items = Zotero.Items.get(ids); var skip = true; @@ -1627,7 +1622,6 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r // If linked item is in the trash, undelete it and remove it from collections // (since it shouldn't be restored to previous collections) if (linkedItem.deleted) { - yield linkedItem.loadCollections(); linkedItem.setCollections(); linkedItem.deleted = false; yield linkedItem.save({ @@ -1693,7 +1687,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r } // Create new clone item in target library - var newItem = yield item.clone(targetLibraryID, false, !options.tags); + var newItem = item.clone(targetLibraryID, false, !options.tags); // Set Rights field for My Publications if (options.license) { @@ -1717,11 +1711,10 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r // Child notes if (options.childNotes) { - yield item.loadChildItems(); var noteIDs = item.getNotes(); - var notes = yield Zotero.Items.getAsync(noteIDs); + var notes = Zotero.Items.get(noteIDs); for each(var note in notes) { - let newNote = yield note.clone(targetLibraryID); + let newNote = note.clone(targetLibraryID); newNote.parentID = newItemID; yield newNote.save({ skipSelect: true @@ -1733,9 +1726,8 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r // Child attachments if (options.childLinks || options.childFileAttachments) { - yield item.loadChildItems(); var attachmentIDs = item.getAttachments(); - var attachments = yield Zotero.Items.getAsync(attachmentIDs); + var attachments = Zotero.Items.get(attachmentIDs); for each(var attachment in attachments) { var linkMode = attachment.attachmentLinkMode; @@ -1864,8 +1856,8 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r if (targetTreeRow.isPublications()) { let items = yield Zotero.Items.getAsync(ids); - let io = yield this._treebox.treeBody.ownerDocument.defaultView.ZoteroPane - .showPublicationsWizard(items); + let io = this._treebox.treeBody.ownerDocument.defaultView + .ZoteroPane.showPublicationsWizard(items); if (!io) { return; } diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index 7205c4211a..b68b90e161 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -28,11 +28,8 @@ Zotero.Collection = function(params = {}) { this._name = null; - this._hasChildCollections = null; - this._childCollections = []; - - this._hasChildItems = false; - this._childItems = []; + this._childCollections = new Set(); + this._childItems = new Set(); Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID', 'parentKey', 'lastSync']); @@ -162,19 +159,13 @@ Zotero.Collection.prototype.loadFromRow = function(row) { Zotero.Collection.prototype.hasChildCollections = function() { - if (this._hasChildCollections !== null) { - return this._hasChildCollections; - } - this._requireData('primaryData'); - return false; + this._requireData('childCollections'); + return this._childCollections.size > 0; } Zotero.Collection.prototype.hasChildItems = function() { - if (this._hasChildItems !== null) { - return this._hasChildItems; - } - this._requireData('primaryData'); - return false; + this._requireData('childItems'); + return this._childItems.size > 0; } @@ -189,19 +180,11 @@ Zotero.Collection.prototype.getChildCollections = function (asIDs) { // Return collectionIDs if (asIDs) { - var ids = []; - for each(var col in this._childCollections) { - ids.push(col.id); - } - return ids; + return this._childCollections.values(); } // Return Zotero.Collection objects - var objs = []; - for each(var col in this._childCollections) { - objs.push(col); - } - return objs; + return Array.from(this._childCollections).map(id => this.ObjectsClass.get(id)); } @@ -215,13 +198,14 @@ Zotero.Collection.prototype.getChildCollections = function (asIDs) { Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) { this._requireData('childItems'); - if (this._childItems.length == 0) { + if (this._childItems.size == 0) { return []; } // Remove deleted items if necessary var childItems = []; - for each(var item in this._childItems) { + for (let itemID of this._childItems) { + let item = this.ChildObjects.get(itemID); if (includeDeleted || !item.deleted) { childItems.push(item); } @@ -229,19 +213,11 @@ Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) { // Return itemIDs if (asIDs) { - var ids = []; - for each(var item in childItems) { - ids.push(item.id); - } - return ids; + return childItems.map(item => item.id); } // Return Zotero.Item objects - var objs = []; - for each(var item in childItems) { - objs.push(item); - } - return objs; + return childItems.slice(); } Zotero.Collection.prototype._initSave = Zotero.Promise.coroutine(function* (env) { @@ -388,7 +364,6 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI return; } - yield this.loadChildItems(); var current = this.getChildItems(true); Zotero.DB.requireTransaction(); @@ -400,15 +375,14 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI continue; } - let item = yield this.ChildObjects.getAsync(itemID); - yield item.loadCollections(); + let item = this.ChildObjects.get(itemID); item.addToCollection(this.id); yield item.save({ skipDateModifiedUpdate: true }); } - yield this.loadChildItems(true); + yield this._loadDataType('childItems'); }); /** @@ -434,7 +408,6 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it return; } - yield this.loadChildItems(); var current = this.getChildItems(true); return Zotero.DB.executeTransaction(function* () { @@ -447,7 +420,6 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it } let item = yield this.ChildObjects.getAsync(itemID); - yield item.loadCollections(); item.removeFromCollection(this.id); yield item.save({ skipDateModifiedUpdate: true @@ -455,7 +427,7 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it } }.bind(this)); - yield this.loadChildItems(true); + yield this._loadDataType('childItems'); }); @@ -464,13 +436,7 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it **/ Zotero.Collection.prototype.hasItem = function(itemID) { this._requireData('childItems'); - - for (let i=0; i 0; + this._childCollections.delete(collectionID); } } @@ -971,8 +861,7 @@ Zotero.Collection.prototype._registerChildItem = function (itemID) { if (this._loaded.childItems) { let item = this.ChildObjects.get(itemID); if (item) { - this._hasChildItems = true; - this._childItems.push(item); + this._childItems.add(itemID); } } } @@ -983,12 +872,6 @@ Zotero.Collection.prototype._registerChildItem = function (itemID) { */ Zotero.Collection.prototype._unregisterChildItem = function (itemID) { if (this._loaded.childItems) { - for (let i = 0; i < this._childItems.length; i++) { - if (this._childItems[i].id == itemID) { - this._childItems.splice(i, 1); - break; - } - } - this._hasChildItems = this._childItems.length > 0; + this._childItems.delete(itemID); } } diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js index a27263b108..75b185a8e8 100644 --- a/chrome/content/zotero/xpcom/data/collections.js +++ b/chrome/content/zotero/xpcom/data/collections.js @@ -85,8 +85,7 @@ Zotero.Collections = function() { let children; if (parentID) { - let parent = yield Zotero.Collections.getAsync(parentID); - yield parent.loadChildCollections(); + let parent = Zotero.Collections.get(parentID); children = parent.getChildCollections(); } else if (libraryID) { let sql = "SELECT collectionID AS id FROM collections " @@ -156,6 +155,103 @@ Zotero.Collections = function() { } + this._loadChildCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) { + var sql = "SELECT C1.collectionID, C2.collectionID AS childCollectionID " + + "FROM collections C1 LEFT JOIN collections C2 ON (C1.collectionID=C2.parentCollectionID) " + + "WHERE C1.libraryID=?" + + (ids.length ? " AND C1.collectionID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : ""); + var params = [libraryID]; + var lastID; + var rows = []; + var setRows = function (collectionID, rows) { + var collection = this._objectCache[collectionID]; + if (!collection) { + throw new Error("Collection " + collectionID + " not found"); + } + + collection._childCollections = new Set(rows); + collection._loaded.childCollections = true; + collection._clearChanged('childCollections'); + }.bind(this); + + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + let collectionID = row.getResultByIndex(0); + + if (lastID && collectionID !== lastID) { + setRows(lastID, rows); + rows = []; + } + + lastID = collectionID; + + let childCollectionID = row.getResultByIndex(1); + // No child collections + if (childCollectionID === null) { + return; + } + rows.push(childCollectionID); + } + } + ); + if (lastID) { + setRows(lastID, rows); + } + }); + + + this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) { + var sql = "SELECT collectionID, itemID FROM collections " + + "LEFT JOIN collectionItems USING (collectionID) " + + "WHERE libraryID=?" + idSQL; + var params = [libraryID]; + var lastID; + var rows = []; + var setRows = function (collectionID, rows) { + var collection = this._objectCache[collectionID]; + if (!collection) { + throw new Error("Collection " + collectionID + " not found"); + } + + collection._childItems = new Set(rows); + collection._loaded.childItems = true; + collection._clearChanged('childItems'); + }.bind(this); + + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + let collectionID = row.getResultByIndex(0); + + if (lastID && collectionID !== lastID) { + setRows(lastID, rows); + rows = []; + } + + lastID = collectionID; + + let itemID = row.getResultByIndex(1); + // No child items + if (itemID === null) { + return; + } + rows.push(itemID); + } + } + ); + if (lastID) { + setRows(lastID, rows); + } + }); + + this.registerChildCollection = function (collectionID, childCollectionID) { if (this._objectCache[collectionID]) { this._objectCache[collectionID]._registerChildCollection(childCollectionID); diff --git a/chrome/content/zotero/xpcom/data/creators.js b/chrome/content/zotero/xpcom/data/creators.js index e65d6e0507..909de38c59 100644 --- a/chrome/content/zotero/xpcom/data/creators.js +++ b/chrome/content/zotero/xpcom/data/creators.js @@ -30,29 +30,35 @@ Zotero.Creators = new function() { var _cache = {}; + this.init = Zotero.Promise.coroutine(function* (libraryID) { + var sql = "SELECT * FROM creators"; + var rows = yield Zotero.DB.queryAsync(sql); + for (let i = 0; i < rows.length; i++) { + let row = rows[i]; + _cache[row.creatorID] = this.cleanData({ + // Avoid "DB column 'name' not found" warnings from the DB row Proxy + firstName: row.firstName, + lastName: row.lastName, + fieldMode: row.fieldMode + }); + } + }); + /* * Returns creator data in internal format for a given creatorID */ - this.getAsync = Zotero.Promise.coroutine(function* (creatorID) { + this.get = function (creatorID) { if (!creatorID) { throw new Error("creatorID not provided"); } - if (_cache[creatorID]) { - return this.cleanData(_cache[creatorID]); - } - - var sql = "SELECT * FROM creators WHERE creatorID=?"; - var row = yield Zotero.DB.rowQueryAsync(sql, creatorID); - if (!row) { + if (!_cache[creatorID]) { throw new Error("Creator " + creatorID + " not found"); } - return _cache[creatorID] = this.cleanData({ - firstName: row.firstName, // avoid "DB column 'name' not found" warnings from the DB row Proxy - lastName: row.lastName, - fieldMode: row.fieldMode - }); - }); + + // Return copy of data + return this.cleanData(_cache[creatorID]); + }; this.getItemsWithCreator = function (creatorID) { @@ -87,12 +93,10 @@ Zotero.Creators = new function() { id = yield Zotero.ID.get('creators'); let sql = "INSERT INTO creators (creatorID, firstName, lastName, fieldMode) " + "VALUES (?, ?, ?, ?)"; - let insertID = yield Zotero.DB.queryAsync( + yield Zotero.DB.queryAsync( sql, [id, data.firstName, data.lastName, data.fieldMode] ); - if (!id) { - id = insertID; - } + _cache[id] = data; } return id; }); diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 183544f585..05d5b59c26 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -401,7 +401,7 @@ Zotero.DataObject.prototype.setRelations = function (newRelations) { // Relations are stored internally as a flat array with individual predicate-object pairs, // so convert the incoming relations to that - var newRelationsFlat = this._flattenRelations(newRelations); + var newRelationsFlat = this.ObjectsClass.flattenRelations(newRelations); var changed = false; if (oldRelations.length != newRelationsFlat.length) { @@ -457,8 +457,6 @@ Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function throw new Error(this._ObjectType + " is already in library " + libraryID); } - yield this.loadRelations(); - var predicate = Zotero.Relations.linkedObjectPredicate; var libraryObjectPrefix = Zotero.URI.getLibraryURI(libraryID) + "/" + this._objectTypePlural + "/"; @@ -514,8 +512,6 @@ Zotero.DataObject.prototype._addLinkedObject = Zotero.Promise.coroutine(function throw new Error("Can't add linked " + this._objectType + " in same library"); } - yield this.loadRelations(); - var predicate = Zotero.Relations.linkedObjectPredicate; var thisURI = Zotero.URI['get' + this._ObjectType + 'URI'](this); var objectURI = Zotero.URI['get' + this._ObjectType + 'URI'](object); @@ -539,7 +535,6 @@ Zotero.DataObject.prototype._addLinkedObject = Zotero.Promise.coroutine(function }); } else { - yield object.loadRelations(); object.addRelation(predicate, thisURI); yield object.save({ skipDateModifiedUpdate: true, @@ -551,9 +546,11 @@ Zotero.DataObject.prototype._addLinkedObject = Zotero.Promise.coroutine(function }); -/* - * Build object from database - */ +// +// Bulk data loading functions +// +// These are called by Zotero.DataObjects.prototype._loadDataType(). +// Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) { if (this._loaded.primaryData && !reload) return; @@ -610,65 +607,6 @@ Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* }); -Zotero.DataObject.prototype.loadRelations = Zotero.Promise.coroutine(function* (reload) { - if (!this.ObjectsClass._relationsTable) { - throw new Error("Relations not supported for " + this._objectTypePlural); - } - - if (this._loaded.relations && !reload) { - return; - } - - Zotero.debug("Loading relations for " + this._objectType + " " + this.libraryKey); - - this._requireData('primaryData'); - - var sql = "SELECT predicate, object FROM " + this.ObjectsClass._relationsTable + " " - + "JOIN relationPredicates USING (predicateID) " - + "WHERE " + this.ObjectsClass.idColumn + "=?"; - var rows = yield Zotero.DB.queryAsync(sql, this.id); - - var relations = {}; - function addRel(predicate, object) { - if (!relations[predicate]) { - relations[predicate] = []; - } - relations[predicate].push(object); - } - - for (let i = 0; i < rows.length; i++) { - let row = rows[i]; - addRel(row.predicate, row.object); - } - - /*if (this._objectType == 'item') { - let getURI = Zotero.URI["get" + this._ObjectType + "URI"].bind(Zotero.URI); - let objectURI = getURI(this); - - // Related items are bidirectional, so include any pointing to this object - let objects = yield Zotero.Relations.getByPredicateAndObject( - Zotero.Relations.relatedItemPredicate, objectURI - ); - for (let i = 0; i < objects.length; i++) { - addRel(Zotero.Relations.relatedItemPredicate, getURI(objects[i])); - } - - // Also include any owl:sameAs relations pointing to this object - objects = yield Zotero.Relations.getByPredicateAndObject( - Zotero.Relations.linkedObjectPredicate, objectURI - ); - for (let i = 0; i < objects.length; i++) { - addRel(Zotero.Relations.linkedObjectPredicate, getURI(objects[i])); - } - }*/ - - // Relations are stored as predicate-object pairs - this._relations = this._flattenRelations(relations); - this._loaded.relations = true; - this._clearChanged('relations'); -}); - - /** * Reloads loaded, changed data * @@ -735,7 +673,7 @@ Zotero.DataObject.prototype._requireData = function (dataType) { * @param {Boolean} reload */ Zotero.DataObject.prototype._loadDataType = function (dataType, reload) { - return this["load" + dataType[0].toUpperCase() + dataType.substr(1)](reload); + return this._ObjectsClass._loadDataType(dataType, this.libraryID, [this.id]); } Zotero.DataObject.prototype.loadAllData = function (reload) { @@ -868,6 +806,16 @@ Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) Zotero.debug('Updating database with new ' + this._objectType + ' data', 4); } + if (env.options.skipAll) { + [ + 'skipDateModifiedUpdate', + 'skipClientDateModifiedUpdate', + 'skipSyncedUpdate', + 'skipEditCheck', + 'skipSelect' + ].forEach(x => env.options[x] = true); + } + try { if (Zotero.DataObject.prototype._finalizeSave == this._finalizeSave) { throw new Error("_finalizeSave not implemented for Zotero." + this._ObjectType); @@ -1214,16 +1162,16 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function* }); -Zotero.DataObject.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (options) { +Zotero.DataObject.prototype.toResponseJSON = function (options) { // TODO: library block? return { key: this.key, version: this.version, meta: {}, - data: yield this.toJSON(options) + data: this.toJSON(options) }; -}); +} Zotero.DataObject.prototype._preToJSON = function (options) { @@ -1272,32 +1220,3 @@ Zotero.DataObject.prototype._disabledCheck = function () { + "use Zotero." + this._ObjectTypePlural + ".getAsync()"); } } - - -/** - * Flatten API JSON relations object into an array of unique predicate-object pairs - * - * @param {Object} relations - Relations object in API JSON format, with predicates as keys - * and arrays of URIs as objects - * @return {Array[]} - Predicate-object pairs - */ -Zotero.DataObject.prototype._flattenRelations = function (relations) { - var relationsFlat = []; - for (let predicate in relations) { - let object = relations[predicate]; - if (Array.isArray(object)) { - object = Zotero.Utilities.arrayUnique(object); - for (let i = 0; i < object.length; i++) { - relationsFlat.push([predicate, object[i]]); - } - } - else if (typeof object == 'string') { - relationsFlat.push([predicate, object]); - } - else { - Zotero.debug(object, 1); - throw new Error("Invalid relation value"); - } - } - return relationsFlat; -} diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js index 7626944352..0ce1a73e53 100644 --- a/chrome/content/zotero/xpcom/data/dataObjects.js +++ b/chrome/content/zotero/xpcom/data/dataObjects.js @@ -336,6 +336,253 @@ Zotero.DataObjects.prototype.getNewer = Zotero.Promise.method(function (libraryI }); +/** + * Loads data for a given data type + * @param {String} dataType + * @param {Integer} libraryID + * @param {Integer[]} [ids] + */ +Zotero.DataObjects.prototype._loadDataType = Zotero.Promise.coroutine(function* (dataType, libraryID, ids) { + var funcName = "_load" + dataType[0].toUpperCase() + dataType.substr(1) + // Single data types need an 's' (e.g., 'note' -> 'loadNotes()') + + ((dataType.endsWith('s') || dataType.endsWith('Data') ? '' : 's')); + if (!this[funcName]) { + throw new Error(`Zotero.${this._ZDO_Objects}.${funcName} is not a function`); + } + + if (ids && ids.length == 0) { + return; + } + + var t = new Date; + var libraryName = Zotero.Libraries.get(libraryID).name; + + var idSQL = ""; + if (ids) { + idSQL = " AND " + this.idColumn + " IN (" + ids.map(id => parseInt(id)).join(", ") + ")"; + } + + Zotero.debug("Loading " + dataType + + (ids + ? " for " + ids.length + " " + (ids.length == 1 ? this._ZDO_object : this._ZDO_objects) + : '') + + " in " + libraryName); + + yield this[funcName](libraryID, ids ? ids : [], idSQL); + + Zotero.debug(`Loaded ${dataType} in ${libraryName} in ${new Date() - t} ms`); +}); + +Zotero.DataObjects.prototype.loadAllData = Zotero.Promise.coroutine(function* (libraryID, ids) { + var t = new Date(); + var libraryName = Zotero.Libraries.get(libraryID).name; + + Zotero.debug("Loading all data" + + (ids ? " for " + ids.length + " " + this._ZDO_objects : '') + + " in " + libraryName); + + let dataTypes = this.ObjectClass.prototype._dataTypes; + for (let i = 0; i < dataTypes.length; i++) { + yield this._loadDataType(dataTypes[i], libraryID, ids); + } + + Zotero.debug(`Loaded all data in ${libraryName} in ${new Date() - t} ms`); +}); + + +Zotero.DataObjects.prototype._loadPrimaryData = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL, options) { + var loaded = {}; + + // If library isn't an integer (presumably false or null), skip it + if (parseInt(libraryID) != libraryID) { + libraryID = false; + } + + var sql = this.primaryDataSQL; + var params = []; + if (libraryID !== false) { + sql += ' AND O.libraryID=?'; + params.push(libraryID); + } + if (ids.length) { + sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')'; + } + + yield Zotero.DB.queryAsync( + sql, + params, + { + onRow: function (row) { + var id = row.getResultByName(this._ZDO_id); + var columns = Object.keys(this._primaryDataSQLParts); + var rowObj = {}; + for (let i=0; i} */ -Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, skipTags) { +Zotero.Item.prototype.clone = function (libraryID, skipTags) { Zotero.debug('Cloning item ' + this.id); if (libraryID !== undefined && libraryID !== null && typeof libraryID !== 'number') { throw new Error("libraryID must be null or an integer"); } - yield this.loadPrimaryData(); - if (libraryID === undefined || libraryID === null) { libraryID = this.libraryID; } @@ -3579,7 +3668,6 @@ Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, ski newItem.libraryID = libraryID; newItem.setType(this.itemTypeID); - yield this.loadItemData(); var fieldIDs = this.getUsedFields(); for (let i = 0; i < fieldIDs.length; i++) { let fieldID = fieldIDs[i]; @@ -3588,11 +3676,9 @@ Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, ski // Regular item if (this.isRegularItem()) { - yield this.loadCreators(); newItem.setCreators(this.getCreators()); } else { - yield this.loadNote(); newItem.setNote(this.getNote()); if (sameLibrary) { var parent = this.parentKey; @@ -3614,18 +3700,16 @@ Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, ski } if (!skipTags) { - yield this.loadTags(); newItem.setTags(this.getTags()); } if (sameLibrary) { // DEBUG: this will add reverse-only relateds too - yield this.loadRelations(); newItem.setRelations(this.getRelations()); } return newItem; -}); +} /** @@ -3721,8 +3805,6 @@ Zotero.Item.prototype.isCollection = function() { /** * Populate the object's data from an API JSON data object - * - * If this object is identified (has an id or library/key), loadAllData() must have been called. */ Zotero.Item.prototype.fromJSON = function (json) { if (!json.itemType && !this._itemTypeID) { @@ -3867,7 +3949,7 @@ Zotero.Item.prototype.fromJSON = function (json) { /** * @param {Object} options */ -Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) { +Zotero.Item.prototype.toJSON = function (options = {}) { var env = this._preToJSON(options); var mode = env.mode; @@ -3877,7 +3959,6 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) obj.itemType = Zotero.ItemTypes.getName(this.itemTypeID); // Fields - yield this.loadItemData(); for (let i in this._itemData) { let val = this.getField(i) + ''; if (val !== '' || mode == 'full') { @@ -3887,7 +3968,6 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) // Creators if (this.isRegularItem()) { - yield this.loadCreators() obj.creators = this.getCreatorsJSON(); } else { @@ -3912,18 +3992,18 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) if (this.isFileAttachment()) { if (options.syncedStorageProperties) { - obj.mtime = yield Zotero.Sync.Storage.Local.getSyncedModificationTime(this.id); - obj.md5 = yield Zotero.Sync.Storage.Local.getSyncedHash(this.id); + obj.mtime = this.attachmentSyncedModificationTime; + obj.md5 = this.attachmentSyncedHash; } else { - obj.mtime = (yield this.attachmentModificationTime) || null; - obj.md5 = (yield this.attachmentHash) || null; + // TEMP + //obj.mtime = (yield this.attachmentModificationTime) || null; + //obj.md5 = (yield this.attachmentHash) || null; } } } // Notes and embedded attachment notes - yield this.loadNote(); let note = this.getNote(); if (note !== "" || mode == 'full' || (mode == 'new' && this.isNote())) { obj.note = note; @@ -3932,7 +4012,6 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) // Tags obj.tags = []; - yield this.loadTags() var tags = this.getTags(); for (let i=0; i/)) { - note = Zotero.Utilities.htmlSpecialChars(note); - note = Zotero.Notes.notePrefix + '

' - + note.replace(/\n/g, '

') - .replace(/\t/g, '    ') - .replace(/ /g, '  ') - + '

' + Zotero.Notes.noteSuffix; - note = note.replace(/

\s*<\/p>/g, '

 

'); - let sql = "UPDATE itemNotes SET note=? WHERE itemID=?"; - yield Zotero.DB.queryAsync(sql, [note, this.id]); - } - - // Don't include
wrapper when returning value - let startLen = note.substr(0, 36).match(/^
/)[0].length; - let endLen = 6; // "
".length - note = note.substr(startLen, note.length - startLen - endLen); - } - - this._noteText = note ? note : ''; - } - - this._loaded.note = true; - this._clearChanged('note'); -}); - - Zotero.Item.prototype.loadDisplayTitle = Zotero.Promise.coroutine(function* (reload) { if (this._displayTitle !== null && !reload) { return; @@ -4097,7 +4082,6 @@ Zotero.Item.prototype.loadDisplayTitle = Zotero.Promise.coroutine(function* (rel var itemTypeName = Zotero.ItemTypes.getName(itemTypeID); if (title === "" && (itemTypeID == 8 || itemTypeID == 10)) { // 'letter' and 'interview' itemTypeIDs - yield this.loadCreators(); var creatorsData = this.getCreators(); var authors = []; var participants = []; @@ -4175,7 +4159,6 @@ Zotero.Item.prototype.loadDisplayTitle = Zotero.Promise.coroutine(function* (rel strParts.push(part); } - yield this.loadCreators() var creatorData = this.getCreator(0); if (creatorData && creatorData.creatorTypeID === 1) { // author strParts.push(creatorData.lastName); @@ -4189,156 +4172,6 @@ Zotero.Item.prototype.loadDisplayTitle = Zotero.Promise.coroutine(function* (rel }); -/* - * Load in the creators from the database - */ -Zotero.Item.prototype.loadCreators = Zotero.Promise.coroutine(function* (reload) { - if (this._loaded.creators && !reload) { - return; - } - - Zotero.debug("Loading creators for item " + this.libraryKey); - - if (!this.id) { - throw new Error('ItemID not set for item before attempting to load creators'); - } - - var sql = 'SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators ' - + 'WHERE itemID=? ORDER BY orderIndex'; - var rows = yield Zotero.DB.queryAsync(sql, this.id); - - this._creators = []; - this._creatorIDs = []; - this._loaded.creators = true; - this._clearChanged('creators'); - - if (!rows) { - return true; - } - - var maxOrderIndex = -1; - for (var i=0; i maxOrderIndex) { - maxOrderIndex = row.orderIndex; - } - let creatorData = yield Zotero.Creators.getAsync(row.creatorID); - creatorData.creatorTypeID = row.creatorTypeID; - this._creators[i] = creatorData; - this._creatorIDs[i] = row.creatorID; - } - if (i <= maxOrderIndex) { - Zotero.debug("Fixing incorrect creator indexes for item " + this.libraryKey - + " (" + i + ", " + maxOrderIndex + ")", 2); - while (i <= maxOrderIndex) { - this._changed.creators[i] = true; - i++; - } - } - - return true; -}); - - -Zotero.Item.prototype.loadChildItems = Zotero.Promise.coroutine(function* (reload) { - if (this._loaded.childItems && !reload) { - return; - } - - if (this.isNote() || this.isAttachment()) { - return; - } - - // Attachments - this._attachments = { - rows: null, - chronologicalWithTrashed: null, - chronologicalWithoutTrashed: null, - alphabeticalWithTrashed: null, - alphabeticalWithoutTrashed: null - }; - var sql = "SELECT A.itemID, value AS title, CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed " - + "FROM itemAttachments A " - + "NATURAL JOIN items I " - + "LEFT JOIN itemData ID ON (fieldID=110 AND A.itemID=ID.itemID) " - + "LEFT JOIN itemDataValues IDV USING (valueID) " - + "LEFT JOIN deletedItems DI USING (itemID) " - + "WHERE parentItemID=?"; - // Since we do the sort here and cache these results, a restart will be required - // if this pref (off by default) is turned on, but that's OK - if (Zotero.Prefs.get('sortAttachmentsChronologically')) { - sql += " ORDER BY dateAdded"; - } - this._attachments.rows = yield Zotero.DB.queryAsync(sql, this.id); - - // - // Notes - // - this._notes = { - rows: null, - rowsEmbedded: null, - chronologicalWithTrashed: null, - chronologicalWithoutTrashed: null, - alphabeticalWithTrashed: null, - alphabeticalWithoutTrashed: null, - numWithTrashed: null, - numWithoutTrashed: null, - numWithTrashedWithEmbedded: null, - numWithoutTrashedWithoutEmbedded: null - }; - var sql = "SELECT N.itemID, title, CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed " - + "FROM itemNotes N " - + "NATURAL JOIN items I " - + "LEFT JOIN deletedItems DI USING (itemID) " - + "WHERE parentItemID=?"; - if (Zotero.Prefs.get('sortAttachmentsChronologically')) { - sql += " ORDER BY dateAdded"; - } - this._notes.rows = yield Zotero.DB.queryAsync(sql, this.id); - - this._loaded.childItems = true; - this._clearChanged('childItems'); -}); - - -Zotero.Item.prototype.loadTags = Zotero.Promise.coroutine(function* (reload) { - if (this._loaded.tags && !reload) { - return; - } - - if (!this._id) { - return; - } - var sql = "SELECT tagID AS id, name AS tag, type FROM itemTags " - + "JOIN tags USING (tagID) WHERE itemID=?"; - var rows = yield Zotero.DB.queryAsync(sql, this.id); - - this._tags = []; - for (let i=0; i 0 ? ',\n' : ''; let item = yield this.getAsync(ids[i], { noCache: true }); - var json = yield item.toResponseJSON(); + var json = item.toResponseJSON(); yield prefix + JSON.stringify(json, null, 4); } @@ -212,105 +215,24 @@ Zotero.Items = function() { }; - this._cachedFields = {}; - this.cacheFields = Zotero.Promise.coroutine(function* (libraryID, fields, items) { - if (items && items.length == 0) { - return; - } - - var t = new Date; - - fields = fields.concat(); - - // Needed for display titles for some item types - if (fields.indexOf('title') != -1) { - fields.push('reporter', 'court'); - } - - Zotero.debug("Caching fields [" + fields.join() + "]" - + (items ? " for " + items.length + " items" : '') - + " in library " + libraryID); - - if (items && items.length > 0) { - yield this._load(libraryID, items); - } - else { - yield this._load(libraryID); - } - - var primaryFields = []; - var fieldIDs = []; - for each(var field in fields) { - // Check if field already cached - if (this._cachedFields[libraryID] && this._cachedFields[libraryID].indexOf(field) != -1) { - continue; - } - - if (!this._cachedFields[libraryID]) { - this._cachedFields[libraryID] = []; - } - this._cachedFields[libraryID].push(field); - - if (this.isPrimaryField(field)) { - primaryFields.push(field); - } - else { - fieldIDs.push(Zotero.ItemFields.getID(field)); - if (Zotero.ItemFields.isBaseField(field)) { - fieldIDs = fieldIDs.concat(Zotero.ItemFields.getTypeFieldsFromBase(field)); - } - } - } - - if (primaryFields.length) { - var sql = "SELECT O.itemID, " - + primaryFields.map((val) => this.getPrimaryDataSQLPart(val)).join(', ') - + this.primaryDataSQLFrom + " AND O.libraryID=?"; - var params = [libraryID]; - if (items) { - sql += " AND O.itemID IN (" + items.join() + ")"; - } - yield Zotero.DB.queryAsync( - sql, - params, - { - onRow: function (row) { - let obj = { - itemID: row.getResultByIndex(0) - }; - for (let i=0; i maxOrderIndex) { + maxOrderIndex = row.orderIndex; + } + + let creatorData = Zotero.Creators.get(row.creatorID); + creatorData.creatorTypeID = row.creatorTypeID; + item._creators[index] = creatorData; + item._creatorIDs[index] = row.creatorID; + index++; } - Zotero.debug("Cached fields in " + ((new Date) - t) + "ms"); + if (index <= maxOrderIndex) { + fixIncorrectIndexes(item, index, maxOrderIndex); + } + }); + + + this._loadNotes = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) { + var notesToUpdate = []; + + var sql = "SELECT itemID, note FROM items " + + "JOIN itemNotes USING (itemID) " + + "WHERE libraryID=?" + idSQL; + var params = [libraryID]; + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + let itemID = row.getResultByIndex(0); + let item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not found"); + } + let note = row.getResultByIndex(1); + + // Convert non-HTML notes on-the-fly + if (note !== "") { + if (!note.substr(0, 36).match(/^
/)) { + note = Zotero.Utilities.htmlSpecialChars(note); + note = Zotero.Notes.notePrefix + '

' + + note.replace(/\n/g, '

') + .replace(/\t/g, '    ') + .replace(/ /g, '  ') + + '

' + Zotero.Notes.noteSuffix; + note = note.replace(/

\s*<\/p>/g, '

 

'); + notesToUpdate.push([item.id, note]); + } + + // Don't include
wrapper when returning value + let startLen = note.substr(0, 36).match(/^
/)[0].length; + let endLen = 6; // "
".length + note = note.substr(startLen, note.length - startLen - endLen); + } + + item._noteText = note ? note : ''; + item._loaded.note = true; + item._clearChanged('note'); + }.bind(this) + } + ); + + if (notesToUpdate.length) { + yield Zotero.DB.executeTransaction(function* () { + for (let i = 0; i < notesToUpdate.length; i++) { + let row = notesToUpdate[i]; + let sql = "UPDATE itemNotes SET note=? WHERE itemID=?"; + yield Zotero.DB.queryAsync(sql, [row[1], row[0]]); + } + }.bind(this)); + } + + // Mark notes and attachments without notes as loaded + sql = "SELECT itemID FROM items WHERE libraryID=?" + idSQL + + " AND itemTypeID IN (?, ?) AND itemID NOT IN (SELECT itemID FROM itemNotes)"; + params = [libraryID, Zotero.ItemTypes.getID('note'), Zotero.ItemTypes.getID('attachment')]; + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + let itemID = row.getResultByIndex(0); + let item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not loaded"); + } + + item._noteText = ''; + item._loaded.note = true; + item._clearChanged('note'); + }.bind(this) + } + ); + }); + + + this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) { + var params = [libraryID]; + var rows = []; + var onRow = function (row, setFunc) { + var itemID = row.getResultByIndex(0); + + if (lastItemID && itemID !== lastItemID) { + setFunc(lastItemID, rows); + rows = []; + } + + lastItemID = itemID; + rows.push({ + itemID: row.getResultByIndex(1), + title: row.getResultByIndex(2), + trashed: row.getResultByIndex(3) + }); + }; + + var sql = "SELECT parentItemID, A.itemID, value AS title, " + + "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed " + + "FROM itemAttachments A " + + "JOIN items I ON (A.parentItemID=I.itemID) " + + "LEFT JOIN itemData ID ON (fieldID=110 AND A.itemID=ID.itemID) " + + "LEFT JOIN itemDataValues IDV USING (valueID) " + + "LEFT JOIN deletedItems DI USING (itemID) " + + "WHERE libraryID=?" + + (ids.length ? " AND parentItemID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : ""); + // Since we do the sort here and cache these results, a restart will be required + // if this pref (off by default) is turned on, but that's OK + if (Zotero.Prefs.get('sortAttachmentsChronologically')) { + sql += " ORDER BY parentItemID, dateAdded"; + } + var setAttachmentItem = function (itemID, rows) { + var item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not loaded"); + } + + item._attachments = { + rows, + chronologicalWithTrashed: null, + chronologicalWithoutTrashed: null, + alphabeticalWithTrashed: null, + alphabeticalWithoutTrashed: null + }; + }.bind(this); + var lastItemID = null; + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + onRow(row, setAttachmentItem); + } + } + ); + if (lastItemID) { + setAttachmentItem(lastItemID, rows); + } + + // + // Notes + // + sql = "SELECT parentItemID, N.itemID, title, " + + "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed " + + "FROM itemNotes N " + + "JOIN items I ON (N.parentItemID=I.itemID) " + + "LEFT JOIN deletedItems DI USING (itemID) " + + "WHERE libraryID=?" + + (ids.length ? " AND parentItemID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : ""); + if (Zotero.Prefs.get('sortNotesChronologically')) { + sql += " ORDER BY parentItemID, dateAdded"; + } + var setNoteItem = function (itemID, rows) { + var item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not loaded"); + } + + item._notes = { + rows, + rowsEmbedded: null, + chronologicalWithTrashed: null, + chronologicalWithoutTrashed: null, + alphabeticalWithTrashed: null, + alphabeticalWithoutTrashed: null, + numWithTrashed: null, + numWithoutTrashed: null, + numWithTrashedWithEmbedded: null, + numWithoutTrashedWithoutEmbedded: null + }; + }.bind(this); + lastItemID = null; + rows = []; + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + onRow(row, setNoteItem); + } + } + ); + if (lastItemID) { + setNoteItem(lastItemID, rows); + } + + // Mark all top-level items as having child items loaded + sql = "SELECT itemID FROM items I WHERE libraryID=?" + idSQL + " AND itemID NOT IN " + + "(SELECT itemID FROM itemAttachments UNION SELECT itemID FROM itemNotes)"; + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + var itemID = row.getResultByIndex(0); + var item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not loaded"); + } + item._loaded.childItems = true; + item._clearChanged('childItems'); + }.bind(this) + } + ); + }); + + + this._loadTags = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) { + var sql = "SELECT itemID, name, type FROM items " + + "LEFT JOIN itemTags USING (itemID) " + + "LEFT JOIN tags USING (tagID) WHERE libraryID=?" + idSQL; + var params = [libraryID]; + + var lastItemID; + var rows = []; + var setRows = function (itemID, rows) { + var item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not found"); + } + + item._tags = []; + for (let i = 0; i < rows.length; i++) { + let row = rows[i]; + item._tags.push(Zotero.Tags.cleanData(row)); + } + + item._loaded.tags = true; + item._clearChanged('tags'); + }.bind(this); + + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + let itemID = row.getResultByIndex(0); + + if (lastItemID && itemID !== lastItemID) { + setRows(lastItemID, rows); + rows = []; + } + + lastItemID = itemID; + + // Item has no tags + let tag = row.getResultByIndex(1); + if (tag === null) { + return; + } + + rows.push({ + tag: tag, + type: row.getResultByIndex(2) + }); + }.bind(this) + } + ); + if (lastItemID) { + setRows(lastItemID, rows); + } + }); + + + this._loadCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) { + var sql = "SELECT itemID, collectionID FROM items " + + "LEFT JOIN collectionItems USING (itemID) " + + "WHERE libraryID=?" + idSQL; + var params = [libraryID]; + + var lastItemID; + var rows = []; + var setRows = function (itemID, rows) { + var item = this._objectCache[itemID]; + if (!item) { + throw new Error("Item " + itemID + " not found"); + } + + item._collections = rows; + item._loaded.collections = true; + item._clearChanged('collections'); + }.bind(this); + + yield Zotero.DB.queryAsync( + sql, + params, + { + noCache: ids.length != 1, + onRow: function (row) { + let itemID = row.getResultByIndex(0); + + if (lastItemID && itemID !== lastItemID) { + setRows(lastItemID, rows); + rows = []; + } + + lastItemID = itemID; + let collectionID = row.getResultByIndex(1); + // No collections + if (collectionID === null) { + return; + } + rows.push(collectionID); + }.bind(this) + } + ); + if (lastItemID) { + setRows(lastItemID, rows); + } }); @@ -409,17 +725,11 @@ Zotero.Items = function() { var otherItemIDs = []; var itemURI = Zotero.URI.getItemURI(item); - yield item.loadTags(); - yield item.loadRelations(); var replPred = Zotero.Relations.replacedItemPredicate; var toSave = {}; toSave[this.id]; for each(var otherItem in otherItems) { - yield otherItem.loadChildItems(); - yield otherItem.loadCollections(); - yield otherItem.loadTags(); - yield otherItem.loadRelations(); let otherItemURI = Zotero.URI.getItemURI(otherItem); // Move child items to master @@ -632,16 +942,6 @@ Zotero.Items = function() { }); - this._postLoad = function (libraryID, ids) { - if (!ids) { - if (!this._cachedFields[libraryID]) { - this._cachedFields[libraryID] = []; - } - this._cachedFields[libraryID] = this.primaryFields.concat(); - } - } - - /* * Generate SQL to retrieve firstCreator field * diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js index 5b0ab097ce..4eff4f0169 100644 --- a/chrome/content/zotero/xpcom/db.js +++ b/chrome/content/zotero/xpcom/db.js @@ -630,7 +630,13 @@ Zotero.DBConnection.prototype.queryAsync = Zotero.Promise.coroutine(function* (s } } } - let rows = yield conn.executeCached(sql, params, onRow); + let rows; + if (options && options.noCache) { + rows = yield conn.execute(sql, params, onRow); + } + else { + rows = yield conn.executeCached(sql, params, onRow); + } // Parse out the SQL command being used let op = sql.match(/^[^a-z]*[^ ]+/i); if (op) { diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js index 920776eb16..ace81c4fe9 100644 --- a/chrome/content/zotero/xpcom/itemTreeView.js +++ b/chrome/content/zotero/xpcom/itemTreeView.js @@ -82,7 +82,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree if (this._treebox) { if (this._needsSort) { - yield this.sort(); + this.sort(); } return; } @@ -133,11 +133,11 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree if (self._treebox.view.selection.count > 1) { switch (event.keyCode) { case 39: - self.expandSelectedRows().done(); + self.expandSelectedRows(); break; case 37: - self.collapseSelectedRows().done(); + self.collapseSelectedRows(); break; } @@ -148,7 +148,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree var key = String.fromCharCode(event.which); if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) { - self.expandAllRows().done(); + self.expandAllRows(); event.preventDefault(); return; } @@ -230,8 +230,8 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree // handleKeyPress() in zoteroPane.js. tree._handleEnter = function () {}; - yield this.sort(); - yield this.expandMatchParents(); + this.sort(); + this.expandMatchParents(); if (this._ownerDocument.defaultView.ZoteroPane_Local) { this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage(); @@ -266,13 +266,10 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree */ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(function* () { Zotero.debug('Refreshing items list for ' + this.id); - //if(!Zotero.ItemTreeView._haveCachedFields) yield Zotero.Promise.resolve(); - var cacheFields = ['title', 'date']; - - // Cache the visible fields so they don't load individually + // DEBUG: necessary? try { - var visibleFields = this.getVisibleFields(); + this._treebox.columns.count } // If treebox isn't ready, skip refresh catch (e) { @@ -286,33 +283,6 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f }); try { - for (let i=0; i=0; i--) { - yield this.toggleOpenState(rowsToOpen[i], true); + this.toggleOpenState(rowsToOpen[i], true); } this._refreshItemRowMap(); if (unsuppress) { //this._treebox.endUpdateBatch(); this.selection.selectEventsSuppressed = false; } -}); +} -Zotero.ItemTreeView.prototype.expandMatchParents = Zotero.Promise.coroutine(function* () { +Zotero.ItemTreeView.prototype.expandMatchParents = function () { // Expand parents of child matches if (!this._searchMode) { return; @@ -2001,7 +1952,7 @@ Zotero.ItemTreeView.prototype.expandMatchParents = Zotero.Promise.coroutine(func for (var i=0; i} - File hash, null if never synced, if false if - * file doesn't exist - */ - getSyncedHash: Zotero.Promise.coroutine(function* (itemID) { - var sql = "SELECT storageHash FROM itemAttachments WHERE itemID=?"; - var hash = yield Zotero.DB.valueQueryAsync(sql, itemID); - if (hash === false) { - throw new Error("Item " + itemID + " not found"); - } - return hash; - }), - - - /** - * @param {Integer} itemID - * @param {String} hash File hash - * @param {Boolean} [updateItem=FALSE] - Mark attachment item as unsynced - */ - setSyncedHash: Zotero.Promise.coroutine(function* (itemID, hash, updateItem) { - if (hash !== null && hash.length != 32) { - throw new Error("Invalid file hash '" + hash + "'"); - } - - Zotero.DB.requireTransaction(); - - var sql = "UPDATE itemAttachments SET storageHash=? WHERE itemID=?"; - yield Zotero.DB.queryAsync(sql, [hash, itemID]); - - if (updateItem) { - let item = yield Zotero.Items.getAsync(itemID); - yield item.updateSynced(false); - } + sql = "UPDATE itemAttachments SET syncState=? WHERE itemID IN (" + sql + ")"; + yield Zotero.DB.queryAsync(sql, [this.SYNC_STATE_TO_UPLOAD].concat(params)); }), @@ -678,11 +577,10 @@ Zotero.Sync.Storage.Local = { // Set the file mtime to the time from the server yield OS.File.setDates(path, null, new Date(parseInt(mtime))); - yield Zotero.DB.executeTransaction(function* () { - yield this.setSyncedHash(item.id, md5); - yield this.setSyncState(item.id, this.SYNC_STATE_IN_SYNC); - yield this.setSyncedModificationTime(item.id, mtime); - }.bind(this)); + item.attachmentSyncState = this.SYNC_STATE_IN_SYNC; + item.attachmentSyncedModificationTime = mtime; + item.attachmentSyncedHash = md5; + yield item.saveTx(); return new Zotero.Sync.Storage.Result({ localChanges: true @@ -1040,7 +938,7 @@ Zotero.Sync.Storage.Local = { for (let localItem of localItems) { // Use the mtime for the dateModified field, since that's all that's shown in the // CR window at the moment - let localItemJSON = yield localItem.toJSON(); + let localItemJSON = localItem.toJSON(); localItemJSON.dateModified = Zotero.Date.dateToISO( new Date(yield localItem.attachmentModificationTime) ); @@ -1101,8 +999,9 @@ Zotero.Sync.Storage.Local = { else { syncState = this.SYNC_STATE_FORCE_DOWNLOAD; } - let itemID = Zotero.Items.getIDFromLibraryAndKey(libraryID, conflict.left.key); - yield Zotero.Sync.Storage.Local.setSyncState(itemID, syncState); + let item = Zotero.Items.getByLibraryAndKey(libraryID, conflict.left.key); + item.attachmentSyncState = syncState; + yield item.save({ skipAll: true }); } }.bind(this)); return true; diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js index 13f64bc866..49c133c6bd 100644 --- a/chrome/content/zotero/xpcom/storage/webdav.js +++ b/chrome/content/zotero/xpcom/storage/webdav.js @@ -288,15 +288,14 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { Zotero.debug("File mod time matches remote file -- skipping download of " + item.libraryKey); - yield Zotero.DB.executeTransaction(function* () { - var syncState = Zotero.Sync.Storage.Local.getSyncState(item.id); - // DEBUG: Necessary to update item? - var updateItem = syncState != 1; - yield Zotero.Sync.Storage.Local.setSyncedModificationTime( - item.id, metadata.mtime, updateItem - ); - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - }); + var updateItem = item.attachmentSyncState != 1 + item.attachmentSyncedModificationTime = metadata.mtime; + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); + // DEBUG: Necessary? + if (updateItem) { + yield item.updateSynced(false); + } return new Zotero.Sync.Storage.Result({ localChanges: true, // ? @@ -416,7 +415,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { } // Check if file already exists on WebDAV server - if ((yield Zotero.Sync.Storage.Local.getSyncState(item.id)) + if (item.attachmentSyncState != Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD) { if (metadata.mtime) { // Local file time @@ -438,15 +437,14 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { // If WebDAV server already has file, update synced properties if (!changed) { - yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncedModificationTime( - item.id, fmtime, true - ); - if (hash) { - yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, hash); - } - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - }); + item.attachmentSyncedModificationTime = fmtime; + if (hash) { + item.attachmentSyncedHash = hash; + } + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); + // skipAll doesn't mark as unsynced, so do that separately + yield item.updateSynced(false); return new Zotero.Sync.Storage.Result; } } @@ -460,9 +458,9 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { // API would ever be updated with the correct values, so we can't just wait for // the API to change.) If a conflict is found, we flag the item as in conflict // and require another file sync, which will trigger conflict resolution. - let smtime = yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id); + let smtime = item.attachmentSyncedModificationTime; if (smtime != mtime) { - let shash = yield Zotero.Sync.Storage.Local.getSyncedHash(item.id); + let shash = item.attachmentSyncedHash; if (shash && metadata.md5 && shash == metadata.md5) { Zotero.debug("Last synced mod time for item " + item.libraryKey + " doesn't match time on storage server but hash does -- ignoring"); @@ -472,12 +470,13 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { Zotero.logError("Conflict -- last synced file mod time for item " + item.libraryKey + " does not match time on storage server" + " (" + smtime + " != " + mtime + ")"); - yield Zotero.DB.executeTransaction(function* () { - // Conflict resolution uses the synced mtime as the remote value, so set - // that to the WebDAV value, since that's the one in conflict. - yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime); - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_conflict"); - }); + + // Conflict resolution uses the synced mtime as the remote value, so set + // that to the WebDAV value, since that's the one in conflict. + item.attachmentSyncedModificationTime = mtime; + item.attachmentSyncState = "in_conflict"; + yield item.saveTx({ skipAll: true }); + return new Zotero.Sync.Storage.Result({ fileSyncRequired: true }); @@ -1191,7 +1190,10 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { throw new Error(Zotero.Sync.Storage.Mode.WebDAV.defaultError); } - return { mtime, md5 }; + return { + mtime: parseInt(mtime), + md5 + }; }), @@ -1243,11 +1245,12 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = { // Update .prop file on WebDAV server yield this._setStorageFileMetadata(item); - yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, params.mtime, true); - yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, params.md5); - }); + item.attachmentSyncedModificationTime = params.mtime; + item.attachmentSyncedHash = params.md5; + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); + // skipAll doesn't mark as unsynced, so do that separately + yield item.updateSynced(false); try { yield OS.File.remove( diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js index 43ee3744b0..e8f2b4bfa0 100644 --- a/chrome/content/zotero/xpcom/storage/zfs.js +++ b/chrome/content/zotero/xpcom/storage/zfs.js @@ -131,15 +131,13 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = { } // Update local metadata and stop request, skipping file download - yield Zotero.DB.executeTransaction(function* () { - if (updateHash) { - yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, requestData.md5); - } - yield Zotero.Sync.Storage.Local.setSyncedModificationTime( - item.id, requestData.mtime - ); - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - }); + item.attachmentSyncedModificationTime = requestData.mtime; + if (updateHash) { + item.attachmentSyncedHash = requestData.md5; + } + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); + return false; }), onProgress: function (a, b, c) { @@ -261,7 +259,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = { var sql = "SELECT value FROM settings WHERE setting=? AND key=?"; var values = yield Zotero.DB.columnQueryAsync(sql, ['storage', 'zfsPurge']); - if (!values) { + if (!values.length) { return false; } @@ -353,7 +351,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = { var headers = { "Content-Type": "application/x-www-form-urlencoded" }; - var storedHash = yield Zotero.Sync.Storage.Local.getSyncedHash(item.id); + var storedHash = item.attachmentSyncedHash; //var storedModTime = yield Zotero.Sync.Storage.getSyncedModificationTime(item.id); if (storedHash) { headers["If-Match"] = storedHash; @@ -538,17 +536,17 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = { Zotero.debug(fileHash); if (json.data.md5 == fileHash) { - yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncedModificationTime( - item.id, fileModTime - ); - yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, fileHash); - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - }); + item.attachmentSyncedModificationTime = fileModTime; + item.attachmentSyncedHash = fileHash; + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); + return new Zotero.Sync.Storage.Result; } - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_conflict"); + item.attachmentSyncState = "in_conflict"; + yield item.saveTx({ skipAll: true }); + return new Zotero.Sync.Storage.Result({ fileSyncRequired: true }); @@ -767,11 +765,12 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = { _updateItemFileInfo: Zotero.Promise.coroutine(function* (item, params) { // Mark as in-sync yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); + // Store file mod time and hash + item.attachmentSyncedModificationTime = params.mtime; + item.attachmentSyncedHash = params.md5; + item.attachmentSyncState = "in_sync"; + yield item.save({ skipAll: true }); - // Store file mod time and hash - yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, params.mtime); - yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, params.md5); // Update sync cache with new file metadata and version from server var json = yield Zotero.Sync.Data.Local.getCacheObject( 'item', item.libraryID, item.key, item.version @@ -933,7 +932,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = { } // Check for conflict - if ((yield Zotero.Sync.Storage.Local.getSyncState(item.id)) + if (item.attachmentSyncState != Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD) { if (info) { // Local file time diff --git a/chrome/content/zotero/xpcom/sync/syncEngine.js b/chrome/content/zotero/xpcom/sync/syncEngine.js index 23b1242ae2..10d65e5e69 100644 --- a/chrome/content/zotero/xpcom/sync/syncEngine.js +++ b/chrome/content/zotero/xpcom/sync/syncEngine.js @@ -316,7 +316,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func // Conflict resolution else if (objectType == 'item') { conflicts.push({ - left: yield obj.toJSON(), + left: obj.toJSON(), right: { deleted: true } diff --git a/chrome/content/zotero/xpcom/sync/syncLocal.js b/chrome/content/zotero/xpcom/sync/syncLocal.js index 997f026c79..a2df6ce3c6 100644 --- a/chrome/content/zotero/xpcom/sync/syncLocal.js +++ b/chrome/content/zotero/xpcom/sync/syncLocal.js @@ -512,7 +512,7 @@ Zotero.Sync.Data.Local = { objectType, obj.libraryID, obj.key, obj.version ); - let jsonDataLocal = yield obj.toJSON(); + let jsonDataLocal = obj.toJSON(); // For items, check if mtime or file hash changed in metadata, // which would indicate that a remote storage sync took place and @@ -780,7 +780,8 @@ Zotero.Sync.Data.Local = { markToDownload = true; } if (markToDownload) { - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download"); + item.attachmentSyncState = "to_download"; + yield item.save({ skipAll: true }); } }), @@ -870,7 +871,6 @@ Zotero.Sync.Data.Local = { _saveObjectFromJSON: Zotero.Promise.coroutine(function* (obj, json, options) { try { - yield obj.loadAllData(); obj.fromJSON(json); if (!options.saveAsChanged) { obj.version = json.version; diff --git a/chrome/content/zotero/xpcom/timeline.js b/chrome/content/zotero/xpcom/timeline.js index ed473c9a00..fdf27afb33 100644 --- a/chrome/content/zotero/xpcom/timeline.js +++ b/chrome/content/zotero/xpcom/timeline.js @@ -31,7 +31,6 @@ Zotero.Timeline = { yield '\n'; for (let i=0; i items.add(item)); } @@ -720,7 +718,7 @@ Zotero.Translate.ItemGetter.prototype = { * Converts an attachment to array format and copies it to the export folder if desired */ "_attachmentToArray":Zotero.Promise.coroutine(function* (attachment) { - var attachmentArray = yield Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy); + var attachmentArray = Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy); var linkMode = attachment.attachmentLinkMode; if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) { var attachFile = attachment.getFile(); @@ -864,13 +862,13 @@ Zotero.Translate.ItemGetter.prototype = { var returnItemArray = yield this._attachmentToArray(returnItem); if(returnItemArray) return returnItemArray; } else { - var returnItemArray = yield Zotero.Utilities.Internal.itemToExportFormat(returnItem, this.legacy); + var returnItemArray = Zotero.Utilities.Internal.itemToExportFormat(returnItem, this.legacy); // get attachments, although only urls will be passed if exportFileData is off returnItemArray.attachments = []; var attachments = returnItem.getAttachments(); for each(var attachmentID in attachments) { - var attachment = yield Zotero.Items.getAsync(attachmentID); + var attachment = Zotero.Items.get(attachmentID); var attachmentInfo = yield this._attachmentToArray(attachment); if(attachmentInfo) { diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index 001099194b..d623696f24 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -1591,8 +1591,9 @@ Zotero.Utilities = { */ "itemToCSLJSON":function(zoteroItem) { if (zoteroItem instanceof Zotero.Item) { - return Zotero.Utilities.Internal.itemToExportFormat(zoteroItem). - then(Zotero.Utilities.itemToCSLJSON); + return this.itemToCSLJSON( + Zotero.Utilities.Internal.itemToExportFormat(zoteroItem) + ); } var cslType = CSL_TYPE_MAPPINGS[zoteroItem.itemType]; diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 5aca232684..e5d6e21dc4 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -610,44 +610,7 @@ Zotero.Utilities.Internal = { * @param {Boolean} legacy Add mappings for legacy (pre-4.0.27) translators * @return {Promise} */ - "itemToExportFormat": new function() { - return Zotero.Promise.coroutine(function* (zoteroItem, legacy) { - var item = yield zoteroItem.toJSON(); - - item.uri = Zotero.URI.getItemURI(zoteroItem); - delete item.key; - - if (!zoteroItem.isAttachment() && !zoteroItem.isNote()) { - yield zoteroItem.loadChildItems(); - - // Include attachments - item.attachments = []; - let attachments = zoteroItem.getAttachments(); - for (let i=0; i x.libraryID); + for (let libraryID of libraryIDs) { + yield Zotero.Collections.loadAllData(libraryID); + yield Zotero.Searches.loadAllData(libraryID); + yield Zotero.Items.loadAllData(libraryID); + } + yield Zotero.QuickCopy.init(); Zotero.Items.startEmptyTrashTimer(); diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 6c835677e5..dace7b126c 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1294,7 +1294,6 @@ var ZoteroPane = new function() var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false; noteEditor.parent = null; - yield item.loadNote(); noteEditor.item = item; // If loading new or different note, disable undo while we repopulate the text field @@ -1325,8 +1324,6 @@ var ZoteroPane = new function() else if (item.isAttachment()) { var attachmentBox = document.getElementById('zotero-attachment-box'); attachmentBox.mode = this.collectionsView.editable ? 'edit' : 'view'; - yield item.loadItemData(); - yield item.loadNote(); attachmentBox.item = item; document.getElementById('zotero-item-pane-content').selectedIndex = 3; @@ -1588,7 +1585,7 @@ var ZoteroPane = new function() var newItem; yield Zotero.DB.executeTransaction(function* () { - newItem = yield item.clone(null, !Zotero.Prefs.get('groups.copyTags')); + newItem = item.clone(null, !Zotero.Prefs.get('groups.copyTags')); yield newItem.save(); if (self.collectionsView.selectedTreeRow.isCollection() && newItem.isTopLevelItem()) { @@ -3641,7 +3638,6 @@ var ZoteroPane = new function() // Fall back to first attachment link if (!uri) { - yield item.loadChildItems(); let attachmentID = item.getAttachments()[0]; if (attachmentID) { let attachment = yield Zotero.Items.getAsync(attachmentID); @@ -3851,7 +3847,7 @@ var ZoteroPane = new function() }); - this.showPublicationsWizard = Zotero.Promise.coroutine(function* (items) { + this.showPublicationsWizard = function (items) { var io = { hasFiles: false, hasNotes: false, @@ -3863,14 +3859,12 @@ var ZoteroPane = new function() for (let i = 0; i < items.length; i++) { let item = items[i]; - yield item.loadItemData(); - yield item.loadChildItems(); - // Files if (!io.hasFiles && item.numAttachments()) { - let attachments = item.getAttachments(); - attachments = yield Zotero.Items.getAsync(attachments); - io.hasFiles = attachments.some(attachment => attachment.isFileAttachment()); + let attachmentIDs = item.getAttachments(); + io.hasFiles = Zotero.Items.get(attachmentIDs).some( + attachment => attachment.isFileAttachment() + ); } // Notes if (!io.hasNotes && item.numNotes()) { @@ -3887,7 +3881,7 @@ var ZoteroPane = new function() io.hasRights = allItemsHaveRights ? 'all' : (noItemsHaveRights ? 'none' : 'some'); window.openDialog('chrome://zotero/content/publicationsDialog.xul','','chrome,modal', io); return io.license ? io : false; - }); + }; /** diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js index a0ce44e088..36c3d16d5f 100644 --- a/components/zotero-protocol-handler.js +++ b/components/zotero-protocol-handler.js @@ -214,7 +214,7 @@ function ZoteroProtocolHandler() { else if (combineChildItems || !results[i].isRegularItem() || results[i].numChildren() == 0) { itemsHash[results[i].id] = [items.length]; - items.push(yield results[i].toJSON({ mode: 'full' })); + items.push(results[i].toJSON({ mode: 'full' })); // Flag item as a search match items[items.length - 1].reportSearchMatch = true; } @@ -241,7 +241,6 @@ function ZoteroProtocolHandler() { } } }; - yield item.loadChildItems(); func(item.getNotes()); func(item.getAttachments()); } @@ -252,7 +251,7 @@ function ZoteroProtocolHandler() { else { for (var i in unhandledParents) { itemsHash[results[i].id] = [items.length]; - items.push(yield results[i].toJSON({ mode: 'full' })); + items.push(results[i].toJSON({ mode: 'full' })); // Flag item as a search match items[items.length - 1].reportSearchMatch = true; } @@ -264,7 +263,7 @@ function ZoteroProtocolHandler() { if (!searchItemIDs[id] && !itemsHash[id]) { var item = yield Zotero.Items.getAsync(id); itemsHash[id] = items.length; - items.push(yield item.toJSON({ mode: 'full' })); + items.push(item.toJSON({ mode: 'full' })); } } @@ -279,10 +278,10 @@ function ZoteroProtocolHandler() { }; } if (item.isNote()) { - items[itemsHash[parentID]].reportChildren.notes.push(yield item.toJSON({ mode: 'full' })); + items[itemsHash[parentID]].reportChildren.notes.push(item.toJSON({ mode: 'full' })); } if (item.isAttachment()) { - items[itemsHash[parentID]].reportChildren.attachments.push(yield item.toJSON({ mode: 'full' })); + items[itemsHash[parentID]].reportChildren.attachments.push(item.toJSON({ mode: 'full' })); } } } @@ -299,7 +298,7 @@ function ZoteroProtocolHandler() { // add on its own if (searchItemIDs[parentID]) { itemsHash[parentID] = [items.length]; - items.push(yield parentItem.toJSON({ mode: 'full' })); + items.push(parentItem.toJSON({ mode: 'full' })); items[items.length - 1].reportSearchMatch = true; } else { @@ -312,14 +311,14 @@ function ZoteroProtocolHandler() { items.push(parentItem.toJSON({ mode: 'full' })); if (item.isNote()) { items[items.length - 1].reportChildren = { - notes: [yield item.toJSON({ mode: 'full' })], + notes: [item.toJSON({ mode: 'full' })], attachments: [] }; } else if (item.isAttachment()) { items[items.length - 1].reportChildren = { notes: [], - attachments: [yield item.toJSON({ mode: 'full' })] + attachments: [item.toJSON({ mode: 'full' })] }; } } @@ -609,7 +608,6 @@ function ZoteroProtocolHandler() { if (params.controller == 'data') { switch (params.scopeObject) { case 'collections': - yield collection.loadChildItems(); var results = collection.getChildItems(); break; diff --git a/test/content/support.js b/test/content/support.js index c4af200b25..38da3e99c5 100644 --- a/test/content/support.js +++ b/test/content/support.js @@ -352,10 +352,9 @@ function getNameProperty(objectType) { return objectType == 'item' ? 'title' : 'name'; } -var modifyDataObject = Zotero.Promise.coroutine(function* (obj, params = {}, saveOptions) { +var modifyDataObject = function (obj, params = {}, saveOptions) { switch (obj.objectType) { case 'item': - yield obj.loadItemData(); obj.setField( 'title', params.title !== undefined ? params.title : Zotero.Utilities.randomString() @@ -366,7 +365,7 @@ var modifyDataObject = Zotero.Promise.coroutine(function* (obj, params = {}, sav obj.name = params.name !== undefined ? params.name : Zotero.Utilities.randomString(); } return obj.saveTx(saveOptions); -}); +}; /** * Return a promise for the error thrown by a promise, or false if none @@ -584,7 +583,7 @@ var generateItemJSONData = Zotero.Promise.coroutine(function* generateItemJSONDa for (let itemName in items) { let zItem = yield Zotero.Items.getAsync(items[itemName].id); - jsonData[itemName] = yield zItem.toJSON(options); + jsonData[itemName] = zItem.toJSON(options); // Don't replace some fields that _always_ change (e.g. item keys) // as long as it follows expected format diff --git a/test/resource/chai b/test/resource/chai index b369f25243..775281e138 160000 --- a/test/resource/chai +++ b/test/resource/chai @@ -1 +1 @@ -Subproject commit b369f252432c3486a66a0e93f441e4abb133d229 +Subproject commit 775281e138df26101fba1e554c516f47438851b5 diff --git a/test/resource/mocha b/test/resource/mocha index 2a8594424c..44b0045463 160000 --- a/test/resource/mocha +++ b/test/resource/mocha @@ -1 +1 @@ -Subproject commit 2a8594424c73ffeca41ef1668446372160528b4a +Subproject commit 44b0045463907b1d7963a2e9560c24d9552aac5d diff --git a/test/tests/collectionTest.js b/test/tests/collectionTest.js index 269f7ab15e..41f975efc6 100644 --- a/test/tests/collectionTest.js +++ b/test/tests/collectionTest.js @@ -152,7 +152,6 @@ describe("Zotero.Collection", function() { var collection2 = yield createDataObject('collection', { parentID: collection1.id }); yield collection1.saveTx(); - yield collection1.loadChildCollections(); var childCollections = collection1.getChildCollections(); assert.lengthOf(childCollections, 1); assert.equal(childCollections[0].id, collection2.id); @@ -163,8 +162,6 @@ describe("Zotero.Collection", function() { var collection2 = yield createDataObject('collection', { parentID: collection1.id }); yield collection1.saveTx(); - yield collection1.loadChildCollections(); - collection2.parentID = false; yield collection2.save() @@ -180,7 +177,6 @@ describe("Zotero.Collection", function() { item.addToCollection(collection.key); yield item.saveTx(); - yield collection.loadChildItems(); assert.lengthOf(collection.getChildItems(), 1); }) @@ -191,7 +187,6 @@ describe("Zotero.Collection", function() { item.addToCollection(collection.key); yield item.saveTx(); - yield collection.loadChildItems(); assert.lengthOf(collection.getChildItems(), 0); }) @@ -202,7 +197,6 @@ describe("Zotero.Collection", function() { item.addToCollection(collection.key); yield item.saveTx(); - yield collection.loadChildItems(); assert.lengthOf(collection.getChildItems(false, true), 1); }) }) diff --git a/test/tests/collectionTreeViewTest.js b/test/tests/collectionTreeViewTest.js index 742c66faca..f8a77e41bd 100644 --- a/test/tests/collectionTreeViewTest.js +++ b/test/tests/collectionTreeViewTest.js @@ -390,12 +390,6 @@ describe("Zotero.CollectionTreeView", function() { parentItemID: item.id }); - // Hack to unload relations to test proper loading - // - // Probably need a better method for this - item._loaded.relations = false; - attachment._loaded.relations = false; - var ids = (yield drop('item', 'L' + group.libraryID, [item.id])).ids; yield cv.selectLibrary(group.libraryID); @@ -413,7 +407,7 @@ describe("Zotero.CollectionTreeView", function() { // Check attachment assert.isTrue(itemsView.isContainer(0)); - yield itemsView.toggleOpenState(0); + itemsView.toggleOpenState(0); assert.equal(itemsView.rowCount, 2); treeRow = itemsView.getRow(1); assert.equal(treeRow.ref.id, ids[1]); diff --git a/test/tests/creatorsTest.js b/test/tests/creatorsTest.js new file mode 100644 index 0000000000..90ccf202a1 --- /dev/null +++ b/test/tests/creatorsTest.js @@ -0,0 +1,21 @@ +"use strict"; + +describe("Zotero.Creators", function() { + describe("#getIDFromData()", function () { + it("should create creator and cache data", function* () { + var data1 = { + firstName: "First", + lastName: "Last" + }; + var creatorID; + yield Zotero.DB.executeTransaction(function* () { + creatorID = yield Zotero.Creators.getIDFromData(data1, true); + }); + assert.typeOf(creatorID, 'number'); + var data2 = Zotero.Creators.get(creatorID); + assert.isObject(data2); + assert.propertyVal(data2, "firstName", data1.firstName); + assert.propertyVal(data2, "lastName", data1.lastName); + }); + }); +}); diff --git a/test/tests/dataObjectTest.js b/test/tests/dataObjectTest.js index 18b4f62d4b..13f178d6cb 100644 --- a/test/tests/dataObjectTest.js +++ b/test/tests/dataObjectTest.js @@ -56,7 +56,6 @@ describe("Zotero.DataObject", function() { yield obj.saveTx(); if (type == 'item') { - yield obj.loadItemData(); obj.setField('title', Zotero.Utilities.randomString()); } else { @@ -131,7 +130,6 @@ describe("Zotero.DataObject", function() { yield obj.saveTx(); if (type == 'item') { - yield obj.loadItemData(); obj.setField('title', Zotero.Utilities.randomString()); } else { @@ -294,7 +292,7 @@ describe("Zotero.DataObject", function() { let obj = yield createDataObject(type); let libraryID = obj.libraryID; let key = obj.key; - let json = yield obj.toJSON(); + let json = obj.toJSON(); yield Zotero.Sync.Data.Local.saveCacheObjects(type, libraryID, [json]); yield obj.eraseTx(); let versions = yield Zotero.Sync.Data.Local.getCacheObjectVersions( diff --git a/test/tests/dataObjectUtilitiesTest.js b/test/tests/dataObjectUtilitiesTest.js index 565f7d66ce..bf425c1a2c 100644 --- a/test/tests/dataObjectUtilitiesTest.js +++ b/test/tests/dataObjectUtilitiesTest.js @@ -25,11 +25,11 @@ describe("Zotero.DataObjectUtilities", function() { yield Zotero.DB.executeTransaction(function* () { var item = new Zotero.Item('book'); id1 = yield item.save(); - json1 = yield item.toJSON(); + json1 = item.toJSON(); var item = new Zotero.Item('book'); id2 = yield item.save(); - json2 = yield item.toJSON(); + json2 = item.toJSON(); }); var changes = Zotero.DataObjectUtilities.diff(json1, json2); diff --git a/test/tests/fileInterfaceTest.js b/test/tests/fileInterfaceTest.js index d098c29140..b3435593b7 100644 --- a/test/tests/fileInterfaceTest.js +++ b/test/tests/fileInterfaceTest.js @@ -21,7 +21,7 @@ describe("Zotero_File_Interface", function() { let childItems = importedCollection[0].getChildItems(); let savedItems = {}; for (let i=0; i o.toResponseJSON())); + objectResponseJSON[type] = objects[type].map(o => o.toResponseJSON()); } server.respond(function (req) { @@ -457,12 +457,11 @@ describe("Zotero.Sync.Data.Engine", function () { var mtime = new Date().getTime(); var md5 = '57f8a4fda823187b91e1191487b87fe6'; - yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime); - yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, md5); - }); + item.attachmentSyncedModificationTime = mtime; + item.attachmentSyncedHash = md5; + yield item.saveTx({ skipAll: true }); - var itemResponseJSON = yield item.toResponseJSON(); + var itemResponseJSON = item.toResponseJSON(); itemResponseJSON.version = itemResponseJSON.data.version = lastLibraryVersion; itemResponseJSON.data.mtime = mtime; itemResponseJSON.data.md5 = md5; @@ -520,7 +519,7 @@ describe("Zotero.Sync.Data.Engine", function () { for (let type of types) { objects[type] = [yield createDataObject(type, { setTitle: true })]; objectNames[type] = {}; - objectResponseJSON[type] = yield Zotero.Promise.all(objects[type].map(o => o.toResponseJSON())); + objectResponseJSON[type] = objects[type].map(o => o.toResponseJSON()); } server.respond(function (req) { @@ -569,7 +568,6 @@ describe("Zotero.Sync.Data.Engine", function () { let version = o.version; let name = objectNames[type][key]; if (type == 'item') { - yield o.loadItemData(); assert.equal(name, o.getField('title')); } else { @@ -675,7 +673,7 @@ describe("Zotero.Sync.Data.Engine", function () { { key: obj.key, version: obj.version, - data: (yield obj.toJSON()) + data: obj.toJSON() } ] ); diff --git a/test/tests/syncLocalTest.js b/test/tests/syncLocalTest.js index 611ae7abb3..3f5b82c7b5 100644 --- a/test/tests/syncLocalTest.js +++ b/test/tests/syncLocalTest.js @@ -105,7 +105,7 @@ describe("Zotero.Sync.Data.Local", function() { for (let type of types) { let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type); let obj = yield createDataObject(type); - let data = yield obj.toJSON(); + let data = obj.toJSON(); data.key = obj.key; data.version = 10; let json = { @@ -130,7 +130,7 @@ describe("Zotero.Sync.Data.Local", function() { var type = 'item'; let obj = yield createDataObject(type, { version: 5 }); - let data = yield obj.toJSON(); + let data = obj.toJSON(); yield Zotero.Sync.Data.Local.saveCacheObjects( type, libraryID, [data] ); @@ -165,7 +165,7 @@ describe("Zotero.Sync.Data.Local", function() { for (let type of types) { let obj = yield createDataObject(type, { version: 5 }); - let data = yield obj.toJSON(); + let data = obj.toJSON(); yield Zotero.Sync.Data.Local.saveCacheObjects( type, libraryID, [data] ); @@ -175,7 +175,7 @@ describe("Zotero.Sync.Data.Local", function() { let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type); let obj = yield createDataObject(type, { version: 10 }); - let data = yield obj.toJSON(); + let data = obj.toJSON(); yield Zotero.Sync.Data.Local.saveCacheObjects( type, libraryID, [data] ); @@ -222,11 +222,8 @@ describe("Zotero.Sync.Data.Local", function() { yield Zotero.Sync.Data.Local.processSyncCacheForObjectType( libraryID, 'item', { stopOnError: true } ); - var id = Zotero.Items.getIDFromLibraryAndKey(libraryID, key); - assert.equal( - (yield Zotero.Sync.Storage.Local.getSyncState(id)), - Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD - ); + var item = Zotero.Items.getByLibraryAndKey(libraryID, key); + assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD); }) it("should mark updated attachment items for download", function* () { @@ -239,18 +236,13 @@ describe("Zotero.Sync.Data.Local", function() { yield item.saveTx(); // Set file as synced - yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncedModificationTime( - item.id, (yield item.attachmentModificationTime) - ); - yield Zotero.Sync.Storage.Local.setSyncedHash( - item.id, (yield item.attachmentHash) - ); - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - }); + item.attachmentSyncedModificationTime = yield item.attachmentModificationTime; + item.attachmentSyncedHash = yield item.attachmentHash; + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); // Simulate download of version with updated attachment - var json = yield item.toResponseJSON(); + var json = item.toResponseJSON(); json.version = 10; json.data.version = 10; json.data.md5 = '57f8a4fda823187b91e1191487b87fe6'; @@ -263,10 +255,7 @@ describe("Zotero.Sync.Data.Local", function() { libraryID, 'item', { stopOnError: true } ); - assert.equal( - (yield Zotero.Sync.Storage.Local.getSyncState(item.id)), - Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD - ); + assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD); }) it("should ignore attachment metadata when resolving metadata conflict", function* () { @@ -276,19 +265,14 @@ describe("Zotero.Sync.Data.Local", function() { var item = yield importFileAttachment('test.png'); item.version = 5; yield item.saveTx(); - var json = yield item.toResponseJSON(); + var json = item.toResponseJSON(); yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json]); // Set file as synced - yield Zotero.DB.executeTransaction(function* () { - yield Zotero.Sync.Storage.Local.setSyncedModificationTime( - item.id, (yield item.attachmentModificationTime) - ); - yield Zotero.Sync.Storage.Local.setSyncedHash( - item.id, (yield item.attachmentHash) - ); - yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync"); - }); + item.attachmentSyncedModificationTime = yield item.attachmentModificationTime; + item.attachmentSyncedHash = yield item.attachmentHash; + item.attachmentSyncState = "in_sync"; + yield item.saveTx({ skipAll: true }); // Modify title locally, leaving item unsynced var newTitle = Zotero.Utilities.randomString(); @@ -307,10 +291,7 @@ describe("Zotero.Sync.Data.Local", function() { ); assert.equal(item.getField('title'), newTitle); - assert.equal( - (yield Zotero.Sync.Storage.Local.getSyncState(item.id)), - Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD - ); + assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD); }) }) @@ -348,7 +329,7 @@ describe("Zotero.Sync.Data.Local", function() { ) } ); - let jsonData = yield obj.toJSON(); + let jsonData = obj.toJSON(); jsonData.key = obj.key; jsonData.version = 10; let json = { @@ -426,7 +407,7 @@ describe("Zotero.Sync.Data.Local", function() { ) } ); - let jsonData = yield obj.toJSON(); + let jsonData = obj.toJSON(); jsonData.key = obj.key; jsonData.version = 10; let json = { @@ -496,7 +477,7 @@ describe("Zotero.Sync.Data.Local", function() { // Create object, generate JSON, and delete var obj = yield createDataObject(type, { version: 10 }); - var jsonData = yield obj.toJSON(); + var jsonData = obj.toJSON(); var key = jsonData.key = obj.key; jsonData.version = 10; let json = { @@ -544,7 +525,7 @@ describe("Zotero.Sync.Data.Local", function() { // Create object, generate JSON, and delete var obj = yield createDataObject(type, { version: 10 }); - var jsonData = yield obj.toJSON(); + var jsonData = obj.toJSON(); var key = jsonData.key = obj.key; jsonData.version = 10; let json = { @@ -576,7 +557,6 @@ describe("Zotero.Sync.Data.Local", function() { obj = objectsClass.getByLibraryAndKey(libraryID, key); assert.ok(obj); - yield obj.loadItemData(); assert.equal(obj.getField('title'), jsonData.title); }) @@ -594,7 +574,7 @@ describe("Zotero.Sync.Data.Local", function() { obj.setNote(""); obj.version = 10; yield obj.saveTx(); - var jsonData = yield obj.toJSON(); + var jsonData = obj.toJSON(); var key = jsonData.key = obj.key; let json = { key: obj.key, @@ -626,7 +606,6 @@ describe("Zotero.Sync.Data.Local", function() { obj = objectsClass.getByLibraryAndKey(libraryID, key); assert.ok(obj); - yield obj.loadNote(); assert.equal(obj.getNote(), noteText2); }) }) diff --git a/test/tests/translateTest.js b/test/tests/translateTest.js index 195413593d..c2d51f2237 100644 --- a/test/tests/translateTest.js +++ b/test/tests/translateTest.js @@ -175,7 +175,7 @@ describe("Zotero.Translate", function() { let newItems = yield saveItemsThroughTranslator("import", saveItems); let savedItems = {}; for (let i=0; i