commit db0fa3c33e9db6a793aef04d9521fea848c31e5c
parent 4ea5e2d426e40ebec69c0012b82bc13d83ba87ad
Author: Dan Stillman <dstillman@zotero.org>
Date: Wed, 6 Aug 2014 17:38:05 -0400
Async DB megacommit
Promise-based rewrite of most of the codebase, with asynchronous database and file access -- see https://github.com/zotero/zotero/issues/518 for details.
WARNING: This includes backwards-incompatible schema changes.
An incomplete list of other changes:
- Schema overhaul
- Replace main tables with new versions with updated schema
- Enable real foreign key support and remove previous triggers
- Don't use NULLs for local libraryID, which broke the UNIQUE index
preventing object key duplication. All code (Zotero and third-party)
using NULL for the local library will need to be updated to use 0
instead (already done for Zotero code)
- Add 'compatibility' DB version that can be incremented manually to break DB
compatibility with previous versions. 'userdata' upgrades will no longer
automatically break compatibility.
- Demote creators and tags from first-class objects to item properties
- New API syncing properties
- 'synced'/'version' properties to data objects
- 'etag' to groups
- 'version' to libraries
- Create Zotero.DataObject that other objects inherit from
- Consolidate data object loading into Zotero.DataObjects
- Change object reloading so that only the loaded and changed parts of objects are reloaded, instead of reloading all data from the database (with some exceptions, including item primary data)
- Items and collections now have .parentItem and .parentKey properties, replacing item.getSource() and item.getSourceKey()
- New function Zotero.serial(fn), to wrap an async function such that all calls are run serially
- New function Zotero.Utilities.Internal.forEachChunkAsync(arr, chunkSize, func)
- Add tag selector loading message
- Various API and name changes, since everything was breaking anyway
Known broken things:
- Syncing (will be completely rewritten for API syncing)
- Translation architecture (needs promise-based rewrite)
- Duplicates view
- DB integrity check (from schema changes)
- Dragging (may be difficult to fix)
Lots of other big and little things are certainly broken, particularly with the UI, which can be affected by async code in all sorts of subtle ways.
Diffstat:
88 files changed, 16881 insertions(+), 15365 deletions(-)
diff --git a/chrome/content/zotero/advancedSearch.js b/chrome/content/zotero/advancedSearch.js
@@ -53,32 +53,24 @@ var ZoteroAdvancedSearch = new function() {
_searchBox.updateSearch();
_searchBox.active = true;
- // A minimal implementation of Zotero.CollectionTreeView
- var itemGroup = {
+ // A minimal implementation of Zotero.CollectionTreeRow
+ var collectionTreeRow = {
+ ref: _searchBox.search,
isSearchMode: function() { return true; },
- getItems: function () {
- var search = _searchBox.search.clone();
+ getItems: Zotero.Promise.coroutine(function* () {
+ var search = yield _searchBox.search.clone();
// Hack to create a condition for the search's library --
// this logic should really go in the search itself instead of here
// and in collectionTreeView.js
var conditions = search.getSearchConditions();
if (!conditions.some(function (condition) condition.condition == 'libraryID')) {
- let libraryID = _searchBox.search.libraryID;
- // TEMP: libraryIDInt
- if (libraryID) {
- search.addCondition('libraryID', 'is', libraryID);
- }
- else {
- let groups = Zotero.Groups.getAll();
- for (let i=0; i<groups.length; i++) {
- search.addCondition('libraryID', 'isNot', groups[i].libraryID);
- }
- }
+ yield search.addCondition('libraryID', 'is', _searchBox.search.libraryID);
}
- return Zotero.Items.get(search.search());
- },
+ var ids = yield search.search();
+ return Zotero.Items.get(ids);
+ }),
isLibrary: function () { return false; },
isCollection: function () { return false; },
isSearch: function () { return true; },
@@ -90,7 +82,7 @@ var ZoteroAdvancedSearch = new function() {
this.itemsView.unregister();
}
- this.itemsView = new Zotero.ItemTreeView(itemGroup, false);
+ this.itemsView = new Zotero.ItemTreeView(collectionTreeRow, false);
document.getElementById('zotero-items-tree').view = this.itemsView;
}
@@ -104,9 +96,11 @@ var ZoteroAdvancedSearch = new function() {
var s = new Zotero.Search();
// Don't clear the selected library
s.libraryID = _searchBox.search.libraryID;
- s.addCondition('title', 'contains', '');
- _searchBox.search = s;
- _searchBox.active = false;
+ s.addCondition('title', 'contains', '')
+ .then(function () {
+ _searchBox.search = s;
+ _searchBox.active = false;
+ });
}
@@ -138,11 +132,14 @@ var ZoteroAdvancedSearch = new function() {
name.value = untitled;
}
- var s = _searchBox.search.clone();
- s.name = name.value;
- s.save();
-
- window.close();
+ return _searchBox.search.clone()
+ .then(function (s) {
+ s.name = name.value;
+ return s.save();
+ })
+ .then(function () {
+ window.close()
+ });
}
diff --git a/chrome/content/zotero/bindings/attachmentbox.xml b/chrome/content/zotero/bindings/attachmentbox.xml
@@ -138,201 +138,203 @@
<!-- Private properties -->
<method name="refresh">
- <body>
- <![CDATA[
- Zotero.debug('Refreshing attachment box');
-
- var attachmentBox = document.getAnonymousNodes(this)[0];
- var title = this._id('title');
- var fileNameRow = this._id('fileNameRow');
- var urlField = this._id('url');
- var accessed = this._id('accessedRow');
- var pagesRow = this._id('pagesRow');
- var dateModifiedRow = this._id('dateModifiedRow');
- var indexStatusRow = this._id('indexStatusRow');
- var selectButton = this._id('select-button');
-
- // DEBUG: this is annoying -- we really want to use an abstracted
- // version of createValueElement() from itemPane.js
- // (ideally in an XBL binding)
-
- // Wrap title to multiple lines if necessary
- while (title.hasChildNodes()) {
- title.removeChild(title.firstChild);
- }
- var val = this.item.getField('title');
-
- if (typeof val != 'string') {
- val += "";
- }
-
- var firstSpace = val.indexOf(" ");
- // Crop long uninterrupted text
- if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29) {
- title.setAttribute('crop', 'end');
- title.setAttribute('value', val);
- }
- // Create a <description> element, essentially
- else {
- title.removeAttribute('value');
- title.appendChild(document.createTextNode(val));
- }
-
- if (this.editable) {
- title.className = 'zotero-clicky';
+ <body><![CDATA[
+ Zotero.spawn(function* () {
+ Zotero.debug('Refreshing attachment box');
- // For the time being, use a silly little popup
- title.addEventListener('click', this.editTitle, false);
- }
-
- var isImportedURL = this.item.attachmentLinkMode ==
- Zotero.Attachments.LINK_MODE_IMPORTED_URL;
-
- // Metadata for URL's
- if (this.item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL
- || isImportedURL) {
+ yield [this.item.loadItemData(), this.item.loadNote()];
+
+ var attachmentBox = document.getAnonymousNodes(this)[0];
+ var title = this._id('title');
+ var fileNameRow = this._id('fileNameRow');
+ var urlField = this._id('url');
+ var accessed = this._id('accessedRow');
+ var pagesRow = this._id('pagesRow');
+ var dateModifiedRow = this._id('dateModifiedRow');
+ var indexStatusRow = this._id('indexStatusRow');
+ var selectButton = this._id('select-button');
+
+ // DEBUG: this is annoying -- we really want to use an abstracted
+ // version of createValueElement() from itemPane.js
+ // (ideally in an XBL binding)
+
+ // Wrap title to multiple lines if necessary
+ while (title.hasChildNodes()) {
+ title.removeChild(title.firstChild);
+ }
+ var val = this.item.getField('title');
+
+ if (typeof val != 'string') {
+ val += "";
+ }
+
+ var firstSpace = val.indexOf(" ");
+ // Crop long uninterrupted text
+ if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29) {
+ title.setAttribute('crop', 'end');
+ title.setAttribute('value', val);
+ }
+ // Create a <description> element, essentially
+ else {
+ title.removeAttribute('value');
+ title.appendChild(document.createTextNode(val));
+ }
+
+ if (this.editable) {
+ title.className = 'zotero-clicky';
+
+ // For the time being, use a silly little popup
+ title.addEventListener('click', this.editTitle, false);
+ }
+
+ var isImportedURL = this.item.attachmentLinkMode ==
+ Zotero.Attachments.LINK_MODE_IMPORTED_URL;
- // URL
- if (this.displayURL) {
- var urlSpec = this.item.getField('url');
- urlField.setAttribute('value', urlSpec);
- urlField.setAttribute('hidden', false);
- if (this.clickableLink) {
- urlField.onclick = function (event) {
- ZoteroPane_Local.loadURI(this.value, event)
- };
- urlField.className = 'zotero-text-link';
+ // Metadata for URL's
+ if (this.item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL
+ || isImportedURL) {
+
+ // URL
+ if (this.displayURL) {
+ var urlSpec = this.item.getField('url');
+ urlField.setAttribute('value', urlSpec);
+ urlField.setAttribute('hidden', false);
+ if (this.clickableLink) {
+ urlField.onclick = function (event) {
+ ZoteroPane_Local.loadURI(this.value, event)
+ };
+ urlField.className = 'zotero-text-link';
+ }
+ else {
+ urlField.className = '';
+ }
+ urlField.hidden = false;
+ }
+ else {
+ urlField.hidden = true;
+ }
+
+ // Access date
+ if (this.displayAccessed) {
+ this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
+ + Zotero.getString('punctuation.colon');
+ this._id("accessed").value = Zotero.Date.sqlToDate(
+ this.item.getField('accessDate'), true
+ ).toLocaleString();
+ accessed.hidden = false;
}
else {
- urlField.className = '';
+ accessed.hidden = true;
}
- urlField.hidden = false;
}
+ // Metadata for files
else {
urlField.hidden = true;
+ accessed.hidden = true;
}
- // Access date
- if (this.displayAccessed) {
- this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
- + Zotero.getString('punctuation.colon');
- this._id("accessed").value = Zotero.Date.sqlToDate(
- this.item.getField('accessDate'), true
- ).toLocaleString();
- accessed.hidden = false;
+ if (this.item.attachmentLinkMode
+ != Zotero.Attachments.LINK_MODE_LINKED_URL
+ && this.displayFileName) {
+ var fileName = this.item.getFilename();
+
+ if (fileName) {
+ this._id("fileName-label").value = Zotero.getString('pane.item.attachments.filename')
+ + Zotero.getString('punctuation.colon');
+ this._id("fileName").value = fileName;
+ fileNameRow.hidden = false;
+ }
+ else {
+ fileNameRow.hidden = true;
+ }
}
else {
- accessed.hidden = true;
+ fileNameRow.hidden = true;
}
- }
- // Metadata for files
- else {
- urlField.hidden = true;
- accessed.hidden = true;
- }
-
- if (this.item.attachmentLinkMode
- != Zotero.Attachments.LINK_MODE_LINKED_URL
- && this.displayFileName) {
- var fileName = this.item.getFilename();
- if (fileName) {
- this._id("fileName-label").value = Zotero.getString('pane.item.attachments.filename')
- + Zotero.getString('punctuation.colon');
- this._id("fileName").value = fileName;
- fileNameRow.hidden = false;
+ // Page count
+ if (this.displayPages) {
+ var pages = yield Zotero.Fulltext.getPages(this.item.id);
+ var pages = pages ? pages.total : null;
+ if (pages) {
+ this._id("pages-label").value = Zotero.getString('itemFields.pages')
+ + Zotero.getString('punctuation.colon');
+ this._id("pages").value = pages;
+ pagesRow.hidden = false;
+ }
+ else {
+ pagesRow.hidden = true;
+ }
}
else {
- fileNameRow.hidden = true;
+ pagesRow.hidden = true;
}
- }
- else {
- fileNameRow.hidden = true;
- }
-
- // Page count
- if (this.displayPages) {
- var pages = Zotero.Fulltext.getPages(this.item.id);
- var pages = pages ? pages.total : null;
- if (pages) {
- this._id("pages-label").value = Zotero.getString('itemFields.pages')
+
+ if (this.displayDateModified) {
+ this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
+ Zotero.getString('punctuation.colon');
- this._id("pages").value = pages;
- pagesRow.hidden = false;
+ var mtime = this.item.attachmentModificationTime;
+ if (mtime) {
+ this._id("dateModified").value = new Date(mtime).toLocaleString();
+ }
+ // Use the item's mod time as a backup (e.g., when sync
+ // passes in the mod time for the nonexistent remote file)
+ else {
+ this._id("dateModified").value = Zotero.Date.sqlToDate(
+ this.item.getField('dateModified'), true
+ ).toLocaleString();
+ }
+ dateModifiedRow.hidden = false;
}
else {
- pagesRow.hidden = true;
+ dateModifiedRow.hidden = true;
}
- }
- else {
- pagesRow.hidden = true;
- }
-
- if (this.displayDateModified) {
- this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
- + Zotero.getString('punctuation.colon');
- var mtime = this.item.attachmentModificationTime;
- if (mtime) {
- this._id("dateModified").value = new Date(mtime).toLocaleString();
+
+ // Full-text index information
+ if (this.displayIndexed) {
+ yield this.updateItemIndexedState();
+ indexStatusRow.hidden = false;
}
- // Use the item's mod time as a backup (e.g., when sync
- // passes in the mod time for the nonexistent remote file)
else {
- this._id("dateModified").value = Zotero.Date.sqlToDate(
- this.item.getField('dateModified'), true
- ).toLocaleString();
+ indexStatusRow.hidden = true;
}
- dateModifiedRow.hidden = false;
- }
- else {
- dateModifiedRow.hidden = true;
- }
-
- // Full-text index information
- if (this.displayIndexed) {
- this.updateItemIndexedState();
- indexStatusRow.hidden = false;
- }
- else {
- indexStatusRow.hidden = true;
- }
-
- // Note editor
- var noteEditor = this._id('attachment-note-editor');
- if (this.displayNote) {
- if (this.displayNoteIfEmpty || this.item.getNote() != '') {
- Zotero.debug("setting links on top");
- noteEditor.linksOnTop = true;
- noteEditor.hidden = false;
-
- // Don't make note editable (at least for now)
- if (this.mode == 'merge' || this.mode == 'mergeedit') {
- noteEditor.mode = 'merge';
- noteEditor.displayButton = false;
- }
- else {
- noteEditor.mode = this.mode;
+
+ // Note editor
+ var noteEditor = this._id('attachment-note-editor');
+ if (this.displayNote) {
+ if (this.displayNoteIfEmpty || this.item.getNote() != '') {
+ Zotero.debug("setting links on top");
+ noteEditor.linksOnTop = true;
+ noteEditor.hidden = false;
+
+ // Don't make note editable (at least for now)
+ if (this.mode == 'merge' || this.mode == 'mergeedit') {
+ noteEditor.mode = 'merge';
+ noteEditor.displayButton = false;
+ }
+ else {
+ noteEditor.mode = this.mode;
+ }
+ noteEditor.parent = null;
+ noteEditor.item = this.item;
}
- noteEditor.parent = null;
- noteEditor.item = this.item;
}
- }
- else {
- noteEditor.hidden = true;
- }
+ else {
+ noteEditor.hidden = true;
+ }
+
-
- if (this.displayButton) {
- selectButton.label = this.buttonCaption;
- selectButton.hidden = false;
- selectButton.setAttribute('oncommand',
- 'document.getBindingParent(this).clickHandler(this)');
- }
- else {
- selectButton.hidden = true;
- }
- ]]>
- </body>
+ if (this.displayButton) {
+ selectButton.label = this.buttonCaption;
+ selectButton.hidden = false;
+ selectButton.setAttribute('oncommand',
+ 'document.getBindingParent(this).clickHandler(this)');
+ }
+ else {
+ selectButton.hidden = true;
+ }
+ }, this);
+ ]]></body>
</method>
@@ -464,43 +466,43 @@
Update Indexed: (Yes|No|Partial) line
-->
<method name="updateItemIndexedState">
- <body>
- <![CDATA[
- var indexStatus = this._id('index-status');
- var reindexButton = this._id('reindex');
-
- var status = Zotero.Fulltext.getIndexedState(this.item.id);
- var str = 'fulltext.indexState.';
- switch (status) {
- case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
- str += 'unavailable';
- break;
- case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
- str = 'general.no';
- break;
- case Zotero.Fulltext.INDEX_STATE_PARTIAL:
- str += 'partial';
- break;
- case Zotero.Fulltext.INDEX_STATE_INDEXED:
- str = 'general.yes';
- break;
- }
- this._id("index-status-label").value = Zotero.getString('fulltext.indexState.indexed')
- + Zotero.getString('punctuation.colon');
- indexStatus.value = Zotero.getString(str);
-
- // Reindex button tooltip (string stored in zotero.properties)
- var str = Zotero.getString('pane.items.menu.reindexItem');
- reindexButton.setAttribute('tooltiptext', str);
-
- if (this.editable && Zotero.Fulltext.canReindex(this.item.id)) {
- reindexButton.setAttribute('hidden', false);
- }
- else {
- reindexButton.setAttribute('hidden', true);
- }
- ]]>
- </body>
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ var indexStatus = this._id('index-status');
+ var reindexButton = this._id('reindex');
+
+ var status = yield Zotero.Fulltext.getIndexedState(this.item);
+ var str = 'fulltext.indexState.';
+ switch (status) {
+ case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
+ str += 'unavailable';
+ break;
+ case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
+ str = 'general.no';
+ break;
+ case Zotero.Fulltext.INDEX_STATE_PARTIAL:
+ str += 'partial';
+ break;
+ case Zotero.Fulltext.INDEX_STATE_INDEXED:
+ str = 'general.yes';
+ break;
+ }
+ this._id("index-status-label").value = Zotero.getString('fulltext.indexState.indexed')
+ + Zotero.getString('punctuation.colon');
+ indexStatus.value = Zotero.getString(str);
+
+ // Reindex button tooltip (string stored in zotero.properties)
+ var str = Zotero.getString('pane.items.menu.reindexItem');
+ reindexButton.setAttribute('tooltiptext', str);
+
+ if (this.editable && (yield Zotero.Fulltext.canReindex(this.item))) {
+ reindexButton.setAttribute('hidden', false);
+ }
+ else {
+ reindexButton.setAttribute('hidden', true);
+ }
+ }, this);
+ ]]></body>
</method>
diff --git a/chrome/content/zotero/bindings/itembox.xml b/chrome/content/zotero/bindings/itembox.xml
@@ -347,7 +347,7 @@
// createValueElement() adds the itemTypeID as an attribute
// and converts it to a localized string for display
if (fieldName == 'itemType') {
- val = this.item.getField('itemTypeID');
+ val = this.item.itemTypeID;
}
else {
val = this.item.getField(fieldName);
@@ -530,8 +530,8 @@
max = num;
}
for (var i = 0; i < max; i++) {
- this.addCreatorRow(this.item.getCreator(i).ref,
- this.item.getCreator(i).creatorTypeID);
+ let data = this.item.getCreator(i);
+ this.addCreatorRow(data, data.creatorType);
// Display "+" button on all but last row
if (i == max - 2) {
@@ -553,7 +553,7 @@
this._displayAllCreators = true;
if (this._addCreatorRow) {
- this.addCreatorRow(false, this.item.getCreator(max-1).creatorTypeID, true);
+ this.addCreatorRow(false, this.item.getCreator(max-1).creatorType, true);
this._addCreatorRow = false;
this.disableCreatorAddButtons();
}
@@ -659,8 +659,8 @@
<method name="addCreatorRow">
- <parameter name="creator"/>
- <parameter name="creatorTypeID"/>
+ <parameter name="creatorData"/>
+ <parameter name="creatorTypeIDOrName"/>
<parameter name="unsaved"/>
<parameter name="defaultRow"/>
<body>
@@ -668,26 +668,34 @@
// getCreatorFields(), switchCreatorMode() and handleCreatorAutoCompleteSelect()
// may need need to be adjusted if this DOM structure changes
- if (!creator) {
- creator = {
- firstName: '',
- lastName: '',
- fieldMode: Zotero.Prefs.get('lastCreatorFieldMode')
- };
+ var fieldMode = Zotero.Prefs.get('lastCreatorFieldMode');
+ var firstName = '';
+ var lastName = '';
+ if (creatorData) {
+ fieldMode = creatorData.fieldMode;
+ firstName = creatorData.firstName;
+ lastName = creatorData.lastName;
}
- if (creator.fieldMode == 1) {
- var firstName = '';
- var lastName = creator.lastName ? creator.lastName : this._defaultFullName;
+ // Sub in placeholder text for empty fields
+ if (fieldMode == 1) {
+ if (lastName === "") {
+ lastName = this._defaultFullName;
+ }
}
else {
- var firstName = creator.firstName ? creator.firstName : this._defaultFirstName;
- var lastName = creator.lastName ? creator.lastName : this._defaultLastName;
+ if (firstName === "") {
+ firstName = this._defaultFirstName;
+ }
+ if (lastName === "") {
+ lastName = this._defaultLastName;
+ }
}
// Use the first entry in the drop-down for the default type if none specified
- var typeID = creatorTypeID ?
- creatorTypeID : this._creatorTypeMenu.childNodes[0].getAttribute('typeid');
+ var typeID = creatorTypeIDOrName
+ ? Zotero.CreatorTypes.getID(creatorTypeIDOrName)
+ : this._creatorTypeMenu.childNodes[0].getAttribute('typeid');
var typeBox = document.createElement("hbox");
typeBox.setAttribute("typeid", typeID);
@@ -738,7 +746,7 @@
tabindex + 1
)
);
- if (creator.fieldMode) {
+ if (fieldMode) {
firstlast.lastChild.setAttribute('hidden', true);
}
@@ -782,7 +790,7 @@
this.disableButton(addButton);
}
else {
- this._enablePlusButton(addButton, typeID, creator.fieldMode);
+ this._enablePlusButton(addButton, typeID, fieldMode);
}
hbox.appendChild(addButton);
@@ -797,7 +805,7 @@
this.addDynamicRow(typeBox, hbox, true);
// Set single/double field toggle mode
- if (creator.fieldMode) {
+ if (fieldMode) {
this.switchCreatorMode(hbox.parentNode, 1, true);
}
else {
@@ -1169,16 +1177,11 @@
<body>
<![CDATA[
button.setAttribute('disabled', false);
- button.setAttribute("onclick",
- "var parent = document.getBindingParent(this); "
- + "parent.disableButton(this); "
- + "var creator = new Zotero.Creator; "
- + "creator.fieldMode = " + (fieldMode ? fieldMode : 0) + "; "
- + "parent.addCreatorRow("
- + "creator, "
- + (creatorTypeID ? creatorTypeID : 'false') + ", true"
- + ");"
- );
+ button.onclick = function () {
+ var parent = document.getBindingParent(this);
+ parent.disableButton(this);
+ parent.addCreatorRow(null, creatorTypeID, true);
+ };
]]>
</body>
</method>
@@ -1373,8 +1376,10 @@
var [field, creatorIndex, creatorField] = fieldName.split('-');
if (field == 'creator') {
- var c = this.item.getCreator(creatorIndex);
- var value = c ? c.ref[creatorField] : '';
+ var value = this.item.getCreator(creatorIndex)[creatorField];
+ if (value === undefined) {
+ value = "";
+ }
var itemID = this.item.id;
}
else {
@@ -1487,72 +1492,73 @@
-->
<method name="handleCreatorAutoCompleteSelect">
<parameter name="textbox"/>
- <body>
- <![CDATA[
- var comment = false;
- var controller = textbox.controller;
- if (!controller.matchCount) return;
-
- for (var i=0; i<controller.matchCount; i++)
- {
- if (controller.getValueAt(i) == textbox.value)
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ var comment = false;
+ var controller = textbox.controller;
+ if (!controller.matchCount) return;
+
+ for (var i=0; i<controller.matchCount; i++)
{
- comment = controller.getCommentAt(i);
- break;
+ if (controller.getValueAt(i) == textbox.value)
+ {
+ comment = controller.getCommentAt(i);
+ break;
+ }
}
- }
-
- var [creatorID, numFields] = comment.split('-');
-
- // If result uses two fields, save both
- if (numFields==2)
- {
- // Manually clear autocomplete controller's reference to
- // textbox to prevent error next time around
- textbox.mController.input = null;
-
- var [field, creatorIndex, creatorField] =
- textbox.getAttribute('fieldname').split('-');
-
- // Stay focused
- this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
- this._tabDirection = 1;
-
- var creator = Zotero.Creators.get(creatorID);
-
- var otherField = creatorField == 'lastName' ? 'firstName' : 'lastName';
- // Update this textbox
- textbox.setAttribute('value', creator[creatorField]);
- textbox.value = creator[creatorField];
+ Zotero.debug(comment);
+ var [creatorID, numFields] = comment.split('-');
- // Update the other label
- if (otherField=='firstName'){
- var label = textbox.nextSibling.nextSibling;
- }
- else if (otherField=='lastName'){
- var label = textbox.previousSibling.previousSibling;
- }
-
- //this._setFieldValue(label, creator[otherField]);
- if (label.firstChild){
- label.firstChild.nodeValue = creator[otherField];
- }
- else {
- label.value = creator[otherField];
+ // If result uses two fields, save both
+ if (numFields==2)
+ {
+ // Manually clear autocomplete controller's reference to
+ // textbox to prevent error next time around
+ textbox.mController.input = null;
+
+ var [field, creatorIndex, creatorField] =
+ textbox.getAttribute('fieldname').split('-');
+
+ // Stay focused
+ this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
+ this._tabDirection = 1;
+
+ var creator = yield Zotero.Creators.getAsync(creatorID);
+
+ var otherField = creatorField == 'lastName' ? 'firstName' : 'lastName';
+
+ // Update this textbox
+ textbox.setAttribute('value', creator[creatorField]);
+ textbox.value = creator[creatorField];
+
+ // Update the other label
+ if (otherField=='firstName'){
+ var label = textbox.nextSibling.nextSibling;
+ }
+ else if (otherField=='lastName'){
+ var label = textbox.previousSibling.previousSibling;
+ }
+
+ //this._setFieldValue(label, creator[otherField]);
+ if (label.firstChild){
+ label.firstChild.nodeValue = creator[otherField];
+ }
+ else {
+ label.value = creator[otherField];
+ }
+
+ var row = Zotero.getAncestorByTagName(textbox, 'row');
+
+ var fields = this.getCreatorFields(row);
+ fields[creatorField] = creator[creatorField];
+ fields[otherField] = creator[otherField];
+ yield this.modifyCreator(creatorIndex, fields);
}
- var row = Zotero.getAncestorByTagName(textbox, 'row');
-
- var fields = this.getCreatorFields(row);
- fields[creatorField] = creator[creatorField];
- fields[otherField] = creator[otherField];
- this.modifyCreator(creatorIndex, fields);
- }
-
- // Otherwise let the autocomplete popup handle matters
- ]]>
- </body>
+ // Otherwise let the autocomplete popup handle matters
+ }, this);
+ ]]></body>
</method>
@@ -1719,8 +1725,8 @@
//Shift existing creators
for (var i=initNumCreators-1; i>=creatorIndex; i--) {
- var shiftedCreator = this.item.getCreator(i);
- this.item.setCreator(nameArray.length+i,shiftedCreator.ref,shiftedCreator.creatorTypeID);
+ let shiftedCreatorData = this.item.getCreator(i);
+ this.item.setCreator(nameArray.length + i, shiftedCreatorData);
}
}
@@ -1753,7 +1759,7 @@
}
var val = this.item.getCreator(creatorIndex);
- val = val ? val.ref[creatorField] : null;
+ val = val ? val[creatorField] : null;
if (!val) {
// Reset to '(first)'/'(last)'/'(name)'
@@ -1977,10 +1983,8 @@
var label2 = label1.parentNode.lastChild;
var fields = {
- lastName: label1.firstChild ? label1.firstChild.nodeValue
- : label1.value,
- firstName: label2.firstChild ? label2.firstChild.nodeValue
- : label2.value,
+ lastName: label1.firstChild ? label1.firstChild.nodeValue : label1.value,
+ firstName: label2.firstChild ? label2.firstChild.nodeValue : label2.value,
fieldMode: label1.getAttribute('fieldMode')
? parseInt(label1.getAttribute('fieldMode')) : 0,
creatorTypeID: parseInt(typeID),
@@ -1996,6 +2000,9 @@
fields.lastName = '';
}
+ Zotero.debug("RETURNING FIELDS");
+ Zotero.debug(fields);
+
return fields;
]]>
</body>
@@ -2006,14 +2013,11 @@
<parameter name="index"/>
<parameter name="fields"/>
<parameter name="changeGlobally"/>
- <body>
- <![CDATA[
- try {
-
+ <body><![CDATA[
+ return Zotero.Promise.try(function () {
var libraryID = this.item.libraryID;
var firstName = fields.firstName;
var lastName = fields.lastName;
- //var shortName = fields.shortName;
var fieldMode = fields.fieldMode;
var creatorTypeID = fields.creatorTypeID;
@@ -2025,130 +2029,27 @@
return;
}
this.item.removeCreator(index);
- this.item.save();
- return;
- }
-
- Zotero.DB.beginTransaction();
-
- var newCreator = new Zotero.Creator;
- newCreator.libraryID = libraryID;
- newCreator.setFields(fields);
-
- var newLinkedCreators = [];
- var creatorDataID = Zotero.Creators.getDataID(fields);
- if (creatorDataID) {
- newLinkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID, libraryID);
- }
-
- if (oldCreator) {
- if (oldCreator.ref.equals(newCreator) || (oldCreator.ref.libraryID != newCreator.libraryID)) {
- if (oldCreator.creatorTypeID == creatorTypeID) {
- Zotero.debug("Creator " + oldCreator.ref.id + " hasn't changed");
- }
- // Just change creatorTypeID
- else {
- this.item.setCreator(index, oldCreator.ref, creatorTypeID);
- if (this.saveOnEdit) {
- this.item.save();
- }
- }
- Zotero.DB.commitTransaction();
- return;
+ if (this.saveOnEdit) {
+ return this.item.save();
}
-
- oldCreator = oldCreator.ref;
+ return;
}
- var creator;
- var creatorID;
-
- if (oldCreator) {
- var numLinkedItems = oldCreator.countLinkedItems();
- // Creator is linked only to the current item
- if (numLinkedItems == 1) {
- if (newLinkedCreators.length) {
- // Use the first creator found with this data
- // TODO: support choosing among options
- creatorID = newLinkedCreators[0];
- creator = Zotero.Creators.get(creatorID);
- }
- else {
- oldCreator.setFields(fields);
- //creatorID = oldCreator.save();
- creator = oldCreator;
- }
- }
- // Creator is linked to multiple items with changeGlobally off
- else if (!changeGlobally) {
- if (newLinkedCreators.length) {
- // Use the first creator found with this data
- // TODO: support choosing among options
- creatorID = newLinkedCreators[0];
- creator = Zotero.Creators.get(creatorID);
- }
- else {
- //creatorID = newCreator.save();
- creator = newCreator;
- }
- }
- // Creator is linked to multiple items with changeGlobally on
- else {
- throw ('changeGlobally unimplemented');
- if (newLinkedCreators.length) {
- // Use the first creator found with this data
- // TODO: support choosing among options
- creatorID = newLinkedCreators[0];
-
- // TODO: switch all linked items to this creator
- }
- else {
- creatorID = newCreator.save();
-
- // TODO: switch all linked items to new creatorID
- }
- }
+ var changed = this.item.setCreator(index, fields);
+ if (changed && this.saveOnEdit) {
+ return this.item.save();
}
- // No existing creator
- else {
- if (newLinkedCreators.length) {
- creatorID = newLinkedCreators[0];
- creator = Zotero.Creators.get(creatorID);
- }
- else {
- //creatorID = newCreator.save();
- creator = newCreator;
- }
- }
-
- this.item.setCreator(index, creator, creatorTypeID);
- if (this.saveOnEdit) {
- try {
- this.item.save();
- }
- catch (e) {
- // DEBUG: Errors aren't being logged in Fx3.1b4pre without this
- Zotero.debug(e);
- Components.utils.reportError(e);
- throw (e);
- }
- }
-
- Zotero.DB.commitTransaction();
-
- }
- catch (e) {
- Zotero.debug(e);
- Components.utils.reportError(e);
- throw (e);
- }
- ]]>
- </body>
+ }.bind(this));
+ ]]></body>
</method>
+ <!--
+ @return {Promise}
+ -->
<method name="swapNames">
<body><![CDATA[
+ return Zotero.Promise.try(function () {
var row = Zotero.getAncestorByTagName(document.popupNode, 'row');
var typeBox = row.getElementsByAttribute('popup', 'creator-type-menu')[0];
var creatorIndex = parseInt(typeBox.getAttribute('fieldname').split('-')[1]);
@@ -2157,16 +2058,20 @@
var firstName = fields.firstName;
fields.lastName = firstName;
fields.firstName = lastName;
- this.modifyCreator(creatorIndex, fields);
- this.item.save();
+ return this.modifyCreator(creatorIndex, fields);
+ }.bind(this));
]]></body>
</method>
+ <!--
+ @return {Promise}
+ -->
<method name="moveCreator">
<parameter name="index"/>
<parameter name="moveUp"/>
<body>
<![CDATA[
+ return Zotero.Promise.try(function () {
if (index == 0 && moveUp) {
Zotero.debug("Can't move up creator 0");
return;
@@ -2177,13 +2082,14 @@
}
var newIndex = moveUp ? index - 1 : index + 1;
- var creator = this.item.getCreator(index);
- var swapCreator = this.item.getCreator(newIndex);
- this.item.setCreator(newIndex, creator.ref, creator.creatorTypeID);
- this.item.setCreator(index, swapCreator.ref, swapCreator.creatorTypeID);
+ var a = this.item.getCreator(index);
+ var b = this.item.getCreator(newIndex);
+ this.item.setCreator(newIndex, a);
+ this.item.setCreator(index, b);
if (this.saveOnEdit) {
- this.item.save();
+ return this.item.save();
}
+ }.bind(this));
]]>
</body>
</method>
@@ -2207,24 +2113,6 @@
</body>
</method>
- <!--
- /*
- function modifyCreatorByID(index, creatorID, creatorTypeID) {
- throw ('Unimplemented');
- var oldCreator = _itemBeingEdited.getCreator(index);
- if (creator) {
- oldCreator = creator.ref;
- var oldCreatorID = oldCreator.creatorID;
- }
-
- Zotero.debug("Old creatorID is " + oldCreatorID);
-
- _itemBeingEdited.setCreator(index, firstName, lastName, typeID, fieldMode);
- _itemBeingEdited.save();
- }
- */
- -->
-
<method name="focusFirstField">
<body>
@@ -2329,14 +2217,14 @@
<method name="blurOpenField">
- <body>
- <![CDATA[
- var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
- if (textboxes && textboxes.length) {
- textboxes[0].inputField.blur();
- }
- ]]>
- </body>
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
+ if (textboxes && textboxes.length) {
+ yield this.blurHandler(textboxes[0].inputField);
+ }
+ }, this);
+ ]]></body>
</method>
@@ -2455,7 +2343,7 @@
var item = document.getBindingParent(this).item;
var exists = item.hasCreatorAt(index);
if (exists) {
- var fieldMode = item.getCreator(index).ref.fieldMode;
+ var fieldMode = item.getCreator(index).name !== undefined ? 1 : 0;
}
var hideTransforms = !exists || !!fieldMode;
return !hideTransforms;">
diff --git a/chrome/content/zotero/bindings/noteeditor.xml b/chrome/content/zotero/bindings/noteeditor.xml
@@ -98,11 +98,11 @@
</setter>
</property>
- <field name="_parent"/>
- <property name="parent" onget="return this._parent;">
+ <field name="_parentItem"/>
+ <property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
- this._parent = this._id('links').parent = val;
+ this._parentItem = this._id('links').parentItem = val;
]]>
</setter>
</property>
@@ -112,20 +112,22 @@
<field name="_item"/>
<property name="item" onget="return this._item;">
<setter>
- <![CDATA[
+ <![CDATA[
+ Zotero.spawn(function* () {
this._item = val;
// TODO: use clientDateModified instead
this._mtime = val.getField('dateModified');
- var parent = this.item.getSourceKey();
- if (parent) {
- this.parent = Zotero.Items.getByLibraryAndKey(this.item.libraryID, parent);
+ var parentKey = this.item.parentKey;
+ if (parentKey) {
+ this.parentItem = yield Zotero.Items.getByLibraryAndKey(this.item.libraryID, parentKey);
}
this._id('links').item = this.item;
- this.refresh();
- ]]>
+ yield this.refresh();
+ }, this);
+ ]]>
</setter>
</property>
@@ -155,109 +157,112 @@
<property name="value" onget="return this._id('noteField').value;" onset="this._id('noteField').value = val;"/>
<method name="refresh">
- <body>
- <![CDATA[
- Zotero.debug('Refreshing note editor');
-
- var textbox = this._id('noteField');
- var textboxReadOnly = this._id('noteFieldReadOnly');
- var button = this._id('goButton');
-
- if (this.editable) {
- textbox.hidden = false;
- textboxReadOnly.hidden = true;
- }
- else {
- textbox.hidden = true;
- textboxReadOnly.hidden = false;
- textbox = textboxReadOnly;
- }
-
- //var scrollPos = textbox.inputField.scrollTop;
- if (this.item) {
- textbox.value = this.item.getNote();
- }
- else {
- textbox.value = ''
- }
- //textbox.inputField.scrollTop = scrollPos;
-
- this._id('linksbox').hidden = !(this.displayTags && this.displayRelated);
-
- if (this.keyDownHandler) {
- textbox.setAttribute('onkeydown',
- 'document.getBindingParent(this).handleKeyDown(event)');
- }
- else {
- textbox.removeAttribute('onkeydown');
- }
-
- if (this.commandHandler) {
- textbox.setAttribute('oncommand',
- 'document.getBindingParent(this).commandHandler()');
- }
- else {
- textbox.removeAttribute('oncommand');
- }
-
- if (this.displayButton) {
- button.label = this.buttonCaption;
- button.hidden = false;
- button.setAttribute('oncommand',
- 'document.getBindingParent(this).clickHandler(this)');
- }
- else {
- button.hidden = true;
- }
- ]]>
- </body>
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ Zotero.debug('Refreshing note editor');
+
+ var textbox = this._id('noteField');
+ var textboxReadOnly = this._id('noteFieldReadOnly');
+ var button = this._id('goButton');
+
+ if (this.editable) {
+ textbox.hidden = false;
+ textboxReadOnly.hidden = true;
+ }
+ else {
+ textbox.hidden = true;
+ textboxReadOnly.hidden = false;
+ textbox = textboxReadOnly;
+ }
+
+ //var scrollPos = textbox.inputField.scrollTop;
+ if (this.item) {
+ yield this.item.loadNote();
+ textbox.value = this.item.getNote();
+ }
+ else {
+ textbox.value = '';
+ }
+ //textbox.inputField.scrollTop = scrollPos;
+
+ this._id('linksbox').hidden = !(this.displayTags && this.displayRelated);
+
+ if (this.keyDownHandler) {
+ textbox.setAttribute('onkeydown',
+ 'document.getBindingParent(this).handleKeyDown(event)');
+ }
+ else {
+ textbox.removeAttribute('onkeydown');
+ }
+
+ if (this.commandHandler) {
+ textbox.setAttribute('oncommand',
+ 'document.getBindingParent(this).commandHandler()');
+ }
+ else {
+ textbox.removeAttribute('oncommand');
+ }
+
+ if (this.displayButton) {
+ button.label = this.buttonCaption;
+ button.hidden = false;
+ button.setAttribute('oncommand',
+ 'document.getBindingParent(this).clickHandler(this)');
+ }
+ else {
+ button.hidden = true;
+ }
+ }, this);
+ ]]></body>
</method>
<method name="save">
<body>
<![CDATA[
- if (this._mode == 'view') {
- Zotero.debug("Not saving read-only note");
- return;
- }
-
- // Update note
- var noteField = this._id('noteField');
- if (this.item) {
- // If note is reselected automatically after save
- // from external note window, don't overwrite content
- //
- // TODO: use clientDateModified instead
- if (this.item.getField('dateModified') != this._mtime) {
- Zotero.debug("Note has already been changed", 4);
+ return Zotero.spawn(function* () {
+ if (this._mode == 'view') {
+ Zotero.debug("Not saving read-only note");
return;
}
- this.item.setNote(noteField.value);
- if (this.saveOnEdit) {
- this.item.save();
+ // Update note
+ var noteField = this._id('noteField');
+ if (this.item) {
+ // If note is reselected automatically after save
+ // from external note window, don't overwrite content
+ //
+ // TODO: use clientDateModified instead
+ if (this.item.getField('dateModified') != this._mtime) {
+ Zotero.debug("Note has already been changed", 4);
+ return;
+ }
+
+ let changed = this.item.setNote(noteField.value);
+ if (changed && this.saveOnEdit) {
+ yield this.item.save();
+ }
+ return;
}
- return;
- }
-
- // Create new note
- var item = new Zotero.Item('note');
- if (this.parent) {
- item.libraryID = this.parent.libraryID;
- }
- item.setNote(noteField.value);
- if (this.parent) {
- item.setSource(this.parent.id);
- }
- if (this.saveOnEdit) {
- var id = item.save();
- if (!this.parent && this.collection) {
- this.collection.addItem(id);
+ // Create new note
+ var item = new Zotero.Item('note');
+ if (this.parentItem) {
+ item.libraryID = this.parentItem.libraryID;
}
- }
-
- this.item = Zotero.Items.get(id);
+ item.setNote(noteField.value);
+ if (this.parentItem) {
+ item.parentKey = this.parentItem.key;
+ }
+ if (this.saveOnEdit) {
+ var id = yield item.save();
+
+ if (!this.parentItem && this.collection) {
+ this.collection.addItem(id);
+ }
+ }
+
+ this.item = yield Zotero.Items.getAsync(id);
+ }.bind(this));
]]>
</body>
</method>
@@ -378,11 +383,11 @@
]]>
</setter>
</property>
- <field name="_parent"/>
- <property name="parent" onget="return this._parent;">
+ <field name="_parentItem"/>
+ <property name="parentItem" onget="return this._parentItem;">
<setter>
<![CDATA[
- this._parent = val;
+ this._parentItem = val;
var parentText = this.id('parentText');
if (parentText.firstChild) {
@@ -392,25 +397,25 @@
if (this._parent && this.getAttribute('notitle') != '1') {
this.id('parent-row').hidden = undefined;
this.id('parentLabel').value = Zotero.getString('pane.item.parentItem');
- parentText.appendChild(document.createTextNode(this._parent.getDisplayTitle(true)));
+ parentText.appendChild(document.createTextNode(this._parentItem.getDisplayTitle(true)));
}
]]>
</setter>
</property>
<method name="tagsClick">
- <body>
- <![CDATA[
- this.id('tags').reload();
+ <body><![CDATA[
+ Zotero.spawn(function* () {
+ yield this.id('tags').reload();
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('tagsPopup').openPopupAtScreen(x, y, false);
- ]]>
- </body>
+ }, this);
+ ]]></body>
</method>
<method name="updateTagsSummary">
- <body>
- <![CDATA[
- var v = this.id('tags').summary;
+ <body><![CDATA[
+ Zotero.spawn(function* () {
+ var v = yield this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
@@ -419,13 +424,14 @@
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
- ]]>
- </body>
+ }, this);
+ ]]></body>
</method>
<method name="seeAlsoClick">
- <body>
- <![CDATA[
- var relatedList = this.item.relatedItemsBidirectional;
+ <body><![CDATA[
+ Zotero.spawn(function* () {
+ yield this.item.loadRelations();
+ var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
@@ -434,13 +440,13 @@
else {
this.id('seeAlso').add();
}
- ]]>
- </body>
+ }, this);
+ ]]></body>
</method>
<method name="updateSeeAlsoSummary">
- <body>
- <![CDATA[
- var v = this.id('seeAlso').summary;
+ <body><![CDATA[
+ Zotero.spawn(function* () {
+ var v = yield this.id('seeAlso').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
@@ -449,8 +455,8 @@
this.id('seeAlsoLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('seeAlsoClick').value = v;
- ]]>
- </body>
+ }, this)
+ ]]></body>
</method>
<method name="parentClick">
<body>
@@ -479,9 +485,11 @@
var zp = lastWin.ZoteroPane;
}
- var parentID = this.item.getSource();
- zp.clearQuicksearch();
- zp.selectItem(parentID);
+ Zotero.spawn(function* () {
+ var parentID = this.item.parentID;
+ yield zp.clearQuicksearch();
+ zp.selectItem(parentID);
+ }, this);
]]>
</body>
</method>
diff --git a/chrome/content/zotero/bindings/relatedbox.xml b/chrome/content/zotero/bindings/relatedbox.xml
@@ -73,27 +73,31 @@
</property>
<property name="summary">
<getter>
- <![CDATA[
+ <![CDATA[
+ return Zotero.spawn(function* () {
var r = "";
-
+
if (this.item) {
- var related = this.item.relatedItemsBidirectional;
+ yield this.item.loadRelations();
+ var related = this.item.relatedItems;
if (related) {
- related = Zotero.Items.get(related);
+ related = yield Zotero.Items.getAsync(related);
for(var i = 0; i < related.length; i++) {
r = r + related[i].getDisplayTitle() + ", ";
}
r = r.substr(0,r.length-2);
}
}
-
+
return r;
- ]]>
+ }, this);
+ ]]>
</getter>
</property>
<method name="reload">
<body>
- <![CDATA[
+ <![CDATA[
+ return Zotero.spawn(function* () {
var addButton = this.id('addButton');
addButton.hidden = !this.editable;
@@ -102,9 +106,10 @@
rows.removeChild(rows.firstChild);
if (this.item) {
- var related = this.item.relatedItemsBidirectional;
+ yield this.item.loadRelations();
+ var related = this.item.relatedItems;
if (related) {
- related = Zotero.Items.get(related);
+ related = yield Zotero.Items.getAsync(related);
for (var i = 0; i < related.length; i++) {
var icon= document.createElement("image");
icon.className = "zotero-box-icon";
@@ -169,12 +174,13 @@
this.updateCount();
}
}
- ]]>
+ }, this);
+ ]]>
</body>
</method>
<method name="add">
- <body>
- <![CDATA[
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
var io = {dataIn: null, dataOut: null};
window.openDialog('chrome://zotero/content/selectItemsDialog.xul', '',
@@ -182,7 +188,7 @@
if(io.dataOut) {
if (io.dataOut.length) {
- var relItem = Zotero.Items.get(io.dataOut[0]);
+ var relItem = yield Zotero.Items.getAsync(io.dataOut[0]);
if (relItem.libraryID != this.item.libraryID) {
// FIXME
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
@@ -194,27 +200,27 @@
for(var i = 0; i < io.dataOut.length; i++) {
this.item.addRelatedItem(io.dataOut[i]);
}
- this.item.save();
+ yield this.item.save();
}
- ]]>
- </body>
+ }, this);
+ ]]></body>
</method>
<method name="remove">
<parameter name="id"/>
- <body>
- <![CDATA[
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
if(id) {
// TODO: set attribute on reload to determine
// which of these is necessary
this.item.removeRelatedItem(id);
- this.item.save();
+ yield this.item.save();
- var item = Zotero.Items.get(id);
+ var item = yield Zotero.Items.getAsync(id);
item.removeRelatedItem(this.item.id);
- item.save();
+ yield item.save();
}
- ]]>
- </body>
+ });
+ ]]></body>
</method>
<method name="showItem">
<parameter name="id"/>
@@ -257,7 +263,7 @@
<body>
<![CDATA[
if (count == null) {
- var count = this.item.relatedItemsBidirectional.length;
+ var count = this.item.relatedItems.length;
}
var str = 'pane.item.related.count.';
diff --git a/chrome/content/zotero/bindings/styled-textbox.xml b/chrome/content/zotero/bindings/styled-textbox.xml
@@ -329,6 +329,8 @@
html = '<div style="'+bodyStyle+'"><p>'+html+"</p></div>";
}
+ Zotero.debug("SETTING CONTENT TO " + html);
+
this._editor.setContent(html);
return val;
]]></setter>
diff --git a/chrome/content/zotero/bindings/tagsbox.xml b/chrome/content/zotero/bindings/tagsbox.xml
@@ -90,26 +90,25 @@
<property name="count"/>
<property name="summary">
- <getter>
- <![CDATA[
+ <getter><![CDATA[
+ return Zotero.spawn(function* () {
var r = "";
-
- if(this.item)
- {
+
+ if (this.item) {
+ yield this.item.loadTags();
var tags = this.item.getTags();
- if(tags)
- {
+ if (tags) {
for(var i = 0; i < tags.length; i++)
{
- r = r + tags[i].name + ", ";
+ r = r + tags[i].tag + ", ";
}
r = r.substr(0,r.length-2);
}
}
return r;
- ]]>
- </getter>
+ }, this);
+ ]]></getter>
</property>
<constructor>
@@ -134,6 +133,7 @@
<parameter name="event"/>
<parameter name="type"/>
<parameter name="ids"/>
+ <parameter name="extraData"/>
<body>
<![CDATA[
if (type == 'setting') {
@@ -143,15 +143,16 @@
return;
}
else if (type == 'item-tag') {
- let itemID, tagID;
+ let itemID, tagName;
- for (var i=0; i<ids.length; i++) {
- [itemID, tagID] = ids[i].split('-');
+ for (let i=0; i<ids.length; i++) {
+ [itemID, tagName] = ids[i].match(/^([0-9]+)-(.+)/).slice(1);
if (!this.item || itemID != this.item.id) {
continue;
}
+
if (event == 'add') {
- var newTabIndex = this.add(tagID);
+ var newTabIndex = this.add(tagName);
if (newTabIndex == -1) {
return;
}
@@ -166,8 +167,15 @@
}
}
}
+ else if (event == 'modify') {
+ Zotero.debug("EXTRA");
+ Zotero.debug(extraData);
+ let oldTagName = extraData[tagName].old.tag;
+ this.remove(oldTagName);
+ this.add(tagName);
+ }
else if (event == 'remove') {
- var oldTabIndex = this.remove(tagID);
+ var oldTabIndex = this.remove(tagName);
if (oldTabIndex == -1) {
return;
}
@@ -197,53 +205,50 @@
<method name="reload">
- <body>
- <![CDATA[
- Zotero.debug('Reloading tags');
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ Zotero.debug('Reloading tags box');
+
+ yield this.item.loadTags();
// Cancel field focusing while we're updating
this._reloading = true;
this.id('addButton').hidden = !this.editable;
- var self = this;
- return Zotero.Tags.getColors(self.item.libraryID)
- .then(function (colors) {
- self._tagColors = colors;
-
- var rows = self.id('tagRows');
- while(rows.hasChildNodes()) {
- rows.removeChild(rows.firstChild);
- }
- var tags = self.item.getTags() || [];
- for (var i=0; i<tags.length; i++) {
- self.addDynamicRow(tags[i], i+1);
- }
- self.updateCount(tags.length);
-
- self._reloading = false;
- self._focusField();
- })
- .done();
- ]]>
- </body>
+ this._tagColors = yield Zotero.Tags.getColors(this.item.libraryID)
+
+ var rows = this.id('tagRows');
+ while(rows.hasChildNodes()) {
+ rows.removeChild(rows.firstChild);
+ }
+ var tags = this.item.getTags();
+
+ // Sort tags alphabetically
+ var collation = Zotero.getLocaleCollation();
+ tags.sort(function (a, b) collation.compareString(1, a.tag, b.tag));
+
+ for (let i=0; i<tags.length; i++) {
+ this.addDynamicRow(tags[i], i+1);
+ }
+ this.updateCount(tags.length);
+
+ this._reloading = false;
+ this._focusField();
+ }, this);
+ ]]></body>
</method>
<method name="addDynamicRow">
- <parameter name="tagObj"/>
+ <parameter name="tagData"/>
<parameter name="tabindex"/>
<parameter name="skipAppend"/>
<body>
<![CDATA[
- if (tagObj) {
- var tagID = tagObj.id;
- var name = tagObj.name;
- var type = tagObj.type;
- }
- if (!name) {
- name = '';
- }
+ var isNew = !tagData;
+ var name = tagData ? tagData.tag : "";
+ var type = tagData ? tagData.type : 0;
if (!tabindex) {
tabindex = this.id('tagRows').childNodes.length + 1;
@@ -265,13 +270,16 @@
}
var row = document.createElement("row");
+ if (isNew) {
+ row.setAttribute('isNew', true);
+ }
row.appendChild(icon);
row.appendChild(label);
if (this.editable) {
row.appendChild(remove);
}
- this.updateRow(row, tagObj);
+ this.updateRow(row, tagData);
if (!skipAppend) {
this.id('tagRows').appendChild(row);
@@ -289,12 +297,10 @@
-->
<method name="updateRow">
<parameter name="row"/>
- <parameter name="tagObj"/>
+ <parameter name="tagData"/>
<body><![CDATA[
- if (tagObj) {
- var tagID = tagObj.id;
- var type = tagObj.type;
- }
+ var tagName = tagData ? tagData.tag : "";
+ var tagType = (tagData && tagData.type) ? tagData.type : 0;
var icon = row.firstChild;
var label = row.firstChild.nextSibling;
@@ -303,17 +309,15 @@
}
// Row
- if (tagObj) {
- row.setAttribute('id', 'tag-' + tagID);
- row.setAttribute('tagType', type);
- }
+ row.setAttribute('tagName', tagName);
+ row.setAttribute('tagType', tagType);
// Icon
var iconFile = 'tag';
- if (type == 0) {
+ if (!tagData || tagType == 0) {
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.user'));
}
- else if (type == 1) {
+ else if (tagType == 1) {
iconFile += '-automatic';
icon.setAttribute('tooltiptext', Zotero.getString('pane.item.tags.icon.automatic'));
}
@@ -321,23 +325,24 @@
// "-" button
if (this.editable) {
- if (tagID) {
- remove.setAttribute('disabled', false);
- var self = this;
- remove.addEventListener('click', function () {
+ remove.setAttribute('disabled', false);
+ var self = this;
+ remove.addEventListener('click', function () {
+ Zotero.spawn(function* () {
self._lastTabIndex = false;
- document.getBindingParent(this).item.removeTag(tagID);
+ if (tagData) {
+ let item = document.getBindingParent(this).item
+ item.removeTag(tagName);
+ yield item.save()
+ }
// Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
- });
- }
- else {
- remove.setAttribute('disabled', true);
- }
+ }.bind(this));
+ });
}
]]></body>
</method>
@@ -362,7 +367,7 @@
if (event.button) {
return;
}
- document.getBindingParent(this).clickHandler(this);
+ document.getBindingParent(this).clickHandler(this, 1, valueText);
}, false);
valueElement.className += ' zotero-clicky';
}
@@ -402,8 +407,7 @@
<parameter name="elem"/>
<parameter name="rows"/>
<parameter name="value"/>
- <body>
- <![CDATA[
+ <body><![CDATA[
// Blur any active fields
/*
if (this._dynamicFields) {
@@ -416,10 +420,6 @@
var fieldName = 'tag';
var tabindex = elem.getAttribute('ztabindex');
- var tagID = elem.parentNode.getAttribute('id').split('-')[1];
- if (!value) {
- var value = tagID ? Zotero.Tags.getName(tagID) : '';
- }
var itemID = Zotero.getAncestorByTagName(elem, 'tagsbox').item.id;
var t = document.createElement("textbox");
@@ -466,114 +466,117 @@
t.select();
return t;
- ]]>
- </body>
+ ]]></body>
</method>
<method name="handleKeyPress">
<parameter name="event"/>
- <body>
- <![CDATA[
- var target = event.target;
- var focused = document.commandDispatcher.focusedElement;
-
- switch (event.keyCode) {
- case event.DOM_VK_RETURN:
- var multiline = target.getAttribute('multiline');
- var empty = target.value == "";
- if (event.shiftKey) {
- if (!multiline) {
- var self = this;
- setTimeout(function () {
- var val = target.value;
- if (val !== "") {
- val += "\n";
- }
- self.makeMultiline(target, val, 6);
- }, 0);
- return false;
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ var target = event.target;
+ var focused = document.commandDispatcher.focusedElement;
+
+ switch (event.keyCode) {
+ case event.DOM_VK_RETURN:
+ var multiline = target.getAttribute('multiline');
+ var empty = target.value == "";
+ if (event.shiftKey) {
+ if (!multiline) {
+ var self = this;
+ setTimeout(function () {
+ var val = target.value;
+ if (val !== "") {
+ val += "\n";
+ }
+ self.makeMultiline(target, val, 6);
+ }, 0);
+ return false;
+ }
+ // Submit
}
- // Submit
- }
- else if (multiline) {
- return true;
- }
-
- var fieldname = 'tag';
-
- // If last tag row, create new one
- var row = Zotero.getAncestorByTagName(target, 'row');
-
- // If non-empty last row, add new row
- if (row == row.parentNode.lastChild && !empty) {
- var focusField = true;
- this._tabDirection = 1;
- }
- // If empty non-last row, refocus current row
- else if (row != row.parentNode.lastChild && empty) {
- var focusField = true;
- }
- // If non-empty non-last row, return focus to items pane
- else {
- var focusField = false;
+ else if (multiline) {
+ return true;
+ }
+
+ var fieldname = 'tag';
+
+ var row = Zotero.getAncestorByTagName(target, 'row');
+
+ // If non-empty last row, add new row
+ if (row == row.parentNode.lastChild && !empty) {
+ var focusField = true;
+ this._tabDirection = 1;
+ }
+ // If empty non-last row, refocus current row
+ else if (row != row.parentNode.lastChild && empty) {
+ var focusField = true;
+ }
+ // If non-empty non-last row, return focus to items pane
+ else {
+ var focusField = false;
+ this._lastTabIndex = false;
+ }
+
+ target.onblur = null;
+ yield this.blurHandler(target);
+
+ if (focusField) {
+ Zotero.debug("FOCUSING FIELD");
+ this._focusField();
+ }
+ // Return focus to items pane
+ else {
+ Zotero.debug("FOCUSING ITEM PANE");
+ var tree = document.getElementById('zotero-items-tree');
+ if (tree) {
+ tree.focus();
+ }
+ }
+
+ return false;
+
+ case event.DOM_VK_ESCAPE:
+ // Reset field to original value
+ target.value = target.getAttribute('value');
+
+ var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
+
this._lastTabIndex = false;
- }
-
- focused.blur();
-
- if (focusField) {
- this._focusField();
- }
- // Return focus to items pane
- else {
+ target.onblur = null;
+ yield this.blurHandler(target);
+
+ if (tagsbox) {
+ tagsbox.closePopup();
+ }
+
+ // Return focus to items pane
var tree = document.getElementById('zotero-items-tree');
if (tree) {
tree.focus();
}
- }
-
- return false;
-
- case event.DOM_VK_ESCAPE:
- // Reset field to original value
- target.value = target.getAttribute('value');
-
- var tagsbox = Zotero.getAncestorByTagName(focused, 'tagsbox');
-
- this._lastTabIndex = false;
- focused.blur();
-
- if (tagsbox) {
- tagsbox.closePopup();
- }
-
- // Return focus to items pane
- var tree = document.getElementById('zotero-items-tree');
- if (tree) {
- tree.focus();
- }
-
- return false;
-
- case event.DOM_VK_TAB:
- // If already an empty last row, ignore forward tab
- if (target.value == "" && !event.shiftKey) {
- var row = Zotero.getAncestorByTagName(target, 'row');
- if (row == row.parentNode.lastChild) {
- return false;
+
+ return false;
+
+ case event.DOM_VK_TAB:
+ // If already an empty last row, ignore forward tab
+ if (target.value == "" && !event.shiftKey) {
+ var row = Zotero.getAncestorByTagName(target, 'row');
+ if (row == row.parentNode.lastChild) {
+ return false;
+ }
}
- }
-
- this._tabDirection = event.shiftKey ? -1 : 1;
- focused.blur();
- this._focusField();
- return false;
- }
-
- return true;
- ]]>
- </body>
+
+ this._tabDirection = event.shiftKey ? -1 : 1;
+ target.onblur = null;
+ yield this.blurHandler(target);
+ this._focusField();
+ return false;
+ }
+
+ return true;
+ }.bind(this));
+ ]]></body>
</method>
<!--
@@ -637,92 +640,94 @@
<method name="hideEditor">
<parameter name="textbox"/>
- <body>
- <![CDATA[
- Zotero.debug('Hiding editor');
-
- var fieldName = 'tag';
- var tabindex = textbox.getAttribute('ztabindex');
-
- var oldValue = textbox.getAttribute('value');
- textbox.value = textbox.value.trim();
- var value = textbox.value;
-
- var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
- if (!tagsbox)
- {
- Zotero.debug('Tagsbox not found', 1);
- return;
- }
-
- var row = textbox.parentNode;
- var rows = row.parentNode;
-
- // Tag id encoded as 'tag-1234'
- var oldTagID = row.getAttribute('id').split('-')[1];
-
- // Remove empty row at end
- if (!oldTagID && !value) {
- row.parentNode.removeChild(row);
- return;
- }
-
- // If row hasn't changed, change back to label
- if (oldValue == value) {
- this.textboxToLabel(textbox);
- return;
- }
-
- var tagArray = value.split(/\r\n?|\n/);
-
- // Modifying existing tag with a single new one
- if (oldTagID && tagArray.length < 2) {
- if (value) {
- tagsbox.replace(oldTagID, value);
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ Zotero.debug('Hiding editor');
+
+ var fieldName = 'tag';
+ var tabindex = textbox.getAttribute('ztabindex');
+
+ var oldValue = textbox.getAttribute('value');
+ var value = textbox.value = textbox.value.trim();
+
+ var tagsbox = Zotero.getAncestorByTagName(textbox, 'tagsbox');
+ if (!tagsbox)
+ {
+ Zotero.debug('Tagsbox not found', 1);
+ return;
}
- // Existing tag cleared
- else {
- this.item.removeTag(oldTagID);
+
+ var row = textbox.parentNode;
+ var rows = row.parentNode;
+
+ var isNew = row.getAttribute('isNew');
+
+ // Remove empty row at end
+ if (isNew && value === "") {
+ row.parentNode.removeChild(row);
+ return;
+ }
+
+ // If row hasn't changed, change back to label
+ if (oldValue == value) {
+ this.textboxToLabel(textbox);
+ return;
}
- }
- // Multiple tags
- else if (tagArray.length > 1) {
- var lastTag = row == row.parentNode.lastChild;
- Zotero.DB.beginTransaction();
+ var tags = value.split(/\r\n?|\n/).map(function (val) val.trim());
- if (oldTagID) {
- var oldValue = Zotero.Tags.getName(oldTagID);
- // If old tag isn't in array, remove it
- if (tagArray.indexOf(oldValue) == -1) {
- this.item.removeTag(oldTagID);
+ // Modifying existing tag with a single new one
+ if (!isNew && tags.length < 2) {
+ if (value !== "") {
+ if (oldValue !== value) {
+ // The existing textbox will be removed in notify()
+ this.item.replaceTag(oldValue, value);
+ yield this.item.save();
+ }
}
- // If old tag is staying, restore the textbox
- // immediately. This isn't strictly necessary, but it
- // makes the transition nicer.
+ // Existing tag cleared
else {
- textbox.value = textbox.getAttribute('value');
- this.textboxToLabel(textbox);
+ this.item.removeTag(oldValue);
+ yield this.item.save();
}
}
-
- this.item.addTags(tagArray);
-
- Zotero.DB.commitTransaction();
-
- if (lastTag) {
- this._lastTabIndex = this.item.getTags().length;
+ // Multiple tags
+ else if (tags.length > 1) {
+ var lastTag = row == row.parentNode.lastChild;
+
+ if (!isNew) {
+ // If old tag isn't in array, remove it
+ if (tags.indexOf(oldValue) == -1) {
+ this.item.removeTag(oldValue);
+ }
+ // If old tag is staying, restore the textbox
+ // immediately. This isn't strictly necessary, but it
+ // makes the transition nicer.
+ else {
+ textbox.value = textbox.getAttribute('value');
+ this.textboxToLabel(textbox);
+ }
+ }
+
+ this.item.addTags(tags);
+ yield this.item.save();
+
+ if (lastTag) {
+ this._lastTabIndex = this.item.getTags().length;
+ }
+
+ yield this.reload();
}
-
- this.reload();
- }
- // Single tag at end
- else {
- row.parentNode.removeChild(row);
- this.item.addTag(value);
- }
- ]]>
- </body>
+ // Single tag at end
+ else {
+ // Remove the textbox row. The new tag will be added in notify()
+ // if it doesn't already exist.
+ row.parentNode.removeChild(row);
+ this.item.addTag(value);
+ yield this.item.save();
+ }
+ }.bind(this));
+ ]]></body>
</method>
@@ -750,22 +755,31 @@
<method name="add">
- <parameter name="tagID"/>
+ <parameter name="tagName"/>
<body><![CDATA[
+ Zotero.debug("ADDING ROW WITH " + tagName);
+
var rowsElement = this.id('tagRows');
var rows = rowsElement.childNodes;
// Get this tag's existing row, if there is one
- var row = rowsElement.getElementsByAttribute('id', 'tag-' + tagID);
- row = row.length ? row[0] : false;
+ var row = false;
+ for (let i=0; i<rows.length; i++) {
+ if (rows[i].getAttribute('tagName') === tagName) {
+ Zotero.debug("FOUND ROW with " + tagName);
+ return rows[i].getAttribute('ztabindex');
+ }
+ }
- var tagObj = Zotero.Tags.get(tagID);
- var name = tagObj.name;
+ var tagData = {
+ tag: tagName,
+ type: this.item.getTagType(tagName)
+ };
if (row) {
// Update row and label
- this.updateRow(row, tagObj);
- var elem = this.createValueElement(name);
+ this.updateRow(row, tagData);
+ var elem = this.createValueElement(tagName);
// Remove the old row, which we'll reinsert at the correct place
rowsElement.removeChild(row);
@@ -779,7 +793,7 @@
}
else {
// Create new row, but don't insert it
- row = this.addDynamicRow(tagObj, false, true);
+ row = this.addDynamicRow(tagData, false, true);
var elem = row.getElementsByAttribute('fieldname', 'tag')[0];
}
@@ -797,7 +811,7 @@
continue;
}
- if (collation.compareString(1, name, labels[i].textContent) > 0) {
+ if (collation.compareString(1, tagName, labels[i].textContent) > 0) {
labels[i].setAttribute('ztabindex', index);
continue;
}
@@ -818,42 +832,22 @@
</method>
- <method name="replace">
- <parameter name="oldTagID"/>
- <parameter name="newTag"/>
- <body>
- <![CDATA[
- if(oldTagID && newTag)
- {
- var oldTag = Zotero.Tags.getName(oldTagID);
- if (oldTag!=newTag)
- {
- return this.item.replaceTag(oldTagID, newTag);
- }
- }
- return false;
- ]]>
- </body>
- </method>
-
-
<method name="remove">
- <parameter name="id"/>
+ <parameter name="tagName"/>
<body><![CDATA[
- var rowsElement = this.id('tagRows');
-
- var row = rowsElement.getElementsByAttribute('id', 'tag-' + id);
- row = row.length ? row[0] : false;
- if (!row) {
- return -1;
- }
+ Zotero.debug("REMOVING ROW WITH " + tagName);
+ var rowsElement = this.id('tagRows');
var rows = rowsElement.childNodes;
var removed = false;
var oldTabIndex = -1;
for (var i=0; i<rows.length; i++) {
- let tagID = rows[i].getAttribute('id').split('-')[1];
- if (tagID == id) {
+ let value = rows[i].getAttribute('tagName');
+ Zotero.debug("-=-=");
+ Zotero.debug(value);
+ Zotero.debug(tagName);
+ Zotero.debug(value === tagName);
+ if (value === tagName) {
oldTabIndex = i + 1;
removed = true;
rowsElement.removeChild(rows[i]);
@@ -861,7 +855,7 @@
continue;
}
// After the removal, update tab indexes
- if (removed && tagID) {
+ if (removed) {
var elem = rows[i].getElementsByAttribute('fieldname', 'tag')[0];
elem.setAttribute('ztabindex', i + 1);
}
@@ -1043,14 +1037,16 @@
<method name="blurOpenField">
- <body>
- <![CDATA[
- this._lastTabIndex = false;
-
- var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
- if (textboxes && textboxes.length) {
- textboxes[0].inputField.blur();
- }
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ this._lastTabIndex = false;
+
+ var textboxes = document.getAnonymousNodes(this)[0].getElementsByTagName('textbox');
+ if (textboxes && textboxes.length) {
+ textboxes[0].inputField.onblur = null;
+ yield this.blurHandler(textboxes[0].inputField);
+ }
+ }.bind(this));
]]>
</body>
</method>
diff --git a/chrome/content/zotero/bindings/tagselector.xml b/chrome/content/zotero/bindings/tagselector.xml
@@ -36,13 +36,17 @@
</resources>
<implementation>
+ <field name="collectionTreeRow"/>
+ <field name="updateScope"/>
+ <field name="selection"/>
+ <field name="onchange"/>
+
<field name="_initialized">false</field>
<field name="_notifierID">false</field>
<field name="_tags">null</field>
<field name="_dirty">null</field>
<field name="_emptyColored">null</field>
<field name="_emptyRegular">null</field>
- <field name="selection"/>
<!-- Modes are predefined settings groups for particular tasks -->
<field name="_mode">"view"</field>
@@ -118,9 +122,16 @@
<property name="scope" onget="return this._scope">
<setter>
<![CDATA[
- if (val && !Zotero.Utilities.isEmpty(val)) {
+ if (val.length) {
this._hasScope = true;
- this._scope = val;
+ this._scope = {};
+ for (let i=0; i<val.length; i++) {
+ let tag = val[i];
+ if (!this._scope[tag.tag]) {
+ this._scope[tag.tag] = [];
+ }
+ this._scope[tag.tag].push(tag.type);
+ }
}
else {
this._hasScope = false;
@@ -161,7 +172,7 @@
<![CDATA[
this._initialized = true;
this.selection = {};
- this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag', 'setting']);
+ this._notifierID = Zotero.Notifier.registerObserver(this, ['collection-item', 'item-tag', 'tag', 'setting'], 'tagSelector');
]]>
</body>
</method>
@@ -177,7 +188,9 @@
this._initialized = false;
this.unregister();
this.selection = {};
- this.doCommand();
+ if (this.onchange) {
+ this.onchange();
+ }
]]>
</body>
</method>
@@ -198,33 +211,29 @@
<parameter name="fetch"/>
<body>
<![CDATA[
- if (!this._initialized) {
- this.init();
- fetch = true;
- }
-
- Zotero.debug('Refreshing tags selector');
- var emptyColored = true;
- var emptyRegular = true;
- var tagsToggleBox = this.id('tags-toggle');
-
- var self = this;
- Zotero.Tags.getColors(this.libraryID)
- .then(function (tagColors) {
- if (fetch || self._dirty) {
- self._tags = Zotero.Tags.getAll(self._types, self.libraryID);
+ Zotero.spawn(function* () {
+ if (!this._initialized) {
+ this.init();
+ fetch = true;
+ }
+
+ Zotero.debug('Refreshing tags selector');
+ var emptyColored = true;
+ var emptyRegular = true;
+ var tagsToggleBox = this.id('tags-toggle');
+
+ var tagColors = yield Zotero.Tags.getColors(this.libraryID);
+ if (fetch || this._dirty) {
+ this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
// Remove children
tagsToggleBox.textContent = "";
// Sort by name
- var orderedTags = [];
var collation = Zotero.getLocaleCollation();
- for (let tagID in self._tags) {
- orderedTags.push(self._tags[tagID])
- }
+ var orderedTags = this._tags.concat();
orderedTags.sort(function(a, b) {
- return collation.compareString(1, a.name, b.name);
+ return collation.compareString(1, a.tag, b.tag);
});
var tagColorsLowerCase = {};
@@ -236,10 +245,8 @@
var positions = Object.keys(colorTags);
for (let i=positions.length-1; i>=0; i--) {
let name = colorTags[positions[i]];
- let ids = Zotero.Tags.getIDs(name, self.libraryID);
orderedTags.unshift({
- id: ids ? ids.join('-') : null,
- name: name,
+ tag: name,
type: 0,
hasColor: true
});
@@ -247,40 +254,41 @@
var lastTag;
for (let i=0; i<orderedTags.length; i++) {
- let tagObj = orderedTags[i];
+ let tagData = orderedTags[i];
// Skip colored tags in the regular section,
// since we add them to the beginning above
- if (!tagObj.hasColor && tagColorsLowerCase[tagObj.name.toLowerCase()]) {
+ if (!tagData.hasColor && tagColorsLowerCase[tagData.tag.toLowerCase()]) {
continue;
}
- let tagButton = self._makeClickableTag(orderedTags[i], lastTag, self.editable);
+ // Only show tags of different types once
+ if (tagData.tag === lastTag) {
+ continue;
+ }
+ lastTag = tagData.tag;
+
+ let tagButton = this._makeClickableTag(tagData, this.editable);
if (tagButton) {
+ var self = this;
tagButton.addEventListener('click', function(event) {
self.handleTagClick(event, this);
});
- if (self.editable) {
- tagButton.addEventListener('dragover', self.dragObserver.onDragOver);
- tagButton.addEventListener('dragexit', self.dragObserver.onDragExit);
- tagButton.addEventListener('drop', self.dragObserver.onDrop, true);
+ if (this.editable) {
+ tagButton.addEventListener('dragover', this.dragObserver.onDragOver);
+ tagButton.addEventListener('dragexit', this.dragObserver.onDragExit);
+ tagButton.addEventListener('drop', this.dragObserver.onDrop, true);
}
- lastTag = tagButton;
tagsToggleBox.appendChild(tagButton);
}
}
- self._dirty = false;
+ this._dirty = false;
}
- var searchTags = self._search ? Zotero.Tags.search(self._search) : {};
-
// Set attributes
var colorTags = {};
var labels = tagsToggleBox.getElementsByTagName('label');
for (let i=0; i<labels.length; i++) {
- var tagIDs = labels[i].getAttribute('tagID');
- tagIDs = tagIDs ? tagIDs.split('-') : [];
-
let name = labels[i].value;
let lcname = name.toLowerCase();
@@ -295,7 +303,7 @@
}
// Restore selection
- if (self.selection[name]){
+ if (this.selection[name]){
labels[i].setAttribute('selected', 'true');
}
else {
@@ -303,39 +311,21 @@
}
// Check tags against search
- if (self._search) {
- var inSearch = false;
- if (tagIDs.length) {
- for (let i=0; i<tagIDs.length; i++) {
- if (searchTags[tagIDs[i]]) {
- inSearch = true;
- break;
- }
- }
- }
- // For colored tags, compare by name
- else if (lcname.indexOf(self._search) != -1) {
- inSearch = true;
- }
+ if (this._search) {
+ var inSearch = lcname.indexOf(this._search) != -1;
}
// Check tags against scope
- if (self._hasScope) {
- var inScope = false;
- for (let i=0; i<tagIDs.length; i++) {
- if (self._scope[tagIDs[i]]) {
- inScope = true;
- break;
- }
- }
+ if (this._hasScope) {
+ var inScope = !!this._scope[name];
}
// If not in search, hide
- if (self._search && !inSearch) {
+ if (this._search && !inSearch) {
labels[i].setAttribute('hidden', true);
}
- else if (self.filterToScope) {
- if (self._hasScope && inScope) {
+ else if (this.filterToScope) {
+ if (this._hasScope && inScope) {
labels[i].className = 'zotero-clicky';
labels[i].setAttribute('inScope', true);
labels[i].setAttribute('hidden', false);
@@ -349,7 +339,7 @@
}
// Display all
else {
- if (self._hasScope && inScope) {
+ if (this._hasScope && inScope) {
labels[i].className = 'zotero-clicky';
labels[i].setAttribute('inScope', true);
}
@@ -364,7 +354,7 @@
// Always show colored tags at top, unless they
// don't match an active tag search
- if (colorData && (!self._search || inSearch)) {
+ if (colorData && (!this._search || inSearch)) {
labels[i].setAttribute('hidden', false);
labels[i].setAttribute('hasColor', true);
emptyColored = false;
@@ -389,7 +379,7 @@
//replace getLinkedItems() with function that gets linked items within the current collection
- var linked = self._tags[tagIDs[0]].getLinkedItems();
+ var linked = this._tags[tagIDs[0]].getLinkedItems();
numlinked.push(parseInt(linked.length));
}
@@ -422,7 +412,7 @@
//replace getLinkedItems() with function that gets linked items within the current collection
- var linked = self._tags[tagIDs[0]].getLinkedItems();
+ var linked = this._tags[tagIDs[0]].getLinkedItems();
numlink = linked.length;
@@ -443,19 +433,21 @@
//end tag cloud code
- self.updateNumSelected();
- self._emptyColored = emptyColored;
- self._emptyRegular = emptyRegular;
+ this.updateNumSelected();
+ this._emptyColored = emptyColored;
+ this._emptyRegular = emptyRegular;
var empty = emptyColored && emptyRegular;
- self.id('tags-toggle').setAttribute('collapsed', empty);
- self.id('no-tags-box').setAttribute('collapsed', !empty);
+ this.id('tags-toggle').setAttribute('collapsed', empty);
+ this.id('no-tags-box').setAttribute('collapsed', !empty);
- if (self.onRefresh) {
- self.onRefresh();
- self.onRefresh = null;
+ if (this.onRefresh) {
+ this.onRefresh();
+ this.onRefresh = null;
}
- })
- .done();
+
+ // Clear "Loading tags…" after the first load
+ this.id('no-tags-deck').selectedIndex = 1;
+ }, this);
]]>
</body>
</method>
@@ -501,80 +493,81 @@
<parameter name="event"/>
<parameter name="type"/>
<parameter name="ids"/>
- <body>
- <![CDATA[
- if (type == 'setting') {
- if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
- this.refresh(true);
- }
- return;
- }
-
- var itemGroup = ZoteroPane_Local.getItemGroup();
-
- // Ignore anything other than deletes in duplicates view
- if (itemGroup.isDuplicates()) {
- switch (event) {
- case 'delete':
- case 'trash':
- break;
-
- default:
- return;
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
+ if (type == 'setting') {
+ if (ids.some(function (val) val.split("/")[1] == 'tagColors')) {
+ this.refresh(true);
+ }
+ return;
}
- }
-
- // If a selected tag no longer exists, deselect it
- if (event == 'delete') {
- this._tags = Zotero.Tags.getAll(this._types, this.libraryID);
- for (var tag in this.selection) {
- for each(var tag2 in this._tags) {
- if (tag == tag2) {
- var found = true;
+ // Ignore anything other than deletes in duplicates view
+ if (this.collectionTreeRow.isDuplicates()) {
+ switch (event) {
+ case 'delete':
+ case 'trash':
break;
- }
+
+ default:
+ return;
}
- if (!found) {
- delete this.selection[tag];
+ }
+
+ var selectionChanged = false;
+
+ // If a selected tag no longer exists, deselect it
+ if (event == 'delete' || event == 'modify') {
+ // TODO: necessary, or just use notifier value?
+ this._tags = yield Zotero.Tags.getAll(this.libraryID, this._types);
+
+ for (var tag in this.selection) {
+ for each(var tag2 in this._tags) {
+ if (tag == tag2) {
+ var found = true;
+ break;
+ }
+ }
+ if (!found) {
+ delete this.selection[tag];
+ selectionChanged = true;
+ }
}
}
- }
-
- if(this._notified) return;
-
- var me = this;
- window.setTimeout(function() {
- me._notified = false;
// This could be more optimized to insert new/changed tags at the appropriate
// spot if we cared, but we probably don't
- var t = me.id('tags-search').inputField;
+ var t = this.id('tags-search').inputField;
if (t.value) {
- me.setSearch(t.value, true);
+ this.setSearch(t.value, true);
}
else {
- me.setSearch(false, true);
+ this.setSearch(false, true);
}
- me._dirty = true;
+ this._dirty = true;
// This is a hack, but set this to run after the refresh,
// since _emptyRegular isn't set until then
- me.onRefresh = function () {
+ this.onRefresh = function () {
// If no regular tags visible after a delete, deselect all.
// This is necessary so that a selected tag that's removed
// from its last item doesn't cause all regular tags to
// disappear without anything being visibly selected.
if ((event == 'remove' || event == 'delete') &&
- me._emptyRegular && me.getNumSelected()) {
+ this._emptyRegular && this.getNumSelected()) {
Zotero.debug('No tags visible after delete -- deselecting all');
- me.clearAll();
+ return this.clearAll();
}
- };
+ }.bind(this);
+
+ // If the selection changed, update the items list
+ if (selectionChanged && this.onchange) {
+ return this.onchange();
+ }
- me.doCommand();
- }, 0);
- this._notified = true;
+ // Otherwise, just update the tag selector
+ return this.updateScope();
+ }, this);
]]>
</body>
</method>
@@ -600,8 +593,8 @@
<method name="clearVisible">
- <body>
- <![CDATA[
+ <body><![CDATA[
+ return Zotero.spawn(function* () {
var tagsToggleBox = this.id('tags-toggle');
var labels = Zotero.Utilities.xpath(tagsToggleBox, 'label[@selected="true"]');
@@ -611,19 +604,19 @@
delete this.selection[label.value];
}
- this.doCommand();
- ]]>
- </body>
+ if (this.onchange) {
+ this.onchange();
+ }
+ }, this);
+ ]]></body>
</method>
<method name="clearAll">
- <body>
- <![CDATA[
- this.selection = {};
- this.clearVisible();
- ]]>
- </body>
+ <body><![CDATA[
+ this.selection = {};
+ return this.clearVisible();
+ ]]></body>
</method>
@@ -682,9 +675,11 @@
label.setAttribute('selected', 'true');
}
- this.doCommand();
-
this.updateNumSelected();
+
+ if (this.onchange) {
+ this.onchange();
+ }
]]>
</body>
</method>
@@ -692,76 +687,45 @@
<method name="rename">
<parameter name="oldName"/>
- <body>
- <![CDATA[
- var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
-
- var newName = { value: oldName };
- var result = promptService.prompt(window,
- Zotero.getString('pane.tagSelector.rename.title'),
- Zotero.getString('pane.tagSelector.rename.message'),
- newName, '', {});
-
- if (!result || !newName.value || oldName == newName.value) {
- return;
- }
-
- // Get current tagIDs with the old name
- var tagIDs = Zotero.Tags.getIDs(oldName, this.libraryID) || [];
- if (tagIDs.length) {
+ <body><![CDATA[
+ Zotero.spawn(function* () {
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+
+ var newName = { value: oldName };
+ var result = promptService.prompt(window,
+ Zotero.getString('pane.tagSelector.rename.title'),
+ Zotero.getString('pane.tagSelector.rename.message'),
+ newName, '', {});
+
+ if (!result || !newName.value || oldName == newName.value) {
+ return;
+ }
+
if (this.selection[oldName]) {
var wasSelected = true;
delete this.selection[oldName];
}
- // TODO: redo transaction for async DB
- var promises = [];
- Zotero.DB.beginTransaction();
-
- for (var i=0; i<tagIDs.length; i++) {
- promises.push(Zotero.Tags.rename(tagIDs[i], newName.value));
+ yield Zotero.Tags.load(this.libraryID);
+ if (Zotero.Tags.getID(this.libraryID, oldName)) {
+ yield Zotero.Tags.rename(this.libraryID, oldName, newName.value);
+ }
+ // Colored tags don't need to exist, so in that case
+ // just rename the color setting
+ else {
+ let color = yield Zotero.Tags.getColor(this.libraryID, oldName);
+ if (!color) {
+ throw new Error("Can't rename missing tag");
+ }
+ yield Zotero.Tags.setColor(this.libraryID, oldName, false);
+ yield Zotero.Tags.setColor(this.libraryID, newName, color);
}
if (wasSelected) {
this.selection[newName.value] = true;
}
- Zotero.DB.commitTransaction();
- Q.all(promises)
- .done();
- }
- // Colored tags don't need to exist, so in that case
- // just rename the color setting
- else {
- var self = this;
- Zotero.Tags.getColor(this.libraryID, oldName)
- .then(function (color) {
- if (color) {
- if (self.selection[oldName]) {
- var wasSelected = true;
- delete self.selection[oldName];
- }
-
- return Zotero.Tags.setColor(
- self.libraryID, oldName, false
- )
- .then(function () {
- return Zotero.Tags.setColor(
- self.libraryID, newName, color
- )
- .then(function () {
- if (wasSelected) {
- self.selection[newName.value] = true;
- }
- });
- });
- }
- else {
- throw new Error("Can't rename missing tag");
- }
- })
- .done();
- }
+ }.bind(this));
]]>
</body>
</method>
@@ -778,30 +742,21 @@
Zotero.getString('pane.tagSelector.delete.title'),
Zotero.getString('pane.tagSelector.delete.message'));
- if (confirmed) {
- Zotero.DB.beginTransaction();
-
- // Add other ids with same tag
- var ids = Zotero.Tags.getIDs(name, this.libraryID);
- var tagIDs = [];
- for each(var id in ids) {
- if (tagIDs.indexOf(id) == -1) {
- tagIDs.push(id);
- }
- }
-
- if (tagIDs.length) {
- Zotero.Tags.erase(tagIDs);
- Zotero.Tags.purge(tagIDs);
- }
-
- Zotero.DB.commitTransaction()
-
- // If only a tag color setting, remove that
- if (!tagIDs.length) {
- Zotero.Tags.setColor(this.libraryID, name, false);
+ if (!confirmed) {
+ return;
+ }
+
+ return Zotero.DB.executeTransaction(function* () {
+ yield Zotero.Tags.load(this.libraryID);
+ var tagID = Zotero.Tags.getID(this.libraryID, name);
+ if (tagID) {
+ yield Zotero.Tags.erase(this.libraryID, tagID);
}
-
+ }.bind(this));
+
+ // If only a tag color setting, remove that
+ if (!tagID) {
+ Zotero.Tags.setColor(this.libraryID, name, false);
}
]]>
</body>
@@ -812,7 +767,7 @@
<body>
<![CDATA[
tagIDs = tagIDs.split('-');
- var name = Zotero.Tags.getName(tagIDs[0]);
+ var name = Zotero.Tags.getName(this.libraryID, tagIDs[0]);
return Zotero.Tags.getColor(this.libraryID, name)
.then(function (colorData) {
return colorData ? colorData.color : '#000000';
@@ -824,25 +779,15 @@
<method name="_makeClickableTag">
<parameter name="tagObj"/>
- <parameter name="lastTag"/>
<parameter name="editable"/>
<body>
<![CDATA[
- var tagID = tagObj.id, tagName = tagObj.name, tagType = tagObj.type;
- // If the last tag was the same, add this tagID and tagType to it
- if(lastTag && lastTag.value === tagName) {
- lastTag.setAttribute('tagID', lastTag.getAttribute('tagID') + '-' + tagID);
- lastTag.setAttribute('tagType', lastTag.getAttribute('tagType') + '-' + tagType);
- return false;
- }
+ var tagName = tagObj.tag;
+ var tagType = tagObj.type;
var label = document.createElement('label');
label.setAttribute('value', tagName);
- // Not used for color tags
- if (tagID) {
- label.setAttribute('tagID', tagID);
- }
label.setAttribute('tagType', tagType);
if (editable) {
label.setAttribute('context', 'tag-menu');
@@ -872,20 +817,24 @@
return;
}
- window.openDialog(
- 'chrome://zotero/content/tagColorChooser.xul',
- "zotero-tagSelector-colorChooser",
- "chrome,modal,centerscreen", io
- );
-
- // Dialog cancel
- if (typeof io.color == 'undefined') {
- return;
- }
-
- return Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
- })
- .done();
+ // Opening a modal window directly from within this promise handler causes
+ // the opened window to block on the first yielded promise until the window
+ // is closed.
+ setTimeout(function () {
+ window.openDialog(
+ 'chrome://zotero/content/tagColorChooser.xul',
+ "zotero-tagSelector-colorChooser",
+ "chrome,modal,centerscreen", io
+ );
+
+ // Dialog cancel
+ if (typeof io.color == 'undefined') {
+ return;
+ }
+
+ Zotero.Tags.setColor(self.libraryID, io.name, io.color, io.position);
+ }, 0);
+ });
]]>
</body>
</method>
@@ -931,7 +880,7 @@
}
- this.onDrop = function (event) {
+ this.onDrop = Zotero.Promise.method(function (event) {
var node = event.target;
node.setAttribute('draggedOver', false);
@@ -941,39 +890,18 @@
return;
}
- Zotero.DB.beginTransaction();
-
- ids = ids.split(',');
- var items = Zotero.Items.get(ids);
-
- // Find a manual tag if there is one
- var tagID = null;
- var tagIDs = node.getAttribute('tagID');
- tagIDs = tagIDs ? node.getAttribute('tagID').split(/\-/) : [];
- var tagTypes = node.getAttribute('tagType').split(/\-/);
- for (var i=0; i<tagIDs.length; i++) {
- if (tagTypes[i] == 0) {
- tagID = Zotero.Tags.get(tagIDs[i]).id
- break;
- }
- }
-
- // Otherwise use value
- if (!tagID) {
- var value = node.getAttribute('value');
- }
-
- for each(var item in items) {
- if (tagID) {
- item.addTagByID(tagID);
- }
- else {
+ return Zotero.DB.executeTransaction(function* () {
+ ids = ids.split(',');
+ var items = Zotero.Items.get(ids);
+ var value = node.getAttribute('value')
+
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
item.addTag(value);
+ yield item.save();
}
- }
-
- Zotero.DB.commitTransaction();
- }
+ }.bind(this));
+ });
]]>
</body>
</method>
@@ -1001,7 +929,10 @@
</menupopup>
<vbox id="no-tags-box" align="center" pack="center" flex="1">
- <label value="&zotero.tagSelector.noTagsToDisplay;"/>
+ <deck id="no-tags-deck">
+ <label value="&zotero.tagSelector.loadingTags;"/>
+ <label value="&zotero.tagSelector.noTagsToDisplay;"/>
+ </deck>
</vbox>
<vbox id="tags-toggle" flex="1"/>
diff --git a/chrome/content/zotero/bindings/zoterosearch.xml b/chrome/content/zotero/bindings/zoterosearch.xml
@@ -177,8 +177,7 @@
this.onLibraryChange(libraryID);
}
- // TODO: libraryIDInt
- this.searchRef.libraryID = libraryID ? libraryID : null;
+ this.searchRef.libraryID = libraryID;
]]></body>
</method>
diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js
@@ -110,7 +110,7 @@ var Zotero_Browser = new function() {
if (!Zotero || Zotero.skipLoading) {
// Zotero either failed to load or is reloading in Connector mode
// In case of the latter, listen for the 'zotero-loaded' event (once) and retry
- var zoteroInitDone_deferred = Q.defer();
+ var zoteroInitDone_deferred = Zotero.Promise.defer();
var obs = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
var observer = {
@@ -123,14 +123,14 @@ var Zotero_Browser = new function() {
zoteroInitDone = zoteroInitDone_deferred.promise;
} else {
- zoteroInitDone = Q();
+ zoteroInitDone = Zotero.Promise.resolve();
}
- var chromeLoaded = Q.defer();
+ var chromeLoaded = Zotero.Promise.defer();
window.addEventListener("load", function(e) { chromeLoaded.resolve() }, false);
// Wait for Zotero to init and chrome to load before proceeding
- Q.all([
+ Zotero.Promise.all([
zoteroInitDone.then(function() {
ZoteroPane_Local.addReloadListener(reload);
reload();
@@ -139,8 +139,7 @@ var Zotero_Browser = new function() {
])
.then(function() {
Zotero_Browser.chromeLoad()
- })
- .done();
+ });
}
/**
diff --git a/chrome/content/zotero/downloadOverlay.js b/chrome/content/zotero/downloadOverlay.js
@@ -63,7 +63,7 @@ var Zotero_DownloadOverlay = new function() {
.getMostRecentWindow("navigator:browser");
var libraryID, collection;
try {
- if(win.ZoteroPane.getItemGroup().filesEditable) {
+ if(win.ZoteroPane.getCollectionTreeRow().filesEditable) {
libraryID = win.ZoteroPane.getSelectedLibraryID();
collection = win.ZoteroPane.getSelectedCollection();
}
@@ -147,7 +147,7 @@ var Zotero_DownloadOverlay = new function() {
var zoteroSelected = document.getElementById('zotero-radio').selected;
var zp = Zotero.getActiveZoteroPane(), canSave = true;
try {
- canSave = zp.getItemGroup().filesEditable;
+ canSave = zp.getCollectionTreeRow().filesEditable;
} catch(e) {
Zotero.logError(e);
};
diff --git a/chrome/content/zotero/duplicatesMerge.js b/chrome/content/zotero/duplicatesMerge.js
@@ -145,7 +145,7 @@ var Zotero_Duplicates_Pane = new function () {
this.merge = function () {
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
- Zotero.ItemGroupCache.clear();
+ Zotero.CollectionTreeCache.clear();
Zotero.Items.merge(itembox.item, _otherItems);
}
}
diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js
@@ -210,7 +210,7 @@ var Zotero_File_Interface = new function() {
}
var translation = new Zotero.Translate.Import();
- (file ? Q(file) : translation.getTranslators().then(function(translators) {
+ (file ? Zotero.Promise.resolve(file) : translation.getTranslators().then(function(translators) {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
diff --git a/chrome/content/zotero/integration/addCitationDialog.js b/chrome/content/zotero/integration/addCitationDialog.js
@@ -141,9 +141,9 @@ var Zotero_Citation_Dialog = new function () {
// If we're in a different library, switch libraries
var id = io.citation.citationItems[0].id;
- var itemGroup = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
+ var collectionTreeRow = collectionsView.selectedTreeRow;
var item = Zotero.Items.get(id);
- if(item.libraryID != itemGroup.ref.libraryID) {
+ if(item.libraryID != collectionTreeRow.ref.libraryID) {
collectionsView.selectLibrary(item.libraryID);
}
var selected = itemsView.selectItem(id);
diff --git a/chrome/content/zotero/itemPane.js b/chrome/content/zotero/itemPane.js
@@ -50,7 +50,7 @@ var ZoteroItemPane = new function() {
/*
* Load a top-level item
*/
- this.viewItem = function (item, mode, index) {
+ this.viewItem = Zotero.Promise.coroutine(function* (item, mode, index) {
if (!index) {
index = 0;
}
@@ -77,7 +77,7 @@ var ZoteroItemPane = new function() {
switch (index) {
case 0:
case 2:
- box.blurOpenField();
+ yield box.blurOpenField();
// DEBUG: Currently broken
//box.scrollToTop();
break;
@@ -94,10 +94,13 @@ var ZoteroItemPane = new function() {
_notesList.removeChild(_notesList.firstChild);
}
- var notes = Zotero.Items.get(item.getNotes());
+ yield item.loadChildItems();
+ let notes = yield Zotero.Items.getAsync(item.getNotes());
if (notes.length) {
- for(var i = 0; i < notes.length; i++) {
+ 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";
@@ -105,7 +108,7 @@ var ZoteroItemPane = new function() {
var label = document.createElement('label');
label.className = "zotero-box-label";
- var title = Zotero.Notes.noteToTitle(notes[i].getNote());
+ var title = note.getNoteTitle();
title = title ? title : Zotero.getString('pane.item.notes.untitled');
label.setAttribute('value', title);
label.setAttribute('flex','1'); //so that the long names will flex smaller
@@ -144,12 +147,14 @@ var ZoteroItemPane = new function() {
else {
box.mode = 'edit';
}
+
+ yield [item.loadItemData(), item.loadCreators()];
box.item = item;
- }
+ });
this.addNote = function (popup) {
- ZoteroPane_Local.newNote(popup, _lastItem.id);
+ ZoteroPane_Local.newNote(popup, _lastItem.key);
}
diff --git a/chrome/content/zotero/locateMenu.js b/chrome/content/zotero/locateMenu.js
@@ -143,14 +143,14 @@ var Zotero_LocateMenu = new function() {
* @param {Boolean} showIcons Whether menu items should have associated icons
* @param {Boolean} addExtraOptions Whether to add options that start with "_" below the separator
*/
- function _addViewOptions(locateMenu, selectedItems, showIcons, addExtraOptions) {
+ var _addViewOptions = Zotero.Promise.coroutine(function* (locateMenu, selectedItems, showIcons, addExtraOptions) {
var optionsToShow = {};
// check which view options are available
for each(var item in selectedItems) {
for(var viewOption in ViewOptions) {
if(!optionsToShow[viewOption]) {
- optionsToShow[viewOption] = ViewOptions[viewOption].canHandleItem(item);
+ optionsToShow[viewOption] = yield ViewOptions[viewOption].canHandleItem(item);
}
}
}
@@ -178,7 +178,7 @@ var Zotero_LocateMenu = new function() {
ViewOptions[viewOption], showIcons), lastNode);
}
}
- }
+ });
/**
* Get available locate engines that can handle a set of items
@@ -343,26 +343,29 @@ var Zotero_LocateMenu = new function() {
ViewOptions.pdf = new function() {
this.icon = "chrome://zotero/skin/treeitem-attachment-pdf.png";
this._mimeTypes = ["application/pdf"];
- this.canHandleItem = function(item) !!_getFirstAttachmentWithMIMEType(item, this._mimeTypes);
- this.handleItems = function(items, event) {
+ this.canHandleItem = function (item) {
+ return _getFirstAttachmentWithMIMEType(item, this._mimeTypes).then((item) => !!item);
+ }
+
+ this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var attachments = [];
for each(var item in items) {
- var attachment = _getFirstAttachmentWithMIMEType(item, this._mimeTypes);
+ var attachment = yield _getFirstAttachmentWithMIMEType(item, this._mimeTypes);
if(attachment) attachments.push(attachment.id);
}
ZoteroPane_Local.viewAttachment(attachments, event);
- }
+ });
- function _getFirstAttachmentWithMIMEType(item, mimeTypes) {
- var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments()));
+ var _getFirstAttachmentWithMIMEType = Zotero.Promise.coroutine(function* (item, mimeTypes) {
+ var attachments = (item.isAttachment() ? [item] : (yield item.getBestAttachments()));
for each(var attachment in attachments) {
- if(mimeTypes.indexOf(attachment.attachmentMIMEType) !== -1
+ if (mimeTypes.indexOf(attachment.attachmentContentType) !== -1
&& attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) return attachment;
}
return false;
- }
+ });
};
/**
@@ -372,14 +375,16 @@ var Zotero_LocateMenu = new function() {
*/
ViewOptions.online = new function() {
this.icon = "chrome://zotero/skin/locate-view-online.png";
- this.canHandleItem = function(item) _getURL(item) !== false;
- this.handleItems = function(items, event) {
- var urls = [_getURL(item) for each(item in items)];
- ZoteroPane_Local.loadURI([url for each(url in urls) if(url)], event);
+ this.canHandleItem = function (item) {
+ return _getURL(item).then((val) => val !== false);
}
+ this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
+ var urls = yield [_getURL(item) for each(item in items)];
+ ZoteroPane_Local.loadURI([url for each(url in urls) if(url)], event);
+ });
- function _getURL(item) {
+ var _getURL = Zotero.Promise.coroutine(function* (item) {
// try url field for item and for attachments
var urlField = item.getField('url');
if(urlField) {
@@ -391,6 +396,7 @@ 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
@@ -412,7 +418,7 @@ var Zotero_LocateMenu = new function() {
}
return false;
- }
+ });
};
/**
@@ -436,29 +442,32 @@ var Zotero_LocateMenu = new function() {
*/
ViewOptions.file = new function() {
this.icon = "chrome://zotero/skin/treeitem-attachment-file.png";
- this.canHandleItem = function(item) !!_getFile(item);
- this.handleItems = function(items, event) {
+ this.canHandleItem = function (item) {
+ return _getFile(item).then((item) => !!item);
+ }
+
+ this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var attachments = [];
for each(var item in items) {
- var attachment = _getFile(item);
+ var attachment = yield _getFile(item);
if(attachment) attachments.push(attachment.id);
}
ZoteroPane_Local.viewAttachment(attachments, event);
- }
+ });
- function _getFile(item) {
- var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments()));
+ var _getFile = Zotero.Promise.coroutine(function* (item) {
+ var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments());
for each(var attachment in attachments) {
- if(!ViewOptions.snapshot.canHandleItem(attachment)
- && !ViewOptions.pdf.canHandleItem(attachment)
+ if (!(yield ViewOptions.snapshot.canHandleItem(attachment))
+ && !(yield ViewOptions.pdf.canHandleItem(attachment))
&& attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) {
return attachment;
}
}
return false;
- }
+ });
};
/**
@@ -471,28 +480,28 @@ var Zotero_LocateMenu = new function() {
this.icon = "chrome://zotero/skin/locate-external-viewer.png";
this.useExternalViewer = true;
- this.canHandleItem = function(item) {
+ this.canHandleItem = Zotero.Promise.coroutine(function* (item) {
return (this.useExternalViewer ^ Zotero.Prefs.get('launchNonNativeFiles'))
- && _getBestNonNativeAttachment(item);
- }
+ && (yield _getBestNonNativeAttachment(item));
+ });
- this.handleItems = function(items, event) {
+ this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
var attachments = [];
for each(var item in items) {
- var attachment = _getBestNonNativeAttachment(item);
+ var attachment = yield _getBestNonNativeAttachment(item);
if(attachment) attachments.push(attachment.id);
}
ZoteroPane_Local.viewAttachment(attachments, event, false, this.useExternalViewer);
- }
+ });
- function _getBestNonNativeAttachment(item) {
- var attachments = (item.isAttachment() ? [item] : Zotero.Items.get(item.getBestAttachments()));
+ var _getBestNonNativeAttachment = Zotero.Promise.coroutine(function* (item) {
+ var attachments = item.isAttachment() ? [item] : (yield item.getBestAttachments());
for each(var attachment in attachments) {
if(attachment.attachmentLinkMode !== Zotero.Attachments.LINK_MODE_LINKED_URL) {
- var file = attachment.getFile();
- if(file) {
- var ext = Zotero.File.getExtension(file);
+ var path = yield attachment.getFilePath();
+ if (path) {
+ var ext = Zotero.File.getExtension(Zotero.File.pathToFile(path));
if(!attachment.attachmentMIMEType ||
Zotero.MIME.hasNativeHandler(attachment.attachmentMIMEType, ext) ||
!Zotero.MIME.hasInternalHandler(attachment.attachmentMIMEType, ext)) {
@@ -503,7 +512,7 @@ var Zotero_LocateMenu = new function() {
}
}
return false;
- }
+ });
};
/**
@@ -529,27 +538,27 @@ var Zotero_LocateMenu = new function() {
this.icon = "chrome://zotero/skin/locate-show-file.png";
this.useExternalViewer = true;
- this.canHandleItem = function(item) {
- return !!_getBestFile(item);
+ this.canHandleItem = function (item) {
+ return _getBestFile(item).then(function (item) !!item);
}
- this.handleItems = function(items, event) {
+ this.handleItems = Zotero.Promise.coroutine(function* (items, event) {
for each(var item in items) {
- var attachment = _getBestFile(item);
+ var attachment = yield _getBestFile(item);
if(attachment) {
ZoteroPane_Local.showAttachmentInFilesystem(attachment.id);
}
}
- }
+ });
- function _getBestFile(item) {
+ var _getBestFile = Zotero.Promise.coroutine(function* (item) {
if(item.isAttachment()) {
if(item.attachmentLinkMode === Zotero.Attachments.LINK_MODE_LINKED_URL) return false;
return item;
} else {
- return Zotero.Items.get(item.getBestAttachment());
+ return yield item.getBestAttachment();
}
- }
+ });
};
/**
@@ -559,8 +568,8 @@ var Zotero_LocateMenu = new function() {
*/
ViewOptions._libraryLookup = new function() {
this.icon = "chrome://zotero/skin/locate-library-lookup.png";
- this.canHandleItem = function(item) item.isRegularItem();
- this.handleItems = function(items, event) {
+ this.canHandleItem = function (item) Zotero.Promise.resolve(item.isRegularItem());
+ this.handleItems = Zotero.Promise.method(function (items, event) {
var urls = [];
for each(var item in items) {
if(!item.isRegularItem()) continue;
@@ -568,6 +577,6 @@ var Zotero_LocateMenu = new function() {
if(url) urls.push(url);
}
ZoteroPane_Local.loadURI(urls, event);
- }
+ });
};
}
\ No newline at end of file
diff --git a/chrome/content/zotero/note.js b/chrome/content/zotero/note.js
@@ -27,6 +27,13 @@ var noteEditor;
var notifierUnregisterID;
function onLoad() {
+ Zotero.spawn(function* () {
+ Zotero.debug('=-=-=');
+ var bar = yield Zotero.Promise.delay(1000).return('DONE');
+ Zotero.debug(bar);
+ Zotero.debug('-----');
+ });
+
noteEditor = document.getElementById('zotero-note-editor');
noteEditor.mode = 'edit';
noteEditor.focus();
@@ -39,38 +46,40 @@ function onLoad() {
}
var itemID = io.itemID;
var collectionID = io.collectionID;
- var parentItemID = io.parentItemID;
+ var parentItemKey = io.parentItemKey;
- if (itemID) {
- var ref = Zotero.Items.get(itemID);
-
- var clearUndo = noteEditor.item ? noteEditor.item.id != ref.id : false;
-
- noteEditor.item = ref;
-
- // If loading new or different note, disable undo while we repopulate the text field
- // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
- // undo content from another note into the current one.
- if (clearUndo) {
- noteEditor.clearUndo();
- }
-
- document.title = ref.getNoteTitle();
- }
- else {
- if (parentItemID) {
- var ref = Zotero.Items.get(parentItemID);
- noteEditor.parent = ref;
+ return Zotero.spawn(function* () {
+ if (itemID) {
+ var ref = yield Zotero.Items.getAsync(itemID);
+
+ var clearUndo = noteEditor.item ? noteEditor.item.id != ref.id : false;
+
+ noteEditor.item = ref;
+
+ // If loading new or different note, disable undo while we repopulate the text field
+ // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
+ // undo content from another note into the current one.
+ if (clearUndo) {
+ noteEditor.clearUndo();
+ }
+
+ document.title = ref.getNoteTitle();
}
else {
- if (collectionID && collectionID != '' && collectionID != 'undefined') {
- noteEditor.collection = Zotero.Collections.get(collectionID);
+ if (parentItemKey) {
+ var ref = Zotero.Items.getByLibraryAndKey(parentItemKey);
+ noteEditor.parentItem = ref;
}
+ else {
+ if (collectionID && collectionID != '' && collectionID != 'undefined') {
+ noteEditor.collection = Zotero.Collections.get(collectionID);
+ }
+ }
+ noteEditor.refresh();
}
- noteEditor.refresh();
- }
-
- notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item');
+
+ notifierUnregisterID = Zotero.Notifier.registerObserver(NotifyCallback, 'item');
+ });
}
function onUnload()
diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js
@@ -60,7 +60,7 @@ var ZoteroOverlay = new function()
var self = this;
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
if (!Zotero || Zotero.skipLoading) {
throw true;
}
@@ -227,12 +227,17 @@ var ZoteroOverlay = new function()
* the foreground.
*/
this.toggleDisplay = function(makeVisible, dontRefocus)
- {
+ {
if (!Zotero || Zotero.skipLoading) {
ZoteroPane.displayStartupError();
return;
}
+ // Don't do anything if pane is already showing
+ if (makeVisible && ZoteroPane.isShowing()) {
+ return;
+ }
+
if(makeVisible || makeVisible === undefined) {
if(Zotero.isConnector) {
// If in connector mode, bring Zotero Standalone to foreground
diff --git a/chrome/content/zotero/preferences/preferences_export.js b/chrome/content/zotero/preferences/preferences_export.js
@@ -181,7 +181,7 @@ Zotero_Preferences.Export = {
},
- refreshQuickCopySiteList: function () {
+ refreshQuickCopySiteList: Zotero.Promise.coroutine(function* () {
var treechildren = document.getElementById('quickCopy-siteSettings-rows');
while (treechildren.hasChildNodes()) {
treechildren.removeChild(treechildren.firstChild);
@@ -189,37 +189,31 @@ Zotero_Preferences.Export = {
var sql = "SELECT key AS domainPath, value AS format FROM settings "
+ "WHERE setting='quickCopySite' ORDER BY domainPath COLLATE NOCASE";
- var siteData = Zotero.DB.query(sql);
-
- if (!siteData) {
- return;
- }
-
- Q.async(function () {
- for (var i=0; i<siteData.length; i++) {
- let treeitem = document.createElement('treeitem');
- let treerow = document.createElement('treerow');
- let domainCell = document.createElement('treecell');
- let formatCell = document.createElement('treecell');
- let HTMLCell = document.createElement('treecell');
-
- domainCell.setAttribute('label', siteData[i].domainPath);
+ var siteData = yield Zotero.DB.queryAsync(sql);
+
+ for (var i=0; i<siteData.length; i++) {
+ let treeitem = document.createElement('treeitem');
+ let treerow = document.createElement('treerow');
+ let domainCell = document.createElement('treecell');
+ let formatCell = document.createElement('treecell');
+ let HTMLCell = document.createElement('treecell');
+
+ domainCell.setAttribute('label', siteData[i].domainPath);
+
+ yield Zotero.QuickCopy.getFormattedNameFromSetting(siteData[i].format)
+ .then(function (formatted) {
+ formatCell.setAttribute('label', formatted);
+ var copyAsHTML = Zotero.QuickCopy.getContentType(siteData[i].format) == 'html';
+ HTMLCell.setAttribute('label', copyAsHTML ? ' ✓ ' : '');
- yield Zotero.QuickCopy.getFormattedNameFromSetting(siteData[i].format)
- .then(function (formatted) {
- formatCell.setAttribute('label', formatted);
- var copyAsHTML = Zotero.QuickCopy.getContentType(siteData[i].format) == 'html';
- HTMLCell.setAttribute('label', copyAsHTML ? ' ✓ ' : '');
-
- treerow.appendChild(domainCell);
- treerow.appendChild(formatCell);
- treerow.appendChild(HTMLCell);
- treeitem.appendChild(treerow);
- treechildren.appendChild(treeitem);
- });
- }
- })().done();
- },
+ treerow.appendChild(domainCell);
+ treerow.appendChild(formatCell);
+ treerow.appendChild(HTMLCell);
+ treeitem.appendChild(treerow);
+ treechildren.appendChild(treeitem);
+ });
+ }
+ }),
deleteSelectedQuickCopySite: function () {
diff --git a/chrome/content/zotero/preferences/preferences_search.js b/chrome/content/zotero/preferences/preferences_search.js
@@ -430,8 +430,8 @@ Zotero_Preferences.Search = {
},
- updateIndexStats: function () {
- var stats = Zotero.Fulltext.getIndexStats();
+ updateIndexStats: Zotero.Promise.coroutine(function* () {
+ var stats = yield Zotero.Fulltext.getIndexStats();
document.getElementById('fulltext-stats-indexed').
lastChild.setAttribute('value', stats.indexed);
document.getElementById('fulltext-stats-partial').
@@ -440,10 +440,10 @@ Zotero_Preferences.Search = {
lastChild.setAttribute('value', stats.unindexed);
document.getElementById('fulltext-stats-words').
lastChild.setAttribute('value', stats.words);
- },
+ }),
- rebuildIndexPrompt: function () {
+ rebuildIndexPrompt: Zotero.Promise.coroutine(function* () {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
@@ -462,17 +462,17 @@ Zotero_Preferences.Search = {
null, {});
if (index == 0) {
- Zotero.Fulltext.rebuildIndex();
+ yield Zotero.Fulltext.rebuildIndex();
}
else if (index == 2) {
- Zotero.Fulltext.rebuildIndex(true)
+ yield Zotero.Fulltext.rebuildIndex(true)
}
- this.updateIndexStats();
- },
+ yield this.updateIndexStats();
+ }),
- clearIndexPrompt: function () {
+ clearIndexPrompt: Zotero.Promise.coroutine(function* () {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
@@ -490,12 +490,12 @@ Zotero_Preferences.Search = {
Zotero.getString('zotero.preferences.search.clearNonLinkedURLs'), null, {});
if (index == 0) {
- Zotero.Fulltext.clearIndex();
+ yield Zotero.Fulltext.clearIndex();
}
else if (index == 2) {
- Zotero.Fulltext.clearIndex(true);
+ yield Zotero.Fulltext.clearIndex(true);
}
- this.updateIndexStats();
- }
+ yield this.updateIndexStats();
+ })
};
diff --git a/chrome/content/zotero/recognizePDF.js b/chrome/content/zotero/recognizePDF.js
@@ -32,7 +32,6 @@
* @namespace
*/
var Zotero_RecognizePDF = new function() {
- Components.utils.import("resource://zotero/q.js");
var _progressWindow, _progressIndicator;
/**
@@ -40,8 +39,9 @@ var Zotero_RecognizePDF = new function() {
* @returns {Boolean} True if the PDF can be recognized, false if it cannot be
*/
this.canRecognize = function(/**Zotero.Item*/ item) {
- return (item.attachmentMIMEType &&
- item.attachmentMIMEType == "application/pdf" && !item.getSource());
+ return item.attachmentMIMEType
+ && item.attachmentMIMEType == "application/pdf"
+ && item.isTopLevelItem();
}
/**
@@ -110,12 +110,12 @@ var Zotero_RecognizePDF = new function() {
translate.setSearch({"itemType":"book", "ISBN":isbns[0]});
promise = _promiseTranslate(translate, libraryID);
} else {
- promise = Q.reject("No ISBN or DOI found");
+ promise = Zotero.Promise.reject("No ISBN or DOI found");
}
}
// If no DOI or ISBN, query Google Scholar
- return promise.fail(function(error) {
+ return promise.catch(function(error) {
Zotero.debug("RecognizePDF: "+error);
return me.GSFullTextSearch.findItem(lines, libraryID, stopCheckCallback);
});
@@ -183,7 +183,7 @@ var Zotero_RecognizePDF = new function() {
* @return {Promise}
*/
function _promiseTranslate(translate, libraryID) {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
translate.setHandler("select", function(translate, items, callback) {
for(var i in items) {
var obj = {};
@@ -337,8 +337,6 @@ var Zotero_RecognizePDF = new function() {
* @private
*/
"_recognizeItem": function() {
- Components.utils.import("resource://zotero/q.js");
-
const SUCCESS_IMAGE = "chrome://zotero/skin/tick.png";
const FAILURE_IMAGE = "chrome://zotero/skin/cross.png";
const LOADING_IMAGE = "chrome://global/skin/icons/loading_16.png";
@@ -383,7 +381,7 @@ var Zotero_RecognizePDF = new function() {
}
// put old item as a child of the new item
- item.setSource(newItem.id);
+ item.parentID = newItem.id;
item.save();
itemTitle.setAttribute("label", newItem.getField("title"));
diff --git a/chrome/content/zotero/selectItemsDialog.js b/chrome/content/zotero/selectItemsDialog.js
@@ -66,7 +66,7 @@ function onCollectionSelected()
if(collectionsView.selection.count == 1 && collectionsView.selection.currentIndex != -1)
{
- var collection = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
+ var collection = collectionsView.getRow(collectionsView.selection.currentIndex);
collection.setSearch('');
try {
diff --git a/chrome/content/zotero/standalone/standalone.js b/chrome/content/zotero/standalone/standalone.js
@@ -33,11 +33,11 @@ const ZoteroStandalone = new function() {
* Run when standalone window first opens
*/
this.onLoad = function() {
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
if(!Zotero) {
throw true;
}
- if(Zotero.initializationPromise.isPending()) {
+ if(Zotero.initializationZotero.Promise.isPending()) {
Zotero.showZoteroPaneProgressMeter();
}
return Zotero.initializationPromise;
diff --git a/chrome/content/zotero/tagColorChooser.js b/chrome/content/zotero/tagColorChooser.js
@@ -28,37 +28,37 @@ var _io;
var Zotero_Tag_Color_Chooser = new function() {
this.init = function () {
- // Set font size from pref
- Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
-
- if (window.arguments && window.arguments.length) {
- _io = window.arguments[0];
- if (_io.wrappedJSObject) _io = _io.wrappedJSObject;
- }
- if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
- if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
-
- window.sizeToContent();
-
var dialog = document.getElementById('tag-color-chooser');
- var colorPicker = document.getElementById('color-picker');
- var tagPosition = document.getElementById('tag-position');
-
- colorPicker.setAttribute('cols', 3);
- colorPicker.setAttribute('tileWidth', 24);
- colorPicker.setAttribute('tileHeight', 24);
- colorPicker.colors = [
- '#990000', '#CC9933', '#FF9900',
- '#FFCC00', '#007439', '#1049A9',
- '#9999FF', '#CC66CC', '#993399'
- ];
- var maxTags = document.getElementById('max-tags');
- maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
-
- var self = this;
- Zotero.Tags.getColors(_io.libraryID)
- .then(function (tagColors) {
+ return Zotero.spawn(function* () {
+ // Set font size from pref
+ Zotero.setFontSize(document.getElementById("tag-color-chooser-container"));
+
+ if (window.arguments && window.arguments.length) {
+ _io = window.arguments[0];
+ if (_io.wrappedJSObject) _io = _io.wrappedJSObject;
+ }
+ if (typeof _io.libraryID == 'undefined') throw new Error("libraryID not set");
+ if (typeof _io.name == 'undefined' || _io.name === "") throw new Error("name not set");
+
+ window.sizeToContent();
+
+ var colorPicker = document.getElementById('color-picker');
+ var tagPosition = document.getElementById('tag-position');
+
+ colorPicker.setAttribute('cols', 3);
+ colorPicker.setAttribute('tileWidth', 24);
+ colorPicker.setAttribute('tileHeight', 24);
+ colorPicker.colors = [
+ '#990000', '#CC9933', '#FF9900',
+ '#FFCC00', '#007439', '#1049A9',
+ '#9999FF', '#CC66CC', '#993399'
+ ];
+
+ var maxTags = document.getElementById('max-tags');
+ maxTags.value = Zotero.getString('tagColorChooser.maxTags', Zotero.Tags.MAX_COLORED_TAGS);
+
+ var tagColors = yield Zotero.Tags.getColors(_io.libraryID);
var colorData = tagColors[_io.name];
// Color
@@ -72,6 +72,7 @@ var Zotero_Tag_Color_Chooser = new function() {
for (var i in tagColors) {
usedColors.push(tagColors[i].color);
}
+
var unusedColors = Zotero.Utilities.arrayDiff(
colorPicker.colors, usedColors
);
@@ -103,15 +104,16 @@ var Zotero_Tag_Color_Chooser = new function() {
tagPosition.selectedIndex = 0;
}
- self.onPositionChange();
+ this.onPositionChange();
window.sizeToContent();
- })
+ }.bind(this))
.catch(function (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
- dialog.cancelDialog();
- })
- .done();
+ if (dialog.cancelDialog) {
+ dialog.cancelDialog();
+ }
+ });
};
diff --git a/chrome/content/zotero/tagColorChooser.xul b/chrome/content/zotero/tagColorChooser.xul
@@ -41,7 +41,7 @@
height="140">
<script src="include.js"/>
- <script src="tagColorChooser.js" type="text/javascript;version=1.8"/>
+ <script src="tagColorChooser.js"/>
<vbox id="tag-color-chooser-container">
<hbox align="center">
diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js
@@ -1,26 +1,26 @@
/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2009 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
*/
Zotero.Attachments = new function(){
@@ -30,24 +30,13 @@ Zotero.Attachments = new function(){
this.LINK_MODE_LINKED_URL = 3;
this.BASE_PATH_PLACEHOLDER = 'attachments:';
- this.importFromFile = importFromFile;
- this.linkFromFile = linkFromFile;
- this.importSnapshotFromFile = importSnapshotFromFile;
- this.importFromURL = importFromURL;
- this.linkFromURL = linkFromURL;
- this.linkFromDocument = linkFromDocument;
- this.importFromDocument = importFromDocument;
- this.createMissingAttachment = createMissingAttachment;
- this.getFileBaseNameFromItem = getFileBaseNameFromItem;
- this.createDirectoryForItem = createDirectoryForItem;
- this.createDirectoryForMissingItem = createDirectoryForMissingItem;
- this.getStorageDirectory = getStorageDirectory;
- this.getPath = getPath;
-
var self = this;
- function importFromFile(file, sourceItemID, libraryID) {
+ /**
+ * @return {Promise}
+ */
+ this.importFromFile = Zotero.Promise.coroutine(function* (file, parentItemID, libraryID) {
Zotero.debug('Importing attachment from file');
var newName = Zotero.File.getValidFileName(file.leafName);
@@ -56,116 +45,128 @@ Zotero.Attachments = new function(){
throw ("'" + file.leafName + "' must be a file in Zotero.Attachments.importFromFile()");
}
- Zotero.DB.beginTransaction();
-
- try {
+ var itemID, newFile;
+ yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
- if (sourceItemID) {
- var parentItem = Zotero.Items.get(sourceItemID);
- attachmentItem.libraryID = parentItem.libraryID;
+ if (parentItemID) {
+ let [parentLibraryID, parentKey] = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
+ attachmentItem.libraryID = parentLibraryID;
}
else if (libraryID) {
attachmentItem.libraryID = libraryID;
}
attachmentItem.setField('title', newName);
- attachmentItem.setSource(sourceItemID);
+ attachmentItem.parentID = parentItemID;
attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_FILE;
- var itemID = attachmentItem.save();
- attachmentItem = Zotero.Items.get(itemID);
+ itemID = yield attachmentItem.save();
+ attachmentItem = yield Zotero.Items.getAsync(itemID);
// Create directory for attachment files within storage directory
- var destDir = this.createDirectoryForItem(itemID);
+ var destDir = yield this.createDirectoryForItem(attachmentItem);
// Point to copied file
- var newFile = destDir.clone();
+ newFile = destDir.clone();
newFile.append(newName);
// Copy file to unique filename, which automatically shortens long filenames
newFile = Zotero.File.copyToUnique(file, newFile);
- var mimeType = Zotero.MIME.getMIMETypeFromFile(newFile);
+ var contentType = yield Zotero.MIME.getMIMETypeFromFile(newFile);
- attachmentItem.attachmentMIMEType = mimeType;
+ attachmentItem.attachmentContentType = contentType;
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_FILE);
- attachmentItem.save();
-
- Zotero.DB.commitTransaction();
+ yield attachmentItem.save();
// Determine charset and build fulltext index
- _postProcessFile(itemID, newFile, mimeType);
- }
- catch (e){
- // hmph
- Zotero.DB.rollbackTransaction();
-
+ yield _postProcessFile(itemID, newFile, contentType);
+ }.bind(this))
+ .catch(function (e) {
var msg = "Failed importing file " + file.path;
Components.utils.reportError(msg);
Zotero.debug(msg, 1);
+ // Clean up
try {
- // Clean up
- if (itemID) {
- var itemDir = this.getStorageDirectory(itemID);
- if (itemDir.exists()) {
- itemDir.remove(true);
- }
+ if (destDir && destDir.exists()) {
+ destDir.remove(true);
}
}
- catch (e) {}
+ catch (e) {
+ Zotero.debug(e, 1);
+ }
- throw (e);
- }
+ throw e;
+ }.bind(this));
return itemID;
- }
+ });
- function linkFromFile(file, sourceItemID){
+ /**
+ * @return {Promise}
+ */
+ this.linkFromFile = Zotero.Promise.coroutine(function* (file, parentItemID) {
Zotero.debug('Linking attachment from file');
var title = file.leafName;
- var mimeType = Zotero.MIME.getMIMETypeFromFile(file);
-
- var itemID = _addToDB(file, null, title, this.LINK_MODE_LINKED_FILE, mimeType,
- null, sourceItemID);
-
- // Determine charset and build fulltext index
- _postProcessFile(itemID, file, mimeType);
-
- return itemID;
- }
+ var contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
+
+ return Zotero.DB.executeTransaction(function* () {
+ var itemID = yield _addToDB({
+ file: file,
+ title: title,
+ linkMode: this.LINK_MODE_LINKED_FILE,
+ contentType: contentType,
+ parentItemID: parentItemID
+ });
+
+ // Determine charset and build fulltext index
+ yield _postProcessFile(itemID, file, contentType);
+
+ return itemID;
+ }.bind(this));
+ });
- function importSnapshotFromFile(file, url, title, mimeType, charset, sourceItemID){
+ /**
+ * @param {Object} options - 'file', 'url', 'title', 'contentType', 'charset', 'parentItemID'
+ * @return {Promise}
+ */
+ this.importSnapshotFromFile = Zotero.Promise.coroutine(function* (options) {
Zotero.debug('Importing snapshot from file');
- var charsetID = charset ? Zotero.CharacterSets.getID(charset) : null;
+ var file = options.file;
+ var url = options.url;
+ var title = options.title;
+ var contentType = options.contentType;
+ var charset = options.charset;
+ var parentItemID = options.parentItemID;
- Zotero.DB.beginTransaction();
+ if (!parentItemID) {
+ throw new Error("parentItemID not provided");
+ }
- try {
+ var destDir;
+ yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
var attachmentItem = new Zotero.Item('attachment');
- if (sourceItemID) {
- var parentItem = Zotero.Items.get(sourceItemID);
- attachmentItem.libraryID = parentItem.libraryID;
- }
+ let [libraryID, parentKey] = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
+ attachmentItem.libraryID = libraryID;
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
- attachmentItem.setSource(sourceItemID);
+ attachmentItem.parentID = parentItemID;
attachmentItem.attachmentLinkMode = this.LINK_MODE_IMPORTED_URL;
- attachmentItem.attachmentMIMEType = mimeType;
+ attachmentItem.attachmentContentType = contentType;
attachmentItem.attachmentCharset = charset;
// DEBUG: this should probably insert access date too so as to
// create a proper item, but at the moment this is only called by
// translate.js, which sets the metadata fields itself
- var itemID = attachmentItem.save();
- attachmentItem = Zotero.Items.get(itemID)
+ var itemID = yield attachmentItem.save();
+ attachmentItem = yield Zotero.Items.getAsync(itemID)
- var storageDir = Zotero.getStorageDirectory();
- var destDir = this.getStorageDirectory(itemID);
- _moveOrphanedDirectory(destDir);
+ destDir = this.getStorageDirectory(attachmentItem);
+ yield _moveOrphanedDirectory(destDir);
file.parent.copyTo(storageDir, destDir.leafName);
// Point to copied file
@@ -173,254 +174,255 @@ Zotero.Attachments = new function(){
newFile.append(file.leafName);
attachmentItem.attachmentPath = this.getPath(newFile, this.LINK_MODE_IMPORTED_URL);
- attachmentItem.save();
-
- Zotero.DB.commitTransaction();
+ yield attachmentItem.save();
// Determine charset and build fulltext index
- _postProcessFile(itemID, newFile, mimeType);
- }
- catch (e){
- Zotero.DB.rollbackTransaction();
+ yield _postProcessFile(itemID, newFile, contentType);
+ }.bind(this))
+ .catch(function (e) {
+ Zotero.debug(e, 1);
+ // Clean up
try {
- // Clean up
- if (itemID) {
- var itemDir = this.getStorageDirectory(itemID);
- if (itemDir.exists()) {
- itemDir.remove(true);
- }
+ if (destDir && destDir.exists()) {
+ destDir.remove(true);
}
}
- catch (e) {}
+ catch (e) {
+ Zotero.debug(e, 1);
+ }
- throw (e);
- }
+ throw e;
+ }.bind(this));
+
return itemID;
- }
+ });
- function importFromURL(url, sourceItemID, forceTitle, forceFileBaseName, parentCollectionIDs,
- mimeType, libraryID, callback, cookieSandbox) {
+ /**
+ * @param {Object} options - 'url', 'parentItemID', 'parentCollectionIDs', 'title',
+ * 'fileBaseName', 'contentType', 'cookieSandbox'
+ * @return {Promise<Zotero.Item>} - A promise for the created attachment item
+ */
+ this.importFromURL = Zotero.Promise.coroutine(function* (options) {
Zotero.debug('Importing attachment from URL');
- if (sourceItemID && parentCollectionIDs) {
- var msg = "parentCollectionIDs is ignored when sourceItemID is set in Zotero.Attachments.importFromURL()";
+ var libraryID = options.libraryID;
+ var url = options.url;
+ var parentItemID = options.parentItemID;
+ var parentCollectionIDs = options.parentCollectionIDs;
+ var title = options.title;
+ var fileBaseName = options.forceFileBaseName;
+ var contentType = options.contentType;
+ var cookieSandbox = options.cookieSandbox;
+
+ if (parentItemID && parentCollectionIDs) {
+ let msg = "parentCollectionIDs is ignored when parentItemID is set in Zotero.Attachments.importFromURL()";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
parentCollectionIDs = undefined;
}
// Throw error on invalid URLs
- //
+ //
// TODO: allow other schemes
var urlRe = /^https?:\/\/[^\s]*$/;
var matches = urlRe.exec(url);
if (!matches) {
- if(callback) callback(false);
- throw ("Invalid URL '" + url + "' in Zotero.Attachments.importFromURL()");
+ Components.utils.reportError("Invalid URL '" + url + "' in Zotero.Attachments.importFromURL()");
+ return false;
}
// Save using a hidden browser
var nativeHandlerImport = function () {
- var browser = Zotero.HTTP.processDocuments(url, function() {
- var importCallback = function (item) {
- Zotero.Browser.deleteHiddenBrowser(browser);
- if(callback) callback(item);
- };
- Zotero.Attachments.importFromDocument(browser.contentDocument,
- sourceItemID, forceTitle, parentCollectionIDs, importCallback, libraryID);
- }, undefined, undefined, true, cookieSandbox);
+ var deferred = Zotero.Promise.defer();
+ var browser = Zotero.HTTP.processDocuments(
+ url,
+ function() {
+ return Zotero.Attachments.importFromDocument({
+ libraryID: libraryID,
+ document: browser.contentDocument,
+ parentItemID: parentItemID,
+ title: title,
+ parentCollectionIDs: parentCollectionIDs
+ })
+ .then(function (attachmentItem) {
+ Zotero.Browser.deleteHiddenBrowser(browser);
+ deferred.resolve(attachmentItem);
+ });
+ },
+ undefined,
+ undefined,
+ true,
+ cookieSandbox
+ );
+ return deferred.promise;
};
// Save using remote web browser persist
- var externalHandlerImport = function (mimeType) {
+ var externalHandlerImport = Zotero.Promise.coroutine(function* (contentType) {
if (forceFileBaseName) {
- var ext = _getExtensionFromURL(url, mimeType);
+ let ext = _getExtensionFromURL(url, contentType);
var fileName = forceFileBaseName + (ext != '' ? '.' + ext : '');
}
else {
- var fileName = _getFileNameFromURL(url, mimeType);
+ var fileName = _getFileNameFromURL(url, contentType);
}
- var title = forceTitle ? forceTitle : fileName;
-
const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
- var wbp = Components
- .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ var wbp = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(nsIWBP);
wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
if(cookieSandbox) cookieSandbox.attachToInterfaceRequestor(wbp);
var encodingFlags = false;
- Zotero.DB.beginTransaction();
+ // Create a temporary directory to save to within the storage directory.
+ // We don't use the normal temp directory because people might have 'storage'
+ // symlinked to another volume, which makes moving complicated.
+ var tmpDir = yield this.createTemporaryStorageDirectory();
+ var tmpFile = tmpDir.clone();
+ tmpFile.append(fileName);
- try {
+ // Save to temp dir
+ var deferred = Zotero.Promise.defer();
+ wbp.progressListener = new Zotero.WebProgressFinishListener(function() {
+ if (contentType == 'application/pdf' &&
+ Zotero.MIME.sniffForMIMEType(str) != 'application/pdf') {
+ let errString = "Downloaded PDF did not have MIME type "
+ + "'application/pdf' in Attachments.importFromURL()";
+ Zotero.debug(errString, 2);
+ Zotero.File.getSample(tmpFile)
+ .then(function (sample) {
+ Zotero.debug(sample, 3);
+ deferred.reject(new Error(errString));
+ });
+ return;
+ }
+ deferred.resolve();
+ });
+
+ var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
+ .createInstance(Components.interfaces.nsIURL);
+ nsIURL.spec = url;
+ wbp.saveURI(nsIURL, null, null, null, null, tmpFile, null);
+ yield deferred.promise;
+
+ // Create DB item
+ var attachmentItem;
+ var destDir;
+ yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
- var attachmentItem = new Zotero.Item('attachment');
+ attachmentItem = new Zotero.Item('attachment');
if (libraryID) {
attachmentItem.libraryID = libraryID;
}
- else if (sourceItemID) {
- var parentItem = Zotero.Items.get(sourceItemID);
- attachmentItem.libraryID = parentItem.libraryID;
+ else if (parentItemID) {
+ let [parentLibraryID, parentKey] = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
+ attachmentItem.libraryID = parentLibraryID;
}
- attachmentItem.setField('title', title);
+ attachmentItem.setField('title', title ? title : fileName);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
- attachmentItem.setSource(sourceItemID);
+ attachmentItem.parentID = parentItemID;
attachmentItem.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL;
- attachmentItem.attachmentMIMEType = mimeType;
- var itemID = attachmentItem.save();
- attachmentItem = Zotero.Items.get(itemID);
-
- // Add to collections
- if (parentCollectionIDs){
- var ids = Zotero.flattenArguments(parentCollectionIDs);
- for each(var id in ids){
- var col = Zotero.Collections.get(id);
- col.addItem(itemID);
- }
- }
+ attachmentItem.attachmentContentType = contentType;
+ var itemID = yield attachmentItem.save();
// Create a new folder for this item in the storage directory
- var destDir = Zotero.Attachments.createDirectoryForItem(itemID);
+ destDir = this.getStorageDirectory(attachmentItem);
+ yield OS.File.move(tmpDir.path, destDir.path);
+ var destFile = destDir.clone();
+ destFile.append(fileName);
- var file = destDir.clone();
- file.append(fileName);
-
- wbp.progressListener = new Zotero.WebProgressFinishListener(function(){
- try {
- var str = Zotero.File.getSample(file);
-
- if (mimeType == 'application/pdf' &&
- Zotero.MIME.sniffForMIMEType(str) != 'application/pdf') {
- var errString = "Downloaded PDF did not have MIME type "
- + "'application/pdf' in Attachments.importFromURL()";
- Zotero.debug(errString, 2);
- Zotero.debug(str);
- attachmentItem.erase();
- if(callback) callback(false, new Error(errString));
- return;
- }
-
- attachmentItem.attachmentPath =
- Zotero.Attachments.getPath(
- file, Zotero.Attachments.LINK_MODE_IMPORTED_URL
- );
- attachmentItem.save();
-
- Zotero.Notifier.trigger('add', 'item', itemID);
- Zotero.Notifier.trigger('modify', 'item', sourceItemID);
+ // Refetch item to update path
+ attachmentItem = yield Zotero.Items.getAsync(itemID);
+ attachmentItem.attachmentPath = Zotero.Attachments.getPath(
+ destFile, Zotero.Attachments.LINK_MODE_IMPORTED_URL
+ );
+ yield attachmentItem.save();
- if(callback) callback(attachmentItem);
-
- // We don't have any way of knowing that the file
- // is flushed to disk, so we just wait a second
- // and hope for the best -- we'll index it later
- // if it fails
- //
- // TODO: index later
- setTimeout(function() {
- Zotero.Fulltext.indexItems([itemID]);
- }, 1000);
- }
- catch (e) {
- // Clean up
- attachmentItem.erase();
- if(callback) callback(false, e);
-
- throw (e);
+ // Add to collections
+ if (parentCollectionIDs) {
+ var ids = Zotero.flattenArguments(parentCollectionIDs);
+ for (let i=0; i<ids.length; i++) {
+ let col = yield Zotero.Collections.getAsync(ids[i]);
+ yield col.addItem(itemID);
}
- });
-
- // Disable the Notifier during the commit
- var disabled = Zotero.Notifier.disable();
-
- // The attachment is still incomplete here, but we can't risk
- // leaving the transaction open if the callback never triggers
- Zotero.DB.commitTransaction();
-
- if (disabled) {
- Zotero.Notifier.enable();
}
+ }.bind(this))
+ .catch(function (e) {
+ Zotero.debug(e, 1);
- var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
- .createInstance(Components.interfaces.nsIURL);
- nsIURL.spec = url;
- try {
- wbp.saveURI(nsIURL, null, null, null, null, file);
- } catch(e if e.name === "NS_ERROR_XPC_NOT_ENOUGH_ARGS") {
- // https://bugzilla.mozilla.org/show_bug.cgi?id=794602
- // XXX Always use when we no longer support Firefox < 18
- wbp.saveURI(nsIURL, null, null, null, null, file, null);
- }
-
- return attachmentItem;
- }
- catch (e){
- Zotero.DB.rollbackTransaction();
-
+ // Clean up
try {
- // Clean up
- if (itemID) {
- var itemDir = this.getStorageDirectory(itemID);
- if (itemDir.exists()) {
- itemDir.remove(true);
- }
+ if (tmpDir && tmpDir.exists()) {
+ tmpDir.remove(true);
+ }
+ if (destDir && destDir.exists()) {
+ destDir.remove(true);
}
}
- catch (e) {}
+ catch (e) {
+ Zotero.debug(e, 1);
+ }
- throw (e);
- }
- }
+ throw e;
+ });
+
+ // We don't have any way of knowing that the file is flushed to disk,
+ // so we just wait a second before indexing and hope for the best.
+ // We'll index it later if it fails. (This may not be necessary.)
+ setTimeout(function () {
+ Zotero.Fulltext.indexItems([attachmentItem.id]);
+ }, 1000);
+
+ return attachmentItem;
+ });
- var process = function (mimeType, hasNativeHandler) {
+ var process = function (contentType, hasNativeHandler) {
// If we can load this natively, use a hidden browser
// (so we can get the charset and title and index the document)
if (hasNativeHandler) {
- nativeHandlerImport();
+ return nativeHandlerImport();
}
+
// Otherwise use a remote web page persist
- else {
- return externalHandlerImport(mimeType);
- }
+ return externalHandlerImport(contentType);
}
- if (mimeType) {
- return process(mimeType, Zotero.MIME.hasNativeHandler(mimeType));
- }
- else {
- Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) {
- process(mimeType, hasNativeHandler);
- }, cookieSandbox);
+ if (contentType) {
+ return process(contentType, Zotero.MIME.hasNativeHandler(contentType));
}
- }
+
+ return Zotero.MIME.getMIMETypeFromURL(url, cookieSandbox).spread(process);
+ });
- /*
+ /**
* Create a link attachment from a URL
*
- * @param {String} url
- * @param {Integer} sourceItemID Parent item
- * @param {String} [mimeType] MIME type of page
- * @param {String} [title] Title to use for attachment
+ * @param {Object} options - 'url', 'parentItemID', 'contentType', 'title'
+ * @return {Promise<Zotero.Item>} - A promise for the created attachment item
*/
- function linkFromURL(url, sourceItemID, mimeType, title){
+ this.linkFromURL = Zotero.Promise.coroutine(function* (options) {
Zotero.debug('Linking attachment from URL');
-
+
+ var url = options.url;
+ var parentItemID = options.parentItemID;
+ var contentType = options.contentType;
+ var title = options.title;
+
/* Throw error on invalid URLs
- We currently accept the following protocols:
- PersonalBrain (brain://)
- DevonThink (x-devonthink-item://)
- Notational Velocity (nv://)
- MyLife Organized (mlo://)
- Evernote (evernote://)
- OneNote (onenote://)
- Kindle (kindle://)
- Logos (logosres:)
- Zotero (zotero://) */
+ We currently accept the following protocols:
+ PersonalBrain (brain://)
+ DevonThink (x-devonthink-item://)
+ Notational Velocity (nv://)
+ MyLife Organized (mlo://)
+ Evernote (evernote://)
+ OneNote (onenote://)
+ Kindle (kindle://)
+ Logos (logosres:)
+ Zotero (zotero://) */
var urlRe = /^((https?|zotero|evernote|onenote|brain|nv|mlo|kindle|x-devonthink-item|ftp):\/\/|logosres:)[^\s]*$/;
var matches = urlRe.exec(url);
@@ -438,21 +440,35 @@ Zotero.Attachments = new function(){
// invalid MIME type (https://www.zotero.org/trac/ticket/460)
var ext = _getExtensionFromURL(url);
if (ext == 'pdf') {
- mimeType = 'application/pdf';
- }
-
- var itemID = _addToDB(null, url, title, this.LINK_MODE_LINKED_URL,
- mimeType, null, sourceItemID);
- return itemID;
- }
+ contentType = 'application/pdf';
+ }
+
+ var itemID = yield _addToDB({
+ url: url,
+ title: title,
+ linkMode: this.LINK_MODE_LINKED_URL,
+ contentType: contentType,
+ parentItemID: parentItemID
+ });
+ return Zotero.Items.get(itemID);
+ });
- // TODO: what if called on file:// document?
- function linkFromDocument(document, sourceItemID, parentCollectionIDs){
+ /**
+ * TODO: what if called on file:// document?
+ *
+ * @param {Object} options - 'document', 'parentItemID', 'parentCollectionIDs'
+ * @return {Promise}
+ */
+ this.linkFromDocument = Zotero.Promise.coroutine(function* (options) {
Zotero.debug('Linking attachment from document');
- if (sourceItemID && parentCollectionIDs) {
- var msg = "parentCollectionIDs is ignored when sourceItemID is set in Zotero.Attachments.linkFromDocument()";
+ var document = options.document;
+ var parentItemID = options.parentItemID;
+ var parentCollectionIDs = options.parentCollectionIDs;
+
+ if (parentItemID && parentCollectionIDs) {
+ let msg = "parentCollectionIDs is ignored when parentItemID is set in Zotero.Attachments.linkFromDocument()";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
parentCollectionIDs = undefined;
@@ -460,29 +476,33 @@ Zotero.Attachments = new function(){
var url = document.location.href;
var title = document.title; // TODO: don't use Mozilla-generated title for images, etc.
- var mimeType = document.contentType;
+ var contentType = document.contentType;
var charsetID = Zotero.CharacterSets.getID(document.characterSet);
- Zotero.DB.beginTransaction();
-
- var itemID = _addToDB(null, url, title, this.LINK_MODE_LINKED_URL,
- mimeType, charsetID, sourceItemID);
-
- // Add to collections
- if (parentCollectionIDs){
- var ids = Zotero.flattenArguments(parentCollectionIDs);
- for each(var id in ids){
- var col = Zotero.Collections.get(id);
- col.addItem(itemID);
+ var itemID;
+ yield Zotero.DB.executeTransaction(function* () {
+ itemID = yield _addToDB({
+ url: url,
+ title: title,
+ linkMode: this.LINK_MODE_LINKED_URL,
+ contentType: contentType,
+ charset: charsetID,
+ parentItemID: parentItemID
+ });
+
+ // Add to collections
+ if (parentCollectionIDs) {
+ var ids = Zotero.flattenArguments(parentCollectionIDs);
+ for (let i=0; i<ids.length; i++) {
+ let col = yield Zotero.Collections.getAsync(id);
+ yield col.addItem(itemID);
+ }
}
- }
+ }.bind(this));
- Zotero.DB.commitTransaction();
-
- // Run the fulltext indexer asynchronously (actually, it hangs the UI
- // thread, but at least it lets the menu close)
- setTimeout(function() {
- if (Zotero.Fulltext.isCachedMIMEType(mimeType)) {
+ // Run the indexer asynchronously
+ setTimeout(function () {
+ if (Zotero.Fulltext.isCachedMIMEType(contentType)) {
// No file, so no point running the PDF indexer
//Zotero.Fulltext.indexItems([itemID]);
}
@@ -491,369 +511,183 @@ Zotero.Attachments = new function(){
}
}, 50);
- return itemID;
- }
+ return Zotero.Items.get(itemID);
+ });
- /*
+ /**
* Save a snapshot -- uses synchronous WebPageDump or asynchronous saveURI()
+ *
+ * @param {Object} options - 'libraryID', 'document', 'parentItemID', 'forceTitle', 'parentCollectionIDs'
+ * @return {Promise<Zotero.Item>} - A promise for the created attachment item
*/
- function importFromDocument(document, sourceItemID, forceTitle, parentCollectionIDs, callback, libraryID) {
+ this.importFromDocument = Zotero.Promise.coroutine(function* (options) {
Zotero.debug('Importing attachment from document');
- if (sourceItemID && parentCollectionIDs) {
- var msg = "parentCollectionIDs is ignored when sourceItemID is set in Zotero.Attachments.importFromDocument()";
+ var libraryID = options.libraryID;
+ var document = options.document;
+ var parentItemID = options.parentItemID;
+ var title = options.title;
+ var parentCollectionIDs = options.parentCollectionIDs;
+
+ if (parentItemID && parentCollectionIDs) {
+ var msg = "parentCollectionIDs is ignored when parentItemID is set in Zotero.Attachments.importFromDocument()";
Zotero.debug(msg, 2);
Components.utils.reportError(msg);
parentCollectionIDs = undefined;
}
var url = document.location.href;
- var title = forceTitle ? forceTitle : document.title;
- var mimeType = document.contentType;
- if(Zotero.Attachments.isPDFJS(document)) {
- mimeType = "application/pdf";
+ title = title ? title : document.title;
+ var contentType = document.contentType;
+ if (Zotero.Attachments.isPDFJS(document)) {
+ contentType = "application/pdf";
}
+ var tmpDir = yield this.createTemporaryStorageDirectory();
+ var tmpFile = tmpDir.clone();
+ var fileName = Zotero.File.truncateFileName(
+ _getFileNameFromURL(url, contentType),
+ 100 //make sure this matches WPD settings in webpagedump/common.js
+ );
+ tmpFile.append(fileName);
+
var charsetID = Zotero.CharacterSets.getID(document.characterSet);
- if (!forceTitle) {
+ // If we're using the title from the document, make some adjustments
+ if (!options.title) {
// Remove e.g. " - Scaled (-17%)" from end of images saved from links,
// though I'm not sure why it's getting added to begin with
- if (mimeType.indexOf('image/') === 0) {
+ if (contentType.indexOf('image/') === 0) {
title = title.replace(/(.+ \([^,]+, [0-9]+x[0-9]+[^\)]+\)) - .+/, "$1" );
}
// If not native type, strip mime type data in parens
- else if (!Zotero.MIME.hasNativeHandler(mimeType, _getExtensionFromURL(url))) {
+ else if (!Zotero.MIME.hasNativeHandler(contentType, _getExtensionFromURL(url))) {
title = title.replace(/(.+) \([a-z]+\/[^\)]+\)/, "$1" );
}
}
- Zotero.DB.beginTransaction();
+ if (contentType === 'text/html' || contentType === 'application/xhtml+xml') {
+ // Load WebPageDump code
+ var wpd = {"Zotero":Zotero};
+ Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/webpagedump/common.js", wpd);
+ Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/webpagedump/domsaver.js", wpd);
+
+ wpd.wpdDOMSaver.init(tmpFile.path, document);
+ wpd.wpdDOMSaver.saveHTMLDocument();
+ }
+ else {
+ Zotero.debug('Saving with saveURI()');
+ const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
+ var wbp = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(nsIWBP);
+ wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION
+ | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var nsIURL = ioService.newURI(url, null, null);
+ var deferred = Zotero.Promise.defer();
+ wbp.progressListener = new Zotero.WebProgressFinishListener(function () {
+ deferred.resolve();
+ });
+ wbp.saveURI(nsIURL, null, null, null, null, file, null);
+ yield deferred.promise;
+ }
- try {
+ var attachmentItem;
+ var destDir;
+ yield Zotero.DB.executeTransaction(function* () {
// Create a new attachment
- var attachmentItem = new Zotero.Item('attachment');
+ attachmentItem = new Zotero.Item('attachment');
if (libraryID) {
attachmentItem.libraryID = libraryID;
}
- else if (sourceItemID) {
- var parentItem = Zotero.Items.get(sourceItemID);
- attachmentItem.libraryID = parentItem.libraryID;
+ else if (parentItemID) {
+ let [parentLibraryID, parentKey] = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
+ Zotero.debug('==-=');
+ Zotero.debug(parentItemID);
+ Zotero.debug(parentLibraryID);
+ Zotero.debug(parentKey);
+ attachmentItem.libraryID = parentLibraryID;
}
attachmentItem.setField('title', title);
attachmentItem.setField('url', url);
attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
- attachmentItem.setSource(sourceItemID);
+ attachmentItem.parentID = parentItemID;
attachmentItem.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_URL;
attachmentItem.attachmentCharset = charsetID;
- attachmentItem.attachmentMIMEType = mimeType;
- var itemID = attachmentItem.save();
- attachmentItem = Zotero.Items.get(itemID);
+ attachmentItem.attachmentContentType = contentType;
+ var itemID = yield attachmentItem.save();
// Create a new folder for this item in the storage directory
- var destDir = this.createDirectoryForItem(itemID);
+ destDir = this.getStorageDirectory(attachmentItem);
+ yield OS.File.move(tmpDir.path, destDir.path);
+ var destFile = destDir.clone();
+ destFile.append(fileName);
- var file = Components.classes["@mozilla.org/file/local;1"].
- createInstance(Components.interfaces.nsILocalFile);
- file.initWithFile(destDir);
-
- var fileName = Zotero.File.truncateFileName(
- _getFileNameFromURL(url, mimeType),
- 100 //make sure this matches WPD settings in webpagedump/common.js
+ attachmentItem = yield Zotero.Items.getAsync(itemID);
+ attachmentItem.attachmentPath = this.getPath(
+ destFile, Zotero.Attachments.LINK_MODE_IMPORTED_URL
);
- file.append(fileName)
-
- var f = function() {
- if (mimeType == 'application/pdf') {
- Zotero.Fulltext.indexPDF(file, itemID);
- }
- else if (Zotero.MIME.isTextType(mimeType)) {
- Zotero.Fulltext.indexDocument(document, itemID);
- }
- if (callback) {
- callback(attachmentItem);
- }
- };
-
- if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml') {
- var sync = true;
-
- // Load WebPageDump code
- var wpd = {"Zotero":Zotero};
- Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Components.interfaces.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/webpagedump/common.js", wpd);
- Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Components.interfaces.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/webpagedump/domsaver.js", wpd);
-
- wpd.wpdDOMSaver.init(file.path, document);
- wpd.wpdDOMSaver.saveHTMLDocument();
-
- attachmentItem.attachmentPath = this.getPath(
- file, Zotero.Attachments.LINK_MODE_IMPORTED_URL
- );
- attachmentItem.save();
- }
- else {
- Zotero.debug('Saving with saveURI()');
- const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
- var wbp = Components
- .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
- .createInstance(nsIWBP);
- wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION
- | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- var nsIURL = ioService.newURI(url, null, null);
- wbp.progressListener = new Zotero.WebProgressFinishListener(function () {
- try {
- attachmentItem.attachmentPath = Zotero.Attachments.getPath(
- file,
- Zotero.Attachments.LINK_MODE_IMPORTED_URL
- );
- attachmentItem.save();
-
- Zotero.Notifier.trigger('add', 'item', itemID);
-
- // We don't have any way of knowing that the file is flushed to
- // disk, so we just wait a second and hope for the best --
- // we'll index it later if it fails
- //
- // TODO: index later
- setTimeout(function () {
- f();
- }, 1000);
- }
- catch (e) {
- // Clean up
- var item = Zotero.Items.get(itemID);
- item.erase();
- if(callback) callback(false, e);
-
- throw (e);
- }
- });
- try {
- wbp.saveURI(nsIURL, null, null, null, null, file);
- } catch(e if e.name === "NS_ERROR_XPC_NOT_ENOUGH_ARGS") {
- // https://bugzilla.mozilla.org/show_bug.cgi?id=794602
- // XXX Always use when we no longer support Firefox < 18
- wbp.saveURI(nsIURL, null, null, null, null, file, null);
- }
- }
+ yield attachmentItem.save();
// Add to collections
- if (parentCollectionIDs){
- var ids = Zotero.flattenArguments(parentCollectionIDs);
- for each(var id in ids){
- var col = Zotero.Collections.get(id);
- col.addItem(itemID);
+ if (parentCollectionIDs) {
+ let ids = Zotero.flattenArguments(parentCollectionIDs);
+ for (let i=0; i<ids.length; i++) {
+ let col = yield Zotero.Collections.getAsync(ids[i]);
+ yield col.addItem(itemID);
}
}
+ }.bind(this))
+ .catch(function (e) {
+ Zotero.debug(e, 1);
- // Disable the Notifier during the commit if this is async
- if (!sync) {
- var disabled = Zotero.Notifier.disable();
- }
-
- Zotero.DB.commitTransaction();
-
- if (disabled) {
- Zotero.Notifier.enable();
- }
-
- if (sync) {
- Zotero.Notifier.trigger('add', 'item', itemID);
-
- // Wait a second before indexing (see note above)
- setTimeout(function () {
- f();
- }, 1000);
- }
-
- // Caution: Take care using this itemID. The notifier may not yet have been called,
- // so the attachment may not be available in, for example, the items list
- return itemID;
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
-
+ // Clean up
try {
- // Clean up
- if (itemID) {
- var itemDir = this.getStorageDirectory(itemID);
- if (itemDir.exists()) {
- itemDir.remove(true);
- }
- }
- }
- catch (e) {}
-
- throw (e);
- }
- }
-
-
- /*
- * Previous asynchronous snapshot method -- disabled in favor of WebPageDump
- */
- /*
- function importFromDocument(document, sourceItemID, forceTitle, parentCollectionIDs, callback){
- Zotero.debug('Importing attachment from document');
-
- var url = document.location.href;
- var title = forceTitle ? forceTitle : document.title;
- var mimeType = document.contentType;
- var charsetID = Zotero.CharacterSets.getID(document.characterSet);
-
- if (!forceTitle) {
- // Remove e.g. " - Scaled (-17%)" from end of images saved from links,
- // though I'm not sure why it's getting added to begin with
- if (mimeType.indexOf('image/') === 0) {
- title = title.replace(/(.+ \([^,]+, [0-9]+x[0-9]+[^\)]+\)) - .+/, "$1" );
- }
- // If not native type, strip mime type data in parens
- else if (!Zotero.MIME.hasNativeHandler(mimeType, _getExtensionFromURL(url))) {
- title = title.replace(/(.+) \([a-z]+\/[^\)]+\)/, "$1" );
- }
- }
-
- const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
- var wbp = Components
- .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
- .createInstance(nsIWBP);
- wbp.persistFlags = nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
- var encodingFlags = false;
-
- Zotero.DB.beginTransaction();
-
- try {
- // Create a new attachment
- var attachmentItem = new Zotero.Item('attachment');
- attachmentItem.setField('title', title);
- attachmentItem.setField('url', url);
- attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
- // Don't send a Notifier event on the incomplete item
- var disabled = Zotero.Notifier.disable();
- attachmentItem.save();
- if (disabled) {
- Zotero.Notifier.enable();
- }
- var itemID = attachmentItem.getID();
-
- // Create a new folder for this item in the storage directory
- var destDir = this.createDirectoryForItem(itemID);
-
- var file = Components.classes["@mozilla.org/file/local;1"].
- createInstance(Components.interfaces.nsILocalFile);
- file.initWithFile(destDir);
-
- var fileName = _getFileNameFromURL(url, mimeType);
-
- file.append(fileName);
-
- wbp.progressListener = new Zotero.WebProgressFinishListener(function(){
- try {
- Zotero.DB.beginTransaction();
-
- _addToDB(file, url, title, Zotero.Attachments.LINK_MODE_IMPORTED_URL, mimeType,
- charsetID, sourceItemID, itemID);
-
- Zotero.Notifier.trigger('add', 'item', itemID);
-
- // Add to collections
- if (parentCollectionIDs){
- var ids = Zotero.flattenArguments(parentCollectionIDs);
- for each(var id in ids){
- var col = Zotero.Collections.get(id);
- col.addItem(itemID);
- }
- }
-
- Zotero.DB.commitTransaction();
+ if (tmpDir && tmpDir.exists()) {
+ tmpDir.remove(true);
}
- catch (e) {
- Zotero.DB.rollbackTransaction();
-
- // Clean up
- if (itemID) {
- var item = Zotero.Items.get(itemID);
- if (item) {
- item.erase();
- }
-
- try {
- var destDir = Zotero.getStorageDirectory();
- destDir.append(itemID);
- if (destDir.exists()) {
- destDir.remove(true);
- }
- }
- catch (e) {}
- }
-
- throw (e);
+ if (destDir && destDir.exists()) {
+ destDir.remove(true);
}
-
- Zotero.Fulltext.indexDocument(document, itemID);
-
- if (callback) {
- callback();
- }
- });
-
- // The attachment is still incomplete here, but we can't risk
- // leaving the transaction open if the callback never triggers
- Zotero.DB.commitTransaction();
-
- if (mimeType == 'text/html') {
- Zotero.debug('Saving with saveDocument() to ' + destDir.path);
- wbp.saveDocument(document, file, destDir, mimeType, encodingFlags, false);
}
- else {
- Zotero.debug('Saving with saveURI()');
- var ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- var nsIURL = ioService.newURI(url, null, null);
- wbp.saveURI(nsIURL, null, null, null, null, file);
+ catch (e) {
+ Zotero.debug(e, 1);
}
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- try {
- // Clean up
- if (itemID) {
- var destDir = Zotero.getStorageDirectory();
- destDir.append(itemID);
- if (destDir.exists()) {
- destDir.remove(true);
- }
- }
+ throw e;
+ });
+
+ // We don't have any way of knowing that the file is flushed to disk,
+ // so we just wait a second before indexing and hope for the best.
+ // We'll index it later if it fails. (This may not be necessary.)
+ setTimeout(function () {
+ if (contentType == 'application/pdf') {
+ Zotero.Fulltext.indexPDF(file, attachmentItem.id);
}
- catch (e) {}
-
- throw (e);
- }
- }
- */
+ else if (Zotero.MIME.isTextType(contentType)) {
+ Zotero.Fulltext.indexDocument(document, attachmentItem.id);
+ }
+ }, 1000);
+
+ return attachmentItem;
+ });
/*
* Create a new attachment with a missing file
*/
- function createMissingAttachment(linkMode, file, url, title, mimeType, charset, sourceItemID) {
- if (linkMode == this.LINK_MODE_LINKED_URL) {
- throw ('Zotero.Attachments.createMissingAttachment() cannot be used to create linked URLs');
+ this.createMissingAttachment = Zotero.Promise.coroutine(function* (options) {
+ if (options.linkMode == this.LINK_MODE_LINKED_URL) {
+ throw new Error('Cannot create missing linked URLs');
}
-
- var charsetID = charset ? Zotero.CharacterSets.getID(charset) : null;
-
- return _addToDB(file, url, title, linkMode, mimeType,
- charsetID, sourceItemID);
- }
+ return _addToDB(options);
+ });
/*
@@ -861,25 +695,27 @@ Zotero.Attachments = new function(){
* based on the metadata of the specified item and a format string
*
* (Optional) |formatString| specifies the format string -- otherwise
- * the 'attachmentRenameFormatString' pref is used
+ * the 'attachmentRenameFormatString' pref is used
*
* Valid substitution markers:
*
- * %c -- firstCreator
- * %y -- year (extracted from Date field)
- * %t -- title
+ * %c -- firstCreator
+ * %y -- year (extracted from Date field)
+ * %t -- title
*
* Fields can be truncated to a certain length by appending an integer
* within curly brackets -- e.g. %t{50} truncates the title to 50 characters
+ *
+ * @param {Zotero.Item} item
+ * @param {String} formatString
*/
- function getFileBaseNameFromItem(itemID, formatString) {
- if (!formatString) {
- formatString = Zotero.Prefs.get('attachmentRenameFormatString');
+ this.getFileBaseNameFromItem = function (item, formatString) {
+ if (!(item instanceof Zotero.Item)) {
+ throw new Error("'item' must be a Zotero.Item");
}
- var item = Zotero.Items.get(itemID);
- if (!item) {
- throw ('Invalid itemID ' + itemID + ' in Zotero.Attachments.getFileBaseNameFromItem()');
+ if (!formatString) {
+ formatString = Zotero.Prefs.get('attachmentRenameFormatString');
}
// Replaces the substitution marker with the field value,
@@ -946,58 +782,53 @@ Zotero.Attachments = new function(){
}
- /*
+ /**
* Create directory for attachment files within storage directory
*
- * @param integer itemID Item id
- *
* If a directory exists with the same name, move it to orphaned-files
+ *
+ * @param {Number} itemID - Item id
+ * @return {Promise}
*/
- function createDirectoryForItem(itemID) {
- var dir = this.getStorageDirectory(itemID);
- _moveOrphanedDirectory(dir);
+ this.createDirectoryForItem = Zotero.Promise.coroutine(function* (item) {
+ if (!(item instanceof Zotero.Item)) {
+ throw new Error("'item' must be a Zotero.Item");
+ }
+ var dir = this.getStorageDirectory(item);
+ yield _moveOrphanedDirectory(dir);
if (!dir.exists()) {
dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
}
return dir;
- }
+ });
- /*
- * Create directory for missing attachment files within storage directory
- *
- * @param string key Item secondary lookup key
- */
- function createDirectoryForMissingItem(key) {
- var dir = this.getStorageDirectoryByKey(key);
- if (!dir.exists()) {
- dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
+ this.getStorageDirectory = function (item) {
+ if (!(item instanceof Zotero.Item)) {
+ throw new Error("'item' must be a Zotero.Item");
}
- return dir;
+ return this.getStorageDirectoryByLibraryAndKey(item.libraryID, item.key);
}
- function getStorageDirectory(itemID) {
+ this.getStorageDirectoryByID = function (itemID) {
if (!itemID) {
- throw new Error("itemID not provided in Zotero.Attachments.getStorageDirectory()");
+ throw new Error("itemID not provided");
}
- var item = Zotero.Items.get(itemID);
- if (!item) {
- throw ("Item " + itemID + " not found in Zotero.Attachments.getStorageDirectory()");
- }
- if (!item.key) {
- throw ("No item key in Zotero.Attachments.getStorageDirectory()");
+ var [libraryID, key] = Zotero.Items.getLibraryAndKeyFromID(itemID);
+ if (!key) {
+ throw new Error("Item " + itemID + " not found");
}
var dir = Zotero.getStorageDirectory();
- dir.append(item.key);
+ dir.append(key);
return dir;
}
- this.getStorageDirectoryByKey = function (key) {
+ this.getStorageDirectoryByLibraryAndKey = function (libraryID, key) {
if (typeof key != 'string' || !key.match(/^[A-Z0-9]{8}$/)) {
throw ('key must be an 8-character string in '
- + 'Zotero.Attachments.getStorageDirectoryByKey()')
+ + 'Zotero.Attachments.getStorageDirectoryByLibraryAndKey()')
}
var dir = Zotero.getStorageDirectory();
dir.append(key);
@@ -1005,11 +836,23 @@ Zotero.Attachments = new function(){
}
+ this.createTemporaryStorageDirectory = Zotero.Promise.coroutine(function* () {
+ var tmpDir = Zotero.getStorageDirectory();
+ tmpDir.append("tmp-" + Zotero.Utilities.randomString(6));
+ Zotero.debug("RANDOM IS " + tmpDir.leafName);
+ yield OS.File.makeDir(tmpDir.path, {
+ unixMode: 0755
+ });
+ Zotero.debug("MADE DIRECTORY at " + tmpDir.path);
+ return tmpDir;
+ });
+
+
/*
* Gets a relative descriptor for imported attachments and a persistent
* descriptor for files outside the storage directory
*/
- function getPath(file, linkMode) {
+ this.getPath = function (file, linkMode) {
file.QueryInterface(Components.interfaces.nsILocalFile);
if (linkMode == self.LINK_MODE_IMPORTED_URL ||
linkMode == self.LINK_MODE_IMPORTED_FILE) {
@@ -1070,11 +913,11 @@ Zotero.Attachments = new function(){
/**
* Get a file from this path, if we can
*
- * @param {String} path Absolute path or relative path prefixed
- * by BASE_PATH_PLACEHOLDER
+ * @param {String} path Absolute path or relative path prefixed
+ * by BASE_PATH_PLACEHOLDER
* @param {Boolean} asFile Return nsIFile instead of path
* @return {String|nsIFile|FALSE} Persistent descriptor string, file,
- * of FALSE if no path
+ * of FALSE if no path
*/
this.resolveRelativePath = function (path) {
if (path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) != 0) {
@@ -1142,7 +985,7 @@ Zotero.Attachments = new function(){
throw ("Invalid attachment link mode in " + funcName);
}
- if (item.attachmentMIMEType != 'text/html') {
+ if (item.attachmentContentType != 'text/html') {
return 1;
}
@@ -1216,7 +1059,7 @@ Zotero.Attachments = new function(){
/**
* Copy attachment item, including files, to another library
*/
- this.copyAttachmentToLibrary = function (attachment, libraryID, sourceItemID) {
+ this.copyAttachmentToLibrary = Zotero.Promise.coroutine(function* (attachment, libraryID, parentItemID) {
var linkMode = attachment.attachmentLinkMode;
if (attachment.libraryID == libraryID) {
@@ -1235,29 +1078,29 @@ Zotero.Attachments = new function(){
var id = newAttachment.save();
newAttachment = Zotero.Items.get(id);
attachment.clone(false, newAttachment);
- if (sourceItemID) {
- newAttachment.setSource(sourceItemID);
+ if (parentItemID) {
+ newAttachment.setSource(parentItemID);
}
newAttachment.save();
// Copy over files if they exist
if (newAttachment.isImportedAttachment() && attachment.getFile()) {
- var dir = Zotero.Attachments.getStorageDirectory(attachment.id);
- var newDir = Zotero.Attachments.createDirectoryForItem(newAttachment.id);
+ var dir = Zotero.Attachments.getStorageDirectory(attachment);
+ var newDir = yield Zotero.Attachments.createDirectoryForItem(newAttachment);
Zotero.File.copyDirectory(dir, newDir);
}
newAttachment.addLinkedItem(attachment);
return newAttachment.id;
- }
+ });
- function _getFileNameFromURL(url, mimeType){
+ function _getFileNameFromURL(url, contentType){
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURL);
nsIURL.spec = url;
- var ext = Zotero.MIME.getPrimaryExtension(mimeType, nsIURL.fileExtension);
+ var ext = Zotero.MIME.getPrimaryExtension(contentType, nsIURL.fileExtension);
if (!nsIURL.fileName) {
var matches = nsIURL.directory.match(/\/([^\/]+)\/$/);
@@ -1303,11 +1146,11 @@ Zotero.Attachments = new function(){
}
- function _getExtensionFromURL(url, mimeType) {
+ function _getExtensionFromURL(url, contentType) {
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURL);
nsIURL.spec = url;
- return Zotero.MIME.getPrimaryExtension(mimeType, nsIURL.fileExtension);
+ return Zotero.MIME.getPrimaryExtension(contentType, nsIURL.fileExtension);
}
@@ -1316,7 +1159,7 @@ Zotero.Attachments = new function(){
*
* If empty, just remove it
*/
- function _moveOrphanedDirectory(dir) {
+ var _moveOrphanedDirectory = Zotero.Promise.coroutine(function* (dir) {
if (!dir.exists()) {
return;
}
@@ -1377,105 +1220,112 @@ Zotero.Attachments = new function(){
// Move target to orphaned files directory
dir.moveTo(orphaned, newName);
- }
+ });
/**
- * Create a new item of type 'attachment' and add to the itemAttachments table
- *
- * Returns the itemID of the new attachment
- **/
- function _addToDB(file, url, title, linkMode, mimeType, charsetID, sourceItemID) {
- Zotero.DB.beginTransaction();
-
- var attachmentItem = new Zotero.Item('attachment');
- if (sourceItemID) {
- var parentItem = Zotero.Items.get(sourceItemID);
- if (parentItem.libraryID && linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
- throw ("Cannot save linked file in non-local library");
+ * Create a new item of type 'attachment' and add to the itemAttachments table
+ *
+ * @param {Object} options - 'file', 'url', 'title', 'linkMode', 'contentType', 'charsetID', 'parentItemID'
+ * @return {Promise<Number>} Returns a promise for the itemID of the new attachment
+ */
+ function _addToDB(options) {
+ var file = options.file;
+ var url = options.url;
+ var title = options.title;
+ var linkMode = options.linkMode;
+ var contentType = options.contentType;
+ var charset = options.charset;
+ var parentItemID = options.parentItemID;
+
+ return Zotero.DB.executeTransaction(function* () {
+ var attachmentItem = new Zotero.Item('attachment');
+ if (parentItemID) {
+ let [parentLibraryID, parentKey] = Zotero.Items.getLibraryAndKeyFromID(parentItemID);
+ if (parentLibraryID && linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
+ throw new Error("Cannot save linked file in non-local library");
+ }
+ attachmentItem.libraryID = parentLibraryID;
}
- attachmentItem.libraryID = parentItem.libraryID;
- }
- attachmentItem.setField('title', title);
- if (linkMode == self.LINK_MODE_IMPORTED_URL
- || linkMode == self.LINK_MODE_LINKED_URL) {
- attachmentItem.setField('url', url);
- attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
- }
-
- // Get path
- if (file) {
- attachmentItem.attachmentPath
- = Zotero.Attachments.getPath(file, linkMode);
- }
-
- attachmentItem.setSource(sourceItemID);
- attachmentItem.attachmentLinkMode = linkMode;
- attachmentItem.attachmentMIMEType = mimeType;
- attachmentItem.attachmentCharset = charsetID;
- attachmentItem.save();
-
- Zotero.DB.commitTransaction();
-
- return attachmentItem.id;
+ attachmentItem.setField('title', title);
+ if (linkMode == self.LINK_MODE_IMPORTED_URL || linkMode == self.LINK_MODE_LINKED_URL) {
+ attachmentItem.setField('url', url);
+ attachmentItem.setField('accessDate', "CURRENT_TIMESTAMP");
+ }
+
+ // Get path
+ if (file) {
+ attachmentItem.attachmentPath = Zotero.Attachments.getPath(file, linkMode);
+ }
+
+ attachmentItem.parentID = parentItemID;
+ attachmentItem.attachmentLinkMode = linkMode;
+ attachmentItem.attachmentContentType = contentType;
+ attachmentItem.attachmentCharset = charset;
+ yield attachmentItem.save();
+
+ return attachmentItem.id;
+ }.bind(this));
}
- /*
+ /**
* Since we have to load the content into the browser to get the
* character set (at least until we figure out a better way to get
* at the native detectors), we create the item above and update
* asynchronously after the fact
+ *
+ * @return {Promise}
*/
- function _postProcessFile(itemID, file, mimeType){
+ function _postProcessFile(itemID, file, contentType){
// Don't try to process if MIME type is unknown
- if (!mimeType) {
+ if (!contentType) {
return;
}
// MIME types that get cached by the fulltext indexer can just be
// indexed directly
- if (Zotero.Fulltext.isCachedMIMEType(mimeType)) {
- Zotero.Fulltext.indexItems([itemID]);
- return;
+ if (Zotero.Fulltext.isCachedMIMEType(contentType)) {
+ return Zotero.Fulltext.indexItems([itemID]);
}
var ext = Zotero.File.getExtension(file);
- if (!Zotero.MIME.hasInternalHandler(mimeType, ext) || !Zotero.MIME.isTextType(mimeType)) {
+ if (!Zotero.MIME.hasInternalHandler(contentType, ext) || !Zotero.MIME.isTextType(contentType)) {
return;
}
+ var deferred = Zotero.Promise.defer();
var browser = Zotero.Browser.createHiddenBrowser();
var callback = function(charset, args) {
// ignore spurious about:blank loads
if(browser.contentDocument.location.href == "about:blank") return;
- var writeCallback = function () {
- var charsetID = Zotero.CharacterSets.getID(charset);
- if (charsetID) {
- var disabled = Zotero.Notifier.disable();
-
- var item = Zotero.Items.get(itemID);
- item.attachmentCharset = charsetID;
- item.save();
-
- if (disabled) {
- Zotero.Notifier.enable();
- }
- }
-
- // Chain fulltext indexer inside the charset callback,
- // since it's asynchronous and a prerequisite
- Zotero.Fulltext.indexDocument(browser.contentDocument, itemID);
- Zotero.Browser.deleteHiddenBrowser(browser);
- }
-
// Since the callback can be called during an import process that uses
// Zotero.wait(), wait until we're unlocked
Zotero.unlockPromise
.then(function () {
- writeCallback();
+ return Zotero.spawn(function* () {
+ var charsetID = Zotero.CharacterSets.getID(charset);
+ if (charsetID) {
+ var disabled = Zotero.Notifier.disable();
+
+ var item = yield Zotero.Items.getAsync(itemID);
+ item.attachmentCharset = charsetID;
+ yield item.save();
+
+ if (disabled) {
+ Zotero.Notifier.enable();
+ }
+ }
+
+ // Chain fulltext indexer inside the charset callback,
+ // since it's asynchronous and a prerequisite
+ yield Zotero.Fulltext.indexDocument(browser.contentDocument, itemID);
+ Zotero.Browser.deleteHiddenBrowser(browser);
+
+ deferred.resolve();
+ });
});
};
@@ -1485,6 +1335,8 @@ Zotero.Attachments = new function(){
.getService(Components.interfaces.nsIFileProtocolHandler)
.getURLSpecFromFile(file);
browser.loadURI(url);
+
+ return deferred.promise;
}
/**
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -41,7 +41,7 @@ Zotero.CollectionTreeView = function()
this._treebox = null;
this._highlightedRows = {};
- this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'trash', 'bucket']);
+ this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'trash', 'bucket'], 'collectionTreeView');
this._containerState = {};
this._duplicateLibraries = [];
this._unfiledLibraries = [];
@@ -51,85 +51,78 @@ Zotero.CollectionTreeView = function()
Zotero.CollectionTreeView.prototype = Object.create(Zotero.LibraryTreeView.prototype);
Zotero.CollectionTreeView.prototype.type = 'collection';
-Object.defineProperty(Zotero.CollectionTreeView.prototype, "itemGroup", {
+Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
get: function () {
- return this.getItemGroupAtRow(this.selection.currentIndex);
+ return this.getRow(this.selection.currentIndex);
}
-})
-
+});
/*
* Called by the tree itself
*/
-Zotero.CollectionTreeView.prototype.setTree = function(treebox)
+Zotero.CollectionTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (treebox)
{
- if (this._treebox || !treebox) {
- return;
- }
- this._treebox = treebox;
-
- // Add a keypress listener for expand/collapse
- var tree = this._treebox.treeBody.parentNode;
- var self = this;
-
- tree.addEventListener('keypress', function(event) {
- var key = String.fromCharCode(event.which);
-
- if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
- self.expandLibrary(self);
+ try {
+ if (this._treebox || !treebox) {
return;
}
- else if (key == '-' && !(event.shiftKey || event.ctrlKey ||
- event.altKey || event.metaKey)) {
- self.collapseLibrary(self);
+ this._treebox = treebox;
+
+ // Add a keypress listener for expand/collapse
+ var tree = this._treebox.treeBody.parentNode;
+ tree.addEventListener('keypress', function(event) {
+ var key = String.fromCharCode(event.which);
+
+ if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
+ this.expandLibrary();
+ return;
+ }
+ else if (key == '-' && !(event.shiftKey || event.ctrlKey ||
+ event.altKey || event.metaKey)) {
+ this.collapseLibrary();
+ return;
+ }
+ }.bind(this), false);
+
+ yield this.refresh();
+ if (!this._treebox.columns) {
return;
}
- }, false);
-
- try {
- this.refresh();
+ this.selection.currentColumn = this._treebox.columns.getFirstColumn();
+
+ var row = yield this.getLastViewedRow();
+ this.selection.select(row);
+ this._treebox.ensureRowIsVisible(row);
}
- // Tree errors don't get caught by default
catch (e) {
- Zotero.debug(e);
+ Zotero.debug(e, 1);
Components.utils.reportError(e);
- throw (e);
+ if (this.onError) {
+ this.onError(e);
+ }
+ throw e;
}
-
- this.selection.currentColumn = this._treebox.columns.getFirstColumn();
-
- var row = this.getLastViewedRow();
- this.selection.select(row);
-
- // TODO: make better
- var tb = this._treebox;
- setTimeout(function () {
- tb.ensureRowIsVisible(row);
- }, 1);
-}
+});
/*
* Reload the rows from the data access methods
* (doesn't call the tree.invalidate methods, etc.)
*/
-Zotero.CollectionTreeView.prototype.refresh = function()
+Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function* ()
{
+ Zotero.debug("Refreshing collections pane");
+
// Record open states before refreshing
- if (this._dataItems) {
- for (var i=0, len=this._dataItems.length; i<len; i++) {
- var itemGroup = this._dataItems[i][0]
- if (itemGroup.ref && itemGroup.ref.id == 'commons-header') {
+ if (this._rows) {
+ for (var i=0, len=this._rows.length; i<len; i++) {
+ var treeRow = this._rows[i][0]
+ if (treeRow.ref && treeRow.ref.id == 'commons-header') {
var commonsExpand = this.isContainerOpen(i);
}
}
}
- this.selection.clearSelection();
- var oldCount = this.rowCount;
- this._dataItems = [];
- this.rowCount = 0;
-
try {
this._containerState = JSON.parse(Zotero.Prefs.get("sourceList.persist"));
}
@@ -157,56 +150,58 @@ Zotero.CollectionTreeView.prototype.refresh = function()
this._unfiledLibraries = [0];
}
+ var oldCount = this.rowCount || 0;
+ var newRows = [];
+
var self = this;
var library = {
libraryID: 0
};
- // itemgroup, level, beforeRow, startOpen
- this._showRow(new Zotero.ItemGroup('library', library), 0, 1);
- this._expandRow(0);
-
- var groups = Zotero.Groups.getAll();
+ // treeRow, level, beforeRow, startOpen
+ this._addRow(newRows, new Zotero.CollectionTreeRow('library', library), 0, 1);
+ yield this._expandRow(newRows, 0);
+ var groups = yield Zotero.Groups.getAll();
if (groups.length) {
- this._showRow(new Zotero.ItemGroup('separator', false));
+ this._addRow(newRows, new Zotero.CollectionTreeRow('separator', false));
var header = {
id: "group-libraries-header",
label: Zotero.getString('pane.collections.groupLibraries'),
- expand: function (beforeRow, groups) {
+ libraryID: -1,
+ expand: Zotero.Promise.coroutine(function* (rows, beforeRow, groups) {
if (!groups) {
- var groups = Zotero.Groups.getAll();
+ groups = yield Zotero.Groups.getAll();
}
-
var newRows = 0;
for (var i = 0, len = groups.length; i < len; i++) {
- var row = self._showRow(new Zotero.ItemGroup('group', groups[i]), 1, beforeRow ? beforeRow + i + newRows : null);
- newRows += self._expandRow(row);
+ var row = self._addRow(
+ rows,
+ new Zotero.CollectionTreeRow('group', groups[i]),
+ 1,
+ beforeRow ? beforeRow + i + newRows : null
+ );
+ newRows += yield self._expandRow(rows, row);
}
return newRows;
- }
+ })
}
- var row = this._showRow(new Zotero.ItemGroup('header', header));
+ var row = this._addRow(newRows, new Zotero.CollectionTreeRow('header', header));
if (this._containerState.HG) {
- this._dataItems[row][1] = true;
- header.expand(null, groups);
+ newRows[row][1] = true;
+ yield header.expand(newRows, null, groups);
}
}
- try {
- this._refreshHashMap();
- }
- catch (e) {
- Components.utils.reportError(e);
- Zotero.debug(e);
- throw (e);
- }
+ this.selection.clearSelection();
+ this._rows = newRows;
+ this.rowCount = this._rows.length;
+ this._refreshCollectionRowMap();
- // Update the treebox's row count
var diff = this.rowCount - oldCount;
if (diff != 0) {
this._treebox.rowCountChanged(0, diff);
}
-}
+});
/*
@@ -214,16 +209,16 @@ Zotero.CollectionTreeView.prototype.refresh = function()
*/
Zotero.CollectionTreeView.prototype.reload = function()
{
- this._treebox.beginUpdateBatch();
- this.refresh();
- this._treebox.invalidate();
- this._treebox.endUpdateBatch();
+ return this.refresh()
+ .then(function () {
+ this._treebox.invalidate();
+ }.bind(this));
}
/*
* Called by Zotero.Notifier on any changes to collections in the data layer
*/
-Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
+Zotero.CollectionTreeView.prototype.notify = Zotero.Promise.coroutine(function* (action, type, ids)
{
if ((!ids || ids.length == 0) && action != 'refresh' && action != 'redraw') {
return;
@@ -235,7 +230,9 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
}
if (action == 'refresh' && type == 'trash') {
- this._trashNotEmpty[ids[0]] = !!Zotero.Items.getDeleted(ids[0]).length;
+ // libraryID is passed as parameter to 'refresh'
+ let deleted = yield Zotero.Items.getDeleted(ids[0], true);
+ this._trashNotEmpty[ids[0]] = !!deleted.length;
return;
}
@@ -253,7 +250,7 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
//Since a delete involves shifting of rows, we have to do it in order
//sort the ids by row
- var rows = new Array();
+ var rows = [];
for (var i in ids)
{
switch (type)
@@ -277,7 +274,7 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
// For now, just reload if a group is removed, since otherwise
// we'd have to remove collections too
- this.reload();
+ yield this.reload();
this.rememberSelection(savedSelection);
break;
}
@@ -290,11 +287,11 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
for(var i=0, len=rows.length; i<len; i++)
{
var row = rows[i];
- this._hideItem(row-i);
+ this._removeRow(row-i);
this._treebox.rowCountChanged(row-i,-1);
}
- this._refreshHashMap();
+ this._refreshCollectionRowMap();
}
if (!this.selection.count) {
@@ -303,22 +300,23 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
}
else if(action == 'move')
{
+ yield this.reload();
+
+ // Open the new parent collection if closed
for (var i=0; i<ids.length; i++) {
- // Open the parent collection if closed
- var collection = Zotero.Collections.get(ids[i]);
- var parentID = collection.parent;
+ var collection = yield Zotero.Collections.getAsync(ids[i]);
+ var parentID = collection.parentID;
if (parentID && this._collectionRowMap[parentID] &&
!this.isContainerOpen(this._collectionRowMap[parentID])) {
- this.toggleOpenState(this._collectionRowMap[parentID]);
+ yield this.toggleOpenState(this._collectionRowMap[parentID]);
}
}
- this.reload();
this.rememberSelection(savedSelection);
}
else if (action == 'modify' || action == 'refresh') {
if (type != 'bucket') {
- this.reload();
+ yield this.reload();
}
this.rememberSelection(savedSelection);
}
@@ -330,17 +328,17 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
switch (type)
{
case 'collection':
- var collection = Zotero.Collections.get(ids);
+ var collection = yield Zotero.Collections.getAsync(ids);
// Open container if creating subcollection
- var parentID = collection.parent;
+ var parentID = collection.parentID;
if (parentID) {
if (!this.isContainerOpen(this._collectionRowMap[parentID])){
this.toggleOpenState(this._collectionRowMap[parentID]);
}
}
- this.reload();
+ yield this.reload();
if (Zotero.suppressUIUpdates) {
this.rememberSelection(savedSelection);
break;
@@ -349,7 +347,7 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
break;
case 'search':
- this.reload();
+ yield this.reload();
if (Zotero.suppressUIUpdates) {
this.rememberSelection(savedSelection);
break;
@@ -358,36 +356,36 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
break;
case 'group':
- this.reload();
+ yield this.reload();
// Groups can only be created during sync
this.rememberSelection(savedSelection);
break;
case 'bucket':
- this.reload();
+ yield this.reload();
this.rememberSelection(savedSelection);
break;
}
}
this.selection.selectEventsSuppressed = false;
-}
+});
/*
* Set the rows that should be highlighted -- actual highlighting is done
* by getRowProperties based on the array set here
*/
-Zotero.CollectionTreeView.prototype.setHighlightedRows = function (ids) {
+Zotero.CollectionTreeView.prototype.setHighlightedRows = Zotero.Promise.coroutine(function* (ids) {
this._highlightedRows = {};
this._treebox.invalidate();
for each(var id in ids) {
- this.expandToCollection(id);
+ yield this.expandToCollection(id);
this._highlightedRows[this._collectionRowMap[id]] = true;
this._treebox.invalidateRow(this._collectionRowMap[id]);
}
-}
+});
/*
@@ -408,7 +406,7 @@ Zotero.CollectionTreeView.prototype.unregister = function()
Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
{
- var obj = this._getItemAtRow(row);
+ var obj = this.getRow(row);
if (column.id == 'zotero-collections-name-column') {
return obj.getName();
@@ -419,8 +417,8 @@ Zotero.CollectionTreeView.prototype.getCellText = function(row, column)
Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
{
- var itemGroup = this._getItemAtRow(row);
- var collectionType = itemGroup.type;
+ var treeRow = this.getRow(row);
+ var collectionType = treeRow.type;
if (collectionType == 'group') {
collectionType = 'library';
@@ -436,16 +434,16 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
break;
case 'trash':
- if (this._trashNotEmpty[itemGroup.ref.libraryID]) {
+ if (this._trashNotEmpty[treeRow.ref.libraryID]) {
collectionType += '-full';
}
break;
case 'header':
- if (itemGroup.ref.id == 'group-libraries-header') {
+ if (treeRow.ref.id == 'group-libraries-header') {
collectionType = 'groups';
}
- else if (itemGroup.ref.id == 'commons-header') {
+ else if (treeRow.ref.id == 'commons-header') {
collectionType = 'commons';
}
break;
@@ -464,13 +462,13 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
Zotero.CollectionTreeView.prototype.isContainer = function(row)
{
- var itemGroup = this._getItemAtRow(row);
- return itemGroup.isLibrary(true) || itemGroup.isCollection() || itemGroup.isHeader() || itemGroup.isBucket();
+ var treeRow = this.getRow(row);
+ return treeRow.isLibrary(true) || treeRow.isCollection() || treeRow.isHeader() || treeRow.isBucket();
}
Zotero.CollectionTreeView.prototype.isContainerOpen = function(row)
{
- return this._dataItems[row][1];
+ return this._rows[row][1];
}
/*
@@ -478,34 +476,34 @@ Zotero.CollectionTreeView.prototype.isContainerOpen = function(row)
*/
Zotero.CollectionTreeView.prototype.isContainerEmpty = function(row)
{
- var itemGroup = this._getItemAtRow(row);
- if (itemGroup.isLibrary()) {
+ var treeRow = this.getRow(row);
+ if (treeRow.isLibrary()) {
return false;
}
- if (itemGroup.isHeader()) {
+ if (treeRow.isHeader()) {
return false;
}
- if (itemGroup.isBucket()) {
+ if (treeRow.isBucket()) {
return true;
}
- if (itemGroup.isGroup()) {
- var libraryID = itemGroup.ref.libraryID;
+ if (treeRow.isGroup()) {
+ var libraryID = treeRow.ref.libraryID;
- return !itemGroup.ref.hasCollections()
- && !itemGroup.ref.hasSearches()
+ return !treeRow.ref.hasCollections()
+ && !treeRow.ref.hasSearches()
&& this._duplicateLibraries.indexOf(libraryID) == -1
&& this._unfiledLibraries.indexOf(libraryID) == -1
&& this.hideSources.indexOf('trash') != -1;
}
- if (itemGroup.isCollection()) {
- return !itemGroup.ref.hasChildCollections();
+ if (treeRow.isCollection()) {
+ return !treeRow.ref.hasChildCollections();
}
return true;
}
Zotero.CollectionTreeView.prototype.getLevel = function(row)
{
- return this._dataItems[row][2];
+ return this._rows[row][2];
}
Zotero.CollectionTreeView.prototype.getParentIndex = function(row)
@@ -532,41 +530,45 @@ Zotero.CollectionTreeView.prototype.hasNextSibling = function(row, afterIndex)
/*
* Opens/closes the specified row
*/
-Zotero.CollectionTreeView.prototype.toggleOpenState = function(row)
+Zotero.CollectionTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(function* (row)
{
- var count = 0; //used to tell the tree how many rows were added/removed
+ var count = 0;
var thisLevel = this.getLevel(row);
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
if (this.isContainerOpen(row)) {
- while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel))
+ while((row + 1 < this._rows.length) && (this.getLevel(row + 1) > thisLevel))
{
- this._hideItem(row+1);
- count--; //count is negative when closing a container because we are removing rows
+ this._removeRow(row+1);
+ count--;
}
+ // Remove from the end of the row's children
+ this._treebox.rowCountChanged(row + 1 + Math.abs(count), count);
}
else {
- var itemGroup = this._getItemAtRow(row);
- if (itemGroup.type == 'header') {
- count = itemGroup.ref.expand(row + 1);
+ var treeRow = this.getRow(row);
+ if (treeRow.type == 'header') {
+ count = yield treeRow.ref.expand(this._rows, row + 1);
}
- else if (itemGroup.isLibrary(true) || itemGroup.isCollection()) {
- count = this._expandRow(row, true);
+ else if (treeRow.isLibrary(true) || treeRow.isCollection()) {
+ count = yield this._expandRow(this._rows, row, true);
}
+ this.rowCount += count;
+ this._treebox.rowCountChanged(row + 1, count);
}
- this._dataItems[row][1] = !this._dataItems[row][1]; //toggle container open value
- this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these
+ // Toggle container open value
+ this._rows[row][1] = !this._rows[row][1];
this._treebox.invalidateRow(row);
- this._treebox.endUpdateBatch();
- this._refreshHashMap();
- this._rememberOpenStates();
-}
+ //this._treebox.endUpdateBatch();
+ this._refreshCollectionRowMap();
+ yield this._rememberOpenStates();
+});
Zotero.CollectionTreeView.prototype.isSelectable = function (row, col) {
- var itemGroup = this._getItemAtRow(row);
- switch (itemGroup.type) {
+ var treeRow = this.getRow(row);
+ switch (treeRow.type) {
case 'separator':
return false;
}
@@ -578,7 +580,7 @@ Zotero.CollectionTreeView.prototype.isSelectable = function (row, col) {
* Tree method for whether to allow inline editing (not to be confused with this.editable)
*/
Zotero.CollectionTreeView.prototype.isEditable = function (row, col) {
- return this.itemGroup.isCollection() && this.editable;
+ return this.selectedTreeRow.isCollection() && this.editable;
}
@@ -587,9 +589,9 @@ Zotero.CollectionTreeView.prototype.setCellText = function (row, col, val) {
if (val === "") {
return;
}
- var itemGroup = this._getItemAtRow(row);
- itemGroup.ref.name = val;
- itemGroup.ref.save();
+ var treeRow = this.getRow(row);
+ treeRow.ref.name = val;
+ treeRow.ref.save();
}
@@ -598,23 +600,23 @@ Zotero.CollectionTreeView.prototype.setCellText = function (row, col, val) {
* Returns TRUE if the underlying view is editable
*/
Zotero.CollectionTreeView.prototype.__defineGetter__('editable', function () {
- return this._getItemAtRow(this.selection.currentIndex).editable;
+ return this.getRow(this.selection.currentIndex).editable;
});
-Zotero.CollectionTreeView.prototype.expandLibrary = function(self) {
- var selectedLibraryID = self.getSelectedLibraryID();
+Zotero.CollectionTreeView.prototype.expandLibrary = Zotero.Promise.coroutine(function* () {
+ var selectedLibraryID = this.getSelectedLibraryID();
if (selectedLibraryID === false) {
return;
}
- self._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
- var selection = self.saveSelection();
+ var selection = this.saveSelection();
var found = false;
- for (var i=0; i<self.rowCount; i++) {
- if (self._getItemAtRow(i).ref.libraryID != selectedLibraryID) {
+ for (var i=0; i<this.rowCount; i++) {
+ if (this.getRow(i).ref.libraryID != selectedLibraryID) {
// Once we've moved beyond the original library, stop looking
if (found) {
break;
@@ -624,28 +626,28 @@ Zotero.CollectionTreeView.prototype.expandLibrary = function(self) {
found = true;
- if (self.isContainer(i) && !self.isContainerOpen(i)) {
- self.toggleOpenState(i);
+ if (this.isContainer(i) && !this.isContainerOpen(i)) {
+ yield this.toggleOpenState(i);
}
}
- self._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
- self.rememberSelection(selection);
-}
+ this.rememberSelection(selection);
+});
-Zotero.CollectionTreeView.prototype.collapseLibrary = function(self) {
- var selectedLibraryID = self.getSelectedLibraryID();
+Zotero.CollectionTreeView.prototype.collapseLibrary = Zotero.Promise.coroutine(function* () {
+ var selectedLibraryID = this.getSelectedLibraryID();
if (selectedLibraryID === false) {
return;
}
- self._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
var found = false;
- for (var i=self.rowCount-1; i>=0; i--) {
- if (self._getItemAtRow(i).ref.libraryID !== selectedLibraryID) {
+ for (var i=this.rowCount-1; i>=0; i--) {
+ if (this.getRow(i).ref.libraryID !== selectedLibraryID) {
// Once we've moved beyond the original library, stop looking
if (found) {
break;
@@ -655,20 +657,20 @@ Zotero.CollectionTreeView.prototype.collapseLibrary = function(self) {
found = true;
- if (self.isContainer(i) && self.isContainerOpen(i)) {
- self.toggleOpenState(i);
+ if (this.isContainer(i) && this.isContainerOpen(i)) {
+ yield this.toggleOpenState(i);
}
}
- self._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
// Select the collapsed library
- self.selectLibrary(selectedLibraryID);
-}
+ yield this.selectLibrary(selectedLibraryID);
+});
-Zotero.CollectionTreeView.prototype.expandToCollection = function(collectionID) {
- var col = Zotero.Collections.get(collectionID);
+Zotero.CollectionTreeView.prototype.expandToCollection = Zotero.Promise.coroutine(function* (collectionID) {
+ var col = yield Zotero.Collections.getAsync(collectionID);
if (!col) {
Zotero.debug("Cannot expand to nonexistent collection " + collectionID, 2);
return false;
@@ -679,18 +681,18 @@ Zotero.CollectionTreeView.prototype.expandToCollection = function(collectionID)
}
var path = [];
var parent;
- while (parent = col.getParent()) {
+ while (parent = col.parentID) {
path.unshift(parent);
- col = Zotero.Collections.get(parent);
+ col = yield Zotero.Collections.getAsync(parentID);
}
for each(var id in path) {
row = this._collectionRowMap[id];
if (!this.isContainerOpen(row)) {
- this.toggleOpenState(row);
+ yield this.toggleOpenState(row);
}
}
return true;
-}
+});
@@ -702,7 +704,7 @@ Zotero.CollectionTreeView.prototype.expandToCollection = function(collectionID)
/**
* @param {Integer} libraryID Library to select
*/
-Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
+Zotero.CollectionTreeView.prototype.selectLibrary = Zotero.Promise.coroutine(function* (libraryID) {
if (Zotero.suppressUIUpdates) {
Zotero.debug("UI updates suppressed -- not changing library selection");
return false;
@@ -717,8 +719,8 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
// Check if library is already selected
if (this.selection.currentIndex != -1) {
- var itemGroup = this._getItemAtRow(this.selection.currentIndex);
- if (itemGroup.isLibrary(true) && itemGroup.ref.libraryID == libraryID) {
+ var treeRow = this.getRow(this.selection.currentIndex);
+ if (treeRow.isLibrary(true) && treeRow.ref.libraryID == libraryID) {
this._treebox.ensureRowIsVisible(this.selection.currentIndex);
return true;
}
@@ -726,16 +728,16 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
// Find library
for (var i = 0; i < this.rowCount; i++) {
- var itemGroup = this._getItemAtRow(i);
+ var treeRow = this.getRow(i);
// If group header is closed, open it
- if (itemGroup.isHeader() && itemGroup.ref.id == 'group-libraries-header'
+ if (treeRow.isHeader() && treeRow.ref.id == 'group-libraries-header'
&& !this.isContainerOpen(i)) {
- this.toggleOpenState(i);
+ yield this.toggleOpenState(i);
continue;
}
- if (itemGroup.ref && itemGroup.ref.libraryID == libraryID) {
+ if (treeRow.ref && treeRow.ref.libraryID == libraryID) {
this._treebox.ensureRowIsVisible(i);
this.selection.select(i);
return true;
@@ -743,13 +745,13 @@ Zotero.CollectionTreeView.prototype.selectLibrary = function (libraryID) {
}
return false;
-}
+});
/**
* Select the last-viewed source
*/
-Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
+Zotero.CollectionTreeView.prototype.getLastViewedRow = Zotero.Promise.coroutine(function* () {
var lastViewedFolder = Zotero.Prefs.get('lastViewedFolder');
var matches = lastViewedFolder.match(/^([A-Z])([G0-9]+)?$/);
var select = 0;
@@ -765,14 +767,14 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
var lastCol = matches[2];
do {
failsafe--;
- var col = Zotero.Collections.get(lastCol);
+ var col = yield Zotero.Collections.getAsync(lastCol);
if (!col) {
var msg = "Last-viewed collection not found";
Zotero.debug(msg);
path = [];
break;
}
- var par = col.getParent();
+ var par = col.parentID;
if (!par) {
var msg = "Parent collection not found in "
+ "Zotero.CollectionTreeView.setTree()";
@@ -797,7 +799,7 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
break;
}
if (!this.isContainerOpen(row)) {
- this.toggleOpenState(row);
+ yield this.toggleOpenState(row);
if (this._collectionRowMap[matches[2]]) {
select = this._collectionRowMap[matches[2]];
break;
@@ -816,22 +818,24 @@ Zotero.CollectionTreeView.prototype.getLastViewedRow = function () {
}
return select;
-}
+});
/*
* Delete the selection
*/
-Zotero.CollectionTreeView.prototype.deleteSelection = function(deleteItems)
+Zotero.CollectionTreeView.prototype.deleteSelection = Zotero.Promise.coroutine(function* (deleteItems)
{
if(this.selection.count == 0)
return;
//collapse open collections
- for(var i=0; i<this.rowCount; i++)
- if(this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i))
- this.toggleOpenState(i);
- this._refreshHashMap();
+ for (let i=0; i<this.rowCount; i++) {
+ if (this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i)) {
+ yield this.toggleOpenState(i);
+ }
+ }
+ this._refreshCollectionRowMap();
//create an array of collections
var rows = new Array();
@@ -841,27 +845,27 @@ Zotero.CollectionTreeView.prototype.deleteSelection = function(deleteItems)
{
this.selection.getRangeAt(i,start,end);
for (var j=start.value; j<=end.value; j++)
- if(!this._getItemAtRow(j).isLibrary())
+ if(!this.getRow(j).isLibrary())
rows.push(j);
}
//iterate and erase...
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
for (var i=0; i<rows.length; i++)
{
//erase collection from DB:
- var itemGroup = this._getItemAtRow(rows[i]-i);
- if (itemGroup.isCollection()) {
- itemGroup.ref.erase(deleteItems);
+ var treeRow = this.getRow(rows[i]-i);
+ if (treeRow.isCollection()) {
+ yield treeRow.ref.erase(deleteItems);
}
- else if (itemGroup.isSearch()) {
- Zotero.Searches.erase(itemGroup.ref.id);
+ else if (treeRow.isSearch()) {
+ yield Zotero.Searches.erase(treeRow.ref.id);
}
}
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
if (end.value < this.rowCount) {
- var row = this._getItemAtRow(end.value);
+ var row = this.getRow(end.value);
if (row.isSeparator()) {
return;
}
@@ -870,30 +874,30 @@ Zotero.CollectionTreeView.prototype.deleteSelection = function(deleteItems)
else {
this.selection.select(this.rowCount-1);
}
-}
+});
/**
* Expand row based on last state, or manually from toggleOpenState()
*/
-Zotero.CollectionTreeView.prototype._expandRow = function (row, forceOpen) {
- var itemGroup = this._getItemAtRow(row);
- var isLibrary = itemGroup.isLibrary(true);
- var isGroup = itemGroup.isGroup();
- var isCollection = itemGroup.isCollection();
- var level = this.getLevel(row);
- var libraryID = itemGroup.ref.libraryID;
+Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(function* (rows, row, forceOpen) {
+ var treeRow = rows[row][0];
+ var level = rows[row][2];
+ var isLibrary = treeRow.isLibrary(true);
+ var isGroup = treeRow.isGroup();
+ var isCollection = treeRow.isCollection();
+ var libraryID = treeRow.ref.libraryID;
if (isGroup) {
- var group = Zotero.Groups.getByLibraryID(libraryID);
- var collections = group.getCollections();
+ var group = yield Zotero.Groups.getByLibraryID(libraryID);
+ var collections = yield group.getCollections();
}
else {
- var collections = Zotero.getCollections(itemGroup.ref.id);
+ var collections = yield Zotero.Collections.getByParent(libraryID, treeRow.ref.id);
}
if (isLibrary) {
- var savedSearches = Zotero.Searches.getAll(libraryID);
+ var savedSearches = yield Zotero.Searches.getAll(libraryID);
var showDuplicates = (this.hideSources.indexOf('duplicates') == -1
&& this._duplicateLibraries.indexOf(libraryID) != -1);
var showUnfiled = this._unfiledLibraries.indexOf(libraryID) != -1;
@@ -910,9 +914,9 @@ Zotero.CollectionTreeView.prototype._expandRow = function (row, forceOpen) {
// or this is a collection that isn't explicitly opened,
// set the initial state to closed
if (!forceOpen &&
- (this._containerState[itemGroup.id] === false
- || (isCollection && !this._containerState[itemGroup.id]))) {
- this._dataItems[row][1] = false;
+ (this._containerState[treeRow.id] === false
+ || (isCollection && !this._containerState[treeRow.id]))) {
+ rows[row][1] = false;
return 0;
}
@@ -921,7 +925,7 @@ Zotero.CollectionTreeView.prototype._expandRow = function (row, forceOpen) {
// If this isn't a manual open, set the initial state depending on whether
// there are child nodes
if (!forceOpen) {
- this._dataItems[row][1] = startOpen;
+ rows[row][1] = startOpen;
}
if (!startOpen) {
@@ -937,10 +941,15 @@ Zotero.CollectionTreeView.prototype._expandRow = function (row, forceOpen) {
continue;
}
- var newRow = this._showRow(new Zotero.ItemGroup('collection', collections[i]), level + 1, row + 1 + newRows);
+ var newRow = this._addRow(
+ rows,
+ new Zotero.CollectionTreeRow('collection', collections[i]),
+ level + 1,
+ row + 1 + newRows
+ );
// Recursively expand child collections that should be open
- newRows += this._expandRow(newRow);
+ newRows += yield this._expandRow(rows, newRow);
newRows++;
}
@@ -951,59 +960,57 @@ Zotero.CollectionTreeView.prototype._expandRow = function (row, forceOpen) {
// Add searches
for (var i = 0, len = savedSearches.length; i < len; i++) {
- this._showRow(new Zotero.ItemGroup('search', savedSearches[i]), level + 1, row + 1 + newRows);
+ this._addRow(rows, new Zotero.CollectionTreeRow('search', savedSearches[i]), level + 1, row + 1 + newRows);
newRows++;
}
// Duplicate items
if (showDuplicates) {
- var d = new Zotero.Duplicates(libraryID);
- this._showRow(new Zotero.ItemGroup('duplicates', d), level + 1, row + 1 + newRows);
+ let d = new Zotero.Duplicates(libraryID);
+ this._addRow(rows, new Zotero.CollectionTreeRow('duplicates', d), level + 1, row + 1 + newRows);
newRows++;
}
// Unfiled items
if (showUnfiled) {
- var s = new Zotero.Search;
+ let s = new Zotero.Search;
s.libraryID = libraryID;
s.name = Zotero.getString('pane.collections.unfiled');
- s.addCondition('libraryID', 'is', libraryID);
- s.addCondition('unfiled', 'true');
- this._showRow(new Zotero.ItemGroup('unfiled', s), level + 1, row + 1 + newRows);
+ yield s.addCondition('libraryID', 'is', libraryID);
+ yield s.addCondition('unfiled', 'true');
+ this._addRow(rows, new Zotero.CollectionTreeRow('unfiled', s), level + 1, row + 1 + newRows);
newRows++;
}
if (showTrash) {
- var deletedItems = Zotero.Items.getDeleted(libraryID);
+ let deletedItems = yield Zotero.Items.getDeleted(libraryID);
if (deletedItems.length || Zotero.Prefs.get("showTrashWhenEmpty")) {
var ref = {
libraryID: libraryID
};
- this._showRow(new Zotero.ItemGroup('trash', ref), level + 1, row + 1 + newRows);
+ this._addRow(rows, new Zotero.CollectionTreeRow('trash', ref), level + 1, row + 1 + newRows);
newRows++;
}
this._trashNotEmpty[libraryID] = !!deletedItems.length;
}
return newRows;
-}
+});
/*
* Called by various view functions to show a row
*/
-Zotero.CollectionTreeView.prototype._showRow = function(itemGroup, level, beforeRow)
-{
+Zotero.CollectionTreeView.prototype._addRow = function (rows, treeRow, level, beforeRow) {
if (!level) {
level = 0;
}
if (!beforeRow) {
- beforeRow = this._dataItems.length;
+ beforeRow = rows.length;
}
- this._dataItems.splice(beforeRow, 0, [itemGroup, false, level]);
- this.rowCount++;
+ rows.splice(beforeRow, 0, [treeRow, false, level]);
return beforeRow;
}
@@ -1012,26 +1019,44 @@ Zotero.CollectionTreeView.prototype._showRow = function(itemGroup, level, before
/*
* Called by view to hide specified row
*/
-Zotero.CollectionTreeView.prototype._hideItem = function(row)
+Zotero.CollectionTreeView.prototype._removeRow = function(row)
{
- this._dataItems.splice(row,1); this.rowCount--;
+ this._rows.splice(row,1);
+ this.rowCount--;
+ if (this.selection.isSelected(row)) {
+ this.selection.toggleSelect(row);
+ }
}
/**
- * Returns Zotero.ItemGroup at row
- *
- * @deprecated Use getItemGroupAtRow()
+ * Returns Zotero.CollectionTreeRow at row
*/
-Zotero.CollectionTreeView.prototype._getItemAtRow = function(row)
-{
- return this._dataItems[row][0];
+Zotero.CollectionTreeView.prototype.getRow = function (row) {
+ return this._rows[row][0];
}
-Zotero.CollectionTreeView.prototype.getItemGroupAtRow = function(row)
-{
- return this._dataItems[row][0];
+/**
+ * Returns libraryID or FALSE if not a library
+ */
+Zotero.CollectionTreeView.prototype.getSelectedLibraryID = function() {
+ var treeRow = this.getRow(this.selection.currentIndex);
+ return treeRow && treeRow.ref && treeRow.ref.libraryID !== undefined
+ && treeRow.ref.libraryID;
+}
+
+
+Zotero.CollectionTreeView.prototype.getSelectedCollection = function(asID) {
+ if (this.selection
+ && this.selection.count > 0
+ && this.selection.currentIndex != -1) {
+ var collection = this.getRow(this.selection.currentIndex);
+ if (collection && collection.isCollection()) {
+ return asID ? collection.ref.id : collection.ref;
+ }
+ }
+ return false;
}
@@ -1042,8 +1067,8 @@ Zotero.CollectionTreeView.prototype.saveSelection = function()
{
for (var i=0, len=this.rowCount; i<len; i++) {
if (this.selection.isSelected(i)) {
- var itemGroup = this._getItemAtRow(i);
- var id = itemGroup.id;
+ var treeRow = this.getRow(i);
+ var id = treeRow.id;
if (id) {
return id;
}
@@ -1058,58 +1083,35 @@ Zotero.CollectionTreeView.prototype.saveSelection = function()
/*
* Sets the selection based on saved selection ids (see above)
*/
-Zotero.CollectionTreeView.prototype.rememberSelection = function(selection)
+Zotero.CollectionTreeView.prototype.rememberSelection = Zotero.Promise.coroutine(function* (selection)
{
if (selection && this._rowMap[selection] != 'undefined') {
this.selection.select(this._rowMap[selection]);
}
-}
-
-
-/**
- * Returns libraryID or FALSE if not a library
- */
-Zotero.CollectionTreeView.prototype.getSelectedLibraryID = function() {
- var itemGroup = this._getItemAtRow(this.selection.currentIndex);
- return itemGroup && itemGroup.ref && itemGroup.ref.libraryID !== undefined
- && itemGroup.ref.libraryID;
-}
-
-
-Zotero.CollectionTreeView.prototype.getSelectedCollection = function(asID) {
- if (this.selection
- && this.selection.count > 0
- && this.selection.currentIndex != -1) {
- var collection = this._getItemAtRow(this.selection.currentIndex);
- if (collection && collection.isCollection()) {
- return asID ? collection.ref.id : collection.ref;
- }
- }
- return false;
-}
+});
/**
* Creates mapping of item group ids to tree rows
*/
-Zotero.CollectionTreeView.prototype._refreshHashMap = function()
+Zotero.CollectionTreeView.prototype._refreshCollectionRowMap = function()
{
this._collectionRowMap = [];
this._rowMap = [];
for(var i = 0, len = this.rowCount; i < len; i++) {
- var itemGroup = this._getItemAtRow(i);
+ var treeRow = this.getRow(i);
// Collections get special treatment for now
- if (itemGroup.isCollection()) {
- this._collectionRowMap[itemGroup.ref.id] = i;
+ if (treeRow.isCollection()) {
+ this._collectionRowMap[treeRow.ref.id] = i;
}
- this._rowMap[itemGroup.id] = i;
+ this._rowMap[treeRow.id] = i;
}
}
-Zotero.CollectionTreeView.prototype._rememberOpenStates = function () {
+Zotero.CollectionTreeView.prototype._rememberOpenStates = Zotero.Promise.coroutine(function* () {
var state = this._containerState;
// Every so often, remove obsolete rows
@@ -1118,7 +1120,7 @@ Zotero.CollectionTreeView.prototype._rememberOpenStates = function () {
for (var id in state) {
var m = id.match(/^C([0-9]+)$/);
if (m) {
- if (!Zotero.Collections.get(m[1])) {
+ if (!(yield Zotero.Collections.getAsync(m[1]))) {
delete state[id];
}
continue;
@@ -1139,25 +1141,25 @@ Zotero.CollectionTreeView.prototype._rememberOpenStates = function () {
continue;
}
- var itemGroup = this._getItemAtRow(i);
- if (!itemGroup.id) {
+ var treeRow = this.getRow(i);
+ if (!treeRow.id) {
continue;
}
var open = this.isContainerOpen(i);
// Collections default to closed
- if (!open && itemGroup.isCollection()) {
- delete state[itemGroup.id];
+ if (!open && treeRow.isCollection()) {
+ delete state[treeRow.id];
continue;
}
- state[itemGroup.id] = open;
+ state[treeRow.id] = open;
}
this._containerState = state;
Zotero.Prefs.set("sourceList.persist", JSON.stringify(state));
-}
+});
////////////////////////////////////////////////////////////////////////////////
@@ -1206,11 +1208,11 @@ Zotero.CollectionTreeView.prototype.onDragStart = function(event) {
event.dataTransfer.effectAllowed = 'move';
}
- var itemGroup = this.itemGroup;
- if (!itemGroup.isCollection()) {
+ var treeRow = this.selectedTreeRow;
+ if (!treeRow.isCollection()) {
return;
}
- event.dataTransfer.setData("zotero/collection", itemGroup.ref.id);
+ event.dataTransfer.setData("zotero/collection", treeRow.ref.id);
}
@@ -1235,13 +1237,14 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
}
// Directly on a row
else if (orient == 0) {
- var itemGroup = this._getItemAtRow(row); //the collection we are dragging over
+ var treeRow = this.getRow(row); //the collection we are dragging over
- if (dataType == 'zotero/item' && itemGroup.isBucket()) {
+ if (dataType == 'zotero/item' && treeRow.isBucket()) {
return true;
}
- if (!itemGroup.editable) {
+ if (!treeRow.editable) {
+ Zotero.debug("Drop target not editable");
return false;
}
@@ -1249,18 +1252,22 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
var ids = data;
var items = Zotero.Items.get(ids);
var skip = true;
+ Zotero.debug(ids);
for each(var item in items) {
// Can only drag top-level items
if (!item.isTopLevelItem()) {
+ Zotero.debug("Can't drag child item");
return false;
}
- if (itemGroup.isWithinGroup() && item.isAttachment()) {
+ if (treeRow.isWithinGroup() && item.isAttachment()) {
// Linked files can't be added to groups
if (item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
+ Zotero.debug("Linked files cannot be added to groups");
return false;
}
- if (!itemGroup.filesEditable) {
+ if (!treeRow.filesEditable) {
+ Zotero.debug("Drop target does not allow files to be edited");
return false;
}
skip = false;
@@ -1268,30 +1275,12 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
}
// Cross-library drag
- if (itemGroup.ref.libraryID != item.libraryID) {
+ if (treeRow.ref.libraryID != item.libraryID) {
// Only allow cross-library drag to root library and collections
- if (!(itemGroup.isLibrary(true) || itemGroup.isCollection())) {
+ if (!(treeRow.isLibrary(true) || treeRow.isCollection())) {
+ Zotero.debug("Cross-library drag to non-collection not allowed");
return false;
}
-
- var linkedItem = item.getLinkedItem(itemGroup.ref.libraryID);
- if (linkedItem && !linkedItem.deleted) {
- // For drag to root, skip if linked item exists
- if (itemGroup.isLibrary(true)) {
- continue;
- }
- // For drag to collection
- else if (itemGroup.isCollection()) {
- // skip if linked item is already in it
- if (itemGroup.ref.hasItem(linkedItem.id)) {
- continue;
- }
- // or if linked item is a child item
- else if (linkedItem.getSource()) {
- continue;
- }
- }
- }
skip = false;
continue;
}
@@ -1299,24 +1288,25 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
// Intra-library drag
// Don't allow drag onto root of same library
- if (itemGroup.isLibrary(true)) {
+ if (treeRow.isLibrary(true)) {
+ Zotero.debug("Can't drag into same library root");
return false;
}
- // Make sure there's at least one item that's not already
- // in this collection
- if (itemGroup.isCollection() && !itemGroup.ref.hasItem(item.id)) {
+ // Allow drags to collections. Item collection membership is an asynchronous
+ // check, so we do that on drop()
+ if (treeRow.isCollection()) {
skip = false;
- continue;
}
}
if (skip) {
+ Zotero.debug("Drag skipped");
return false;
}
return true;
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
- if (itemGroup.isSearch()) {
+ if (treeRow.isSearch()) {
return false;
}
if (dataType == 'application/x-moz-file') {
@@ -1325,7 +1315,7 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
return false;
}
// Don't allow drop if no permissions
- if (!itemGroup.filesEditable) {
+ if (!treeRow.filesEditable) {
return false;
}
}
@@ -1333,56 +1323,148 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr
return true;
}
else if (dataType == 'zotero/collection') {
- var col = Zotero.Collections.get(data[0]);
+ let draggedCollectionID = data[0];
+ let draggedCollection = Zotero.Collections.get(draggedCollectionID);
- if (itemGroup.ref.libraryID == col.libraryID) {
+ if (treeRow.ref.libraryID == draggedCollection.libraryID) {
// Collections cannot be dropped on themselves
- if (data[0] == itemGroup.ref.id) {
+ if (draggedCollectionID == treeRow.ref.id) {
return false;
}
// Nor in their children
- if (Zotero.Collections.get(data[0]).hasDescendent('collection', itemGroup.ref.id)) {
+ // TODO: figure out synchronously from tree
+ /*if (yield col.hasDescendent('collection', treeRow.ref.id)) {
return false;
- }
+ }*/
}
// Dragging a collection to a different library
else {
// Allow cross-library drag only to root library and collections
- if (!itemGroup.isLibrary(true) && !itemGroup.isCollection()) {
+ if (!treeRow.isLibrary(true) && !treeRow.isCollection()) {
return false;
}
+ }
+
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * Perform additional asynchronous drop checks
+ *
+ * Called by treechildren.drop()
+ */
+Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine(function* (row, orient, dataTransfer) {
+ //Zotero.debug("Row is " + row + "; orient is " + orient);
+
+ var dragData = Zotero.DragDrop.getDataFromDataTransfer(dataTransfer);
+ if (!dragData) {
+ Zotero.debug("No drag data");
+ return false;
+ }
+ var dataType = dragData.dataType;
+ var data = dragData.data;
+
+ if (orient == 0) {
+ var treeRow = this.getRow(row); //the collection we are dragging over
+
+ if (dataType == 'zotero/item' && treeRow.isBucket()) {
+ return true;
+ }
+
+ if (dataType == 'zotero/item') {
+ if (treeRow.isCollection()) {
+ yield treeRow.ref.loadChildItems();
+ }
+
+ var ids = data;
+ var items = Zotero.Items.get(ids);
+ var skip = true;
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+
+ // Cross-library drag
+ if (treeRow.ref.libraryID != item.libraryID) {
+ let linkedItem = yield item.getLinkedItem(treeRow.ref.libraryID);
+ if (linkedItem && !linkedItem.deleted) {
+ // For drag to root, skip if linked item exists
+ if (treeRow.isLibrary(true)) {
+ continue;
+ }
+ // For drag to collection
+ else if (treeRow.isCollection()) {
+ // skip if linked item is already in it
+ if (treeRow.ref.hasItem(linkedItem.id)) {
+ continue;
+ }
+ // or if linked item is a child item
+ else if (!linkedItem.isTopLevelItem()) {
+ continue;
+ }
+ }
+ }
+ skip = false;
+ continue;
+ }
+
+ // Intra-library drag
+ // Make sure there's at least one item that's not already
+ // in this collection
+ if (treeRow.isCollection()) {
+ if (treeRow.ref.hasItem(item.id)) {
+ Zotero.debug("Item " + item.id + " already exists in collection");
+ continue;
+ }
+ skip = false;
+ continue;
+ }
+ }
+ if (skip) {
+ Zotero.debug("Drag skipped");
+ return false;
+ }
+ }
+ else if (dataType == 'zotero/collection') {
+ let draggedCollectionID = data[0];
+ let draggedCollection = Zotero.Collections.get(draggedCollectionID);
+
+ // Dragging a collection to a different library
+ if (treeRow.ref.libraryID != draggedCollection.libraryID) {
// Disallow if linked collection already exists
- if (col.getLinkedCollection(itemGroup.ref.libraryID)) {
+ if (yield col.getLinkedCollection(treeRow.ref.libraryID)) {
return false;
}
- var descendents = col.getDescendents(false, 'collection');
+ var descendents = yield col.getDescendents(false, 'collection');
for each(var descendent in descendents) {
- descendent = Zotero.Collections.get(descendent.id);
+ descendent = yield Zotero.Collections.getAsync(descendent.id);
// Disallow if linked collection already exists for any subcollections
//
// If this is allowed in the future for the root collection,
// need to allow drag only to root
- if (descendent.getLinkedCollection(itemGroup.ref.libraryID)) {
+ if (yield descendent.getLinkedCollection(treeRow.ref.libraryID)) {
return false;
}
}
}
-
- return true;
}
}
- return false;
-}
+ return true;
+});
+
/*
* Called when something's been dropped on or next to a row
*/
-Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
+Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, orient, dataTransfer)
{
- if (!this.canDropCheck(row, orient, dataTransfer)) {
+ if (!this.canDrop(row, orient, dataTransfer)
+ || !(yield this.canDropCheckAsync(row, orient, dataTransfer))) {
return false;
}
@@ -1394,23 +1476,26 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
var dropEffect = dragData.dropEffect;
var dataType = dragData.dataType;
var data = dragData.data;
- var itemGroup = this.getItemGroupAtRow(row);
+ var event = Zotero.DragDrop.currentEvent;
+ var sourceTreeRow = Zotero.DragDrop.getDragSource(dataTransfer);
+ var targetTreeRow = Zotero.DragDrop.getDragTarget(event);
- function copyItem (item, targetLibraryID) {
+ var copyItem = Zotero.Promise.coroutine(function* (item, targetLibraryID) {
// Check if there's already a copy of this item in the library
- var linkedItem = item.getLinkedItem(targetLibraryID);
+ var linkedItem = yield item.getLinkedItem(targetLibraryID);
if (linkedItem) {
// If linked item is in the trash, undelete it
if (linkedItem.deleted) {
+ yield linkedItems.loadCollections();
// Remove from any existing collections, or else when it gets
// undeleted it would reappear in those collections
var collectionIDs = linkedItem.getCollections();
for each(var collectionID in collectionIDs) {
- var col = Zotero.Collections.get(collectionID);
+ var col = yield Zotero.Collections.getAsync(collectionID);
col.removeItem(linkedItem.id);
}
linkedItem.deleted = false;
- linkedItem.save();
+ yield linkedItem.save();
}
return linkedItem.id;
@@ -1462,7 +1547,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
return false;
}
- if (!itemGroup.filesEditable) {
+ if (!targetTreeRow.filesEditable) {
Zotero.debug("Skipping standalone file attachment on drag");
return false;
}
@@ -1474,15 +1559,15 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
var newItem = new Zotero.Item(item.itemTypeID);
newItem.libraryID = targetLibraryID;
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
- var id = newItem.save();
- newItem = Zotero.Items.get(id);
- item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
- newItem.save();
+ var id = yield newItem.save();
+ newItem = yield Zotero.Items.getAsync(id);
+ yield item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
+ yield newItem.save();
//var id = newItem.save();
//var newItem = Zotero.Items.get(id);
// Record link
- newItem.addLinkedItem(item);
+ yield newItem.addLinkedItem(item);
var newID = id;
if (item.isNote()) {
@@ -1494,18 +1579,18 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
// Child notes
if (Zotero.Prefs.get('groups.copyChildNotes')) {
var noteIDs = item.getNotes();
- var notes = Zotero.Items.get(noteIDs);
+ var notes = yield Zotero.Items.getAsync(noteIDs);
for each(var note in notes) {
var newNote = new Zotero.Item('note');
newNote.libraryID = targetLibraryID;
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
- var id = newNote.save();
- newNote = Zotero.Items.get(id);
- note.clone(false, newNote);
- newNote.setSource(newItem.id);
- newNote.save();
+ var id = yield newNote.save();
+ newNote = yield Zotero.Items.getAsync(id);
+ yield note.clone(false, newNote);
+ newNote.parentID = newItem.id;
+ yield newNote.save();
- newNote.addLinkedItem(note);
+ yield newNote.addLinkedItem(note);
}
}
@@ -1514,7 +1599,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
var copyChildFileAttachments = Zotero.Prefs.get('groups.copyChildFileAttachments');
if (copyChildLinks || copyChildFileAttachments) {
var attachmentIDs = item.getAttachments();
- var attachments = Zotero.Items.get(attachmentIDs);
+ var attachments = yield Zotero.Items.getAsync(attachmentIDs);
for each(var attachment in attachments) {
var linkMode = attachment.attachmentLinkMode;
@@ -1532,7 +1617,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
}
}
else {
- if (!copyChildFileAttachments || !itemGroup.filesEditable) {
+ if (!copyChildFileAttachments || !targetTreeRow.filesEditable) {
Zotero.debug("Skipping child file attachment on drag");
continue;
}
@@ -1543,82 +1628,79 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
}
return newID;
- }
-
+ });
- var targetLibraryID = itemGroup.ref.libraryID;
- var targetCollectionID = itemGroup.isCollection() ? itemGroup.ref.id : false;
+ var targetLibraryID = targetTreeRow.ref.libraryID;
+ var targetCollectionID = targetTreeRow.isCollection() ? targetTreeRow.ref.id : false;
if (dataType == 'zotero/collection') {
- var droppedCollection = Zotero.Collections.get(data[0]);
+ var droppedCollection = yield Zotero.Collections.getAsync(data[0]);
// Collection drag between libraries
if (targetLibraryID != droppedCollection.libraryID) {
- Zotero.DB.beginTransaction();
-
- function copyCollections(descendents, parent, addItems) {
- for each(var desc in descendents) {
- // Collections
- if (desc.type == 'collection') {
- var c = Zotero.Collections.get(desc.id);
-
- var newCollection = new Zotero.Collection;
- newCollection.libraryID = targetLibraryID;
- c.clone(false, newCollection);
- if (parent) {
- newCollection.parent = parent;
- }
- var collectionID = newCollection.save();
-
- // Record link
- c.addLinkedCollection(newCollection);
-
- // Recursively copy subcollections
- if (desc.children.length) {
- copyCollections(desc.children, collectionID, addItems);
- }
- }
- // Items
- else {
- var item = Zotero.Items.get(desc.id);
- var id = copyItem(item, targetLibraryID);
- // Standalone attachments might not get copied
- if (!id) {
- continue;
+ yield Zotero.DB.executeTransaction(function () {
+ function copyCollections(descendents, parentID, addItems) {
+ for each(var desc in descendents) {
+ // Collections
+ if (desc.type == 'collection') {
+ var c = yield Zotero.Collections.getAsync(desc.id);
+
+ var newCollection = new Zotero.Collection;
+ newCollection.libraryID = targetLibraryID;
+ yield c.clone(false, newCollection);
+ if (parentID) {
+ newCollection.parentID = parentID;
+ }
+ var collectionID = yield newCollection.save();
+
+ // Record link
+ c.addLinkedCollection(newCollection);
+
+ // Recursively copy subcollections
+ if (desc.children.length) {
+ copyCollections(desc.children, collectionID, addItems);
+ }
}
- // Mark copied item for adding to collection
- if (parent) {
- if (!addItems[parent]) {
- addItems[parent] = [];
+ // Items
+ else {
+ var item = yield Zotero.Items.getAsync(desc.id);
+ var id = yield copyItem(item, targetLibraryID);
+ // Standalone attachments might not get copied
+ if (!id) {
+ continue;
+ }
+ // Mark copied item for adding to collection
+ if (parentID) {
+ if (!addItems[parentID]) {
+ addItems[parentID] = [];
+ }
+ addItems[parentID].push(id);
}
- addItems[parent].push(id);
}
}
}
- }
-
- var collections = [{
- id: droppedCollection.id,
- children: droppedCollection.getDescendents(true),
- type: 'collection'
- }];
-
- var addItems = {};
- copyCollections(collections, targetCollectionID, addItems);
- for (var collectionID in addItems) {
- var collection = Zotero.Collections.get(collectionID);
- collection.addItems(addItems[collectionID]);
- }
-
- // TODO: add subcollections and subitems, if they don't already exist,
- // and display a warning if any of the subcollections already exist
-
- Zotero.DB.commitTransaction();
+
+ var collections = [{
+ id: droppedCollection.id,
+ children: droppedCollection.getDescendents(true),
+ type: 'collection'
+ }];
+
+ var addItems = {};
+ copyCollections(collections, targetCollectionID, addItems);
+ for (var collectionID in addItems) {
+ var collection = yield Zotero.Collections.getAsync(collectionID);
+ collection.addItems(addItems[collectionID]);
+ }
+
+ // TODO: add subcollections and subitems, if they don't already exist,
+ // and display a warning if any of the subcollections already exist
+ });
}
// Collection drag within a library
else {
- droppedCollection.parent = targetCollectionID;
- droppedCollection.save();
+ droppedCollection.parentID = targetCollectionID;
+ yield droppedCollection.save();
}
}
else if (dataType == 'zotero/item') {
@@ -1627,120 +1709,118 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
return;
}
- if(itemGroup.isBucket()) {
- itemGroup.ref.uploadItems(ids);
+ if (targetTreeRow.isBucket()) {
+ targetTreeRow.ref.uploadItems(ids);
return;
}
- Zotero.DB.beginTransaction();
-
- var items = Zotero.Items.get(ids);
- if (!items) {
- Zotero.DB.commitTransaction();
- return;
- }
-
- var newItems = [];
- var newIDs = [];
- var toMove = [];
- // TODO: support items coming from different sources?
- if (items[0].libraryID == targetLibraryID) {
- var sameLibrary = true;
- }
- else {
- var sameLibrary = false;
- }
-
- for each(var item in items) {
- if (!(item.isRegularItem() || !item.getSource())) {
- continue;
+ yield Zotero.DB.executeTransaction(function* () {
+ var items = yield Zotero.Items.getAsync(ids);
+ if (!items) {
+ return;
}
- if (sameLibrary) {
- newIDs.push(item.id);
- toMove.push(item.id);
+ var newItems = [];
+ var newIDs = [];
+ var toMove = [];
+ // TODO: support items coming from different sources?
+ if (items[0].libraryID == targetLibraryID) {
+ var sameLibrary = true;
}
else {
- newItems.push(item);
+ var sameLibrary = false;
}
- }
-
- if (!sameLibrary) {
- var toReconcile = [];
- var newIDs = [];
- for each(var item in newItems) {
- var id = copyItem(item, targetLibraryID)
- // Standalone attachments might not get copied
- if (!id) {
+ for each(var item in items) {
+ if (!item.isTopLevelItem()) {
continue;
}
- newIDs.push(id);
+
+ if (sameLibrary) {
+ newIDs.push(item.id);
+ toMove.push(item.id);
+ }
+ else {
+ newItems.push(item);
+ }
}
- if (toReconcile.length) {
- var sourceName = items[0].libraryID ? Zotero.Libraries.getName(items[0].libraryID)
- : Zotero.getString('pane.collections.library');
- var targetName = targetLibraryID ? Zotero.Libraries.getName(libraryID)
- : Zotero.getString('pane.collections.library');
-
- var io = {
- dataIn: {
- type: "item",
- captions: [
- // TODO: localize
- sourceName,
- targetName,
- "Merged Item"
- ],
- objects: toReconcile
- }
- };
+ if (!sameLibrary) {
+ var toReconcile = [];
- /*
- if (type == 'item') {
- if (!Zotero.Utilities.isEmpty(changedCreators)) {
- io.dataIn.changedCreators = changedCreators;
+ var newIDs = [];
+ for each(var item in newItems) {
+ var id = yield copyItem(item, targetLibraryID)
+ // Standalone attachments might not get copied
+ if (!id) {
+ continue;
}
+ newIDs.push(id);
}
- */
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var lastWin = wm.getMostRecentWindow("navigator:browser");
- lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
-
- for each(var obj in io.dataOut) {
- obj.ref.save();
+ if (toReconcile.length) {
+ var sourceName = items[0].libraryID ? Zotero.Libraries.getName(items[0].libraryID)
+ : Zotero.getString('pane.collections.library');
+ var targetName = targetLibraryID ? Zotero.Libraries.getName(libraryID)
+ : Zotero.getString('pane.collections.library');
+
+ var io = {
+ dataIn: {
+ type: "item",
+ captions: [
+ // TODO: localize
+ sourceName,
+ targetName,
+ "Merged Item"
+ ],
+ objects: toReconcile
+ }
+ };
+
+ /*
+ if (type == 'item') {
+ if (!Zotero.Utilities.isEmpty(changedCreators)) {
+ io.dataIn.changedCreators = changedCreators;
+ }
+ }
+ */
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var lastWin = wm.getMostRecentWindow("navigator:browser");
+ lastWin.openDialog('chrome://zotero/content/merge.xul', '', 'chrome,modal,centerscreen', io);
+
+ for each(var obj in io.dataOut) {
+ yield obj.ref.save();
+ }
}
}
- }
-
- // Add items to target collection
- if (targetCollectionID) {
- var collection = Zotero.Collections.get(targetCollectionID);
- collection.addItems(newIDs);
- }
-
- // If moving, remove items from source collection
- if (dropEffect == 'move' && toMove.length) {
- if (!sameLibrary) {
- throw new Error("Cannot move items between libraries");
+
+ // Add items to target collection
+ if (targetCollectionID) {
+ var collection = yield Zotero.Collections.getAsync(targetCollectionID);
+ Zotero.debug('adding');
+ yield collection.addItems(newIDs);
+ Zotero.debug('added');
}
- let itemGroup = Zotero.DragDrop.getDragSource();
- if (!itemGroup || !itemGroup.isCollection()) {
- throw new Error("Drag source must be a collection for move action");
+
+ // If moving, remove items from source collection
+ if (dropEffect == 'move' && toMove.length) {
+ if (!sameLibrary) {
+ throw new Error("Cannot move items between libraries");
+ }
+ if (!sourceTreeRow || !sourceTreeRow.isCollection()) {
+ throw new Error("Drag source must be a collection for move action");
+ }
+ yield sourceTreeRow.ref.removeItems(toMove);
}
- itemGroup.ref.removeItems(toMove);
- }
-
- Zotero.DB.commitTransaction();
+ });
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
- var targetLibraryID = itemGroup.ref.libraryID;
+ var targetLibraryID = targetTreeRow.ref.libraryID;
- if (itemGroup.isCollection()) {
- var parentCollectionID = itemGroup.ref.id;
+ if (targetTreeRow.isCollection()) {
+ var parentCollectionID = targetTreeRow.ref.id;
}
else {
var parentCollectionID = false;
@@ -1785,8 +1865,7 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
// Otherwise file, so fall through
}
- try {
- Zotero.DB.beginTransaction();
+ yield Zotero.DB.executeTransaction(function () {
if (dropEffect == 'link') {
var itemID = Zotero.Attachments.linkFromFile(file);
}
@@ -1803,24 +1882,19 @@ Zotero.CollectionTreeView.prototype.drop = function(row, orient, dataTransfer)
}
}
if (parentCollectionID) {
- var col = Zotero.Collections.get(parentCollectionID);
+ var col = yield Zotero.Collections.getAsync(parentCollectionID);
if (col) {
col.addItem(itemID);
}
}
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
+ });
}
}
finally {
Zotero.Notifier.commit(unlock);
}
}
-}
+});
@@ -1855,7 +1929,7 @@ Zotero.CollectionTreeView.prototype.getRowProperties = function(row, prop) {
Zotero.CollectionTreeView.prototype.getColumnProperties = function(col, prop) { }
Zotero.CollectionTreeView.prototype.getCellProperties = function(row, col, prop) { }
Zotero.CollectionTreeView.prototype.isSeparator = function(index) {
- var source = this._getItemAtRow(index);
+ var source = this.getRow(index);
return source.type == 'separator';
}
Zotero.CollectionTreeView.prototype.performAction = function(action) { }
@@ -1863,38 +1937,32 @@ Zotero.CollectionTreeView.prototype.performActionOnCell = function(action, row,
Zotero.CollectionTreeView.prototype.getProgressMode = function(row, col) { }
Zotero.CollectionTreeView.prototype.cycleHeader = function(column) { }
-////////////////////////////////////////////////////////////////////////////////
-///
-/// Zotero ItemGroup -- a sort of "super class" for collection, library,
-/// and saved search
-///
-////////////////////////////////////////////////////////////////////////////////
-Zotero.ItemGroupCache = {
- "lastItemGroup":null,
+Zotero.CollectionTreeCache = {
+ "lastTreeRow":null,
"lastTempTable":null,
"lastSearch":null,
"lastResults":null,
"clear":function() {
- this.lastItemGroup = null;
+ this.lastTreeRow = null;
this.lastSearch = null;
if(this.lastTempTable) {
- Zotero.DB.query("DROP TABLE "+this.lastTempTable);
+ Zotero.DB.queryAsync("DROP TABLE " + this.lastTempTable);
}
this.lastTempTable = null;
this.lastResults = null;
}
};
-Zotero.ItemGroup = function(type, ref)
+Zotero.CollectionTreeRow = function(type, ref)
{
this.type = type;
this.ref = ref;
}
-Zotero.ItemGroup.prototype.__defineGetter__('id', function () {
+Zotero.CollectionTreeRow.prototype.__defineGetter__('id', function () {
switch (this.type) {
case 'library':
return 'L';
@@ -1927,7 +1995,7 @@ Zotero.ItemGroup.prototype.__defineGetter__('id', function () {
return '';
});
-Zotero.ItemGroup.prototype.isLibrary = function (includeGlobal)
+Zotero.CollectionTreeRow.prototype.isLibrary = function (includeGlobal)
{
if (includeGlobal) {
return this.type == 'library' || this.type == 'group';
@@ -1935,47 +2003,47 @@ Zotero.ItemGroup.prototype.isLibrary = function (includeGlobal)
return this.type == 'library';
}
-Zotero.ItemGroup.prototype.isCollection = function()
+Zotero.CollectionTreeRow.prototype.isCollection = function()
{
return this.type == 'collection';
}
-Zotero.ItemGroup.prototype.isSearch = function()
+Zotero.CollectionTreeRow.prototype.isSearch = function()
{
return this.type == 'search';
}
-Zotero.ItemGroup.prototype.isDuplicates = function () {
+Zotero.CollectionTreeRow.prototype.isDuplicates = function () {
return this.type == 'duplicates';
}
-Zotero.ItemGroup.prototype.isUnfiled = function () {
+Zotero.CollectionTreeRow.prototype.isUnfiled = function () {
return this.type == 'unfiled';
}
-Zotero.ItemGroup.prototype.isTrash = function()
+Zotero.CollectionTreeRow.prototype.isTrash = function()
{
return this.type == 'trash';
}
-Zotero.ItemGroup.prototype.isHeader = function () {
+Zotero.CollectionTreeRow.prototype.isHeader = function () {
return this.type == 'header';
}
-Zotero.ItemGroup.prototype.isGroup = function() {
+Zotero.CollectionTreeRow.prototype.isGroup = function() {
return this.type == 'group';
}
-Zotero.ItemGroup.prototype.isSeparator = function () {
+Zotero.CollectionTreeRow.prototype.isSeparator = function () {
return this.type == 'separator';
}
-Zotero.ItemGroup.prototype.isBucket = function()
+Zotero.CollectionTreeRow.prototype.isBucket = function()
{
return this.type == 'bucket';
}
-Zotero.ItemGroup.prototype.isShare = function()
+Zotero.CollectionTreeRow.prototype.isShare = function()
{
return this.type == 'share';
}
@@ -1983,11 +2051,11 @@ Zotero.ItemGroup.prototype.isShare = function()
// Special
-Zotero.ItemGroup.prototype.isWithinGroup = function () {
+Zotero.CollectionTreeRow.prototype.isWithinGroup = function () {
return this.ref && !!this.ref.libraryID;
}
-Zotero.ItemGroup.prototype.isWithinEditableGroup = function () {
+Zotero.CollectionTreeRow.prototype.isWithinEditableGroup = function () {
if (!this.isWithinGroup()) {
return false;
}
@@ -1995,7 +2063,7 @@ Zotero.ItemGroup.prototype.isWithinEditableGroup = function () {
return Zotero.Groups.get(groupID).editable;
}
-Zotero.ItemGroup.prototype.__defineGetter__('editable', function () {
+Zotero.CollectionTreeRow.prototype.__defineGetter__('editable', function () {
if (this.isTrash() || this.isShare() || this.isBucket()) {
return false;
}
@@ -2013,12 +2081,12 @@ Zotero.ItemGroup.prototype.__defineGetter__('editable', function () {
var group = Zotero.Groups.get(groupID);
return group.editable;
}
- throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.editable");
+ throw ("Unknown library type '" + type + "' in Zotero.CollectionTreeRow.editable");
}
return false;
});
-Zotero.ItemGroup.prototype.__defineGetter__('filesEditable', function () {
+Zotero.CollectionTreeRow.prototype.__defineGetter__('filesEditable', function () {
if (this.isTrash() || this.isShare()) {
return false;
}
@@ -2036,12 +2104,12 @@ Zotero.ItemGroup.prototype.__defineGetter__('filesEditable', function () {
var group = Zotero.Groups.get(groupID);
return group.filesEditable;
}
- throw ("Unknown library type '" + type + "' in Zotero.ItemGroup.filesEditable");
+ throw ("Unknown library type '" + type + "' in Zotero.CollectionTreeRow.filesEditable");
}
return false;
});
-Zotero.ItemGroup.prototype.getName = function()
+Zotero.CollectionTreeRow.prototype.getName = function()
{
switch (this.type) {
case 'library':
@@ -2061,7 +2129,7 @@ Zotero.ItemGroup.prototype.getName = function()
}
}
-Zotero.ItemGroup.prototype.getItems = function()
+Zotero.CollectionTreeRow.prototype.getItems = Zotero.Promise.coroutine(function* ()
{
switch (this.type) {
// Fake results if this is a shared library
@@ -2075,65 +2143,60 @@ Zotero.ItemGroup.prototype.getItems = function()
return [];
}
- try {
- var ids = this.getSearchResults();
+ var ids = yield this.getSearchResults();
+ if (!ids.length) {
+ return []
}
- catch (e) {
- Zotero.DB.rollbackAllTransactions();
- Zotero.debug(e, 2);
- throw (e);
- }
-
return Zotero.Items.get(ids);
-}
+});
-Zotero.ItemGroup.prototype.getSearchResults = function(asTempTable) {
- if(Zotero.ItemGroupCache.lastItemGroup !== this) {
- Zotero.ItemGroupCache.clear();
+Zotero.CollectionTreeRow.prototype.getSearchResults = Zotero.Promise.coroutine(function* (asTempTable) {
+ if(Zotero.CollectionTreeCache.lastTreeRow !== this) {
+ Zotero.CollectionTreeCache.clear();
}
- if(!Zotero.ItemGroupCache.lastResults) {
- var s = this.getSearchObject();
-
+ if(!Zotero.CollectionTreeCache.lastResults) {
+ var s = yield this.getSearchObject();
+
// FIXME: Hack to exclude group libraries for now
if (this.isSearch()) {
var currentLibraryID = this.ref.libraryID;
if (currentLibraryID) {
- s.addCondition('libraryID', 'is', currentLibraryID);
+ yield s.addCondition('libraryID', 'is', currentLibraryID);
}
else {
- var groups = Zotero.Groups.getAll();
+ var groups = yield Zotero.Groups.getAll();
for each(var group in groups) {
- s.addCondition('libraryID', 'isNot', group.libraryID);
+ yield s.addCondition('libraryID', 'isNot', group.libraryID);
}
}
}
- Zotero.ItemGroupCache.lastResults = s.search();
- Zotero.ItemGroupCache.lastItemGroup = this;
+ Zotero.CollectionTreeCache.lastResults = yield s.search();
+ Zotero.CollectionTreeCache.lastTreeRow = this;
}
if(asTempTable) {
- if(!Zotero.ItemGroupCache.lastTempTable) {
- Zotero.ItemGroupCache.lastTempTable = Zotero.Search.idsToTempTable(Zotero.ItemGroupCache.lastResults);
+ if(!Zotero.CollectionTreeCache.lastTempTable) {
+ Zotero.CollectionTreeCache.lastTempTable = yield Zotero.Search.idsToTempTable(Zotero.CollectionTreeCache.lastResults);
}
- return Zotero.ItemGroupCache.lastTempTable;
+ return Zotero.CollectionTreeCache.lastTempTable;
}
- return Zotero.ItemGroupCache.lastResults;
-}
+ return Zotero.CollectionTreeCache.lastResults;
+});
/*
* Returns the search object for the currently display
*
* This accounts for the collection, saved search, quicksearch, tags, etc.
*/
-Zotero.ItemGroup.prototype.getSearchObject = function() {
- if(Zotero.ItemGroupCache.lastItemGroup !== this) {
- Zotero.ItemGroupCache.clear();
+Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(function* () {
+ if(Zotero.CollectionTreeCache.lastTreeRow !== this) {
+ Zotero.CollectionTreeCache.clear();
}
- if(Zotero.ItemGroupCache.lastSearch) {
- return Zotero.ItemGroupCache.lastSearch;
+ if(Zotero.CollectionTreeCache.lastSearch) {
+ return Zotero.CollectionTreeCache.lastSearch;
}
var includeScopeChildren = false;
@@ -2143,63 +2206,65 @@ Zotero.ItemGroup.prototype.getSearchObject = function() {
var s = this.ref;
}
else if (this.isDuplicates()) {
- var s = this.ref.getSearchObject();
+ var s = yield this.ref.getSearchObject();
}
else {
var s = new Zotero.Search();
// Library root
if (this.isLibrary(true)) {
- s.addCondition('libraryID', 'is', this.ref.libraryID);
- s.addCondition('noChildren', 'true');
+ yield s.addCondition('libraryID', 'is', this.ref.libraryID);
+ yield s.addCondition('noChildren', 'true');
includeScopeChildren = true;
}
else if (this.isCollection()) {
- s.addCondition('noChildren', 'true');
- s.addCondition('collectionID', 'is', this.ref.id);
+ yield s.addCondition('noChildren', 'true');
+ yield s.addCondition('collectionID', 'is', this.ref.id);
if (Zotero.Prefs.get('recursiveCollections')) {
- s.addCondition('recursive', 'true');
+ yield s.addCondition('recursive', 'true');
}
includeScopeChildren = true;
}
else if (this.isTrash()) {
- s.addCondition('libraryID', 'is', this.ref.libraryID);
- s.addCondition('deleted', 'true');
+ yield s.addCondition('libraryID', 'is', this.ref.libraryID);
+ yield s.addCondition('deleted', 'true');
}
else {
- throw ('Invalid search mode in Zotero.ItemGroup.getSearchObject()');
+ throw ('Invalid search mode in Zotero.CollectionTreeRow.getSearchObject()');
}
}
// Create the outer (filter) search
var s2 = new Zotero.Search();
if (this.isTrash()) {
- s2.addCondition('deleted', 'true');
+ yield s2.addCondition('deleted', 'true');
}
s2.setScope(s, includeScopeChildren);
if (this.searchText) {
var cond = 'quicksearch-' + Zotero.Prefs.get('search.quicksearch-mode');
- s2.addCondition(cond, 'contains', this.searchText);
+ yield s2.addCondition(cond, 'contains', this.searchText);
}
if (this.tags){
for (var tag in this.tags){
if (this.tags[tag]){
- s2.addCondition('tag', 'is', tag);
+ yield s2.addCondition('tag', 'is', tag);
}
}
}
- Zotero.ItemGroupCache.lastItemGroup = this;
- Zotero.ItemGroupCache.lastSearch = s2;
+ Zotero.CollectionTreeCache.lastTreeRow = this;
+ Zotero.CollectionTreeCache.lastSearch = s2;
return s2;
-}
+});
-/*
+/**
* Returns all the tags used by items in the current view
+ *
+ * @return {Promise}
*/
-Zotero.ItemGroup.prototype.getChildTags = function() {
+Zotero.CollectionTreeRow.prototype.getChildTags = Zotero.Promise.method(function () {
switch (this.type) {
// TODO: implement?
case 'share':
@@ -2212,27 +2277,26 @@ Zotero.ItemGroup.prototype.getChildTags = function() {
return false;
}
- return Zotero.Tags.getAllWithinSearch(this.getSearchObject(),
- undefined, this.getSearchResults(true));
-}
+ return Zotero.Tags.getAllWithinSearchResults(this.getSearchResults(true));
+});
-Zotero.ItemGroup.prototype.setSearch = function(searchText)
+Zotero.CollectionTreeRow.prototype.setSearch = function(searchText)
{
- Zotero.ItemGroupCache.clear();
+ Zotero.CollectionTreeCache.clear();
this.searchText = searchText;
}
-Zotero.ItemGroup.prototype.setTags = function(tags)
+Zotero.CollectionTreeRow.prototype.setTags = function(tags)
{
- Zotero.ItemGroupCache.clear();
+ Zotero.CollectionTreeCache.clear();
this.tags = tags;
}
/*
* Returns TRUE if saved search, quicksearch or tag filter
*/
-Zotero.ItemGroup.prototype.isSearchMode = function() {
+Zotero.CollectionTreeRow.prototype.isSearchMode = function() {
switch (this.type) {
case 'search':
case 'trash':
diff --git a/chrome/content/zotero/xpcom/data/cachedTypes.js b/chrome/content/zotero/xpcom/data/cachedTypes.js
@@ -71,6 +71,7 @@ Zotero.CachedTypes = function() {
if (!_types['_' + idOrName]) {
Zotero.debug('Invalid ' + this._typeDesc + ' ' + idOrName, 1);
+ Zotero.debug((new Error()).stack, 1);
return '';
}
@@ -90,6 +91,7 @@ Zotero.CachedTypes = function() {
if (!_types['_' + idOrName]) {
Zotero.debug('Invalid ' + this._typeDesc + ' ' + idOrName, 1);
+ Zotero.debug((new Error()).stack, 1);
return false;
}
diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js
@@ -23,42 +23,28 @@
***** END LICENSE BLOCK *****
*/
-
Zotero.Collection = function() {
- if (arguments[0]) {
- throw ("Zotero.Collection constructor doesn't take any parameters");
- }
+ let dataTypes = [
+ 'primaryData',
+ 'childCollections',
+ 'childItems'
+ ];
+ Zotero.DataObject.apply(this, ['collection', false, dataTypes]);
- this._init();
-}
-
-Zotero.Collection.prototype._init = function () {
- // Public members for access by public methods -- do not access directly
- this._id = null;
- this._libraryID = null
- this._key = null;
this._name = null;
- this._parent = false;
- this._dateAdded = null;
- this._dateModified = null;
+ this._parentID = null;
+ this._parentKey = null;
- this._loaded = false;
- this._changed = false;
- this._previousData = false;
-
- this._hasChildCollections;
+ this._hasChildCollections = null;
this._childCollections = [];
- this._childCollectionsLoaded = false;
this._hasChildItems = false;
this._childItems = [];
- this._childItemsLoaded = false;
-
- this._dateModifiedLocked = false;
}
+Zotero.Collection.prototype = Object.create(Zotero.DataObject.prototype);
+Zotero.Collection.constructor = Zotero.Collection;
-Zotero.Collection.prototype.__defineGetter__('objectType', function () { return 'collection'; });
Zotero.Collection.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Collection.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Collection.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
@@ -67,87 +53,48 @@ Zotero.Collection.prototype.__defineGetter__('key', function () { return this._g
Zotero.Collection.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Collection.prototype.__defineGetter__('name', function () { return this._get('name'); });
Zotero.Collection.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
-Zotero.Collection.prototype.__defineGetter__('parent', function () { return this._get('parent'); });
-Zotero.Collection.prototype.__defineSetter__('parent', function (val) { this._set('parent', val); });
-Zotero.Collection.prototype.__defineGetter__('parentKey', function () { return this._get('parentKey'); });
-Zotero.Collection.prototype.__defineSetter__('parentKey', function (val) { this._set('parentKey', val); });
-Zotero.Collection.prototype.__defineGetter__('dateAdded', function () { return this._get('dateAdded'); });
-Zotero.Collection.prototype.__defineSetter__('dateAdded', function (val) { this._set('dateAdded', val); });
-Zotero.Collection.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
-Zotero.Collection.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
-
-//Zotero.Collection.prototype.__defineSetter__('childCollections', function (arr) { this._setChildCollections(arr); });
-Zotero.Collection.prototype.__defineSetter__('childItems', function (arr) { this._setChildItems(arr); });
-
-Zotero.Collection.prototype._get = function (field) {
- if ((this._id || this._key) && !this._loaded) {
- this.load();
- }
-
- switch (field) {
- case 'parent':
- return this._getParent();
-
- case 'parentKey':
- return this._getParentKey();
- }
-
- return this['_' + field];
-}
+// .parentKey and .parentID defined in dataObject.js
+Zotero.Collection.prototype.__defineGetter__('version', function () { return this._get('version'); });
+Zotero.Collection.prototype.__defineSetter__('version', function (val) { this._set('version', val); });
+Zotero.Collection.prototype.__defineGetter__('synced', function () { return this._get('synced'); });
+Zotero.Collection.prototype.__defineSetter__('synced', function (val) { this._set('synced', val); });
+Zotero.Collection.prototype.__defineGetter__('parent', function (val) {
+ Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
+ return this.parentID;
+});
+Zotero.Collection.prototype.__defineSetter__('parent', function (val) {
+ Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
+ this.parentID = val;
+});
-Zotero.Collection.prototype._set = function (field, val) {
- switch (field) {
- case 'id':
- case 'libraryID':
- case 'key':
- if (field == 'libraryID') {
- val = Zotero.DataObjectUtilities.checkLibraryID(val);
- }
-
- if (val == this['_' + field]) {
- return;
- }
-
- if (this._loaded) {
- throw ("Cannot set " + field + " after object is already loaded in Zotero.Collection._set()");
- }
-
- //this._checkValue(field, val);
-
- this['_' + field] = val;
- return;
-
- case 'name':
- val = Zotero.Utilities.trim(val);
- break;
+Zotero.Collection.prototype._set = function (field, value) {
+ if (field == 'id' || field == 'libraryID' || field == 'key') {
+ return this._setIdentifier(field, value);
}
- if (this.id || this.key) {
- if (!this._loaded) {
- this.load();
- }
- }
- else {
- this._loaded = true;
- }
+ this._requireData('primaryData');
switch (field) {
- case 'parent':
- this._setParent(val);
- return;
-
- case 'parentKey':
- this._setParentKey(val);
- return;
+ case 'name':
+ value = value.trim();
+ break;
+
+ case 'version':
+ value = parseInt(value);
+ break;
+
+ case 'synced':
+ value = !!value;
+ break;
}
- if (this['_' + field] != val) {
+ if (this['_' + field] != value) {
this._prepFieldChange(field);
switch (field) {
default:
- this['_' + field] = val;
+ this['_' + field] = value;
}
}
}
@@ -162,95 +109,86 @@ Zotero.Collection.prototype.getName = function() {
return this.name;
}
-Zotero.Collection.prototype.getParent = function() {
- Zotero.debug('Collection.getParent() deprecated -- use Collection.parent');
- return this.parent;
-}
-
/*
* Build collection from database
*/
-Zotero.Collection.prototype.load = function() {
+Zotero.Collection.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.primaryData && !reload) return;
+
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
- //var desc = id ? id : libraryID + "/" + key;
// Should be same as query in Zotero.Collections, just with collectionID
- var sql = "SELECT C.*, "
- + "(SELECT COUNT(*) FROM collections WHERE "
- + "parentCollectionID=C.collectionID)!=0 AS hasChildCollections, "
- + "(SELECT COUNT(*) FROM collectionItems WHERE "
- + "collectionID=C.collectionID)!=0 AS hasChildItems "
- + "FROM collections C WHERE ";
+ var sql = Zotero.Collections._getPrimaryDataSQL();
if (id) {
- sql += "collectionID=?";
+ sql += " AND O.collectionID=?";
var params = id;
}
else {
- sql += "key=? AND libraryID=?";
- var params = [key, libraryID];
+ sql += " AND O.libraryID=? AND O.key=?";
+ var params = [libraryID, key];
}
- var data = Zotero.DB.rowQuery(sql, params);
+ var data = yield Zotero.DB.rowQueryAsync(sql, params);
- this._loaded = true;
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
if (!data) {
return;
}
this.loadFromRow(data);
-}
+});
/*
* Populate collection data from a database row
*/
Zotero.Collection.prototype.loadFromRow = function(row) {
- this._loaded = true;
- this._changed = false;
- this._previousData = false;
+ Zotero.debug("Loading collection from row");
+
+ for each(let col in Zotero.Collections.primaryFields) {
+ if (row[col] === undefined) {
+ Zotero.debug('Skipping missing collection field ' + col);
+ }
+ }
this._id = row.collectionID;
- this._libraryID = row.libraryID;
+ this._libraryID = parseInt(row.libraryID);
this._key = row.key;
- this._name = row.collectionName;
- this._parent = row.parentCollectionID;
- this._dateAdded = row.dateAdded;
- this._dateModified = row.dateModified;
- this._childCollectionsLoaded = false;
+ this._name = row.name;
+ this._parentID = row.parentID || false;
+ this._parentKey = row.parentKey || false;
+ this._version = parseInt(row.version);
+ this._synced = !!row.synced;
+
this._hasChildCollections = !!row.hasChildCollections;
- this._childItemsLoaded = false;
+ this._childCollectionsLoaded = false;
this._hasChildItems = !!row.hasChildItems;
-}
-
-
-Zotero.Collection.prototype.isEmpty = function() {
- if (this._hasChildCollections == undefined) {
- this.hasChildCollections();
- }
+ this._childItemsLoaded = false;
- return !(this._hasChildCollections || this._hasChildItems);
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
+ this._identified = true;
}
+
Zotero.Collection.prototype.hasChildCollections = function() {
- if (!this.id) {
- throw ("Zotero.Collection.hasChildCollections cannot be called "
- + "on an unsaved collection");
- }
-
- if (this._hasChildCollections == undefined) {
- var sql = "SELECT COUNT(*) FROM collections WHERE "
- + "parentCollectionID=?";
- this._hasChildCollections = !!Zotero.DB.valueQuery(sql, this.id);
+ if (this._hasChildCollections !== null) {
+ return this._hasChildCollections;
}
-
- return this._hasChildCollections;
+ this._requireData('primaryData');
+ return false;
}
Zotero.Collection.prototype.hasChildItems = function() {
- return !!this._hasChildItems;
+ if (this._hasChildItems !== null) {
+ return this._hasChildItems;
+ }
+ this._requireData('primaryData');
+ return false;
}
/**
@@ -276,9 +214,7 @@ Zotero.Collection.prototype.exists = function() {
* or collectionIDs, or FALSE if none
*/
Zotero.Collection.prototype.getChildCollections = function (asIDs) {
- if (!this._childCollectionsLoaded) {
- this._loadChildCollections();
- }
+ this._requireData('childCollections');
if (this._childCollections.length == 0) {
return false;
@@ -311,9 +247,7 @@ Zotero.Collection.prototype.getChildCollections = function (asIDs) {
* or FALSE if none
*/
Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
- if (!this._childItemsLoaded) {
- this._loadChildItems();
- }
+ this._requireData('childItems');
if (this._childItems.length == 0) {
return false;
@@ -345,484 +279,272 @@ Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
}
-/**
- * Prevent dateModified from being updated when removing an item
- *
- * Used for a tricky sync case
- */
-Zotero.Collection.prototype.lockDateModified = function () {
- this._dateModifiedLocked = true;
-}
-
-
-Zotero.Collection.prototype.unlockDateModified = function () {
- this._dateModifiedLocked = false;
-}
-
-
-Zotero.Collection.prototype.save = function () {
- Zotero.Collections.editCheck(this);
-
- if (!this.name) {
- throw ('Collection name is empty in Zotero.Collection.save()');
- }
-
- if (!this._changed) {
- Zotero.debug("Collection " + this.id + " has not changed");
- return false;
- }
-
- Zotero.DB.beginTransaction();
-
- var isNew = !this.id || !this.exists();
-
+Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
try {
- // how to know if date modified changed (in server code too?)
+ Zotero.Collections.editCheck(this);
- var collectionID = this.id ? this.id : Zotero.ID.get('collections');
+ if (!this.name) {
+ throw new Error('Collection name is empty');
+ }
- Zotero.debug("Saving collection " + this.id);
+ if (Zotero.Utilities.isEmpty(this._changed)) {
+ Zotero.debug("Collection " + this.id + " has not changed");
+ return false;
+ }
- var key = this.key ? this.key : this._generateKey();
+ var isNew = !this.id;
- // Verify parent
- if (this._parent) {
- if (typeof this._parent == 'number') {
- var newParent = Zotero.Collections.get(this._parent);
- }
- else {
- var newParent = Zotero.Collections.getByLibraryAndKey(this.libraryID, this._parent);
- }
-
- if (!newParent) {
- throw("Cannot set parent to invalid collection " + this._parent + " in Zotero.Collection.save()");
- }
-
- if (newParent.id == this.id) {
- throw ('Cannot move collection into itself!');
- }
-
- if (this.id && this.hasDescendent('collection', newParent.id)) {
- throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
- }
-
- var parent = newParent.id;
+ // Register this item's identifiers in Zotero.DataObjects on transaction commit,
+ // before other callbacks run
+ var collectionID, libraryID, key;
+ if (isNew) {
+ var transactionOptions = {
+ onCommit: function () {
+ Zotero.Collections.registerIdentifiers(collectionID, libraryID, key);
+ }
+ };
}
else {
- var parent = null;
+ var transactionOptions = null;
}
- var columns = [
- 'collectionID',
- 'collectionName',
- 'parentCollectionID',
- 'dateAdded',
- 'dateModified',
- 'clientDateModified',
- 'libraryID',
- 'key'
- ];
- var placeholders = ['?', '?', '?', '?', '?', '?', '?', '?'];
- var sqlValues = [
- collectionID ? { int: collectionID } : null,
- { string: this.name },
- parent ? parent : null,
- // If date added isn't set, use current timestamp
- this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
- // If date modified hasn't changed, use current timestamp
- this._changed.dateModified ?
- this.dateModified : Zotero.DB.transactionDateTime,
- Zotero.DB.transactionDateTime,
- this.libraryID ? this.libraryID : 0,
- key
- ];
-
- var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
- + "VALUES (" + placeholders + ")";
- var insertID = Zotero.DB.query(sql, sqlValues);
- if (!collectionID) {
- collectionID = insertID;
- }
-
- if (this._changed.parent) {
- var parentIDs = [];
- if (this.id && this._previousData.parent) {
- parentIDs.push(this._previousData.parent);
+ return Zotero.DB.executeTransaction(function* () {
+ // how to know if date modified changed (in server code too?)
+
+ collectionID = this._id = this.id ? this.id : yield Zotero.ID.get('collections');
+ libraryID = this.libraryID;
+ key = this._key = this.key ? this.key : this._generateKey();
+
+ Zotero.debug("Saving collection " + this.id);
+
+ // Verify parent
+ if (this._parentKey) {
+ let newParent = yield Zotero.Collections.getByLibraryAndKey(
+ this.libraryID, this._parentKey
+ );
+
+ if (!newParent) {
+ throw new Error("Cannot set parent to invalid collection " + this._parentKey);
+ }
+
+ if (newParent.id == this.id) {
+ throw new Error('Cannot move collection into itself!');
+ }
+
+ if (this.id && (yield this.hasDescendent('collection', newParent.id))) {
+ throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
+ }
+
+ var parent = newParent.id;
}
- if (this.parent) {
- parentIDs.push(this.parent);
+ else {
+ var parent = null;
}
- if (this.id) {
- Zotero.Notifier.trigger('move', 'collection', this.id);
+
+ var columns = [
+ 'collectionID',
+ 'collectionName',
+ 'parentCollectionID',
+ 'clientDateModified',
+ 'libraryID',
+ 'key',
+ 'version',
+ 'synced'
+ ];
+ var sqlValues = [
+ collectionID ? { int: collectionID } : null,
+ { string: this.name },
+ parent ? parent : null,
+ Zotero.DB.transactionDateTime,
+ this.libraryID ? this.libraryID : 0,
+ key,
+ this.version ? this.version : 0,
+ this.synced ? 1 : 0
+ ];
+ if (isNew) {
+ var placeholders = columns.map(function () '?').join();
+
+ var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
+ + "VALUES (" + placeholders + ")";
+ var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
+ if (!collectionID) {
+ collectionID = insertID;
+ }
}
- }
-
- /*
- // Subcollections
- if (this._changed.childCollections) {
- var removed = [];
- var newids = [];
- var currentIDs = this.getChildCollections(true);
- if (!currentIDs) {
- currentIDs = [];
+ else {
+ columns.shift();
+ sqlValues.push(sqlValues.shift());
+ let sql = 'UPDATE collections SET '
+ + columns.map(function (x) x + '=?').join(', ')
+ + ' WHERE collectionID=?';
+ yield Zotero.DB.queryAsync(sql, sqlValues);
}
- if (this._previousData) {
- for each(var id in this._previousData.childCollections) {
- if (currentIDs.indexOf(id) == -1) {
- removed.push(id);
- }
+ if (this._changed.parentKey) {
+ var parentIDs = [];
+ if (this.id && this._previousData.parentKey) {
+ parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
+ this.libraryID, this._previousData.parentKey
+ ));
}
- }
- for each(var id in currentIDs) {
- if (this._previousData &&
- this._previousData.childCollections.indexOf(id) != -1) {
- continue;
+ if (this.parentKey) {
+ parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
+ this.libraryID, this.parentKey
+ ));
+ }
+ if (this.id) {
+ Zotero.Notifier.trigger('move', 'collection', this.id);
}
- newids.push(id);
}
- if (removed.length) {
- var sql = "UPDATE collections SET parentCollectionID=NULL "
- + "WHERE collectionID IN ("
- + removed.map(function () '?').join()
- + ")";
- Zotero.DB.query(sql, removed);
+ if (isNew && this.libraryID) {
+ var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
+ var group = Zotero.Groups.get(groupID);
+ group.clearCollectionCache();
}
- if (newids.length) {
- var sql = "UPDATE collections SET parentCollectionID=? "
- + "WHERE collectionID IN ("
- + newids.map(function () '?').join()
- + ")";
- Zotero.DB.query(sql, [collectionID].concat(newids));
+ if (isNew) {
+ Zotero.Notifier.trigger('add', 'collection', this.id);
}
-
- // TODO: notifier
- }
- */
-
- // Child items
- if (this._changed.childItems) {
- var removed = [];
- var newids = [];
- var currentIDs = this.getChildItems(true);
- if (!currentIDs) {
- currentIDs = [];
+ else {
+ Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
}
- if (this._previousData) {
- for each(var id in this._previousData.childItems) {
- if (currentIDs.indexOf(id) == -1) {
- removed.push(id);
- }
- }
- }
- for each(var id in currentIDs) {
- if (this._previousData &&
- this._previousData.childItems.indexOf(id) != -1) {
- continue;
- }
- newids.push(id);
+ // Invalidate cached child collections
+ if (parentIDs) {
+ Zotero.Collections.refreshChildCollections(parentIDs);
}
- if (removed.length) {
- var sql = "DELETE FROM collectionItems WHERE collectionID=? "
- + "AND itemID IN (" + removed.join() + ")";
- Zotero.DB.query(sql, collectionID);
+ // New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled
+ if (isNew) {
+ var id = this.id;
+ this._disabled = true;
+ return id;
}
- if (newids.length) {
- // TEMP: Remove duplicates, which shouldn't be necessary
- var len1 = newids.length;
- newids = Zotero.Utilities.arrayUnique(newids);
- if (len1 != newids.length) {
- Zotero.debug("newids was not unique in Zotero.Collection.save()", 2);
- }
-
- var sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) "
- + "FROM collectionItems WHERE collectionID=?"
- var orderStatement = Zotero.DB.getStatement(sql);
-
- var sql = "INSERT INTO collectionItems "
- + "(collectionID, itemID, orderIndex) VALUES (?,?,?)";
- var insertStatement = Zotero.DB.getStatement(sql);
-
- for each(var itemID in newids) {
- orderStatement.bindInt32Parameter(0, collectionID);
- try {
- if (orderStatement.executeStep()) {
- var orderIndex = orderStatement.getInt32(0);
- }
- }
- catch (e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
- }
-
- orderStatement.reset();
-
- Zotero.debug("Adding item " + itemID + " to collection " + collectionID, 4);
-
- insertStatement.bindInt32Parameter(0, collectionID);
- insertStatement.bindInt32Parameter(1, itemID);
- insertStatement.bindInt32Parameter(2,
- orderIndex ? orderIndex : 0);
-
- try {
- insertStatement.execute();
- }
- catch (e) {
- Zotero.debug("collectionID: " + collectionID);
- Zotero.debug("itemID: " + itemID);
- Zotero.debug("orderIndex: " + orderIndex);
- var errmsg = Zotero.DB.getLastErrorString();
- Zotero.debug(Zotero.DB.query("SELECT * FROM collections WHERE collectionID=?", collectionID));
- Zotero.debug(Zotero.DB.query("SELECT * FROM collectionItems WHERE collectionID=?", collectionID));
- throw (e + ' [ERROR: ' + errmsg + ']');
- }
- }
- }
+ yield this.reload();
+ this._clearChanged();
- //Zotero.Notifier.trigger('add', 'collection-item', this.id + '-' + itemID);
+ return true;
+ }.bind(this), transactionOptions);
+ }
+ catch (e) {
+ try {
+ yield this.reload();
+ this._clearChanged();
}
-
- if (isNew && this.libraryID) {
- var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
- var group = Zotero.Groups.get(groupID);
- group.clearCollectionCache();
+ catch (e2) {
+ Zotero.debug(e2, 1);
}
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
-
- // If successful, set values in object
- if (!this.id) {
- this._id = collectionID;
- }
-
- if (!this.key) {
- this._key = key;
- }
-
- Zotero.Collections.reload(this.id);
-
- if (isNew) {
- Zotero.Notifier.trigger('add', 'collection', this.id);
- }
- else {
- Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
+ Zotero.debug(e, 1);
+ throw e;
}
-
- // Invalidate cached child collections
- if (parentIDs) {
- Zotero.Collections.refreshChildCollections(parentIDs);
- }
-
- return this.id;
-}
+});
/**
- * Add an item to the collection
- *
- * Warning: Operates on DB directly without separate save()
+ * @param {Number} itemID
+ * @return {Promise}
*/
-Zotero.Collection.prototype.addItem = function(itemID) {
- var current = this.getChildItems(true);
- if (current && current.indexOf(itemID) != -1) {
- Zotero.debug("Item " + itemID + " already a child of collection "
- + this.id + " in Zotero.Collection.addItem()");
- return false;
- }
-
- Zotero.DB.beginTransaction();
-
- if (!Zotero.Items.get(itemID)) {
- Zotero.DB.rollbackTransaction();
- throw(itemID + ' is not a valid item id');
- }
-
- var sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) "
- + "FROM collectionItems WHERE collectionID=?";
- var nextOrderIndex = Zotero.DB.valueQuery(sql, this.id);
-
- sql = "INSERT OR IGNORE INTO collectionItems VALUES (?,?,?)";
- Zotero.DB.query(sql, [this.id, itemID, nextOrderIndex]);
-
- sql = "UPDATE collections SET dateModified=?, clientDateModified=? WHERE collectionID=?";
- Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id]);
-
- Zotero.DB.commitTransaction();
-
- Zotero.Collections.reload(this.id);
-
- Zotero.Notifier.trigger('add', 'collection-item', this.id + '-' + itemID);
-
- return true;
+Zotero.Collection.prototype.addItem = function (itemID) {
+ return this.addItems([itemID]);
}
/**
* Add multiple items to the collection in batch
*
- * Warning: Operates on DB directly without separate save()
+ * Does not require a separate save()
+ *
+ * @param {Number[]} itemIDs
+ * @return {Promise}
*/
-Zotero.Collection.prototype.addItems = function(itemIDs) {
+Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemIDs) {
if (!itemIDs || !itemIDs.length) {
return;
}
+ yield this.loadChildItems();
var current = this.getChildItems(true);
- Zotero.DB.beginTransaction();
-
- var sql = "SELECT IFNULL(MAX(orderIndex), 0) FROM collectionItems WHERE collectionID=?";
- var max = Zotero.DB.valueQuery(sql, this.id);
- var nextOrderIndex = 0;
-
- sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) FROM collectionItems WHERE collectionID=?";
- var selectStatement = Zotero.DB.getStatement(sql);
-
- sql = "INSERT OR IGNORE INTO collectionItems VALUES (?,?,?)";
- var insertStatement = Zotero.DB.getStatement(sql);
-
- var notifierPairs = [];
-
- var reenableScriptIndicator = false;
- if(itemIDs.length > 25) {
- // Disable unresponsive script indicator for long lists
- // Re-enable later only if it wasn't disabled before
- reenableScriptIndicator = Zotero.UnresponsiveScriptIndicator.disable();
- }
-
- try {
- for (var i=0; i<itemIDs.length; i++) {
- var itemID = itemIDs[i];
+ return Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<itemIDs.length; i++) {
+ let itemID = itemIDs[i];
+
if (current && current.indexOf(itemID) != -1) {
- Zotero.debug("Item " + itemID + " already a child of collection "
- + this.id + " in Zotero.Collection.addItems()");
+ Zotero.debug("Item " + itemID + " already a child of collection " + this.id);
continue;
}
- if (!Zotero.Items.get(itemID)) {
- Zotero.DB.rollbackTransaction();
- throw(itemID + ' is not a valid item id');
- }
-
- // If we're already above the max, just increment
- if (nextOrderIndex>max) {
- nextOrderIndex++;
- }
- else {
- selectStatement.bindInt32Parameter(0, this.id);
- selectStatement.executeStep();
- nextOrderIndex = selectStatement.getInt32(0);
- selectStatement.reset();
- }
-
- insertStatement.bindInt32Parameter(0, this.id);
- insertStatement.bindInt32Parameter(1, itemID);
- insertStatement.bindInt32Parameter(2, nextOrderIndex);
-
- try {
- insertStatement.execute();
- }
- catch(e) {
- var errMsg = Zotero.DB.getLastErrorString()
- + " (" + this.id + "," + itemID + "," + nextOrderIndex + ")";
- throw (e + ' [ERROR: ' + errMsg + ']');
- }
-
- notifierPairs.push(this.id + '-' + itemID);
- }
-
- sql = "UPDATE collections SET dateModified=?, clientDateModified=? WHERE collectionID=?";
- Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id]);
-
- Zotero.DB.commitTransaction();
- } finally {
- if(reenableScriptIndicator) {
- Zotero.UnresponsiveScriptIndicator.enable();
+ let item = yield Zotero.Items.getAsync(itemID);
+ yield item.loadCollections();
+ item.addToCollection(this.id);
+ yield item.save({
+ skipDateModifiedUpdate: true
+ });
}
- }
+ }.bind(this));
- Zotero.Collections.reload(this.id);
- Zotero.Notifier.trigger('add', 'collection-item', notifierPairs);
-}
-
+ yield this.loadChildItems(true);
+});
/**
- * Remove an item from the collection (does not delete item from library)
+ * Remove a item from the collection. The item is not deleted from the library.
*
- * Warning: Operates on DB directly without separate save()
+ * Does not require a separate save()
+ *
+ * @return {Promise}
*/
-Zotero.Collection.prototype.removeItem = function(itemID) {
- var childItems = this.getChildItems(true, true);
- if (childItems) {
- var index = childItems.indexOf(itemID);
- if (index == -1) {
- Zotero.debug("Item " + itemID + " not a child of collection "
- + this.id + " in Zotero.Collection.removeItem()");
- return false;
- }
- }
- else {
- return false;
- }
-
- Zotero.DB.beginTransaction();
-
- var sql = "DELETE FROM collectionItems WHERE collectionID=? AND itemID=?";
- Zotero.DB.query(sql, [this.id, itemID]);
-
- if (!this._dateModifiedLocked) {
- sql = "UPDATE collections SET dateModified=?, clientDateModified=? WHERE collectionID=?";
- Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id])
- }
-
- Zotero.DB.commitTransaction();
-
- Zotero.Collections.reload(this.id);
-
- Zotero.Notifier.trigger('remove', 'collection-item', this.id + '-' + itemID);
-
- return true;
+Zotero.Collection.prototype.removeItem = function (itemIDs) {
+ return this.removeItems([itemID]);
}
/**
- * Remove multiple items from the collection in batch
- * (does not delete item from library)
+ * Remove multiple items from the collection in batch.
+ * The items are not deleted from the library.
*
- * Warning: Operates on DB directly without separate save()
+ * Does not require a separate save()
*/
-Zotero.Collection.prototype.removeItems = function(itemIDs) {
+Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (itemIDs) {
if (!itemIDs || !itemIDs.length) {
return;
}
- Zotero.DB.beginTransaction();
- for (var i=0; i<itemIDs.length; i++) {
- this.removeItem(itemIDs[i]);
- }
- Zotero.DB.commitTransaction();
-}
+ yield this.loadChildItems();
+ var current = this.getChildItems(true);
+
+ return Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<itemIDs.length; i++) {
+ let itemID = itemIDs[i];
+
+ if (current.indexOf(itemID) == -1) {
+ Zotero.debug("Item " + itemID + " not a child of collection " + this.id);
+ continue;
+ }
+
+ let item = yield Zotero.Items.getAsync(itemID);
+ yield item.loadCollections();
+ item.removeFromCollection(this.id);
+ yield item.save({
+ skipDateModifiedUpdate: true
+ })
+ }
+ }.bind(this));
+
+ yield this.loadChildItems(true);
+});
/**
* Check if an item belongs to the collection
**/
Zotero.Collection.prototype.hasItem = function(itemID) {
- if (!this._childItemsLoaded) {
- this._loadChildItems();
- }
+ this._requireData('childItems');
- for each(var item in this._childItems) {
- if (item.id == itemID) {
+ for (let i=0; i<this._childItems.length; i++) {
+ if (this._childItems[i].id == itemID) {
return true;
}
}
@@ -830,15 +552,15 @@ Zotero.Collection.prototype.hasItem = function(itemID) {
}
-Zotero.Collection.prototype.hasDescendent = function(type, id) {
- var descendents = this.getDescendents();
+Zotero.Collection.prototype.hasDescendent = Zotero.Promise.coroutine(function* (type, id) {
+ var descendents = yield this.getDescendents();
for (var i=0, len=descendents.length; i<len; i++) {
if (descendents[i].type == type && descendents[i].id == id) {
return true;
}
}
return false;
-}
+});
/**
@@ -849,10 +571,8 @@ Zotero.Collection.prototype.hasDescendent = function(type, id) {
*
* @param {Zotero.Collection} collection Zotero.Collection to compare this item to
* @param {Boolean} includeMatches Include all fields, even those that aren't different
- * @param {Boolean} ignoreOnlyDateModified If no fields other than dateModified
- * are different, just return false
*/
-Zotero.Collection.prototype.diff = function (collection, includeMatches, ignoreOnlyDateModified) {
+Zotero.Collection.prototype.diff = function (collection, includeMatches) {
var diff = [];
var thisData = this.serialize();
var otherData = collection.serialize();
@@ -893,10 +613,7 @@ Zotero.Collection.prototype.diff = function (collection, includeMatches, ignoreO
diff[1].childItems = [];
}
- // DEBUG: ignoreOnlyDateModified wouldn't work if includeMatches was set?
- if (numDiffs == 0 ||
- (ignoreOnlyDateModified && numDiffs == 1
- && diff[0].primary && diff[0].primary.dateModified)) {
+ if (numDiffs == 0) {
return false;
}
@@ -942,62 +659,64 @@ Zotero.Collection.prototype.clone = function (includePrimary, newCollection) {
* Deletes collection and all descendent collections (and optionally items)
**/
Zotero.Collection.prototype.erase = function(deleteItems) {
- Zotero.DB.beginTransaction();
-
- var descendents = this.getDescendents(false, null, true);
var collections = [this.id];
- var items = [];
var notifierData = {};
- notifierData[this.id] = { old: this.serialize() };
- var del = [];
- for(var i=0, len=descendents.length; i<len; i++) {
- // Descendent collections
- if (descendents[i].type == 'collection') {
- collections.push(descendents[i].id);
- var c = Zotero.Collections.get(descendents[i].id);
- if (c) {
- notifierData[c.id] = { old: c.serialize() };
+ return Zotero.DB.executeTransaction(function* () {
+ var descendents = yield this.getDescendents(false, null, true);
+ var items = [];
+ notifierData[this.id] = { old: this.toJSON() };
+
+ var del = [];
+ for(var i=0, len=descendents.length; i<len; i++) {
+ // Descendent collections
+ if (descendents[i].type == 'collection') {
+ collections.push(descendents[i].id);
+ var c = yield Zotero.Collections.getAsync(descendents[i].id);
+ if (c) {
+ notifierData[c.id] = { old: c.toJSON() };
+ }
}
- }
- // Descendent items
- else {
- // Delete items from DB
- if (deleteItems) {
- del.push(descendents[i].id);
+ // Descendent items
+ else {
+ // Delete items from DB
+ if (deleteItems) {
+ del.push(descendents[i].id);
+ }
}
}
- }
- if (del.length) {
- Zotero.Items.trash(del);
- }
-
- // Remove relations
- var uri = Zotero.URI.getCollectionURI(this);
- Zotero.Relations.eraseByURI(uri);
-
- var placeholders = collections.map(function () '?').join();
-
- // Remove item associations for all descendent collections
- Zotero.DB.query('DELETE FROM collectionItems WHERE collectionID IN '
- + '(' + placeholders + ')', collections);
-
- // Remove parent definitions first for FK check
- Zotero.DB.query('UPDATE collections SET parentCollectionID=NULL '
- + 'WHERE parentCollectionID IN (' + placeholders + ')', collections);
-
- // And delete all descendent collections
- Zotero.DB.query('DELETE FROM collections WHERE collectionID IN '
- + '(' + placeholders + ')', collections);
-
- Zotero.DB.commitTransaction();
-
- // Clear deleted collection from internal memory
- Zotero.Collections.unload(collections);
-
- Zotero.Collections.reloadAll();
-
- Zotero.Notifier.trigger('delete', 'collection', collections, notifierData);
+ if (del.length) {
+ yield Zotero.Items.trash(del);
+ }
+
+ // Remove relations
+ var uri = Zotero.URI.getCollectionURI(this);
+ yield Zotero.Relations.eraseByURI(uri);
+
+ var placeholders = collections.map(function () '?').join();
+
+ // Remove item associations for all descendent collections
+ yield Zotero.DB.queryAsync('DELETE FROM collectionItems WHERE collectionID IN '
+ + '(' + placeholders + ')', collections);
+
+ // Remove parent definitions first for FK check
+ yield Zotero.DB.queryAsync('UPDATE collections SET parentCollectionID=NULL '
+ + 'WHERE parentCollectionID IN (' + placeholders + ')', collections);
+
+ // And delete all descendent collections
+ yield Zotero.DB.queryAsync ('DELETE FROM collections WHERE collectionID IN '
+ + '(' + placeholders + ')', collections);
+
+ // TODO: Update member items
+ }.bind(this))
+ .then(function () {
+ // Clear deleted collection from internal memory
+ Zotero.Collections.unload(collections);
+ //return Zotero.Collections.reloadAll();
+ })
+ .then(function () {
+ Zotero.Notifier.trigger('delete', 'collection', collections, notifierData);
+ });
}
@@ -1006,12 +725,6 @@ Zotero.Collection.prototype.isCollection = function() {
}
-Zotero.Collection.prototype.toArray = function() {
- Zotero.debug('Collection.toArray() is deprecated -- use Collection.serialize()');
- return this.serialize();
-}
-
-
Zotero.Collection.prototype.serialize = function(nested) {
var childCollections = this.getChildCollections(true);
var childItems = this.getChildItems(true);
@@ -1019,9 +732,7 @@ Zotero.Collection.prototype.serialize = function(nested) {
primary: {
collectionID: this.id,
libraryID: this.libraryID,
- key: this.key,
- dateAdded: this.dateAdded,
- dateModified: this.dateModified
+ key: this.key
},
fields: {
name: this.name,
@@ -1035,6 +746,20 @@ Zotero.Collection.prototype.serialize = function(nested) {
}
+Zotero.Collection.prototype.toJSON = function (options, patch) {
+ var obj = {};
+ if (options && options.includeKey) {
+ obj.collectionKey = this.key;
+ }
+ if (options && options.includeVersion) {
+ obj.collectionVersion = this.version;
+ }
+ obj.name = this.name;
+ obj.parentCollection = this.parentKey ? this.parentKey : false;
+ return obj;
+}
+
+
/**
* Returns an array of descendent collections and items
*
@@ -1047,7 +772,7 @@ Zotero.Collection.prototype.serialize = function(nested) {
* 'type' ('item' or 'collection'), 'parent',
* and, if collection, 'name' and the nesting 'level'
*/
-Zotero.Collection.prototype.getChildren = function(recursive, nested, type, includeDeletedItems, level) {
+Zotero.Collection.prototype.getChildren = Zotero.Promise.coroutine(function* (recursive, nested, type, includeDeletedItems, level) {
if (!this.id) {
throw ('Zotero.Collection.getChildren() cannot be called on an unsaved item');
}
@@ -1080,7 +805,7 @@ Zotero.Collection.prototype.getChildren = function(recursive, nested, type, incl
sql += " AND itemID NOT IN (SELECT itemID FROM deletedItems)";
}
}
- var children = Zotero.DB.query(sql, this.id);
+ var children = yield Zotero.DB.queryAsync(sql, this.id);
for(var i=0, len=children.length; i<len; i++) {
// This seems to not work without parseInt() even though
@@ -1101,9 +826,10 @@ Zotero.Collection.prototype.getChildren = function(recursive, nested, type, incl
}
if (recursive) {
- var descendents =
- Zotero.Collections.get(children[i].id).
- getChildren(true, nested, type, includeDeletedItems, level+1);
+ let child = yield Zotero.Collections.getAsync(children[i].id);
+ let descendents = yield child.getChildren(
+ true, nested, type, includeDeletedItems, level+1
+ );
if (nested) {
toReturn[toReturn.length-1].children = descendents;
@@ -1130,13 +856,15 @@ Zotero.Collection.prototype.getChildren = function(recursive, nested, type, incl
}
return toReturn;
-}
+});
/**
* Alias for the recursive mode of getChildren()
+ *
+ * @return {Promise}
*/
-Zotero.Collection.prototype.getDescendents = function(nested, type, includeDeletedItems) {
+Zotero.Collection.prototype.getDescendents = function (nested, type, includeDeletedItems) {
return this.getChildren(true, nested, type, includeDeletedItems);
}
@@ -1145,46 +873,15 @@ Zotero.Collection.prototype.getDescendents = function(nested, type, includeDelet
* Return a collection in the specified library equivalent to this collection
*/
Zotero.Collection.prototype.getLinkedCollection = function (libraryID) {
- if (libraryID == this.libraryID) {
- throw ("Collection is already in library " + libraryID + " in Zotero.Collection.getLinkedCollection()");
- }
-
- var predicate = Zotero.Relations.linkedObjectPredicate;
- var collectionURI = Zotero.URI.getCollectionURI(this);
- var links = Zotero.Relations.getObject(collectionURI, predicate, false).concat(
- Zotero.Relations.getSubject(false, predicate, collectionURI)
- );
-
- if (!links.length) {
- return false;
- }
-
- if (libraryID) {
- var libraryCollectionPrefix = Zotero.URI.getLibraryURI(libraryID) + "/collections/";
- }
- else {
- var libraryCollectionPrefix = Zotero.URI.getCurrentUserURI() + "/collections/";
- }
- for each(var link in links) {
- if (link.indexOf(libraryCollectionPrefix) == 0) {
- var collection = Zotero.URI.getURICollection(link);
- if (!collection) {
- Zotero.debug("Referenced linked collection '" + link + "' not found in Zotero.Collection.getLinkedCollection()", 2);
- continue;
- }
- return collection;
- }
- }
- return false;
-}
+ return this._getLinkedObject(libraryID);
+};
-
-Zotero.Collection.prototype.addLinkedCollection = function (collection) {
+Zotero.Collection.prototype.addLinkedCollection = Zotero.Promise.coroutine(function* (collection) {
var url1 = Zotero.URI.getCollectionURI(this);
var url2 = Zotero.URI.getCollectionURI(collection);
var predicate = Zotero.Relations.linkedObjectPredicate;
- if (Zotero.Relations.getByURIs(url1, predicate, url2).length
- || Zotero.Relations.getByURIs(url2, predicate, url1).length) {
+ if ((yield Zotero.Relations.getByURIs(url1, predicate, url2)).length
+ || (yield Zotero.Relations.getByURIs(url2, predicate, url1)).length) {
Zotero.debug("Collections " + this.key + " and " + collection.key + " are already linked");
return false;
}
@@ -1193,13 +890,12 @@ Zotero.Collection.prototype.addLinkedCollection = function (collection) {
// Otherwise, store with personal library.
var libraryID = (this.libraryID && collection.libraryID) ? this.libraryID : 0;
- Zotero.Relations.add(libraryID, url1, predicate, url2);
-}
+ yield Zotero.Relations.add(libraryID, url1, predicate, url2);
+});
//
// Private methods
//
-
Zotero.Collection.prototype._prepFieldChange = function (field) {
if (!this._changed) {
this._changed = {};
@@ -1214,242 +910,27 @@ Zotero.Collection.prototype._prepFieldChange = function (field) {
}
-/**
- * Get the collectionID of the parent collection
- * @return {Integer}
- */
-Zotero.Collection.prototype._getParent = function() {
- if (this._parent !== false) {
- if (!this._parent) {
- return null;
- }
- if (typeof this._parent == 'number') {
- return this._parent;
- }
- var parentCollection = Zotero.Collections.getByLibraryAndKey(this.libraryID, this._parent);
- if (!parentCollection) {
- var msg = "Parent collection for keyed parent doesn't exist in Zotero.Collection._getParent()";
- var e = new Zotero.Error(msg, "MISSING_OBJECT");
- throw (e);
- }
- // Replace stored key with id
- this._parent = parentCollection.id;
- return parentCollection.id;
- }
-
- if (!this.id) {
- return false;
- }
-
- var sql = "SELECT parentCollectionID FROM collections WHERE collectionID=?";
- var parentCollectionID = Zotero.DB.valueQuery(sql, this.id);
- if (!parentCollectionID) {
- parentCollectionID = null;
- }
- this._parent = parentCollectionID;
- return parentCollectionID;
-}
+Zotero.Collection.prototype.reloadHasChildCollections = Zotero.Promise.coroutine(function* () {
+ var sql = "SELECT COUNT(*) FROM collections WHERE parentCollectionID=?";
+ this._hasChildCollections = !!(yield Zotero.DB.valueQueryAsync(sql, this.id));
+});
-/**
- * Get the key of the parent collection
- * @return {String}
- */
-Zotero.Collection.prototype._getParentKey = function() {
- if (this._parent !== false) {
- if (!this._parent) {
- return null;
- }
- if (typeof this._parent == 'string') {
- return this._parent;
- }
- var parentCollection = Zotero.Collections.get(this._parent);
- return parentCollection.key;
- }
-
- if (!this.id) {
- return false;
- }
-
- var sql = "SELECT B.key FROM collections A JOIN collections B "
- + "ON (A.parentCollectionID=B.collectionID) WHERE A.collectionID=?";
- var key = Zotero.DB.valueQuery(sql, this.id);
- if (!key) {
- key = null;
- }
- this._parent = key;
- return key;
-}
-
-
-Zotero.Collection.prototype._setParent = function(parentCollectionID) {
- if (this.id || this.key) {
- if (!this.loaded) {
- this.load(true);
- }
- }
- else {
- this.loaded = true;
- }
-
- var oldParentCollectionID = this._getParent();
- if (oldParentCollectionID == parentCollectionID) {
- Zotero.debug("Parent collection has not changed for collection " + this.id);
- return false;
- }
-
- if (this.id && this.exists() && !this._previousData) {
- this._previousData = this.serialize();
- }
-
- this._parent = parentCollectionID ? parseInt(parentCollectionID) : null;
- if (!this._changed) {
- this._changed = {};
- }
- this._changed.parent = true;
-
- return true;
-}
-
-
-Zotero.Collection.prototype._setParentKey = function(parentCollectionKey) {
- if (this.id || this.key) {
- if (!this.loaded) {
- this.load(true);
- }
- }
- else {
- this.loaded = true;
- }
-
- var oldParentCollectionID = this._getParent();
- if (oldParentCollectionID) {
- var parentCollection = Zotero.Collections.get(oldParentCollectionID)
- var oldParentCollectionKey = parentCollection.key;
- }
- else {
- var oldParentCollectionKey = null;
- }
- if (oldParentCollectionKey == parentCollectionKey) {
- Zotero.debug("Parent collection has not changed in Zotero.Collection._setParentKey()");
- return false;
- }
-
- if (this.id && this.exists() && !this._previousData) {
- this._previousData = this.serialize();
- }
-
- this._parent = parentCollectionKey ? parentCollectionKey : null;
- if (!this._changed) {
- this._changed = {};
- }
- this._changed.parent = true;
-
- return true;
-}
-
-
-/*
-Zotero.Collection.prototype._setChildCollections = function (collectionIDs) {
- this._setChildren('collection', collectionIDs);
-}
-*/
-
-
-Zotero.Collection.prototype._setChildItems = function (itemIDs) {
- this._setChildren('item', itemIDs);
-}
-
-
-Zotero.Collection.prototype._setChildren = function (type, ids) {
- if (type != 'collection' && type != 'item') {
- throw ("Invalid type '" + type + "' in Zotero.Collection._setChildren()");
- }
-
- var Type = type.charAt(0).toUpperCase() + type.substr(1);
- var Types = Type + 's'; // 'Items'
- var types = type + 's'; // 'items'
-
- if (!this['_child' + Types + 'Loaded']) {
- this['_loadChild' + Types]();
- }
-
- if (ids.constructor.name != 'Array') {
- throw (type + 'IDs must be an array in Zotero.Collection._setChildren()');
- }
-
- var currentIDs = this['getChild' + Types](true);
- if (!currentIDs) {
- currentIDs = [];
- }
- var oldIDs = []; // children being kept
- var newIDs = []; // new children
-
- if (ids.length == 0) {
- if (this['_child' + Types].length == 0) {
- Zotero.debug('No child ' + types + ' added', 4);
- return false;
- }
- }
- else {
- for (var i in ids) {
- var id = parseInt(ids[i]);
- if (isNaN(id)) {
- throw ("Invalid " + type + "ID '" + ids[i]
- + "' in Zotero.Collection._setChildren()");
- }
-
- if (currentIDs.indexOf(id) != -1) {
- Zotero.debug(Type + " " + ids[i]
- + " is already a child of collection " + this.id);
- oldIDs.push(id);
- continue;
- }
-
- newIDs.push(id);
- }
- }
-
- // Mark as changed if new or removed ids
- if (newIDs.length > 0 || oldIDs.length != this['_child' + Types].length) {
- this._prepFieldChange('child' + Types);
- }
- else {
- Zotero.debug('Child ' + types + ' not changed', 4);
- return false;
- }
-
- newIDs = oldIDs.concat(newIDs);
-
- this['_child' + Types] = [];
- // Items.get() can take an array
- if (type == 'item') {
- this._childItems = Zotero.Items.get(newIDs);
- }
- else {
- for each(var id in newIDs) {
- var obj = Zotero[Types].get(id);
- if (!obj) {
- throw (type + ' ' + id + ' not found in Zotero.Collection._setChildren()');
- }
- this['_child' + Types].push(obj);
- }
+Zotero.Collection.prototype.loadChildCollections = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.childCollections && !reload) {
+ return;
}
- return true;
-}
-
-Zotero.Collection.prototype._loadChildCollections = function () {
var sql = "SELECT collectionID FROM collections WHERE parentCollectionID=?";
- var ids = Zotero.DB.columnQuery(sql, this.id);
+ var ids = yield Zotero.DB.columnQueryAsync(sql, this.id);
this._childCollections = [];
if (ids) {
for each(var id in ids) {
- var col = Zotero.Collections.get(id);
+ var col = yield Zotero.Collections.getAsync(id);
if (!col) {
- throw ('Collection ' + id + ' not found in Zotero.Collection._loadChildCollections()');
+ throw new Error('Collection ' + id + ' not found');
}
this._childCollections.push(col);
}
@@ -1459,13 +940,19 @@ Zotero.Collection.prototype._loadChildCollections = function () {
this._hasChildCollections = false;
}
- this._childCollectionsLoaded = true;
-}
+ this._loaded.childCollections = true;
+ this._clearChanged('childCollections');
+});
-Zotero.Collection.prototype._loadChildItems = function() {
- if (!this.id) {
- //throw ("Collection id not set in Zotero.Collection._loadChildItems()");
- this._childItemsLoaded = true;
+
+Zotero.Collection.prototype.reloadHasChildItems = Zotero.Promise.coroutine(function* () {
+ var sql = "SELECT COUNT(*) FROM collectionItems WHERE collectionID=?";
+ this._hasChildItems = !!(yield Zotero.DB.valueQueryAsync(sql, this.id));
+});
+
+
+Zotero.Collection.prototype.loadChildItems = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.childItems && !reload) {
return;
}
@@ -1473,35 +960,54 @@ Zotero.Collection.prototype._loadChildItems = function() {
// DEBUG: Fix for child items created via context menu on parent within
// a collection being added to the current collection
+ "AND itemID NOT IN "
- + "(SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL) "
+ + "(SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
+ "AND itemID NOT IN "
- + "(SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL)";
- var ids = Zotero.DB.columnQuery(sql, this.id);
+ + "(SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL)";
+ var ids = yield Zotero.DB.columnQueryAsync(sql, this.id);
this._childItems = [];
if (ids) {
- var items = Zotero.Items.get(ids)
+ var items = yield Zotero.Items.getAsync(ids)
if (items) {
this._childItems = items;
}
}
- this._childItemsLoaded = true;
-}
+ this._loaded.childItems = true;
+ this._clearChanged('childItems');
+});
/**
- * Invalid child collection cache
+ * Invalidate child collection cache, if collections are loaded
*
* Note: This is called by Zotero.Collections.refreshChildCollections()
*
* @private
+ * @return {Promise}
*/
-Zotero.Collection.prototype._refreshChildCollections = function () {
- this._hasChildCollections = undefined;
- this._childCollectionsLoaded = false;
-}
+Zotero.Collection.prototype._refreshChildCollections = Zotero.Promise.coroutine(function* () {
+ yield this.reloadHasChildCollections();
+ if (this._loaded.childCollections) {
+ return this.loadChildCollections(true);
+ }
+});;
+
+
+/**
+ * Invalidate child item cache, if items are loaded
+ *
+ * Note: This is called by Zotero.Collections.refreshChildItems()
+ *
+ * @private
+ */
+Zotero.Collection.prototype._refreshChildItems = Zotero.Promise.coroutine(function* () {
+ yield this.reloadHasChildItems();
+ if (this._loaded.childItems) {
+ return this.loadChildItems(true);
+ }
+});
Zotero.Collection.prototype._generateKey = function () {
diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js
@@ -31,20 +31,22 @@ Zotero.Collections = new function() {
Zotero.DataObjects.apply(this, ['collection']);
this.constructor.prototype = new Zotero.DataObjects();
- this.get = get;
- this.add = add;
- this.erase = erase;
-
- /*
- * Returns a Zotero.Collection object for a collectionID
- */
- function get(id) {
- if (this._reloadCache) {
- this.reloadAll();
- }
- return this._objectCache[id] ? this._objectCache[id] : false;
- }
-
+ this._primaryDataSQLParts = {
+ collectionID: "O.collectionID",
+ name: "O.collectionName AS name",
+ libraryID: "O.libraryID",
+ key: "O.key",
+ version: "O.version",
+ synced: "O.synced",
+
+ parentID: "O.parentCollectionID AS parentID",
+ parentKey: "CP.key AS parentKey",
+
+ hasChildCollections: "(SELECT COUNT(*) FROM collections WHERE "
+ + "parentCollectionID=O.collectionID) != 0 AS hasChildCollections",
+ hasChildItems: "(SELECT COUNT(*) FROM collectionItems WHERE "
+ + "collectionID=O.collectionID) != 0 AS hasChildItems "
+ };
/**
* Add new collection to DB and return Collection object
@@ -54,19 +56,84 @@ Zotero.Collections = new function() {
*
* Returns true on success; false on error
**/
- function add(name, parent) {
+ this.add = function (name, parent) {
var col = new Zotero.Collection;
col.name = name;
col.parent = parent;
var id = col.save();
- return this.get(id);
+ return this.getAsync(id);
}
+ /*
+ * Zotero.getCollections(parent)
+ *
+ * Returns an array of all collections are children of a collection
+ * as Zotero.Collection instances
+ *
+ * Takes parent collectionID as optional parameter;
+ * by default, returns root collections
+ */
+ this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parent, recursive) {
+ var toReturn = [];
+
+ if (!parent) {
+ parent = null;
+ }
+
+ var sql = "SELECT collectionID AS id, collectionName AS name FROM collections C "
+ + "WHERE libraryID=? AND parentCollectionID " + (parent ? '= ' + parent : 'IS NULL');
+ var children = yield Zotero.DB.queryAsync(sql, [libraryID]);
+
+ if (!children) {
+ Zotero.debug('No child collections of collection ' + parent, 5);
+ return toReturn;
+ }
+
+ // Do proper collation sort
+ var collation = Zotero.getLocaleCollation();
+ children.sort(function (a, b) {
+ return collation.compareString(1, a.name, b.name);
+ });
+
+ for (var i=0, len=children.length; i<len; i++) {
+ var obj = yield this.getAsync(children[i].id);
+ if (!obj) {
+ throw ('Collection ' + children[i].id + ' not found');
+ }
+
+ toReturn.push(obj);
+
+ // If recursive, get descendents
+ if (recursive) {
+ var desc = obj.getDescendents(false, 'collection');
+ for (var j in desc) {
+ var obj2 = yield this.getAsync(desc[j]['id']);
+ if (!obj2) {
+ throw new Error('Collection ' + desc[j] + ' not found');
+ }
+
+ // TODO: This is a quick hack so that we can indent subcollections
+ // in the search dialog -- ideally collections would have a
+ // getLevel() method, but there's no particularly quick way
+ // of calculating that without either storing it in the DB or
+ // changing the schema to Modified Preorder Tree Traversal,
+ // and I don't know if we'll actually need it anywhere else.
+ obj2.level = desc[j].level;
+
+ toReturn.push(obj2);
+ }
+ }
+ }
+
+ return toReturn;
+ });
+
+
this.getCollectionsContainingItems = function (itemIDs, asIDs) {
// If an unreasonable number of items, don't try
if (itemIDs.length > 100) {
- return Q([]);
+ return Zotero.Promise.resolve([]);
}
var sql = "SELECT collectionID FROM collections WHERE ";
@@ -86,28 +153,45 @@ Zotero.Collections = new function() {
/**
- * Invalidate child collection cache in specified collections, skipping
- * any that aren't loaded
+ * Invalidate child collection cache in specified collections, skipping any that aren't loaded
+ *
+ * @param {Integer|Integer[]} ids One or more collectionIDs
+ */
+ this.refreshChildCollections = Zotero.Promise.coroutine(function* (ids) {
+ ids = Zotero.flattenArguments(ids);
+
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ if (this._objectCache[id]) {
+ yield this._objectCache[id]._refreshChildCollections();
+ }
+ }
+ });
+
+
+ /**
+ * Invalidate child item cache in specified collections, skipping any that aren't loaded
*
* @param {Integer|Integer[]} ids One or more itemIDs
*/
- this.refreshChildCollections = function (ids) {
+ this.refreshChildItems = Zotero.Promise.coroutine(function* (ids) {
ids = Zotero.flattenArguments(ids);
- for each(var id in ids) {
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
if (this._objectCache[id]) {
- this._objectCache[id]._refreshChildCollections();
+ yield this._objectCache[id]._refreshChildItems();
}
}
- }
+ });
- function erase(ids) {
+ this.erase = function (ids) {
ids = Zotero.flattenArguments(ids);
Zotero.DB.beginTransaction();
for each(var id in ids) {
- var collection = this.get(id);
+ var collection = this.getAsync(id);
if (collection) {
collection.erase();
}
@@ -120,50 +204,14 @@ Zotero.Collections = new function() {
}
- this._load = function () {
- if (!arguments[0] && !this._reloadCache) {
- return;
- }
-
- this._reloadCache = false;
-
+ this._getPrimaryDataSQL = function () {
// This should be the same as the query in Zotero.Collection.load(),
// just without a specific collectionID
- var sql = "SELECT C.*, "
- + "(SELECT COUNT(*) FROM collections WHERE "
- + "parentCollectionID=C.collectionID)!=0 AS hasChildCollections, "
- + "(SELECT COUNT(*) FROM collectionItems WHERE "
- + "collectionID=C.collectionID)!=0 AS hasChildItems "
- + "FROM collections C WHERE 1";
- if (arguments[0]) {
- sql += " AND collectionID IN (" + Zotero.join(arguments[0], ",") + ")";
- }
- var rows = Zotero.DB.query(sql);
- var ids = [];
- for each(var row in rows) {
- var id = row.collectionID;
- ids.push(id);
-
- // Collection doesn't exist -- create new object and stuff in array
- if (!this._objectCache[id]) {
- //this.get(id);
- this._objectCache[id] = new Zotero.Collection;
- this._objectCache[id].loadFromRow(row);
- }
- // Existing collection -- reload in place
- else {
- this._objectCache[id].loadFromRow(row);
- }
- }
-
- // If loading all creators, remove old creators that no longer exist
- if (!arguments[0]) {
- for each(var c in this._objectCache) {
- if (ids.indexOf(c.id) == -1) {
- this.unload(c.id);
- }
- }
- }
+ return "SELECT "
+ + Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
+ + "FROM collections O "
+ + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID) "
+ + "WHERE 1";
}
}
diff --git a/chrome/content/zotero/xpcom/data/creator.js b/chrome/content/zotero/xpcom/data/creator.js
@@ -1,560 +0,0 @@
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright © 2009 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This file is part of Zotero.
-
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see <http://www.gnu.org/licenses/>.
-
- ***** END LICENSE BLOCK *****
-*/
-
-
-Zotero.Creator = function () {
- if (arguments[0]) {
- throw ("Zotero.Creator constructor doesn't take any parameters");
- }
-
- this._init();
-}
-
-
-Zotero.Creator.prototype._init = function () {
- this._id = null;
- this._libraryID = null
- this._key = null;
- this._firstName = null;
- this._lastName = null;
- this._fieldMode = null;
- this._birthYear = null;
- this._dateAdded = null;
- this._dateModified = null;
-
- this._creatorDataID = null;
- this._loaded = false;
- this._changed = false;
- this._previousData = false;
-}
-
-
-Zotero.Creator.prototype.__defineGetter__('objectType', function () { return 'creator'; });
-Zotero.Creator.prototype.__defineGetter__('id', function () { return this._get('id'); });
-Zotero.Creator.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
-Zotero.Creator.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
-Zotero.Creator.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
-Zotero.Creator.prototype.__defineGetter__('key', function () { return this._get('key'); });
-Zotero.Creator.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
-Zotero.Creator.prototype.__defineGetter__('creatorDataID', function () { return this._get('creatorDataID'); });
-Zotero.Creator.prototype.__defineGetter__('firstName', function () { return this._get('firstName'); });
-Zotero.Creator.prototype.__defineSetter__('firstName', function (val) { this._set('firstName', val); });
-Zotero.Creator.prototype.__defineGetter__('lastName', function () { return this._get('lastName'); });
-Zotero.Creator.prototype.__defineSetter__('lastName', function (val) { this._set('lastName', val); });
-Zotero.Creator.prototype.__defineGetter__('fieldMode', function () { return this._get('fieldMode'); });
-Zotero.Creator.prototype.__defineSetter__('fieldMode', function (val) { this._set('fieldMode', val); });
-Zotero.Creator.prototype.__defineGetter__('birthYear', function () { return this._get('birthYear'); });
-Zotero.Creator.prototype.__defineSetter__('birthYear', function (val) { this._set('birthYear', val); });
-Zotero.Creator.prototype.__defineGetter__('dateAdded', function () { return this._get('dateAdded'); });
-Zotero.Creator.prototype.__defineSetter__('dateAdded', function (val) { this._set('dateAdded', val); });
-Zotero.Creator.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
-Zotero.Creator.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
-
-// Block properties that can't be set this way
-//Zotero.Creator.prototype.__defineSetter__('creatorDataID', function () { this._set('creatorDataID', val); });
-
-
-Zotero.Creator.prototype._get = function (field) {
- if ((this._id || this._key) && !this._loaded) {
- this.load(true);
- }
- return this['_' + field];
-}
-
-
-Zotero.Creator.prototype._set = function (field, val) {
- switch (field) {
- case 'id':
- case 'libraryID':
- case 'key':
- if (field == 'libraryID') {
- val = Zotero.DataObjectUtilities.checkLibraryID(val);
- }
-
- if (val == this['_' + field]) {
- return;
- }
-
- if (this._loaded) {
- throw ("Cannot set " + field + " after object is already loaded in Zotero.Creator._set()");
- }
-
- this._checkValue(field, val);
-
- this['_' + field] = val;
- return;
-
- case 'firstName':
- case 'lastName':
- case 'shortName':
- if (val) {
- val = Zotero.Utilities.trim(val);
- }
- else {
- val = '';
- }
- break;
-
- case 'fieldMode':
- val = val ? parseInt(val) : 0;
- break;
-
- case 'creatorDataID':
- throw ("Invalid field '" + field + "' in Zotero.Creator.set()");
- }
-
- if (this.id || this.key) {
- if (!this._loaded) {
- this.load(true);
- }
- }
- else {
- this._loaded = true;
- }
-
- this._checkValue(field, val);
-
- if (this['_' + field] != val) {
- if (!this._changed) {
- this._changed = {};
- }
- this._changed[field] = true;
- if (this.id && this.exists() && !this._previousData) {
- this._previousData = this.serialize();
- }
-
- this['_' + field] = val;
- }
-}
-
-
-Zotero.Creator.prototype.setFields = function (fields) {
- this.firstName = fields.firstName;
- this.lastName = fields.lastName;
- this.fieldMode = fields.fieldMode;
- this.birthYear = fields.birthYear;
-}
-
-
-/**
- * Check if creator exists in the database
- *
- * @return bool TRUE if the creator exists, FALSE if not
- */
-Zotero.Creator.prototype.exists = function() {
- if (!this.id) {
- throw ('creatorID not set in Zotero.Creator.exists()');
- }
-
- var sql = "SELECT COUNT(*) FROM creators WHERE creatorID=?";
- return !!Zotero.DB.valueQuery(sql, this.id);
-}
-
-
-Zotero.Creator.prototype.hasChanged = function () {
- return this._changed;
-}
-
-
-Zotero.Creator.prototype.save = function () {
- Zotero.Creators.editCheck(this);
-
- Zotero.debug("Saving creator " + this.id);
-
- if (!this.firstName && !this.lastName) {
- throw ('First and last name are empty in Zotero.Creator.save()');
- }
-
- if (!this.hasChanged()) {
- Zotero.debug("Creator " + this.id + " has not changed");
- return false;
- }
-
- if (this.fieldMode == 1 && this.firstName) {
- throw ("First name ('" + this.firstName + "') must be empty in single-field mode in Zotero.Creator.save()");
- }
-
- Zotero.DB.beginTransaction();
-
- var isNew = !this.id || !this.exists();
-
- try {
- // how to know if date modified changed (in server code too?)
-
- var creatorID = this.id ? this.id : Zotero.ID.get('creators');
-
- var key = this.key ? this.key : this._generateKey();
-
- // If this was the only creator with the previous data,
- // see if we can reuse or remove the old data row
- if (this.creatorDataID) {
- var count = Zotero.Creators.countCreatorsWithData(this.creatorDataID);
- if (count == 1) {
- var newCreatorDataID = Zotero.Creators.getDataID(this);
- // Data hasn't changed
- if (this.creatorDataID == newCreatorDataID) {
- var creatorDataID = this.creatorDataID;
- }
- // Existing data row with the new data -- switch to that
- // and flag old row for deletion below
- else if (newCreatorDataID) {
- var deleteDataID = this.creatorDataID;
- var creatorDataID = newCreatorDataID;
- }
- // Update current data row with new data
- else {
- Zotero.Creators.updateData(this.creatorDataID, this);
- var creatorDataID = this.creatorDataID;
- }
- }
- }
-
- if (!creatorDataID) {
- var creatorDataID = Zotero.Creators.getDataID(this, true);
- if (creatorDataID != this.creatorDataID) {
- this._creatorDataID = creatorDataID;
- }
- }
-
- var columns = [
- 'creatorID',
- 'creatorDataID',
- 'dateAdded',
- 'dateModified',
- 'clientDateModified',
- 'libraryID',
- 'key'
- ];
- var placeholders = columns.map(function () '?').join();
- var sqlValues = [
- creatorID ? { int: creatorID } : null,
- { int: creatorDataID },
- // If date added isn't set, use current timestamp
- this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
- // If date modified hasn't changed, use current timestamp
- this._changed.dateModified ?
- this.dateModified : Zotero.DB.transactionDateTime,
- Zotero.DB.transactionDateTime,
- this.libraryID ? this.libraryID : 0,
- key
- ];
-
- if (isNew) {
- var sql = "INSERT INTO creators (" + columns.join(', ') + ") "
- + "VALUES (" + placeholders + ")";
- var insertID = Zotero.DB.query(sql, sqlValues);
- if (!creatorID) {
- creatorID = insertID;
- }
- }
- else {
- // Remove tagID from beginning
- columns.shift();
- sqlValues.shift();
- sqlValues.push(creatorID);
-
- var sql = "UPDATE creators SET " + columns.join("=?, ") + "=?"
- + " WHERE creatorID=?";
- Zotero.DB.query(sql, sqlValues);
- }
-
- if (deleteDataID) {
- Zotero.Creators.deleteData(deleteDataID);
- }
-
- if (this.id) {
- Zotero.debug("Updating linked items");
- this.updateLinkedItems();
- }
-
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
-
- // If successful, set values in object
- if (!this.id) {
- this._id = creatorID;
- }
- if (!this.key) {
- this._key = key;
- }
- if (!this.creatorDataID) {
- this._creatorDataID = creatorDataID;
- }
-
- Zotero.Creators.reload(this.id);
-
- if (isNew) {
- Zotero.Notifier.trigger('add', 'creator', this.id);
- }
- else {
- Zotero.Notifier.trigger('modify', 'creator', this.id, this._previousData);
- }
-
- return this.id;
-}
-
-
-Zotero.Creator.prototype.countLinkedItems = function() {
- var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
- return Zotero.DB.valueQuery(sql, this.id);
-}
-
-
-Zotero.Creator.prototype.getLinkedItems = function () {
- var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
- return Zotero.DB.columnQuery(sql, this.id);
-}
-
-
-Zotero.Creator.prototype.updateLinkedItems = function () {
- Zotero.DB.beginTransaction();
-
- var sql = "SELECT itemID FROM itemCreators WHERE creatorID=?";
- var changedItemIDs = Zotero.DB.columnQuery(sql, this.id);
-
- if (!changedItemIDs) {
- Zotero.DB.commitTransaction();
- return;
- }
-
- var notifierData = {};
- for each(var id in changedItemIDs) {
- var item = Zotero.Items.get(id);
- if (item) {
- notifierData[item.id] = { old: item.serialize() };
- }
- }
-
- sql = "UPDATE items SET dateModified=?, clientDateModified=? WHERE itemID IN "
- + "(SELECT itemID FROM itemCreators WHERE creatorID=?)";
- Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime, this.id]);
-
- Zotero.Items.reload(changedItemIDs);
-
- Zotero.DB.commitTransaction();
-
- Zotero.Notifier.trigger('modify', 'item', changedItemIDs, notifierData);
-}
-
-
-Zotero.Creator.prototype.equals = function (creator) {
- return (creator.firstName == this.firstName) &&
- (creator.lastName == this.lastName) &&
- (creator.fieldMode == this.fieldMode) &&
- (creator.shortName == this.shortName) &&
- (creator.birthYear == this.birthYear);
-}
-
-
-Zotero.Creator.prototype.serialize = function () {
- var obj = {};
-
- obj.primary = {};
- obj.primary.creatorID = this.id;
- obj.primary.libraryID = this.libraryID;
- obj.primary.key = this.key;
- obj.primary.dateAdded = this.dateAdded;
- obj.primary.dateModified = this.dateModified;
-
- obj.fields = {};
- if (this.fieldMode == 1) {
- obj.fields.name = this.lastName;
- }
- else {
- obj.fields.firstName = this.firstName;
- obj.fields.lastName = this.lastName;
- }
- obj.fields.fieldMode = this.fieldMode;
- obj.fields.shortName = this.shortName;
- obj.fields.birthYear = this.birthYear;
-
- return obj;
-}
-
-
-/**
- * Remove creator from all linked items
- *
- * Creators.erase() should be used instead of this
- *
- * Actual deletion of creator occurs in Zotero.Creators.purge(),
- * which is called by Creators.erase()
- */
-Zotero.Creator.prototype.erase = function () {
- if (!this.id) {
- return false;
- }
-
- Zotero.debug("Deleting creator " + this.id);
-
- // TODO: notifier
- var changedItems = [];
- var changedItemsNotifierData = {};
-
- Zotero.DB.beginTransaction();
-
- var toSave = {};
-
- var linkedItemIDs = this.getLinkedItems();
- for each(var itemID in linkedItemIDs) {
- var item = Zotero.Items.get(itemID)
- if (!item) {
- throw ('Linked item not found in Zotero.Creator.erase()');
- }
-
- var pos = item.getCreatorPosition(this.id);
- if (!pos) {
- throw ('Creator not found in linked item in Zotero.Creator.erase()');
- }
-
- item.removeCreator(pos);
-
- if (!toSave[item.id]) {
- toSave[item.id] = item;
- }
- }
-
- for each(var item in toSave) {
- item.save();
- }
-
- Zotero.DB.commitTransaction();
-
- Zotero.Prefs.set('purge.creators', true);
-}
-
-
-Zotero.Creator.prototype.load = function (allowFail) {
- var id = this._id;
- var key = this._key;
- var libraryID = this._libraryID;
- var desc = id ? id : libraryID + "/" + key;
-
- Zotero.debug("Loading data for creator " + desc + " in Zotero.Creator.load()");
-
- if (!id && !key) {
- throw ("ID or key not set in Zotero.Creator.load()");
- }
-
- var sql = "SELECT O.*, CD.* FROM creators O NATURAL JOIN creatorData CD WHERE ";
- if (id) {
- sql += "creatorID=?";
- var params = id;
- }
- else {
- sql += "key=? AND libraryID=?";
- var params = [key, libraryID];
- }
- var row = Zotero.DB.rowQuery(sql, params);
-
- if (!row) {
- if (allowFail) {
- this._loaded = true;
- return false;
- }
- throw ("Creator " + desc + " not found in Zotero.Item.load()");
- }
-
- this.loadFromRow(row);
- return true;
-}
-
-
-Zotero.Creator.prototype.loadFromRow = function (row) {
- this._init();
- for (var col in row) {
- //Zotero.debug("Setting field '" + col + "' to '" + row[col] + "' for creator " + this.id);
- switch (col) {
- case 'clientDateModified':
- continue;
-
- case 'creatorID':
- this._id = row[col];
- continue;
-
- case 'libraryID':
- this['_' + col] = row[col];
- continue;
- }
- this['_' + col] = row[col] ? row[col] : '';
- }
- this._loaded = true;
-}
-
-
-
-Zotero.Creator.prototype._checkValue = function (field, value) {
- if (this['_' + field] === undefined) {
- throw ("Invalid property " + field + " in Zotero.Creator._checkValue()");
- }
-
- // Data validation
- switch (field) {
- case 'id':
- if (parseInt(value) != value) {
- this._invalidValueError(field, value);
- }
- break;
-
- case 'libraryID':
- if (parseInt(value) != value) {
- this._invalidValueError(field, value);
- }
- break;
-
- case 'fieldMode':
- if (value !== 0 && value !== 1) {
- this._invalidValueError(field, value);
- }
- break;
-
- case 'key':
- if (!Zotero.ID.isValidKey(value)) {
- this._invalidValueError(field, value);
- }
- break;
-
- case 'dateAdded':
- case 'dateModified':
- if (value !== '' && !Zotero.Date.isSQLDateTime(value)) {
- this._invalidValueError(field, value);
- }
- break;
- }
-}
-
-
-Zotero.Creator.prototype._generateKey = function () {
- return Zotero.Utilities.generateObjectKey();
-}
-
-
-Zotero.Creator.prototype._invalidValueError = function (field, value) {
- throw ("Invalid '" + field + "' value '" + value + "' in Zotero.Creator._invalidValueError()");
-}
diff --git a/chrome/content/zotero/xpcom/data/creators.js b/chrome/content/zotero/xpcom/data/creators.js
@@ -25,327 +25,195 @@
Zotero.Creators = new function() {
- Zotero.DataObjects.apply(this, ['creator']);
- this.constructor.prototype = new Zotero.DataObjects();
+ this.fields = ['firstName', 'lastName', 'fieldMode'];
+ this.totes = 0;
- this.get = get;
- this.getDataID = getDataID;
- this.getCreatorsWithData = getCreatorsWithData;
- this.countCreatorsWithData = countCreatorsWithData;
- this.updateData = updateData;
- this.deleteData = deleteData;
- this.erase = erase;
- this.purge = purge;
-
- this.__defineGetter__('fields', function () ['firstName', 'lastName', 'shortName', 'fieldMode', 'birthYear']);
-
- var _creatorDataHash = {}; // creatorDataIDs indexed by md5 hash of data
+ var _creatorCache = {};
/*
- * Returns a Zotero.Creator object for a given creatorID
+ * Returns creator data in internal format for a given creatorID
*/
- function get(creatorID) {
+ this.getAsync = Zotero.Promise.coroutine(function* (creatorID) {
if (!creatorID) {
- throw ("creatorID not provided in Zotero.Creators.get()");
+ throw new Error("creatorID not provided");
}
- if (this._objectCache[creatorID]) {
- return this._objectCache[creatorID];
+ if (_creatorCache[creatorID]) {
+ return this.cleanData(_creatorCache[creatorID]);
}
- var sql = 'SELECT COUNT(*) FROM creators WHERE creatorID=?';
- var result = Zotero.DB.valueQuery(sql, creatorID);
-
- if (!result) {
- return false;
+ var sql = "SELECT * FROM creators WHERE creatorID=?";
+ var row = yield Zotero.DB.rowQueryAsync(sql, creatorID);
+ if (!row) {
+ throw new Error("Creator " + creatorID + " not found");
}
-
- var creator = new Zotero.Creator;
- creator.id = creatorID;
- this._objectCache[creatorID] = creator;
- return this._objectCache[creatorID];
- }
+ return _creatorCache[creatorID] = this.cleanData({
+ firstName: row.firstName, // avoid "DB column 'name' not found" warnings from the DB row Proxy
+ lastName: row.lastName,
+ fieldMode: row.fieldMode
+ });
+ });
- /**
- * Returns the creatorDataID matching given fields
- *
- * @param array fields
- * @param bool create If no matching creatorDataID, create one
- */
- function getDataID(fields, create) {
- fields = _cleanFields(fields);
-
- if (!fields.firstName && !fields.lastName) {
- throw ("First or last name must be provided in Zotero.Creators.getDataID()");
- }
-
- var hash = _getHash(fields);
- if (_creatorDataHash[hash]) {
- return _creatorDataHash[hash];
- }
-
- var params = [];
- for each(var field in fields) {
- params.push(field);
- }
-
- Zotero.DB.beginTransaction();
-
- var sql = "SELECT creatorDataID FROM creatorData WHERE "
- + "firstName=? AND lastName=? AND shortName=? "
- + "AND fieldMode=? AND birthYear=?";
- var id = Zotero.DB.valueQuery(sql, params);
-
- if (!id && create) {
- id = Zotero.ID.get('creatorData');
- params.unshift(id);
-
- sql = "INSERT INTO creatorData (creatorDataID, "
- + "firstName, lastName, shortName, fieldMode, birthYear) "
- + "VALUES (?, ?, ?, ?, ?, ?)";
- var insertID = Zotero.DB.query(sql, params);
- if (!id) {
- id = insertID;
- }
- }
-
- Zotero.DB.commitTransaction();
-
- if (id) {
- _creatorDataHash[hash] = id;
- }
-
- return id;
- }
-
-
- function getCreatorsWithData(creatorDataID, libraryID) {
- var sql = "SELECT creatorID FROM creators WHERE creatorDataID=?";
- if (libraryID !== undefined) {
- sql += " AND libraryID=?";
- return Zotero.DB.columnQuery(sql, [creatorDataID, libraryID])
- }
- return Zotero.DB.columnQuery(sql, [creatorDataID]);
+ this.getItemsWithCreator = function (creatorID) {
+ var sql = "SELECT DISTINCT itemID FROM itemCreators WHERE creatorID=?";
+ return Zotero.DB.columnQueryAsync(sql, creatorID);
}
- function countCreatorsWithData(creatorDataID, libraryID) {
- var sql = "SELECT COUNT(*) FROM creators WHERE creatorDataID=?";
- return Zotero.DB.valueQuery(sql, [creatorDataID]);
- }
-
-
- function updateData(creatorDataID, fields) {
- fields = _cleanFields(fields);
-
- var sqlFields = [];
- var sqlParams = [];
- for (var field in fields) {
- // Skip fields not specified as changeable creator fields
- if (this.fields.indexOf(field) == -1) {
- continue;
- }
- sqlFields.push(field + '=?');
- sqlParams.push(fields[field]);
- }
-
- var sql = "UPDATE creatorData SET " + sqlFields.join(', ')
- + " WHERE creatorDataID=?";
-
- sqlParams.push(creatorDataID);
- Zotero.DB.query(sql, sqlParams);
-
- _updateCachedData(creatorDataID);
- }
-
-
- function deleteData(creatorDataID) {
- var sql = "DELETE FROM creatorData WHERE creatorDataID=?";
- Zotero.DB.query(sql, creatorDataID);
- _updateCachedData(creatorDataID);
+ this.countItemAssociations = function (creatorID) {
+ var sql = "SELECT COUNT(*) FROM itemCreators WHERE creatorID=?";
+ return Zotero.DB.valueQueryAsync(sql, creatorID);
}
/**
- * Remove creator(s) from all linked items and call this.purge()
- * to delete creator rows
+ * Returns the creatorID matching given fields, or creates a new creator and returns its id
+ *
+ * @param {Object} data Creator data in API JSON format
+ * @param {Boolean} [create=false] If no matching creator, create one
+ * @return {Promise<Integer>} creatorID
*/
- function erase(ids) {
- ids = Zotero.flattenArguments(ids);
-
- var unlock = Zotero.Notifier.begin(true);
- Zotero.UnresponsiveScriptIndicator.disable();
- try {
- Zotero.DB.beginTransaction();
- for each(var id in ids) {
- var creator = this.get(id);
- if (!creator) {
- Zotero.debug('Creator ' + id + ' does not exist in Creators.erase()!', 1);
- Zotero.Notifier.trigger('delete', 'creator', id);
- continue;
+ this.getIDFromData = Zotero.Promise.method(function (data, create) {
+ data = this.cleanData(data);
+ return Zotero.DB.executeTransaction(function* () {
+ var sql = "SELECT creatorID FROM creators WHERE "
+ + "firstName=? AND lastName=? AND fieldMode=?";
+ var id = yield Zotero.DB.valueQueryAsync(
+ sql, [data.firstName, data.lastName, data.fieldMode]
+ );
+ if (!id && create) {
+ id = yield Zotero.ID.get('creators');
+ let sql = "INSERT INTO creators (creatorID, firstName, lastName, fieldMode) "
+ + "VALUES (?, ?, ?, ?)";
+ let insertID = yield Zotero.DB.queryAsync(
+ sql, [id, data.firstName, data.lastName, data.fieldMode]
+ );
+ if (!id) {
+ id = insertID;
}
- creator.erase();
- creator = undefined;
}
- this.purge();
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
- finally {
- Zotero.Notifier.commit(unlock);
- Zotero.UnresponsiveScriptIndicator.enable();
+ return id;
+ });
+ });
+
+
+ this.updateCreator = Zotero.Promise.coroutine(function* (creatorID, creatorData) {
+ var creator = yield this.get(creatorID);
+ if (!creator) {
+ throw new Error("Creator " + creatorID + " doesn't exist");
}
- }
+ creator.fieldMode = creatorData.fieldMode;
+ creator.firstName = creatorData.firstName;
+ creator.lastName = creatorData.lastName;
+ return creator.save();
+ });
- /*
- * Delete obsolete creator/creatorData rows from database
- * and clear internal array entries
+ /**
+ * Delete obsolete creator rows from database and clear internal cache entries
+ *
+ * @return {Promise}
*/
- function purge() {
+ this.purge = Zotero.Promise.coroutine(function* () {
if (!Zotero.Prefs.get('purge.creators')) {
return;
}
Zotero.debug("Purging creator tables");
- // Purge unused creators
var sql = 'SELECT creatorID FROM creators WHERE creatorID NOT IN '
+ '(SELECT creatorID FROM itemCreators)';
- var toDelete = Zotero.DB.columnQuery(sql);
-
- if (toDelete) {
+ var toDelete = yield Zotero.DB.columnQueryAsync(sql);
+ if (toDelete.length) {
// Clear creator entries in internal array
- for each(var creatorID in toDelete) {
- delete this._objectCache[creatorID];
+ for (let i=0; i<toDelete.length; i++) {
+ delete _creatorCache[toDelete[i]];
}
var sql = "DELETE FROM creators WHERE creatorID NOT IN "
+ "(SELECT creatorID FROM itemCreators)";
- Zotero.DB.query(sql);
- }
-
- // Purge unused creatorData rows
- var sql = 'SELECT creatorDataID FROM creatorData WHERE creatorDataID NOT IN '
- + '(SELECT creatorDataID FROM creators)';
- var toDelete = Zotero.DB.columnQuery(sql);
-
- if (toDelete) {
- // Clear creator entries in internal array
- for each(var creatorDataID in toDelete) {
- _updateCachedData(creatorDataID);
- }
-
- var sql = "DELETE FROM creatorData WHERE creatorDataID NOT IN "
- + "(SELECT creatorDataID FROM creators)";
- Zotero.DB.query(sql);
+ yield Zotero.DB.queryAsync(sql);
}
Zotero.Prefs.set('purge.creators', false);
- }
+ });
- this._load = function () {
- if (!arguments[0] && !this._reloadCache) {
- return;
+ this.cleanData = function (data) {
+ // Validate data
+ if (data.name === undefined && data.lastName === undefined) {
+ throw new Error("Creator data must contain either 'name' or 'firstName'/'lastName' properties");
}
-
- if (this._reloadCache) {
- Zotero.debug("Clearing creator data hash");
- _creatorDataHash = {};
+ if (data.name !== undefined && (data.firstName !== undefined || data.lastName !== undefined)) {
+ throw new Error("Creator data cannot contain both 'name' and 'firstName'/'lastName' properties");
}
-
- var sql = "SELECT C.*, CD.* FROM creators C NATURAL JOIN creatorData CD "
- + "WHERE 1";
- if (arguments[0]) {
- sql += " AND creatorID IN (" + Zotero.join(arguments[0], ",") + ")";
+ if (data.name !== undefined && data.fieldMode === 0) {
+ throw new Error("'fieldMode' cannot be 0 with 'name' property");
+ }
+ if (data.fieldMode === 1 && !(data.firstName === undefined || data.firstName === "")) {
+ throw new Error("'fieldMode' cannot be 1 with 'firstName' property");
+ }
+ if (data.name !== undefined && typeof data.name != 'string') {
+ throw new Error("'name' must be a string");
+ }
+ if (data.firstName !== undefined && typeof data.firstName != 'string') {
+ throw new Error("'firstName' must be a string");
+ }
+ if (data.lastName !== undefined && typeof data.lastName != 'string') {
+ throw new Error("'lastName' must be a string");
}
- var rows = Zotero.DB.query(sql);
- var ids = [];
- for each(var row in rows) {
- var id = row.creatorID;
- ids.push(id);
+
+ var cleanedData = {
+ fieldMode: 0,
+ firstName: '',
+ lastName: ''
+ };
+ for (i=0; i<this.fields.length; i++) {
+ let field = this.fields[i];
+ let val = data[field];
+ switch (field) {
+ case 'firstName':
+ case 'lastName':
+ cleanedData[field] = val.trim();
+ break;
- // Creator doesn't exist -- create new object and stuff in array
- if (!this._objectCache[id]) {
- this.get(id);
- }
- // Existing creator -- reload in place
- else {
- this._objectCache[id].loadFromRow(row);
+ case 'fieldMode':
+ cleanedData[field] = val ? parseInt(val) : 0;
+ break;
}
}
- // If loading all creators, remove old creators that no longer exist
- if (!arguments[0]) {
- for each(var c in this._objectCache) {
- if (ids.indexOf(c.id) == -1) {
- this.unload(c.id);
- }
- }
-
- this._reloadCache = false;
+ // Handle API JSON format
+ if (data.name !== undefined) {
+ cleanedData.lastName = data.name.trim();
+ cleanedData.fieldMode = 1;
}
- }
-
-
- function _cleanFields(fields) {
- var cleanedFields = {};
- for each(var field in Zotero.Creators.fields) {
- switch (field) {
- // Strings
- case 'firstName':
- case 'lastName':
- case 'shortName':
- cleanedFields[field] = fields[field] ? fields[field] + '' : '';
- break;
-
- // Integer
- case 'fieldMode':
- cleanedFields[field] = fields[field] ? fields[field] : 0;
- break;
-
- // Null if empty
- case 'birthYear':
- cleanedFields[field] = fields[field] ? fields[field] : null;
+
+ var creatorType = data.creatorType || data.creatorTypeID;
+ if (creatorType) {
+ cleanedData.creatorTypeID = Zotero.CreatorTypes.getID(creatorType);
+ if (!cleanedData.creatorTypeID) {
+ let msg = "'" + creatorType + "' isn't a valid creator type";
+ Zotero.debug(msg, 2);
+ Components.utils.reportError(msg);
}
}
- return cleanedFields;
- }
-
-
- function _getHash(fields) {
- var hashFields = [];
- for each(var field in Zotero.Creators.fields) {
- hashFields.push(fields[field]);
- }
- return Zotero.Utilities.Internal.md5(hashFields.join('_'));
- }
-
-
- function _getDataFromID(creatorDataID) {
- var sql = "SELECT * FROM creatorData WHERE creatorDataID=?";
- return Zotero.DB.rowQuery(sql, creatorDataID);
+ return cleanedData;
}
- function _updateCachedData(creatorDataID) {
- for (var hash in _creatorDataHash) {
- if (_creatorDataHash[hash] == creatorDataID) {
- delete _creatorDataHash[hash];
- }
+ this.internalToJSON = function (fields) {
+ var obj = {};
+ if (fields.fieldMode == 1) {
+ obj.name = fields.lastName;
}
-
- var creators = getCreatorsWithData(creatorDataID);
- for each(var creatorID in creators) {
- if (Zotero.Creators._objectCache[creatorID]) {
- Zotero.Creators._objectCache[creatorID].load();
- }
+ else {
+ obj.firstName = fields.firstName;
+ obj.lastName = fields.lastName;
}
+ obj.creatorType = Zotero.CreatorTypes.getName(fields.creatorTypeID);
+ return obj;
}
}
diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js
@@ -0,0 +1,394 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2013 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see <http://www.gnu.org/licenses/>.
+
+ ***** END LICENSE BLOCK *****
+*/
+
+Zotero.DataObject = function (objectType, objectTypePlural, dataTypes) {
+ this._objectType = objectType;
+ this._ObjectType = objectType[0].toUpperCase() + objectType.substr(1);
+ this._objectTypePlural = objectTypePlural ? objectTypePlural : objectType + 's';
+ this._dataTypes = dataTypes;
+
+ // Public members for access by public methods -- do not access directly
+ if (this._id === undefined) {
+ this._id = null;
+ }
+ if (this._libraryID === undefined) {
+ this._libraryID = null;
+ }
+ if (this._key === undefined) {
+ this._key = null;
+ }
+
+ this._dateAdded = null;
+ this._dateModified = null;
+ this._version = null;
+ this._synced = null;
+ this._identified = false;
+
+ if (this._dataTypes === undefined) {
+ throw new Error("this._dataTypes is undefined");
+ }
+ this._loaded = {};
+ for (let i=0; i<this._dataTypes.length; i++) {
+ this._loaded[this._dataTypes[i]] = false;
+ }
+
+ this._clearChanged();
+};
+
+Zotero.DataObject.prototype.__defineGetter__('objectType', function () { return this._objectType; });
+Zotero.DataObject.prototype.__defineGetter__('libraryKey', function () this.libraryID + "/" + this.key);
+Zotero.DataObject.prototype.__defineGetter__('parentKey', function () this._parentKey );
+Zotero.DataObject.prototype.__defineSetter__('parentKey', function (val) this._setParentKey(val) );
+Zotero.DataObject.prototype.__defineGetter__('parentID', function () this._getParentID() );
+Zotero.DataObject.prototype.__defineSetter__('parentID', function (val) this._setParentID(val) );
+
+
+Zotero.DataObject.prototype._get = function (field) {
+ if (this._objectType == 'item') {
+ throw new Error("_get is not valid for items");
+ }
+
+ if (this['_' + field] !== null) {
+ return this['_' + field];
+ }
+ this._requireData('primaryData');
+ return null;
+}
+
+
+Zotero.DataObject.prototype._setIdentifier = function (field, value) {
+ // If primary data is loaded, the only allowed identifier change is libraryID
+ // (allowed mainly for the library switcher in the advanced search window),
+ // and then only for unsaved objects (i.e., those without an id or key)
+ if (this._loaded.primaryData) {
+ if (field != 'libraryID' || this._id || this._key) {
+ throw new Error("Cannot set " + field + " after object is already loaded");
+ }
+ }
+
+ switch (field) {
+ case 'id':
+ if (this._key) {
+ throw new Error("Cannot set id if key is already set");
+ }
+ value = parseInt(value);
+ this._identified = true;
+ break;
+
+ case 'libraryID':
+ value = Zotero.DataObjectUtilities.checkLibraryID(value);
+ break;
+
+ case 'key':
+ if (this._libraryID === undefined) {
+ throw new Error("libraryID must be set before key");
+ }
+ if (this._id) {
+ throw new Error("Cannot set key if id is already set");
+ }
+ this._identified = true;
+ }
+
+ if (value === this['_' + field]) {
+ return;
+ }
+
+ this['_' + field] = value;
+}
+
+
+/**
+ * Get the id of the parent object
+ *
+ * @return {Integer|false|undefined} The id of the parent object, false if none, or undefined
+ * on object types to which it doesn't apply (e.g., searches)
+ */
+Zotero.DataObject.prototype._getParentID = function () {
+ if (this._parentID !== null) {
+ return this._parentID;
+ }
+ if (!this._parentKey) {
+ return false;
+ }
+ return this._parentID = this._getClass().getIDFromLibraryAndKey(this._libraryID, this._parentKey);
+}
+
+
+/**
+ * Set the id of the parent object
+ *
+ * @param {Number|false} [id=false]
+ */
+Zotero.DataObject.prototype._setParentID = function (id) {
+ return this._setParentKey(id ? this._getClass().getLibraryAndKeyFromID(id)[1] : false);
+}
+
+
+Zotero.DataObject.prototype._setParentKey = function(key) {
+ if (this._objectType == 'item') {
+ if (!this.isNote() && !this.isAttachment()) {
+ throw new Error("_setParentKey() can only be called on items of type 'note' or 'attachment'");
+ }
+ }
+
+ if (this._parentKey == key) {
+ return false;
+ }
+ this._markFieldChange('parentKey', this._parentKey);
+ this._changed.parentKey = true;
+ this._parentKey = key ? key : null;
+ this._parentID = null;
+ return true;
+}
+
+
+/**
+ * Returns all relations of the object
+ *
+ * @return object Object with predicates as keys and URIs as values
+ */
+Zotero.DataObject.prototype.getRelations = function () {
+ this._requireData('relations');
+
+ var relations = {};
+ for (let i=0; i<this._relations.length; i++) {
+ let relation = this._relations[i];
+
+ // Relations are stored internally as predicate-object pairs
+ let predicate = relation[0];
+ if (relations[predicate]) {
+ // If object with predicate exists, convert to an array
+ if (typeof relations[predicate] == 'string') {
+ relations[predicate] = [relations[predicate]];
+ }
+ // Add new object to array
+ relations[predicate].push(relation[1]);
+ }
+ // Add first object as a string
+ else {
+ relations[predicate] = relation[1];
+ }
+ }
+ return relations;
+}
+
+
+/**
+ * Updates the object's relations
+ *
+ * @param {Object} newRelations Object with predicates as keys and URIs/arrays-of-URIs as values
+ */
+Zotero.DataObject.prototype.setRelations = function (newRelations) {
+ this._requireData('relations');
+
+ // There can be more than one object for a given predicate, so build
+ // flat arrays with individual predicate-object pairs converted to
+ // JSON strings so we can use array_diff to determine what changed
+ var oldRelations = this._relations;
+
+ var sortFunc = function (a, b) {
+ if (a[0] < b[0]) return -1;
+ if (a[0] > b[0]) return 1;
+ if (a[1] < b[1]) return -1;
+ if (a[1] > b[1]) return 1;
+ return 0;
+ };
+
+ var newRelationsFlat = [];
+ for (let predicate in newRelations) {
+ let object = newRelations[predicate];
+ if (Array.isArray(object)) {
+ for (let i=0; i<object.length; i++) {
+ newRelationsFlat.push([predicate, object[i]]);
+ }
+ }
+ else {
+ newRelationsFlat.push([predicate, object]);
+ }
+ }
+
+ var changed = false;
+ if (oldRelations.length != newRelationsFlat.length) {
+ changed = true;
+ }
+ else {
+ oldRelations.sort(sortFunc);
+ newRelationsFlat.sort(sortFunc);
+
+ for (let i=0; i<oldRelations.length; i++) {
+ if (!newRelationsFlat || oldRelations[i] != newRelationsFlat[i]) {
+ changed = true;
+ break;
+ }
+ }
+ }
+
+ if (!changed) {
+ Zotero.debug("Relations have not changed for " + this._objectType + " " + this.libraryKey, 4);
+ return false;
+ }
+
+ this._markFieldChange('relations', this._relations);
+ // Store relations internally as array of predicate-object pairs
+ this._relations = newRelationsFlat;
+ this._changed.relations = true;
+}
+
+
+/**
+ * Return an object in the specified library equivalent to this object
+ */
+Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function* (libraryID) {
+ if (libraryID == this.libraryID) {
+ throw new Error(this._ObjectType + " is already in library " + libraryID);
+ }
+
+ var predicate = Zotero.Relations.linkedObjectPredicate;
+ var uri = Zotero.URI['get' + this._ObjectType + 'URI'](this);
+
+ var links = yield [
+ Zotero.Relations.getSubject(false, predicate, uri),
+ Zotero.Relations.getObject(uri, predicate, false)
+ ];
+ links = links[0].concat(links[1]);
+
+ if (!links.length) {
+ return false;
+ }
+
+ if (libraryID) {
+ var libraryObjectPrefix = Zotero.URI.getLibraryURI(libraryID) + "/" + this._objectTypePlural + "/";
+ }
+ else {
+ var libraryObjectPrefix = Zotero.URI.getCurrentUserURI() + "/" + this._objectTypePlural + "/";
+ }
+ for (let i=0; i<links.length; i++) {
+ let link = links[i];
+ if (link.indexOf(libraryObjectPrefix) == 0) {
+ var obj = yield Zotero.URI['getURI' + this._ObjectType](link);
+ if (!obj) {
+ Zotero.debug("Referenced linked " + this._objectType + " '" + link + "' not found "
+ + "in Zotero." + this._ObjectType + ".getLinked" + this._ObjectType + "()", 2);
+ continue;
+ }
+ return obj;
+ }
+ }
+ return false;
+});
+
+
+/**
+ * Reloads loaded, changed data
+ *
+ * @param {Array} [dataTypes] - Data types to reload, or all loaded types if not provide
+ * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally.
+ * This should be set to true for data that was
+ * changed externally (e.g., globally renamed tags).
+ */
+Zotero.DataObject.prototype.reload = Zotero.Promise.coroutine(function* (dataTypes, reloadUnchanged) {
+ if (!this._id) {
+ return;
+ }
+
+ if (!dataTypes) {
+ dataTypes = Object.keys(this._loaded).filter(
+ val => this._loaded[val]
+ );
+ }
+
+ if (dataTypes && dataTypes.length) {
+ for (let i=0; i<dataTypes.length; i++) {
+ let dataType = dataTypes[i];
+ if (!this._loaded[dataType] || (!reloadUnchanged && !this._changed[dataType])) {
+ continue;
+ }
+ yield this._loadDataType(dataType, true);
+ }
+ }
+});
+
+
+Zotero.DataObject.prototype._requireData = function (dataType) {
+ if (dataType != 'primaryData') {
+ this._requireData('primaryData');
+ }
+
+ if (!this._identified) {
+ this._loaded[dataType] = true;
+ }
+ else if (!this._loaded[dataType]) {
+ throw new Zotero.DataObjects.UnloadedDataException(
+ "'" + dataType + "' not loaded for " + this._objectType + " ("
+ + this._id + "/" + this._libraryID + "/" + this._key + ")",
+ this._objectType + dataType[0].toUpperCase() + dataType.substr(1)
+ );
+ }
+}
+
+
+
+Zotero.DataObject.prototype._getClass = function () {
+ return Zotero.DataObjectUtilities.getClassForObjectType(this._objectType);
+}
+
+
+Zotero.DataObject.prototype._loadDataType = function (dataType, reload) {
+ return this["load" + dataType[0].toUpperCase() + dataType.substr(1)](reload);
+}
+
+
+/**
+ * Save old version of data that's being changed, to pass to the notifier
+ */
+Zotero.DataObject.prototype._markFieldChange = function (field, oldValue) {
+ // Only save if object already exists and field not already changed
+ if (!this.id || typeof this._previousData[field] != 'undefined') {
+ return;
+ }
+ this._previousData[field] = oldValue;
+}
+
+
+Zotero.DataObject.prototype._clearChanged = function (dataType) {
+ if (dataType) {
+ delete this._changed[dataType];
+ delete this._previousData[dataType];
+ }
+ else {
+ this._changed = {};
+ this._previousData = {};
+ }
+}
+
+
+Zotero.DataObject.prototype._clearFieldChange = function (field) {
+ delete this._previousData[field];
+}
+
+
+Zotero.DataObject.prototype._generateKey = function () {
+ return Zotero.Utilities.generateObjectKey();
+}
diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js
@@ -51,7 +51,139 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this._objectCache = {};
- this._reloadCache = true;
+ this._objectKeys = {};
+ this._objectIDs = {};
+ this._loadedLibraries = {};
+ this._loadPromise = null;
+
+ // Public properties
+ this.table = this._ZDO_table;
+
+
+ this.init = function () {
+ this._loadIDsAndKeys();
+ }
+
+
+ this.__defineGetter__('primaryFields', function () {
+ var primaryFields = Object.keys(this._primaryDataSQLParts);
+
+ // Once primary fields have been cached, get rid of getter for speed purposes
+ delete this.primaryFields;
+ this.primaryFields = primaryFields;
+
+ return primaryFields;
+ });
+
+
+ this.isPrimaryField = function (field) {
+ return this.primaryFields.indexOf(field) != -1;
+ }
+
+
+ /**
+ * Retrieves one or more already-loaded items
+ *
+ * If an item hasn't been loaded, an error is thrown
+ *
+ * @param {Array|Integer} ids An individual object id or an array of object ids
+ * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
+ * otherwise, an array of Zotero.[Object]
+ */
+ this.get = function (ids) {
+ if (Array.isArray(ids)) {
+ var singleObject = false;
+ }
+ else {
+ var singleObject = true;
+ ids = [ids];
+ }
+
+ var toReturn = [];
+
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ // Check if already loaded
+ if (!this._objectCache[id]) {
+ throw new Error(this._ZDO_Object + " " + id + " not yet loaded");
+ }
+ toReturn.push(this._objectCache[id]);
+ }
+
+ // If single id, return the object directly
+ if (singleObject) {
+ return toReturn.length ? toReturn[0] : false;
+ }
+
+ return toReturn;
+ };
+
+
+ /**
+ * Retrieves (and loads, if necessary) one or more items
+ *
+ * @param {Array|Integer} ids An individual object id or an array of object ids
+ * @param {Object} options 'noCache': Don't cache loaded objects
+ * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
+ * otherwise, an array of Zotero.[Object]
+ */
+ this.getAsync = Zotero.Promise.coroutine(function* (ids, options) {
+ // Serialize loads
+ if (this._loadPromise && this._loadPromise.isPending()) {
+ yield this._loadPromise;
+ }
+ var deferred = Zotero.Promise.defer();
+ this._loadPromise = deferred.promise;
+
+ var toLoad = [];
+ var toReturn = [];
+
+ if (!ids) {
+ throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()");
+ }
+
+ if (Array.isArray(ids)) {
+ var singleObject = false;
+ }
+ else {
+ var singleObject = true;
+ ids = [ids];
+ }
+
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ // Check if already loaded
+ if (this._objectCache[id]) {
+ toReturn.push(this._objectCache[id]);
+ }
+ else {
+ toLoad.push(id);
+ }
+ }
+
+ // New object to load
+ if (toLoad.length) {
+ let loaded = yield this._load(null, toLoad, options);
+ for (let i=0; i<toLoad.length; i++) {
+ let id = toLoad[i];
+ let obj = loaded[id];
+ if (!obj) {
+ Zotero.debug(this._ZDO_Object + " " + id + " doesn't exist", 2);
+ continue;
+ }
+ toReturn.push(obj);
+ }
+ }
+
+ deferred.resolve();
+
+ // If single id, return the object directly
+ if (singleObject) {
+ return toReturn.length ? toReturn[0] : false;
+ }
+
+ return toReturn;
+ });
this.makeLibraryKeyHash = function (libraryID, key) {
@@ -83,7 +215,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @param {String} key
* @return {Zotero.DataObject} Zotero data object, or FALSE if not found
*/
- this.getByLibraryAndKey = function (libraryID, key) {
+ this.getByLibraryAndKey = Zotero.Promise.coroutine(function* (libraryID, key, options) {
var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE ";
if (this._ZDO_idOnly) {
sql += "ROWID=?";
@@ -93,130 +225,147 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
sql += "libraryID=? AND key=?";
var params = [libraryID, key];
}
- var id = Zotero.DB.valueQuery(sql, params);
+ var id = yield Zotero.DB.valueQueryAsync(sql, params);
if (!id) {
return false;
}
- return Zotero[this._ZDO_Objects].get(id);
+ return Zotero[this._ZDO_Objects].get(id, options);
+ });
+
+
+ this.exists = function (itemID) {
+ return !!this.getLibraryAndKeyFromID(itemID);
}
- this.getOlder = function (date) {
+ /**
+ * @return {Array} Array with libraryID and key
+ */
+ this.getLibraryAndKeyFromID = function (id) {
+ return this._objectKeys[id] ? this._objectKeys[id] : false;
+ }
+
+
+ this.getIDFromLibraryAndKey = function (libraryID, key) {
+ return this._objectIDs[libraryID][key] ? this._objectIDs[libraryID][key] : false;
+ }
+
+
+ this.getOlder = function (libraryID, date) {
if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getOlder()")
}
- var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE ";
- if (this._ZDO_object == 'relation') {
- sql += "clientDateModified<?";
- }
- else {
- sql += "dateModified<?";
- }
- return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
+ var sql = "SELECT ROWID FROM " + this._ZDO_table
+ + " WHERE libraryID=? AND clientDateModified<?";
+ return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
}
- this.getNewer = function (date, ignoreFutureDates) {
- if (date && date.constructor.name != 'Date') {
+ this.getNewer = function (libraryID, date, ignoreFutureDates) {
+ if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getNewer()")
}
- var sql = "SELECT ROWID FROM " + this._ZDO_table;
- if (date) {
- sql += " WHERE clientDateModified>?";
- if (ignoreFutureDates) {
- sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
- }
- return Zotero.DB.columnQuery(sql, Zotero.Date.dateToSQL(date, true));
+ var sql = "SELECT ROWID FROM " + this._ZDO_table
+ + " WHERE libraryID=? AND clientDateModified>?";
+ if (ignoreFutureDates) {
+ sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
}
- return Zotero.DB.columnQuery(sql);
+ return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
}
- /*
- * Reloads data for specified items into internal array
+ /**
+ * @param {Integer} libraryID
+ * @return {Promise} A promise for an array of object ids
+ */
+ this.getUnsynced = function (libraryID) {
+ var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table
+ + " WHERE libraryID=? AND synced=0";
+ return Zotero.DB.columnQueryAsync(sql, [libraryID]);
+ }
+
+
+ /**
+ * Get JSON from the sync cache that hasn't yet been written to the
+ * main object tables
*
- * Can be passed ids as individual parameters or as an array of ids, or both
+ * @param {Integer} libraryID
+ * @return {Promise} A promise for an array of JSON objects
*/
- this.reload = function () {
- if (!arguments[0]) {
- return false;
- }
-
- var ids = Zotero.flattenArguments(arguments);
- Zotero.debug('Reloading ' + this._ZDO_objects + ' ' + ids);
-
- /*
- // Reset cache keys to itemIDs stored in database using object keys
- var sql = "SELECT " + this._ZDO_id + " AS id, key FROM " + this._ZDO_table
- + " WHERE " + this._ZDO_id + " IN ("
- + ids.map(function () '?').join() + ")";
- var rows = Zotero.DB.query(sql, ids);
+ this.getUnwrittenData = function (libraryID) {
+ var sql = "SELECT data FROM syncCache SC "
+ + "LEFT JOIN " + this._ZDO_table + " "
+ + "USING (libraryID) "
+ + "WHERE SC.libraryID=? AND "
+ + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM "
+ + "syncObjectTypes WHERE name='" + this._ZDO_object + "') "
+ + "AND IFNULL(O.version, 0) < SC.version";
+ return Zotero.DB.columnQueryAsync(sql, [libraryID]);
+ }
+
+
+ /**
+ * Reload loaded data of loaded objects
+ *
+ * @param {Array|Number} ids - An id or array of ids
+ * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded
+ * types if not provided
+ * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally.
+ * This should be set to true for data that was
+ * changed externally (e.g., globally renamed tags).
+ */
+ this.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
+ ids = Zotero.flattenArguments(ids);
- var keyIDs = {};
- for each(var row in rows) {
- keyIDs[row.key] = row.id
- }
- var store = {};
+ Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
+ + this._ZDO_objects + ' ' + ids);
- Zotero.debug('==================');
- for (var id in this._objectCache) {
- Zotero.debug('id is ' + id);
- var obj = this._objectCache[id];
- //Zotero.debug(obj);
- var dbID = keyIDs[obj.key];
- Zotero.debug("DBID: " + dbID);
- if (!dbID || id == dbID) {
- Zotero.debug('continuing');
- continue;
+ for (let i=0; i<ids.length; i++) {
+ if (this._objectCache[ids[i]]) {
+ yield this._objectCache[ids[i]].reload(dataTypes, reloadUnchanged);
}
- Zotero.debug('Assigning ' + dbID + ' to store');
- store[dbID] = obj;
- Zotero.debug('deleting ' + id);
- delete this._objectCache[id];
}
- Zotero.debug('------------------');
- for (var id in store) {
- Zotero.debug(id);
- if (this._objectCache[id]) {
- throw("Existing " + this._ZDO_object + " " + id
- + " exists in cache in Zotero.DataObjects.reload()");
- }
- this._objectCache[id] = store[id];
- }
- */
-
- // If there's an internal reload hook, call it
- if (this._reload) {
- this._reload(ids)
- }
-
- // Reload data
- this._load(ids);
return true;
- }
+ });
- this.reloadAll = function () {
+ this.reloadAll = function (libraryID) {
Zotero.debug("Reloading all " + this._ZDO_objects);
// Remove objects not stored in database
var sql = "SELECT ROWID FROM " + this._ZDO_table;
- var ids = Zotero.DB.columnQuery(sql);
-
- for (var id in this._objectCache) {
- if (!ids || ids.indexOf(parseInt(id)) == -1) {
- delete this._objectCache[id];
+ var params = [];
+ if (libraryID !== undefined) {
+ sql += ' WHERE libraryID=?';
+ params.push(libraryID);
+ }
+ return Zotero.DB.columnQueryAsync(sql, params)
+ .then(function (ids) {
+ for (var id in this._objectCache) {
+ if (!ids || ids.indexOf(parseInt(id)) == -1) {
+ delete this._objectCache[id];
+ }
}
+
+ // Reload data
+ this._loadedLibraries[libraryID] = false;
+ return this._load(libraryID);
+ });
+ }
+
+
+ this.registerIdentifiers = function (id, libraryID, key) {
+ Zotero.debug("Registering " + this._ZDO_object + " " + id + " as " + libraryID + "/" + key);
+ if (!this._objectIDs[libraryID]) {
+ this._objectIDs[libraryID] = {};
}
-
- // Reload data
- this._reloadCache = true;
- this._load();
+ this._objectIDs[libraryID][key] = id;
+ this._objectKeys[id] = [libraryID, key];
}
@@ -228,7 +377,13 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
this.unload = function () {
var ids = Zotero.flattenArguments(arguments);
for (var i=0; i<ids.length; i++) {
- delete this._objectCache[ids[i]];
+ let id = ids[i];
+ let [libraryID, key] = this.getLibraryAndKeyFromID(id);
+ if (key) {
+ delete this._objectIDs[libraryID][key];
+ delete this._objectKeys[id];
+ }
+ delete this._objectCache[id];
}
}
@@ -342,5 +497,121 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
}
}
+
+
+ this.getPrimaryDataSQLPart = function (part) {
+ var sql = this._primaryDataSQLParts[part];
+ if (!sql) {
+ throw new Error("Invalid primary data SQL part '" + part + "'");
+ }
+ return sql;
+ }
+
+
+ this._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
+ var loaded = {};
+
+ // If library isn't an integer (presumably false or null), skip it
+ if (parseInt(libraryID) != libraryID) {
+ libraryID = false;
+ }
+
+ if (libraryID === false && !ids) {
+ throw new Error("Either libraryID or ids must be provided");
+ }
+
+ if (libraryID !== false && this._loadedLibraries[libraryID]) {
+ return loaded;
+ }
+
+ // _getPrimaryDataSQL() should use "O" for the primary table alias
+ var sql = this._getPrimaryDataSQL();
+ var params = [];
+ if (libraryID !== false) {
+ sql += ' AND O.libraryID=?';
+ params.push(libraryID);
+ }
+ if (ids) {
+ sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')';
+ }
+
+ var t = new Date();
+ yield Zotero.DB.queryAsync(
+ sql,
+ params,
+ {
+ onRow: function (row) {
+ var id = row.getResultByIndex(this._ZDO_id);
+ var columns = Object.keys(this._primaryDataSQLParts);
+ var rowObj = {};
+ for (let i=0; i<columns.length; i++) {
+ rowObj[columns[i]] = row.getResultByIndex(i);
+ }
+ var obj;
+
+ // Existing object -- reload in place
+ if (this._objectCache[id]) {
+ this._objectCache[id].loadFromRow(rowObj, true);
+ obj = this._objectCache[id];
+ }
+ // Object doesn't exist -- create new object and stuff in cache
+ else {
+ obj = new Zotero[this._ZDO_Object];
+ obj.loadFromRow(rowObj, true);
+ if (!options || !options.noCache) {
+ this._objectCache[id] = obj;
+ }
+ }
+ loaded[id] = obj;
+ }.bind(this)
+ }
+ );
+ Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms");
+
+ if (!ids) {
+ this._loadedLibraries[libraryID] = true;
+
+ // If loading all objects, remove cached objects that no longer exist
+ for (let i in this._objectCache) {
+ let obj = this._objectCache[i];
+ if (libraryID !== false && obj.libraryID !== libraryID) {
+ continue;
+ }
+ if (!loaded[obj.id]) {
+ this.unload(obj.id);
+ }
+ }
+
+ if (this._postLoad) {
+ this._postLoad(libraryID, ids);
+ }
+ }
+
+ return loaded;
+ });
+
+
+ this._loadIDsAndKeys = function () {
+ var sql = "SELECT ROWID AS id, libraryID, key FROM " + this._ZDO_table;
+ return Zotero.DB.queryAsync(sql)
+ .then(function (rows) {
+ for (let i=0; i<rows.length; i++) {
+ let row = rows[i];
+ this._objectKeys[row.id] = [row.libraryID, row.key];
+ if (!this._objectIDs[row.libraryID]) {
+ this._objectIDs[row.libraryID] = {};
+ }
+ this._objectIDs[row.libraryID][row.key] = row.id;
+ }
+ }.bind(this));
+ }
}
+
+Zotero.DataObjects.UnloadedDataException = function (msg, dataType) {
+ this.message = msg;
+ this.dataType = dataType;
+ this.stack = (new Error).stack;
+}
+Zotero.DataObjects.UnloadedDataException.prototype = Object.create(Error.prototype);
+Zotero.DataObjects.UnloadedDataException.prototype.name = "UnloadedDataException"
diff --git a/chrome/content/zotero/xpcom/data/group.js b/chrome/content/zotero/xpcom/data/group.js
@@ -32,7 +32,6 @@ Zotero.Group = function () {
this._init();
}
-
Zotero.Group.prototype._init = function () {
this._id = null;
this._libraryID = null;
@@ -40,6 +39,7 @@ Zotero.Group.prototype._init = function () {
this._description = null;
this._editable = null;
this._filesEditable = null;
+ this._etag = null;
this._loaded = false;
this._changed = false;
@@ -61,13 +61,15 @@ Zotero.Group.prototype.__defineGetter__('editable', function () { return this._g
Zotero.Group.prototype.__defineSetter__('editable', function (val) { this._set('editable', val); });
Zotero.Group.prototype.__defineGetter__('filesEditable', function () { if (!this.editable) { return false; } return this._get('filesEditable'); });
Zotero.Group.prototype.__defineSetter__('filesEditable', function (val) { this._set('filesEditable', val); });
-
+Zotero.Group.prototype.__defineGetter__('etag', function () { return this._get('etag'); });
+Zotero.Group.prototype.__defineSetter__('etag', function (val) { this._set('etag', val); });
Zotero.Group.prototype._get = function (field) {
- if (this._id && !this._loaded) {
- this.load();
+ if (this['_' + field] !== null) {
+ return this['_' + field];
}
- return this['_' + field];
+ this._requireLoad();
+ return null;
}
@@ -80,23 +82,16 @@ Zotero.Group.prototype._set = function (field, val) {
}
if (this._loaded) {
- throw ("Cannot set " + field + " after object is already loaded in Zotero.Group._set()");
+ throw new Error("Cannot set " + field + " after object is already loaded");
}
//this._checkValue(field, val);
this['_' + field] = val;
return;
}
- if (this.id) {
- if (!this._loaded) {
- this.load();
- }
- }
- else {
- this._loaded = true;
- }
+ this._requireLoad();
- if (this['_' + field] != val) {
+ if (this['_' + field] !== val) {
this._prepFieldChange(field);
switch (field) {
@@ -113,24 +108,24 @@ Zotero.Group.prototype._set = function (field, val) {
/*
* Build group from database
*/
-Zotero.Group.prototype.load = function() {
+Zotero.Group.prototype.load = Zotero.Promise.coroutine(function* () {
var id = this._id;
if (!id) {
- throw ("ID not set in Zotero.Group.load()");
+ throw new Error("ID not set");
}
var sql = "SELECT G.* FROM groups G WHERE groupID=?";
- var data = Zotero.DB.rowQuery(sql, id);
-
- this._loaded = true;
-
+ var data = yield Zotero.DB.rowQueryAsync(sql, id);
if (!data) {
- return;
+ this._loaded = true;
+ return false;
}
this.loadFromRow(data);
-}
+
+ return true;
+});
/*
@@ -148,6 +143,7 @@ Zotero.Group.prototype.loadFromRow = function(row) {
this._description = row.description;
this._editable = !!row.editable;
this._filesEditable = !!row.filesEditable;
+ this._etag = row.etag;
}
@@ -157,12 +153,7 @@ Zotero.Group.prototype.loadFromRow = function(row) {
* @return bool TRUE if the group exists, FALSE if not
*/
Zotero.Group.prototype.exists = function() {
- if (!this.id) {
- throw ('groupID not set in Zotero.Group.exists()');
- }
-
- var sql = "SELECT COUNT(*) FROM groups WHERE groupID=?";
- return !!Zotero.DB.valueQuery(sql, this.id);
+ return Zotero.Groups.exists(this.id);
}
@@ -170,9 +161,8 @@ Zotero.Group.prototype.hasCollections = function () {
if (this._hasCollections !== null) {
return this._hasCollections;
}
-
- this._hasCollections = !!this.getCollections().length;
- return this._hasCollections;
+ this._requireLoad();
+ return false;
}
@@ -180,9 +170,8 @@ Zotero.Group.prototype.hasSearches = function () {
if (this._hasSearches !== null) {
return this._hasSearches;
}
-
- this._hasSearches = !!Zotero.Searches.getAll(this.id).length;
- return this._hasSearches;
+ this._requireLoad();
+ return false;
}
@@ -200,15 +189,15 @@ Zotero.Group.prototype.clearSearchCache = function () {
* @param {Boolean} asIDs Return as collectionIDs
* @return {Zotero.Collection[]} Array of Zotero.Collection instances
*/
-Zotero.Group.prototype.getCollections = function (parent) {
+Zotero.Group.prototype.getCollections = Zotero.Promise.coroutine(function* (parent) {
var sql = "SELECT collectionID FROM collections WHERE libraryID=? AND "
+ "parentCollectionID " + (parent ? '=' + parent : 'IS NULL');
- var ids = Zotero.DB.columnQuery(sql, this.libraryID);
+ var ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
// Return Zotero.Collection objects
var objs = [];
for each(var id in ids) {
- var col = Zotero.Collections.get(id);
+ var col = yield Zotero.Collections.getAsync(id);
objs.push(col);
}
@@ -219,11 +208,13 @@ Zotero.Group.prototype.getCollections = function (parent) {
});
return objs;
-}
+});
-Zotero.Group.prototype.hasItem = function (itemID) {
- var item = Zotero.Items.get(itemID);
+Zotero.Group.prototype.hasItem = function (item) {
+ if (!(item instanceof Zotero.Item)) {
+ throw new Error("item must be a Zotero.Item");
+ }
return item.libraryID == this.libraryID;
}
@@ -255,16 +246,18 @@ Zotero.Group.prototype.save = function () {
'name',
'description',
'editable',
- 'filesEditable'
+ 'filesEditable',
+ 'etag'
];
- var placeholders = ['?', '?', '?', '?', '?', '?'];
+ var placeholders = columns.map(function () '?').join();
var sqlValues = [
this.id,
this.libraryID,
this.name,
this.description,
this.editable ? 1 : 0,
- this.filesEditable ? 1 : 0
+ this.filesEditable ? 1 : 0,
+ this.etag
];
if (isNew) {
@@ -303,83 +296,81 @@ Zotero.Group.prototype.save = function () {
/**
* Deletes group and all descendant objects
**/
-Zotero.Group.prototype.erase = function() {
+Zotero.Group.prototype.erase = Zotero.Promise.coroutine(function* () {
// Don't send notifications for items and other groups objects that are deleted,
// since we're really only removing the group from the client
var notifierDisabled = Zotero.Notifier.disable();
- Zotero.DB.beginTransaction();
-
- var sql, ids, obj;
-
- // Delete items
- sql = "SELECT itemID FROM items WHERE libraryID=?";
- ids = Zotero.DB.columnQuery(sql, this.libraryID);
- Zotero.Items.erase(ids);
-
- // Delete collections
- sql = "SELECT collectionID FROM collections WHERE libraryID=?";
- ids = Zotero.DB.columnQuery(sql, this.libraryID);
- for each(var id in ids) {
- obj = Zotero.Collections.get(id);
- // Subcollections might've already been deleted
- if (obj) {
- obj.erase();
+ yield Zotero.DB.executeTransaction(function* () {
+ var sql, ids, obj;
+
+ // Delete items
+ sql = "SELECT itemID FROM items WHERE libraryID=?";
+ ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
+ yield Zotero.Items.erase(ids);
+
+ // Delete collections
+ sql = "SELECT collectionID FROM collections WHERE libraryID=?";
+ ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
+ for each(var id in ids) {
+ obj = yield Zotero.Collections.getAsync(id);
+ // Subcollections might've already been deleted
+ if (obj) {
+ yield obj.erase();
+ }
}
- }
-
- // Delete creators
- sql = "SELECT creatorID FROM creators WHERE libraryID=?";
- ids = Zotero.DB.columnQuery(sql, this.libraryID);
- for each(var id in ids) {
- obj = Zotero.Creators.get(id);
- obj.erase();
- }
-
- // Delete saved searches
- sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
- ids = Zotero.DB.columnQuery(sql, this.libraryID);
- if (ids) {
- Zotero.Searches.erase(ids);
- }
-
- // Delete tags
- sql = "SELECT tagID FROM tags WHERE libraryID=?";
- ids = Zotero.DB.columnQuery(sql, this.libraryID);
- Zotero.Tags.erase(ids);
-
- // Delete delete log entries
- sql = "DELETE FROM syncDeleteLog WHERE libraryID=?";
- Zotero.DB.query(sql, this.libraryID);
-
- var prefix = "groups/" + this.id;
- Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix);
-
- // Delete settings
- sql = "DELETE FROM syncedSettings WHERE libraryID=?";
- Zotero.DB.query(sql, this.libraryID ? parseInt(this.libraryID) : 0);
-
- // Delete group
- sql = "DELETE FROM groups WHERE groupID=?";
- Zotero.DB.query(sql, this.id)
-
- // Delete library
- sql = "DELETE FROM libraries WHERE libraryID=?";
- Zotero.DB.query(sql, this.libraryID)
-
- Zotero.purgeDataObjects();
-
- var notifierData = {};
- notifierData[this.id] = this.serialize();
-
- Zotero.DB.commitTransaction();
+
+ // Delete creators
+ sql = "SELECT creatorID FROM creators WHERE libraryID=?";
+ ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
+ for (let i=0; i<ids.length; i++) {
+ obj = yield Zotero.Creators.getAsync(ids[i]);
+ yield obj.erase();
+ }
+
+ // Delete saved searches
+ sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
+ ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
+ if (ids) {
+ yield Zotero.Searches.erase(ids);
+ }
+
+ // Delete tags
+ sql = "SELECT tagID FROM tags WHERE libraryID=?";
+ ids = yield Zotero.DB.columnQueryAsync(sql, this.libraryID);
+ yield Zotero.Tags.erase(ids);
+
+ // Delete delete log entries
+ sql = "DELETE FROM syncDeleteLog WHERE libraryID=?";
+ yield Zotero.DB.queryAsync(sql, this.libraryID);
+
+ var prefix = "groups/" + this.id;
+ yield Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix);
+
+ // Delete settings
+ sql = "DELETE FROM syncedSettings WHERE libraryID=?";
+ yield Zotero.DB.queryAsync(sql, this.libraryID ? parseInt(this.libraryID) : 0);
+
+ // Delete group
+ sql = "DELETE FROM groups WHERE groupID=?";
+ yield Zotero.DB.queryAsync(sql, this.id)
+
+ // Delete library
+ sql = "DELETE FROM libraries WHERE libraryID=?";
+ yield Zotero.DB.queryAsync(sql, this.libraryID)
+
+ yield Zotero.purgeDataObjects();
+
+ var notifierData = {};
+ notifierData[this.id] = this.serialize();
+ }.bind(this));
if (notifierDisabled) {
Zotero.Notifier.enable();
}
Zotero.Notifier.trigger('delete', 'group', this.id, notifierData);
-}
+});
Zotero.Group.prototype.serialize = function() {
@@ -399,6 +390,13 @@ Zotero.Group.prototype.serialize = function() {
}
+Zotero.Group.prototype._requireLoad = function () {
+ if (!this._loaded) {
+ throw new Error("Group has not been loaded");
+ }
+}
+
+
Zotero.Group.prototype._prepFieldChange = function (field) {
if (!this._changed) {
this._changed = {};
diff --git a/chrome/content/zotero/xpcom/data/groups.js b/chrome/content/zotero/xpcom/data/groups.js
@@ -27,32 +27,39 @@
Zotero.Groups = new function () {
this.__defineGetter__('addGroupURL', function () ZOTERO_CONFIG.WWW_BASE_URL + 'groups/new/');
- this.get = function (id) {
+ var _groupIDsByLibraryID = {};
+ var _libraryIDsByGroupID = {};
+
+
+ this.init = function () {
+ _loadIDs();
+ }
+
+ this.get = Zotero.Promise.coroutine(function* (id) {
if (!id) {
- throw ("groupID not provided in Zotero.Groups.get()");
+ throw new Error("groupID not provided");
}
var group = new Zotero.Group;
group.id = id;
- if (!group.exists()) {
+ if (!(yield group.load())) {
return false;
}
return group;
- }
+ });
- this.getAll = function () {
+ this.getAll = Zotero.Promise.coroutine(function* () {
var groups = [];
var sql = "SELECT groupID FROM groups ORDER BY name COLLATE locale";
- var groupIDs = Zotero.DB.columnQuery(sql);
- if (!groupIDs) {
+ var groupIDs = yield Zotero.DB.columnQueryAsync(sql);
+ if (!groupIDs.length) {
return groups;
}
for each(var groupID in groupIDs) {
- var group = this.get(groupID);
- groups.push(group);
+ groups.push(this.get(groupID));
}
- return groups;
- }
+ return Zotero.Promise.all(groups);
+ });
this.getByLibraryID = function (libraryID) {
@@ -61,24 +68,38 @@ Zotero.Groups = new function () {
}
+ this.exists = function (groupID) {
+ return !!_libraryIDsByGroupID[groupID];
+ }
+
+
this.getGroupIDFromLibraryID = function (libraryID) {
- var sql = "SELECT groupID FROM groups WHERE libraryID=?";
- var groupID = Zotero.DB.valueQuery(sql, libraryID);
+ var groupID = _groupIDsByLibraryID[libraryID];
if (!groupID) {
- throw ("Group with libraryID " + libraryID + " does not exist "
- + "in Zotero.Groups.getGroupIDFromLibraryID()");
+ throw new Error("Group with libraryID " + libraryID + " does not exist");
}
return groupID;
}
this.getLibraryIDFromGroupID = function (groupID) {
- var sql = "SELECT libraryID FROM groups WHERE groupID=?";
- var libraryID = Zotero.DB.valueQuery(sql, groupID);
+ var libraryID = _libraryIDsByGroupID[groupID];
if (!libraryID) {
- throw ("Group with groupID " + groupID + " does not exist "
- + "in Zotero.Groups.getLibraryIDFromGroupID()");
+ throw new Error("Group with groupID " + groupID + " does not exist");
}
return libraryID;
}
+
+
+ function _loadIDs() {
+ var sql = "SELECT libraryID, groupID FROM groups";
+ return Zotero.DB.queryAsync(sql)
+ .then(function (rows) {
+ for (let i=0; i<rows.length; i++) {
+ let row = rows[i];
+ _groupIDsByLibraryID[row.libraryID] = row.groupID;
+ _libraryIDsByGroupID[row.groupID] = row.libraryID;
+ }
+ }.bind(this));
+ }
}
diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
@@ -26,120 +26,122 @@
/*
* Constructor for Item object
- *
- * Generally should be called through Zotero.Items rather than directly
*/
Zotero.Item = function(itemTypeOrID) {
if (arguments[1] || arguments[2]) {
throw ("Zotero.Item constructor only takes one parameter");
}
+ var dataTypes = [
+ 'primaryData',
+ 'itemData',
+ 'note',
+ 'creators',
+ 'childItems',
+ 'relatedItems', // TODO: remove
+ 'tags',
+ 'collections',
+ 'relations'
+ ];
+ Zotero.DataObject.apply(this, ['item', false, dataTypes]);
+
this._disabled = false;
- this._init();
- if (itemTypeOrID) {
- // setType initializes type-specific properties in this._itemData
- this.setType(Zotero.ItemTypes.getID(itemTypeOrID));
- }
-}
-
-Zotero.Item.prototype._init = function () {
- // Primary fields
- this._id = null;
- this._libraryID = null
- this._key = null;
+ // loadPrimaryData (additional properties in dataObjet.js)
this._itemTypeID = null;
- this._dateAdded = null;
- this._dateModified = null;
this._firstCreator = null;
+ this._sortCreator = null;
+ this._itemVersion = null;
this._numNotes = null;
this._numNotesTrashed = null;
this._numNotesEmbedded = null;
- this._numNotesEmbeddedIncludingTrashed = null;
+ this._numNotesEmbeddedTrashed = null;
this._numAttachments = null;
+ this._numAttachmentsTrashed = null;
+ this._parentID = null;
+ this._parentKey = null;
+ this._attachmentCharset = null;
+ this._attachmentLinkMode = null;
+ this._attachmentContentType = null;
+ this._attachmentPath = null;
+ this._attachmentSyncState = null;
+ // loadCreators
this._creators = [];
- this._itemData = null;
- this._sourceItem = null;
+ this._creatorIDs = [];
- this._primaryDataLoaded = false;
- this._creatorsLoaded = false;
- this._itemDataLoaded = false;
- this._relatedItemsLoaded = false;
+ // loadItemData
+ this._itemData = null;
+ this._noteTitle = null; // also loaded by Items.cacheFields()
+ this._noteText = null; // also loaded by Items.cacheFields()
+ this._displayTitle = null; // also loaded by Items.cacheFields()
- this._changed = {};
- this._changedPrimaryData = false;
- this._changedItemData = false;
- this._changedCreators = false;
- this._changedDeleted = false;
- this._changedNote = false;
- this._changedSource = false;
- this._changedAttachmentData = false;
+ // loadChildItems
+ this._attachments = null;
+ this._notes = null;
- this._previousData = {};
+ this._tags = {};
+ this._collections = {};
+ this._relations = {};
this._bestAttachmentState = null;
this._fileExists = null;
this._deleted = null;
this._hasNote = null;
- this._noteTitle = null;
- this._noteText = null;
- this._noteAccessTime = null;
- this._cachedAttachments = null;
- this._cachedNotes = null;
- this._attachmentLinkMode = null;
- this._attachmentMIMEType = null;
- this._attachmentCharset;
- this._attachmentPath = null;
- this._attachmentSyncState;
+ this._noteAccessTime = null;
this._relatedItems = false;
+
+ if (itemTypeOrID) {
+ // setType initializes type-specific properties in this._itemData
+ this.setType(Zotero.ItemTypes.getID(itemTypeOrID));
+ }
}
+Zotero.Item.prototype = Object.create(Zotero.DataObject.prototype);
+Zotero.Item.constructor = Zotero.Item;
Zotero.Item.prototype.__defineGetter__('objectType', function () { return 'item'; });
-Zotero.Item.prototype.__defineGetter__('id', function () {
- if(!this._id && this._key && !this._primaryDataLoaded) {
- this.loadPrimaryData(true);
- }
- return this._id;
-});
+Zotero.Item.prototype.__defineGetter__('id', function () this._id);
Zotero.Item.prototype.__defineGetter__('itemID', function () {
Zotero.debug("Item.itemID is deprecated -- use Item.id");
- return this.id;
+ return this._id;
});
Zotero.Item.prototype.__defineSetter__('id', function (val) { this.setField('id', val); });
-Zotero.Item.prototype.__defineGetter__('libraryID', function () { return this.getField('libraryID'); });
+Zotero.Item.prototype.__defineGetter__('libraryID', function () this._libraryID );
Zotero.Item.prototype.__defineSetter__('libraryID', function (val) { this.setField('libraryID', val); });
-Zotero.Item.prototype.__defineGetter__('key', function () { return this.getField('key'); });
+Zotero.Item.prototype.__defineGetter__('key', function () this._key );
Zotero.Item.prototype.__defineSetter__('key', function (val) { this.setField('key', val) });
-Zotero.Item.prototype.__defineGetter__('itemTypeID', function () {
- if(!this._itemTypeID && (this._id || this._key) && !this._primaryDataLoaded) {
- this.loadPrimaryData(true);
- }
- return this._itemTypeID;
-});
-Zotero.Item.prototype.__defineGetter__('dateAdded', function () { return this.getField('dateAdded'); });
-Zotero.Item.prototype.__defineGetter__('dateModified', function () { return this.getField('dateModified'); });
-Zotero.Item.prototype.__defineGetter__('firstCreator', function () { return this.getField('firstCreator'); });
-
-Zotero.Item.prototype.__defineGetter__('relatedItems', function () { var ids = this._getRelatedItems(true); return ids; });
+Zotero.Item.prototype.__defineGetter__('itemTypeID', function () this._itemTypeID);
+Zotero.Item.prototype.__defineGetter__('dateAdded', function () this._dateAdded );
+Zotero.Item.prototype.__defineGetter__('dateModified', function () this._dateModified );
+Zotero.Item.prototype.__defineGetter__('version', function () this._itemVersion );
+Zotero.Item.prototype.__defineSetter__('version', function (val) { return this.setField('itemVersion', val); });
+Zotero.Item.prototype.__defineGetter__('synced', function () this._synced );
+Zotero.Item.prototype.__defineSetter__('synced', function (val) { return this.setField('synced', val); });
+
+// .parentKey and .parentID defined in dataObject.js, but create aliases
+Zotero.Item.prototype.__defineGetter__('parentItemKey', function () this._parentKey );
+Zotero.Item.prototype.__defineSetter__('parentItemKey', function (val) this._setParentKey(val) );
+Zotero.Item.prototype.__defineGetter__('parentItemID', function () this._getParentID() );
+Zotero.Item.prototype.__defineSetter__('parentItemID', function (val) this._setParentID(val) );
+
+Zotero.Item.prototype.__defineGetter__('firstCreator', function () this._firstCreator );
+Zotero.Item.prototype.__defineGetter__('sortCreator', function () this._sortCreator );
+
+Zotero.Item.prototype.__defineGetter__('relatedItems', function () { return this._getRelatedItems(true); });
Zotero.Item.prototype.__defineSetter__('relatedItems', function (arr) { this._setRelatedItems(arr); });
-Zotero.Item.prototype.__defineGetter__('relatedItemsReverse', function () { var ids = this._getRelatedItemsReverse(); return ids; });
-Zotero.Item.prototype.__defineGetter__('relatedItemsBidirectional', function () { var ids = this._getRelatedItemsBidirectional(); return ids; });
-
-Zotero.Item.prototype.__defineGetter__('libraryKey', function () this.libraryID + "/" + this.key);
Zotero.Item.prototype.getID = function() {
Zotero.debug('Item.getID() is deprecated -- use Item.id');
- return this.id;
+ return this._id;
}
Zotero.Item.prototype.getType = function() {
Zotero.debug('Item.getType() is deprecated -- use Item.itemTypeID');
- return this.getField('itemTypeID');
+ return this._itemTypeID;
}
Zotero.Item.prototype.isPrimaryField = function (fieldName) {
@@ -188,21 +190,20 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
//Zotero.debug('Requesting field ' + field + ' for item ' + this._id, 4);
- if ((this._id || this._key) && !this._primaryDataLoaded) {
- this.loadPrimaryData(true);
- }
+ this._requireData('primaryData');
- if (field === 'firstCreator' && !this.id) {
+ // TODO: Fix logic and add sortCreator
+ if (field === 'firstCreator' && !this._id) {
// Hack to get a firstCreator for an unsaved item
- var creators = this.getCreators();
- if(creators.length === 0) {
+ var creatorsData = this.getCreators(true);
+ if (creators.length === 0) {
return "";
- } else if(creators.length === 1) {
- return creators[0].ref.lastName;
- } else if(creators.length === 2) {
- return creators[0].ref.lastName+" "+Zotero.getString('general.and')+" "+creators[1].ref.lastName;
- } else if(creators.length > 3) {
- return creators[0].ref.lastName+" "+Zotero.getString('general.etAl');
+ } else if (creators.length === 1) {
+ return creatorsData[0].lastName;
+ } else if (creators.length === 2) {
+ return creatorsData[0].lastName + " " + Zotero.getString('general.and') + " " + creatorsData[1].lastName;
+ } else if (creators.length > 3) {
+ return creatorsData[0].lastName + " " + Zotero.getString('general.etAl');
}
} else if (field === 'id' || Zotero.Items.isPrimaryField(field)) {
var privField = '_' + field;
@@ -222,7 +223,7 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
if (includeBaseMapped) {
var fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(
- this.itemTypeID, field
+ this._itemTypeID, field
);
}
@@ -230,16 +231,23 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
var fieldID = Zotero.ItemFields.getID(field);
}
- if (typeof this._itemData[fieldID] == 'undefined') {
+ let value = this._itemData[fieldID];
+
+ if (value === undefined) {
//Zotero.debug("Field '" + field + "' doesn't exist for item type " + this._itemTypeID + " in Item.getField()");
return '';
}
- if (!this._itemDataLoaded && this.id && this._itemData[fieldID] === null) {
- this._loadItemData();
+ // Either item data has to be loaded (which sets empty valid fields to false)
+ // or this field has to be populated (e.g., by Zotero.Items.cacheFields())
+ // before getField() is called.
+ if (value === null) {
+ throw new Zotero.DataObjects.UnloadedDataException(
+ "Item data not loaded and field '" + field + "' not set", "itemData"
+ );
}
- var value = this._itemData[fieldID] ? this._itemData[fieldID] : '';
+ var value = value ? value : '';
if (!unformatted) {
// Multipart date fields
@@ -257,7 +265,7 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
* @param {Boolean} asNames
* @return {Integer{}|String[]}
*/
-Zotero.Item.prototype.getUsedFields = function(asNames) {
+Zotero.Item.prototype.getUsedFields = Zotero.Promise.coroutine(function* (asNames) {
if (!this.id) {
return [];
}
@@ -265,151 +273,215 @@ Zotero.Item.prototype.getUsedFields = function(asNames) {
if (asNames) {
sql = "SELECT fieldName FROM fields WHERE fieldID IN (" + sql + ")";
}
- var fields = Zotero.DB.columnQuery(sql, this.id);
+ var fields = yield Zotero.DB.columnQueryAsync(sql, this._id);
if (!fields) {
return [];
}
return fields;
-}
+});
/*
* Build object from database
*/
-Zotero.Item.prototype.loadPrimaryData = function(allowFail) {
+Zotero.Item.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) {
+ if (this._loaded.primaryData && !reload) return;
+
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
if (!id && !key) {
- throw ('ID or key not set in Zotero.Item.loadPrimaryData()');
+ throw new Error('ID or key not set in Zotero.Item.loadPrimaryData()');
}
var columns = [], join = [], where = [];
- for each(var field in Zotero.Items.primaryFields) {
- var colSQL = null, joinSQL = null, whereSQL = null;
-
+ var primaryFields = Zotero.Items.primaryFields;
+ for (let i=0; i<primaryFields.length; i++) {
+ let field = primaryFields[i];
// If field not already set
- if (field == 'itemID' || this['_' + field] === null) {
- // Parts should be the same as query in Zotero.Items._load, just
- // without itemID clause
- switch (field) {
- case 'itemID':
- case 'itemTypeID':
- case 'dateAdded':
- case 'dateModified':
- case 'key':
- colSQL = 'I.' + field;
- break;
-
- case 'firstCreator':
- colSQL = Zotero.Items.getFirstCreatorSQL();
- break;
-
- case 'numNotes':
- colSQL = '(SELECT COUNT(*) FROM itemNotes INo '
- + 'WHERE sourceItemID=I.itemID AND INo.itemID '
- + 'NOT IN (SELECT itemID FROM deletedItems)) AS numNotes';
- break;
-
- case 'numAttachments':
- colSQL = '(SELECT COUNT(*) FROM itemAttachments IA '
- + 'WHERE sourceItemID=I.itemID AND IA.itemID '
- + 'NOT IN (SELECT itemID FROM deletedItems)) AS numAttachments';
- break;
- }
- if (colSQL) {
- columns.push(colSQL);
- }
- if (joinSQL) {
- join.push(joinSQL);
- }
- if (whereSQL) {
- where.push(whereSQL);
- }
+ if (field == 'itemID' || this['_' + field] === null || reload) {
+ columns.push(Zotero.Items.getPrimaryDataSQLPart(field));
}
}
-
if (!columns.length) {
- throw ("No columns to load in Zotero.Item.loadPrimaryData()");
+ return;
}
-
- var sql = 'SELECT ' + columns.join(', ') + " FROM items I "
- + (join.length ? join.join(' ') + ' ' : '') + "WHERE ";
+ // This should match Zotero.Items._getPrimaryDataSQL(), but without
+ // necessarily including all columns
+ var sql = "SELECT " + columns.join(", ") + Zotero.Items.primaryDataSQLFrom;
if (id) {
- sql += "itemID=? ";
+ sql += " AND O.itemID=? ";
var params = id;
}
else {
- sql += "key=? AND libraryID=? ";
+ sql += " AND O.key=? AND O.libraryID=? ";
var params = [key, libraryID];
}
sql += (where.length ? ' AND ' + where.join(' AND ') : '');
- var row = Zotero.DB.rowQuery(sql, params);
+ var row = yield Zotero.DB.rowQueryAsync(sql, params);
if (!row) {
- if (allowFail) {
- this._primaryDataLoaded = true;
- return false;
- }
- throw ("Item " + (id ? id : libraryID + "/" + key)
+ if (failOnMissing) {
+ throw new Error("Item " + (id ? id : libraryID + "/" + key)
+ " not found in Zotero.Item.loadPrimaryData()");
+ }
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
+ return;
}
- this.loadFromRow(row);
- return true;
-}
+ this.loadFromRow(row, reload);
+ return;
+});
/*
* Populate basic item data from a database row
*/
Zotero.Item.prototype.loadFromRow = function(row, reload) {
- if (reload) {
- this._init();
- }
-
// If necessary or reloading, set the type and reinitialize this._itemData
if (reload || (!this._itemTypeID && row.itemTypeID)) {
this.setType(row.itemTypeID, true);
}
- for (var col in row) {
- if (col == 'clientDateModified') {
- continue;
+ if (false) {
+ var primaryFields = Zotero.Items.primaryFields;
+ for (let i=0; i<primaryFields.length; i++) {
+ if (primaryFields[i] === undefined) {
+ Zotero.debug('Skipping missing field ' + primaryFields[i]);
+ continue;
+ }
}
- // Only accept primary field data through loadFromRow()
- if (Zotero.Items.isPrimaryField(col)) {
- //Zotero.debug("Setting field '" + col + "' to '" + row[col] + "' for item " + this.id);
+ if (row.itemID !== undefined) {
+ this._id = parseInt(row.itemID);
+ }
+ if (row.itemTypeID !== undefined) {
+ this._id = parseInt(row.itemTypeID);
+ }
+ if (row.libraryID !== undefined) {
+ this._libraryID = parseInt(row.libraryID);
+ }
+ if (row.key !== undefined) {
+ this._key = row.key;
+ }
+ if (row.dateAdded !== undefined) {
+ this._dateAdded = row.dateAdded;
+ }
+ if (row.dateModified !== undefined) {
+ this._dateModified = row.dateModified;
+ }
+ if (row.itemVersion !== undefined) {
+ this._itemVersion = parseInt(row.itemVersion);
+ }
+ if (row.numNotes !== undefined) {
+ this._numNotes = parseInt(row.numNotes);
+ }
+ if (row.numNotesTrashed !== undefined) {
+ this._numNotesTrashed = parseInt(row.numNotesTrashed);
+ }
+ if (row.numNotesEmbedded !== undefined) {
+ this._numNotesEmbedded = parseInt(row.numNotesEmbedded);
+ }
+ if (row.numNotesEmbeddedTrashed !== undefined) {
+ this._numNotesEmbeddedTrashed = parseInt(row.numNotesEmbeddedTrashed);
+ }
+ if (row.numAttachments !== undefined) {
+ this._numAttachments = parseInt(row.numAttachments);
+ }
+ if (row.numAttachmentsTrashed !== undefined) {
+ this._numAttachmentsTrashed = parseInt(row.numAttachmentsTrashed);
+ }
+ if (row.parentKey !== undefined) {
+ this._parentKey = row.parentKey || false;
+ }
+ if (row.parentID !== undefined) {
+ this._parentID = row.parentID ? parseInt(row.parentID) : false;
+ }
+ if (row.synced !== undefined) {
+ this._synced = !!row.synced;
+ }
+ if (row.firstCreator !== undefined) {
+ this._firstCreator = row.firstCreator ? row.firstCreator : '';
+ }
+ if (row.sortCreator !== undefined) {
+ this._sortCreator = row.sortCreator ? row.sortCreator : '';
+ }
+ if (row.attachmentCharset !== undefined) {
+ this._attachmentCharset = row.attachmentCharset ? parseInt(row.attachmentCharset) : null;
+ }
+ if (row.attachmentLinkMode !== undefined) {
+ this._attachmentLinkMode = parseInt(row.attachmentLinkMode);
+ }
+ if (row.attachmentContentType !== undefined) {
+ this._attachmentContentType = row.attachmentContentType ? row.attachmentContentType : '';
+ }
+ if (row.attachmentPath !== undefined) {
+ this._attachmentPath = row.attachmentPath ? row.attachmentPath : '';
+ }
+ if (row.attachmentSyncState !== undefined) {
+ this._attachmentSyncState = parseInt(row.attachmentSyncState);
+ }
+ }
+ else {
+ var primaryFields = Zotero.Items.primaryFields;
+ for (let i=0; i<primaryFields.length; i++) {
+ let col = primaryFields[i];
+
+ if (row[col] === undefined) {
+ Zotero.debug('Skipping missing field ' + col);
+ continue;
+ }
+
+ let val = row[col];
+
+ //Zotero.debug("Setting field '" + col + "' to '" + val + "' for item " + this.id);
switch (col) {
case 'itemID':
- this._id = row[col];
+ this._id = val;
break;
case 'libraryID':
- this['_' + col] = row[col];
+ this['_' + col] = val;
break;
+ case 'itemVersion':
case 'numNotes':
+ case 'numNotesTrashed':
+ case 'numNotesEmbedded':
+ case 'numNotesEmbeddedTrashed':
case 'numAttachments':
- this['_' + col] = row[col] ? parseInt(row[col]) : 0;
+ case 'numAttachmentsTrashed':
+ this['_' + col] = val ? parseInt(val) : 0;
+ break;
+
+ case 'parentKey':
+ this['_parentKey'] = val || false;
+ break;
+
+ case 'parentID':
+ case 'attachmentCharset':
+ this['_' + col] = val ? parseInt(val) : false;
break;
- case 'sourceItemID':
- this['_sourceItem'] = row[col] || false;
+ case 'attachmentLinkMode':
+ this['_' + col] = val !== null ? parseInt(val) : false;
+ break;
+ case 'synced':
+ this['_synced'] = !!val;
+ break;
+
default:
- this['_' + col] = row[col] ? row[col] : '';
+ this['_' + col] = val ? val : '';
}
}
- else {
- Zotero.debug(col + ' is not a valid primary field');
- }
}
-
- this._primaryDataLoaded = true;
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
+ this._identified = true;
}
@@ -417,14 +489,8 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
* Check if any data fields have changed since last save
*/
Zotero.Item.prototype.hasChanged = function() {
- return !!(Object.keys(this._changed).length
- || this._changedPrimaryData
- || this._changedItemData
- || this._changedCreators
- || this._changedDeleted
- || this._changedNote
- || this._changedSource
- || this._changedAttachmentData);
+ Zotero.debug(this._changed);
+ return !!Object.keys(this._changed).filter((dataType) => this._changed[dataType]).length
}
@@ -437,17 +503,17 @@ Zotero.Item.prototype.setType = function(itemTypeID, loadIn) {
}
var oldItemTypeID = this._itemTypeID;
- var newNotifierFields = [];
-
if (oldItemTypeID) {
if (loadIn) {
- throw ('Cannot change type in loadIn mode in Zotero.Item.setType()');
- }
- if (!this._itemDataLoaded && this.id) {
- this._loadItemData();
+ throw new Error('Cannot change type in loadIn mode');
}
+ // Changing the item type can affect fields and creators, so they need to be loaded
+ this._requireData('itemData');
+ this._requireData('creators');
+
var copiedFields = [];
+ var newNotifierFields = [];
// Special cases handled below
var bookTypeID = Zotero.ItemTypes.getID('book');
@@ -488,10 +554,10 @@ Zotero.Item.prototype.setType = function(itemTypeID, loadIn) {
// Clear old field
/*
delete this._itemData[oldFieldID];
- if (!this._changedItemData) {
- this._changedItemData = {};
+ if (!this._changed.itemData) {
+ this._changed.itemData = {};
}
- this._changedItemData[oldFieldID] = true;
+ this._changed.itemData[oldFieldID] = true;
*/
this.setField(oldFieldID, false);
}
@@ -525,10 +591,10 @@ Zotero.Item.prototype.setType = function(itemTypeID, loadIn) {
// If there's an existing type
if (oldItemTypeID) {
// Reset custom creator types to the default
- var creators = this.getCreators();
+ let creators = this.getCreators();
if (creators) {
- var removeAll = !Zotero.CreatorTypes.itemTypeHasCreators(itemTypeID);
- for (var i in creators) {
+ let removeAll = !Zotero.CreatorTypes.itemTypeHasCreators(itemTypeID);
+ for (let i=0; i<creators.length; i++) {
// Remove all creators if new item type doesn't have any
if (removeAll) {
this.removeCreator(i);
@@ -539,13 +605,14 @@ Zotero.Item.prototype.setType = function(itemTypeID, loadIn) {
// Convert existing primary creator type to new item type's
// primary creator type, or contributor (creatorTypeID 2)
// if none or not currently primary
- var oldPrimary = Zotero.CreatorTypes.getPrimaryIDForType(oldItemTypeID);
+ let oldPrimary = Zotero.CreatorTypes.getPrimaryIDForType(oldItemTypeID);
+ let newPrimary = false;
if (oldPrimary == creators[i].creatorTypeID) {
- var newPrimary = Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID);
+ newPrimary = Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID);
}
- var target = newPrimary ? newPrimary : 2;
+ creators[i].creatorTypeID = newPrimary ? newPrimary : 2;
- this.setCreator(i, creators[i].ref, target);
+ this.setCreator(i, creators[i]);
}
}
}
@@ -579,16 +646,16 @@ Zotero.Item.prototype.setType = function(itemTypeID, loadIn) {
}
if (loadIn) {
- this._itemDataLoaded = false;
+ this._loaded['itemData'] = false;
}
else {
if (oldItemTypeID) {
this._markFieldChange('itemType', Zotero.ItemTypes.getName(oldItemTypeID));
}
- if (!this._changedPrimaryData) {
- this._changedPrimaryData = {};
+ if (!this._changed.primaryData) {
+ this._changed.primaryData = {};
}
- this._changedPrimaryData['itemTypeID'] = true;
+ this._changed.primaryData.itemTypeID = true;
}
return true;
@@ -654,27 +721,6 @@ Zotero.Item.prototype.getFieldsNotInType = function (itemTypeID, allowBaseConver
}
-/**
-* Return an array of collectionIDs for all collections the item belongs to
-**/
-Zotero.Item.prototype.getCollections = function() {
- var ids = Zotero.DB.columnQuery(
- "SELECT collectionID FROM collectionItems WHERE itemID=?", this.id
- );
- return ids ? ids : [];
-}
-
-
-/**
-* Determine whether the item belongs to a given collectionID
-**/
-Zotero.Item.prototype.inCollection = function(collectionID) {
- return !!parseInt(Zotero.DB.valueQuery("SELECT COUNT(*) "
- + "FROM collectionItems WHERE collectionID=" + collectionID + " AND "
- + "itemID=" + this.id));
-}
-
-
/*
* Set a field value, loading existing itemData first if necessary
*
@@ -690,51 +736,32 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
//Zotero.debug("Setting field '" + field + "' to '" + value + "' (loadIn: " + (loadIn ? 'true' : 'false') + ") for item " + this.id + " ");
if (!field) {
- throw ("Field not specified in Item.setField()");
- }
-
- // Set id, libraryID, and key without loading data first
- switch (field) {
- case 'id':
- case 'libraryID':
- case 'key':
- if (field == 'libraryID') {
- value = Zotero.DataObjectUtilities.checkLibraryID(value);
- }
-
- if (value == this['_' + field]) {
- return;
- }
-
- if (this._primaryDataLoaded) {
- throw ("Cannot set " + field + " after object is already loaded in Zotero.Item.setField()");
- }
- this['_' + field] = value;
- return;
+ throw new Error("Field not specified");
}
- if (this._id || this._key) {
- if (!this._primaryDataLoaded) {
- this.loadPrimaryData(true);
- }
- }
- else {
- this._primaryDataLoaded = true;
+ if (field == 'id' || field == 'libraryID' || field == 'key') {
+ return this._setIdentifier(field, value);
}
// Primary field
if (Zotero.Items.isPrimaryField(field)) {
+ this._requireData('primaryData');
+
if (loadIn) {
throw('Cannot set primary field ' + field + ' in loadIn mode in Zotero.Item.setField()');
}
switch (field) {
- case 'itemID':
- case 'firstCreator':
- case 'numNotes':
- case 'numAttachments':
- case 'sourceItemID':
+ case 'itemTypeID':
+ case 'dateAdded':
+ case 'dateModified':
+ case 'version':
+ case 'synced':
+ break;
+
+ default:
throw ('Primary field ' + field + ' cannot be changed in Zotero.Item.setField()');
+
}
/*
@@ -754,12 +781,22 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
this.setType(value, loadIn);
}
else {
+ switch (field) {
+ case 'version':
+ value = parseInt(value);
+ break;
+
+ case 'synced':
+ value = !!value;
+ break;
+ }
+
this['_' + field] = value;
- if (!this._changedPrimaryData) {
- this._changedPrimaryData = {};
+ if (!this._changed.primaryData) {
+ this._changed.primaryData = {};
}
- this._changedPrimaryData[field] = true;
+ this._changed.primaryData[field] = true;
}
}
else {
@@ -768,23 +805,16 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
return true;
}
- if (!this.itemTypeID) {
- throw ('Item type must be set before setting field data');
+ if (!loadIn) {
+ this._requireData('itemData');
}
- // If existing item, load field data first unless we're already in
- // the middle of a load
- if (this.id) {
- if (!loadIn && !this._itemDataLoaded) {
- this._loadItemData();
- }
- }
- else {
- this._itemDataLoaded = true;
+ let itemTypeID = this.itemTypeID;
+ if (!itemTypeID) {
+ throw ('Item type must be set before setting field data');
}
var fieldID = Zotero.ItemFields.getID(field);
-
if (!fieldID) {
throw ('"' + field + '" is not a valid itemData field.');
}
@@ -798,8 +828,8 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
value = false;
}
- if (value !== false && !Zotero.ItemFields.isValidForType(fieldID, this.itemTypeID)) {
- var msg = "'" + field + "' is not a valid field for type " + this.itemTypeID;
+ if (value !== false && !Zotero.ItemFields.isValidForType(fieldID, itemTypeID)) {
+ var msg = "'" + field + "' is not a valid field for type " + itemTypeID;
if (loadIn) {
Zotero.debug(msg + " -- ignoring value '" + value + "'", 2);
@@ -828,8 +858,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
if (value && (!Zotero.Date.isSQLDate(value) &&
!Zotero.Date.isSQLDateTime(value) &&
value != 'CURRENT_TIMESTAMP')) {
- Zotero.debug("Discarding invalid accessDate '" + value
- + "' in Item.setField()");
+ Zotero.debug("Discarding invalid accessDate '" + value + "' in Item.setField()");
return false;
}
}
@@ -850,10 +879,10 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
this._itemData[fieldID] = value;
if (!loadIn) {
- if (!this._changedItemData) {
- this._changedItemData = {};
+ if (!this._changed.itemData) {
+ this._changed.itemData = {};
}
- this._changedItemData[fieldID] = true;
+ this._changed.itemData[fieldID] = true;
}
return true;
}
@@ -863,125 +892,13 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
*
* This is the same as the standard title field (with includeBaseMapped on)
* except for letters and interviews, which get placeholder titles in
- * square braces (e.g. "[Letter to Thoreau]")
+ * square braces (e.g. "[Letter to Thoreau]"), and cases
*/
Zotero.Item.prototype.getDisplayTitle = function (includeAuthorAndDate) {
- var title = this.getField('title', false, true);
- var itemTypeID = this.itemTypeID;
- var itemTypeName = Zotero.ItemTypes.getName(itemTypeID);
-
- if (!title && (itemTypeID == 8 || itemTypeID == 10)) { // 'letter' and 'interview' itemTypeIDs
- var creators = this.getCreators();
- var authors = [];
- var participants = [];
- if (creators) {
- for each(var creator in creators) {
- if ((itemTypeID == 8 && creator.creatorTypeID == 16) || // 'letter'/'recipient'
- (itemTypeID == 10 && creator.creatorTypeID == 7)) { // 'interview'/'interviewer'
- participants.push(creator);
- }
- else if ((itemTypeID == 8 && creator.creatorTypeID == 1) || // 'letter'/'author'
- (itemTypeID == 10 && creator.creatorTypeID == 6)) { // 'interview'/'interviewee'
- authors.push(creator);
- }
- }
- }
-
- var strParts = [];
-
- if (includeAuthorAndDate) {
- var names = [];
- for each(author in authors) {
- names.push(author.ref.lastName);
- }
-
- // TODO: Use same logic as getFirstCreatorSQL() (including "et al.")
- if (names.length) {
- strParts.push(Zotero.localeJoin(names, ', '));
- }
- }
-
- if (participants.length > 0) {
- var names = [];
- for each(participant in participants) {
- names.push(participant.ref.lastName);
- }
- switch (names.length) {
- case 1:
- var str = 'oneParticipant';
- break;
-
- case 2:
- var str = 'twoParticipants';
- break;
-
- case 3:
- var str = 'threeParticipants';
- break;
-
- default:
- var str = 'manyParticipants';
- }
- strParts.push(Zotero.getString('pane.items.' + itemTypeName + '.' + str, names));
- }
- else {
- strParts.push(Zotero.ItemTypes.getLocalizedString(itemTypeID));
- }
-
- if (includeAuthorAndDate) {
- var d = this.getField('date');
- if (d) {
- strParts.push(d);
- }
- }
-
- title = '[';
- title += Zotero.localeJoin(strParts, '; ');
- title += ']';
- }
- else if (itemTypeID == 17) { // 'case' itemTypeID
- if (title) { // common law cases always have case names
- var reporter = this.getField('reporter');
- if (reporter) {
- title = title + ' (' + reporter + ')';
- } else {
- var court = this.getField('court');
- if (court) {
- title = title + ' (' + court + ')';
- }
- }
- }
- else { // civil law cases have only shortTitle as case name
- var strParts = [];
- var caseinfo = "";
-
- var part = this.getField('court');
- if (part) {
- strParts.push(part);
- }
-
- part = Zotero.Date.multipartToSQL(this.getField('date', true, true));
- if (part) {
- strParts.push(part);
- }
-
- part = this.getField('shortTitle');
- if (part) {
- strParts.push(part);
- }
-
- var creators = this.getCreators();
- if (creators.length && creators[0].creatorTypeID === 1) {
- strParts.push(creators[0].ref.lastName);
- }
-
- title = '[';
- title += Zotero.localeJoin(strParts, ', ');
- title += ']';
- }
+ if (this._displayTitle !== null) {
+ return this._displayTitle;
}
-
- return title;
+ return this._displayTitle = this.getField('title', false, true);
}
@@ -989,126 +906,121 @@ Zotero.Item.prototype.getDisplayTitle = function (includeAuthorAndDate) {
* Returns the number of creators for this item
*/
Zotero.Item.prototype.numCreators = function() {
- if (!this._creatorsLoaded && this.id) {
- this._loadCreators();
- }
+ this._requireData('creators');
return this._creators.length;
}
Zotero.Item.prototype.hasCreatorAt = function(pos) {
- if (!this._creatorsLoaded && this.id) {
- this._loadCreators();
- }
-
+ this._requireData('creators');
return !!this._creators[pos];
}
-/*
- * Returns an array of the creator data at the given position, or false if none
- *
- * Note: Creator data array is returned by reference
+/**
+ * @param {Integer} pos
+ * @return {Object|Boolean} The internal creator data object at the given position, or FALSE if none
*/
-Zotero.Item.prototype.getCreator = function(pos) {
- if (!this._creatorsLoaded && this.id) {
- this._loadCreators();
+Zotero.Item.prototype.getCreator = function (pos) {
+ this._requireData('creators');
+ if (!this._creators[pos]) {
+ return false;
}
-
- return this._creators[pos] ? this._creators[pos] : false;
+ var creator = {};
+ for (let i in this._creators[pos]) {
+ creator[i] = this._creators[pos][i];
+ }
+ return creator;
}
/**
- * Return the position of the given creator, or FALSE if not found
+ * @param {Integer} pos
+ * @return {Object|Boolean} The API JSON creator data at the given position, or FALSE if none
*/
-Zotero.Item.prototype.getCreatorPosition = function(creatorID) {
- if (!this._creatorsLoaded && this.id) {
- this._loadCreators();
- }
-
- for (var pos in this._creators) {
- if (this._creators[pos].creatorID == creatorID) {
- return pos;
- }
- }
-
- return false;
+Zotero.Item.prototype.getCreatorsJSON = function (pos) {
+ this._requireData('creators');
+ return this._creators[pos] ? Zotero.Creators.internalToAPIJSON(this._creators[pos]) : false;
}
-/*
- * Returns a multidimensional array of creators, or an empty array if none
+/**
+ * Returns creator data in internal format
*
- * Note: Creator data array is returned by reference
+ * @return {Array<Object>} An array of internal creator data objects
+ * ('firstName', 'lastName', 'fieldMode', 'creatorTypeID')
*/
-Zotero.Item.prototype.getCreators = function() {
- if (!this._creatorsLoaded && this.id) {
- this._loadCreators();
- }
-
- return this._creators;
+Zotero.Item.prototype.getCreators = function () {
+ this._requireData('creators');
+ // Create copies of the creator data objects
+ return this._creators.map(function (data) {
+ var creator = {};
+ for (let i in data) {
+ creator[i] = data[i];
+ }
+ return creator;
+ });
}
-/*
- * Set or update the creator at the specified position
- *
- * |orderIndex|: the position of this creator in the item (from 0)
- * |creatorTypeIDOrName|: id or type name
+/**
+ * @return {Array<Object>} An array of creator data objects in API JSON format
+ * ('firstName'/'lastName' or 'name', 'creatorType')
*/
-Zotero.Item.prototype.setCreator = function(orderIndex, creator, creatorTypeIDOrName) {
- if (this.id) {
- if (!this._creatorsLoaded) {
- this._loadCreators();
- }
- }
- else {
- this._creatorsLoaded = true;
- }
-
- if (!(creator instanceof Zotero.Creator)) {
- throw ('Creator must be a Zotero.Creator object in Zotero.Item.setCreator()');
- }
+Zotero.Item.prototype.getCreatorsAPIData = function () {
+ this._requireData('creators');
+ return this._creators.map(function (data) Zotero.Creators.internalToAPIJSON(data));
+}
+
+
+/**
+ * Set or update the creator at the specified position
+ *
+ * @param {Integer} orderIndex
+ * @param {Object} Creator data in internal or API JSON format:
+ * <ul>
+ * <li>'name' or 'firstName'/'lastName', or 'firstName'/'lastName'/'fieldMode'</li>
+ * <li>'creatorType' (can be name or id) or 'creatorTypeID'</li>
+ * </ul>
+ */
+Zotero.Item.prototype.setCreator = function (orderIndex, data) {
+ this._requireData('creators');
- var creatorTypeID = Zotero.CreatorTypes.getID(creatorTypeIDOrName);
+ data = Zotero.Creators.cleanData(data);
- if (!creatorTypeID) {
- creatorTypeID = 1;
+ if (data.creatorTypeID === undefined) {
+ throw new Error("Creator data must include a valid 'creatorType' or 'creatorTypeID' property");
}
// If creatorTypeID isn't valid for this type, use the primary type
- if (!Zotero.CreatorTypes.isValidForItemType(creatorTypeID, this.itemTypeID)) {
- var msg = "Invalid creator type " + creatorTypeID + " for item type " + this.itemTypeID
- + " -- changing to primary creator";
- Zotero.debug(msg);
- Components.utils.reportError(msg)
- creatorTypeID = Zotero.CreatorTypes.getPrimaryIDForType(this.itemTypeID);
+ var itemTypeID = this._itemTypeID;
+ if (!data.creatorTypeID || !Zotero.CreatorTypes.isValidForItemType(data.creatorTypeID, itemTypeID)) {
+ var msg = "Creator type '" + Zotero.CreatorTypes.getName(data.creatorTypeID) + "' "
+ + "isn't valid for " + Zotero.ItemTypes.getName(itemTypeID)
+ + " -- changing to primary creator";
+ Zotero.debug(msg, 2);
+ Components.utils.reportError(msg);
+ data.creatorTypeID = Zotero.CreatorTypes.getPrimaryIDForType(itemTypeID);
}
// If creator at this position hasn't changed, cancel
- if (this._creators[orderIndex] &&
- this._creators[orderIndex].ref.id == creator.id &&
- this._creators[orderIndex].creatorTypeID == creatorTypeID &&
- !creator.hasChanged()) {
+ let previousData = this._creators[orderIndex];
+ if (previousData
+ && previousData.creatorTypeID === data.creatorTypeID
+ && previousData.fieldMode === data.fieldMode
+ && previousData.firstName === data.firstName
+ && previousData.lastName === data.lastName) {
Zotero.debug("Creator in position " + orderIndex + " hasn't changed", 4);
return false;
}
- // Save copy of old creators for notifier
- if (!this._changedCreators) {
- this._changedCreators = {};
-
- var oldCreators = this._getOldCreators()
- this._markFieldChange('creators', oldCreators);
+ // Save copy of old creators for save() and notifier
+ if (!this._changed.creators) {
+ this._changed.creators = {};
+ this._markFieldChange('creators', this._getOldCreators());
}
- this._changedCreators[orderIndex] = true;
-
- this._creators[orderIndex] = {
- ref: creator,
- creatorTypeID: creatorTypeID
- };
-
+ this._changed.creators[orderIndex] = true;
+ this._creators[orderIndex] = data;
return true;
}
@@ -1116,24 +1028,15 @@ Zotero.Item.prototype.setCreator = function(orderIndex, creator, creatorTypeIDOr
/*
* Remove a creator and shift others down
*/
-Zotero.Item.prototype.removeCreator = function(orderIndex) {
- if (!this._creatorsLoaded && this.id) {
- this._loadCreators();
- }
-
- var creator = this.getCreator(orderIndex);
- if (!creator) {
- throw ('No creator exists at position ' + orderIndex
- + ' in Zotero.Item.removeCreator()');
- }
-
- if (creator.ref.countLinkedItems() == 1) {
- Zotero.Prefs.set('purge.creators', true);
+Zotero.Item.prototype.removeCreator = function(orderIndex, allowMissing) {
+ var creatorData = this.getCreator(orderIndex);
+ if (!creatorData && !allowMissing) {
+ throw new Error('No creator exists at position ' + orderIndex);
}
// Save copy of old creators for notifier
- if (!this._changedCreators) {
- this._changedCreators = {};
+ if (!this._changed.creators) {
+ this._changed.creators = {};
var oldCreators = this._getOldCreators();
this._markFieldChange('creators', oldCreators);
@@ -1149,7 +1052,7 @@ Zotero.Item.prototype.removeCreator = function(orderIndex) {
this._creators.splice(i, 1);
}
- this._changedCreators[i] = true;
+ this._changed.creators[i] = true;
}
return true;
@@ -1157,37 +1060,30 @@ Zotero.Item.prototype.removeCreator = function(orderIndex) {
Zotero.Item.prototype.__defineGetter__('deleted', function () {
- if (this._deleted !== null) {
- return this._deleted;
- }
-
if (!this.id) {
return false;
}
-
- var sql = "SELECT COUNT(*) FROM deletedItems WHERE itemID=?";
- var deleted = !!Zotero.DB.valueQuery(sql, this.id);
- this._deleted = deleted;
- return deleted;
+ if (this._deleted !== null) {
+ return this._deleted;
+ }
+ this._requireData('primaryData');
});
Zotero.Item.prototype.__defineSetter__('deleted', function (val) {
var deleted = !!val;
- if (this.deleted == deleted) {
+ if (this._deleted == deleted) {
Zotero.debug("Deleted state hasn't changed for item " + this.id);
return;
}
-
- if (!this._changedDeleted) {
- this._changedDeleted = true;
- }
+ this._markFieldChange('deleted', !!this._deleted);
+ this._changed.deleted = true;
this._deleted = deleted;
});
-Zotero.Item.prototype.addRelatedItem = function (itemID) {
+Zotero.Item.prototype.addRelatedItem = Zotero.Promise.coroutine(function* (itemID) {
var parsedInt = parseInt(itemID);
if (parsedInt != itemID) {
throw ("itemID '" + itemID + "' not an integer in Zotero.Item.addRelatedItem()");
@@ -1199,14 +1095,14 @@ Zotero.Item.prototype.addRelatedItem = function (itemID) {
return false;
}
- var current = this._getRelatedItems(true);
+ var current = yield this._getRelatedItems(true);
if (current.indexOf(itemID) != -1) {
Zotero.debug("Item " + this.id + " already related to item "
+ itemID + " in Zotero.Item.addItem()");
return false;
}
- var item = Zotero.Items.get(itemID);
+ var item = yield Zotero.Items.getAsync(itemID);
if (!item) {
throw ("Can't relate item to invalid item " + itemID
+ " in Zotero.Item.addRelatedItem()");
@@ -1224,17 +1120,17 @@ Zotero.Item.prototype.addRelatedItem = function (itemID) {
this._changed.relatedItems = true;
this._relatedItems.push(item);
return true;
-}
+});
-Zotero.Item.prototype.removeRelatedItem = function (itemID) {
+Zotero.Item.prototype.removeRelatedItem = Zotero.Promise.coroutine(function* (itemID) {
var parsedInt = parseInt(itemID);
if (parsedInt != itemID) {
throw ("itemID '" + itemID + "' not an integer in Zotero.Item.removeRelatedItem()");
}
itemID = parsedInt;
- var current = this._getRelatedItems(true);
+ var current = yield this._getRelatedItems(true);
var index = current.indexOf(itemID);
if (index == -1) {
@@ -1247,293 +1143,401 @@ Zotero.Item.prototype.removeRelatedItem = function (itemID) {
this._changed.relatedItems = true;
this._relatedItems.splice(index, 1);
return true;
-}
+});
-/*
- * Save changes back to database
+/**
+ * Save changes to database
*
- * Returns true on item update or itemID of new item
+ * @return {Promise<Integer|Boolean>} Promise for itemID of new item,
+ * TRUE on item update, or FALSE if item was unchanged
*/
-Zotero.Item.prototype.save = function(options) {
- if (!options) {
- options = {};
- }
-
- Zotero.Items.editCheck(this);
-
- if (!this.hasChanged()) {
- Zotero.debug('Item ' + this.id + ' has not changed', 4);
- return false;
- }
-
- // Make sure there are no gaps in the creator indexes
- var creators = this.getCreators();
- var lastPos = -1;
- for (var pos in creators) {
- if (pos != lastPos + 1) {
- throw ("Creator index " + pos + " out of sequence in Zotero.Item.save()");
- }
- lastPos++;
- }
-
- Zotero.DB.beginTransaction();
-
- var isNew = !this.id || !this.exists();
-
+Zotero.Item.prototype.save = Zotero.Promise.coroutine(function* (options) {
try {
- //
- // New item, insert and return id
- //
+ if (!options) {
+ options = {};
+ }
+
+ var isNew = !this.id;
+
+ Zotero.Items.editCheck(this);
+
+ if (!this.hasChanged()) {
+ Zotero.debug('Item ' + this.id + ' has not changed', 4);
+ return false;
+ }
+
+ // Register this item's identifiers in Zotero.DataObjects on transaction commit,
+ // before other callbacks run
+ var itemID, libraryID, key;
if (isNew) {
- Zotero.debug('Saving data for new item to database');
+ var transactionOptions = {
+ onCommit: function () {
+ Zotero.Items.registerIdentifiers(itemID, libraryID, key);
+ }
+ };
+ }
+ else {
+ var transactionOptions = null;
+ }
+
+ var itemTypeID = this.itemTypeID;
+
+ return Zotero.DB.executeTransaction(function* () {
+ if (isNew) {
+ Zotero.debug('Saving data for new item to database');
+ }
+ else {
+ Zotero.debug('Updating database with new item data', 4);
+ }
var sqlColumns = [];
var sqlValues = [];
+ var reloadParentChildItems = {};
//
// Primary fields
//
-
// If available id value, use it -- otherwise we'll use autoincrement
- var itemID = this.id ? this.id : Zotero.ID.get('items');
- if (itemID) {
- sqlColumns.push('itemID');
- sqlValues.push({ int: itemID });
- }
-
- var key = this.key ? this.key : this._generateKey();
+ itemID = this._id = this.id ? this.id : yield Zotero.ID.get('items');
+ Zotero.debug('=');
+ libraryID = this.libraryID;
+ key = this._key = this.key ? this.key : this._generateKey();
sqlColumns.push(
'itemTypeID',
'dateAdded',
- 'dateModified',
- 'clientDateModified',
'libraryID',
- 'key'
+ 'key',
+ 'version',
+ 'synced'
);
+
sqlValues.push(
- { int: this.getField('itemTypeID') },
+ { int: itemTypeID },
this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
- this.dateModified ? this.dateModified : Zotero.DB.transactionDateTime,
- Zotero.DB.transactionDateTime,
this.libraryID ? this.libraryID : 0,
- key
+ key,
+ this.version ? this.version : 0,
+ this.synced ? 1 : 0
);
- // Begin history transaction
- // No associated id yet, so we use false
- //Zotero.History.begin('add-item', false);
-
- //
- // Primary fields
- //
- var sql = "INSERT INTO items (" + sqlColumns.join(', ') + ') VALUES (';
- // Insert placeholders for bind parameters
- for (var i=0; i<sqlValues.length; i++) {
- sql += '?, ';
+ if (isNew) {
+ sqlColumns.push('dateModified', 'clientDateModified');
+ sqlValues.push(Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime);
+ }
+ else {
+ for each (let field in ['dateModified', 'clientDateModified']) {
+ switch (field) {
+ case 'dateModified':
+ case 'clientDateModified':
+ let skipFlag = "skip" + field[0].toUpperCase() + field.substr(1) + "Update";
+ if (!options[skipFlag]) {
+ sqlColumns.push(field);
+ sqlValues.push(Zotero.DB.transactionDateTime);
+ }
+ break;
+ }
+ }
}
- sql = sql.substring(0, sql.length-2) + ")";
-
- // Save basic data to items table
- try {
- // Needed to work around startup crash in Fx3.5
- var l = this.libraryID;
- var k = this.key;
+ if (isNew) {
+ sqlColumns.unshift('itemID');
+ sqlValues.unshift(parseInt(itemID));
- var insertID = Zotero.DB.query(sql, sqlValues);
- }
- catch (e) {
- if (l &&
- ((e.indexOf && e.indexOf('fki_items_libraryID_libraries_libraryID') != -1)
- || (!Zotero.Libraries.exists(l)))) {
- var msg = "Library " + l + " for item " + k + " not found";;
- var e = new Zotero.Error(msg, "MISSING_OBJECT");
+ var sql = "INSERT INTO items (" + sqlColumns.join(", ") + ") "
+ + "VALUES (" + sqlValues.map(function () "?").join() + ")";
+ var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
+ if (!itemID) {
+ itemID = insertID;
}
- throw (e);
+
+ Zotero.Notifier.trigger('add', 'item', itemID);
}
- if (!itemID) {
- itemID = insertID;
+ else {
+ var sql = "UPDATE items SET " + sqlColumns.join("=?, ") + "=? WHERE itemID=?";
+ sqlValues.push(parseInt(itemID));
+ yield Zotero.DB.queryAsync(sql, sqlValues);
+
+ var notifierData = {};
+ notifierData[itemID] = { changed: this._previousData };
+ Zotero.Notifier.trigger('modify', 'item', itemID, notifierData);
}
- //Zotero.History.setAssociatedID(itemID);
- //Zotero.History.add('items', 'itemID', itemID);
-
//
// ItemData
//
- if (this._changedItemData) {
- // Use manual bound parameters to speed things up
- sql = "SELECT valueID FROM itemDataValues WHERE value=?";
- var valueStatement = Zotero.DB.getStatement(sql);
+ if (this._changed.itemData) {
+ let del = [];
- sql = "INSERT INTO itemDataValues VALUES (?,?)";
- var insertValueStatement = Zotero.DB.getStatement(sql);
+ let valueSQL = "SELECT valueID FROM itemDataValues WHERE value=?";
+ let insertValueSQL = "INSERT INTO itemDataValues VALUES (?,?)";
+ let replaceSQL = "REPLACE INTO itemData VALUES (?,?,?)";
- sql = "INSERT INTO itemData VALUES (?,?,?)";
- var insertStatement = Zotero.DB.getStatement(sql);
-
- for (fieldID in this._changedItemData) {
- var value = this.getField(fieldID, true);
+ for (let fieldID in this._changed.itemData) {
+ fieldID = parseInt(fieldID);
+ let value = this.getField(fieldID, true);
+
+ // If field changed and is empty, mark row for deletion
if (!value) {
+ del.push(fieldID);
continue;
}
if (Zotero.ItemFields.getID('accessDate') == fieldID
- && this.getField(fieldID) == 'CURRENT_TIMESTAMP') {
+ && (this.getField(fieldID)) == 'CURRENT_TIMESTAMP') {
value = Zotero.DB.transactionDateTime;
}
- var dataType = Zotero.DB.getSQLDataType(value);
-
- switch (dataType) {
- case 32:
- valueStatement.bindInt32Parameter(0, value);
- break;
-
- case 64:
- valueStatement.bindInt64Parameter(0, value);
- break;
-
- default:
- valueStatement.bindUTF8StringParameter(0, value);
+ let valueID = yield Zotero.DB.valueQueryAsync(valueSQL, [value], { debug: true })
+ if (!valueID) {
+ valueID = yield Zotero.ID.get('itemDataValues');
+ yield Zotero.DB.queryAsync(insertValueSQL, [valueID, value], { debug: false });
}
- if (valueStatement.executeStep()) {
- var valueID = valueStatement.getInt32(0);
+
+ yield Zotero.DB.queryAsync(replaceSQL, [itemID, fieldID, valueID], { debug: false });
+ }
+
+ // Delete blank fields
+ if (del.length) {
+ sql = 'DELETE from itemData WHERE itemID=? AND '
+ + 'fieldID IN (' + del.map(function () '?').join() + ')';
+ yield Zotero.DB.queryAsync(sql, [itemID].concat(del));
+ }
+ }
+
+ //
+ // Creators
+ //
+ if (this._changed.creators) {
+ for (let orderIndex in this._changed.creators) {
+ orderIndex = parseInt(orderIndex);
+
+ if (isNew) {
+ Zotero.debug('Adding creator in position ' + orderIndex, 4);
}
else {
- var valueID = null;
+ Zotero.debug('Creator ' + orderIndex + ' has changed', 4);
}
- valueStatement.reset();
-
- if (!valueID) {
- valueID = Zotero.ID.get('itemDataValues');
- insertValueStatement.bindInt32Parameter(0, valueID);
-
- switch (dataType) {
- case 32:
- insertValueStatement.
- bindInt32Parameter(1, value);
- break;
-
- case 64:
- insertValueStatement.
- bindInt64Parameter(1, value);
- break;
-
- default:
- insertValueStatement.
- bindUTF8StringParameter(1, value);
- }
-
- try {
- insertValueStatement.execute();
- }
- catch (e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
- }
+ let creatorData = this.getCreator(orderIndex);
+ // If no creator in this position, just remove the item-creator association
+ if (!creatorData) {
+ let sql = "DELETE FROM itemCreators WHERE itemID=? AND orderIndex=?";
+ yield Zotero.DB.queryAsync(sql, [itemID, orderIndex]);
+ Zotero.Prefs.set('purge.creators', true);
+ continue;
}
- insertStatement.bindInt32Parameter(0, itemID);
- insertStatement.bindInt32Parameter(1, fieldID);
- insertStatement.bindInt32Parameter(2, valueID);
+ let previousCreatorID = this._previousData.creators[orderIndex]
+ ? this._previousData.creators[orderIndex].id
+ : false;
+ let newCreatorID = yield Zotero.Creators.getIDFromData(creatorData, true);
- try {
- insertStatement.execute();
- }
- catch(e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
+ // If there was previously a creator at this position and it's different from
+ // the new one, the old one might need to be purged.
+ if (previousCreatorID && previousCreatorID != newCreatorID) {
+ Zotero.Prefs.set('purge.creators', true);
}
- /*
- Zotero.History.add('itemData', 'itemID-fieldID',
- [itemID, fieldID]);
- */
+ let sql = "INSERT OR REPLACE INTO itemCreators "
+ + "(itemID, creatorID, creatorTypeID, orderIndex) VALUES (?, ?, ?, ?)";
+ yield Zotero.DB.queryAsync(
+ sql,
+ [
+ itemID,
+ newCreatorID,
+ creatorData.creatorTypeID,
+ orderIndex
+ ]
+ );
}
}
- //
- // Creators
- //
- if (this._changedCreators) {
- for (var orderIndex in this._changedCreators) {
- Zotero.debug('Adding creator in position ' + orderIndex, 4);
- var creator = this.getCreator(orderIndex);
+ // Parent item
+ let parentItem = this.parentKey;
+ parentItem = parentItem ? yield Zotero.Items.getByLibraryAndKey(this.libraryID, parentItem) : null;
+ if (this._changed.parentKey) {
+ if (isNew) {
+ if (!parentItem) {
+ // TODO: clear caches?
+ let msg = this._parentKey + " is not a valid item key";
+ throw new Zotero.Error(msg, "MISSING_OBJECT");
+ }
+
+ let newParentItemNotifierData = {};
+ //newParentItemNotifierData[newParentItem.id] = {};
+ Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData);
+
+ switch (Zotero.ItemTypes.getName(itemTypeID)) {
+ case 'note':
+ case 'attachment':
+ reloadParentChildItems[parentItem.id] = true;
+ break;
+ }
+ }
+ else {
+ let type = Zotero.ItemTypes.getName(itemTypeID);
+ let Type = type[0].toUpperCase() + type.substr(1);
- /*
- if (!creator.ref.exists()) {
- throw ("Creator in position " + orderIndex + " doesn't exist");
+ if (this._parentKey) {
+ if (!parentItem) {
+ // TODO: clear caches
+ let msg = "Cannot set source to invalid item " + this._parentKey;
+ throw new Zotero.Error(msg, "MISSING_OBJECT");
+ }
+
+ let newParentItemNotifierData = {};
+ //newParentItemNotifierData[newParentItem.id] = {};
+ Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData);
}
- */
- if (!creator) {
- continue;
+ var oldParentKey = this._previousData.parentKey;
+ if (oldParentKey) {
+ var oldParentItem = yield Zotero.Items.getByLibraryAndKey(this.libraryID, oldParentKey);
+ if (oldParentItem) {
+ let oldParentItemNotifierData = {};
+ //oldParentItemNotifierData[oldParentItem.id] = {};
+ Zotero.Notifier.trigger('modify', 'item', oldParentItem.id, oldParentItemNotifierData);
+ }
+ else {
+ Zotero.debug("Old source item " + oldParentKey
+ + " didn't exist in Zotero.Item.save()", 2);
+ }
}
- if (creator.ref.hasChanged()) {
- Zotero.debug("Auto-saving changed creator " + creator.ref.id);
- creator.ref.save();
+ // If this was an independent item, remove from any collections
+ // where it existed previously and add parent instead
+ if (!oldParentKey) {
+ let sql = "SELECT collectionID FROM collectionItems WHERE itemID=?";
+ let changedCollections = yield Zotero.DB.columnQueryAsync(sql, this.id);
+ if (changedCollections) {
+ for (let i=0; i<changedCollections.length; i++) {
+ yield parentItem.loadCollections();
+ parentItem.addToCollection(changedCollections[i]);
+ yield this.loadCollections();
+ this.removeFromCollection(changedCollections[i]);
+
+ Zotero.Notifier.trigger(
+ 'remove',
+ 'collection-item',
+ changedCollections[i] + '-' + this.id
+ );
+ }
+ parentItem.save({
+ skipDateModifiedUpdate: true
+ });
+ }
}
- sql = 'INSERT INTO itemCreators VALUES (?, ?, ?, ?)';
- Zotero.DB.query(sql,
- [{ int: itemID }, { int: creator.ref.id },
- { int: creator.creatorTypeID }, { int: orderIndex }]);
+ // Update DB, if not a note or attachment we're changing below
+ if (!this._changed.attachmentData &&
+ (!this._changed.note || !this.isNote())) {
+ var sql = "UPDATE item" + Type + "s SET parentItemID=? "
+ + "WHERE itemID=?";
+ var bindParams = [parentItem ? parentItem.id : null, this.id];
+ yield Zotero.DB.queryAsync(sql, bindParams);
+ }
- /*
- Zotero.History.add('itemCreators',
- 'itemID-creatorID-creatorTypeID',
- [this.id, creatorID, creator['creatorTypeID']]);
- */
+ // Update the counts of the previous and new sources
+ if (oldParentItem) {
+ reloadParentChildItems[oldParentItem.id] = true;
+ }
+ if (parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
}
}
-
- if (this._changedDeleted) {
+ // Trashed status
+ if (this._changed.deleted) {
if (this.deleted) {
sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
}
else {
+ // If undeleting, remove any merge-tracking relations
+ var relations = yield Zotero.Relations.getByURIs(
+ Zotero.URI.getItemURI(this),
+ Zotero.Relations.deletedItemPredicate,
+ false
+ );
+ for each(let relation in relations) {
+ relation.erase();
+ }
+
sql = "DELETE FROM deletedItems WHERE itemID=?";
}
- Zotero.DB.query(sql, itemID);
+ yield Zotero.DB.queryAsync(sql, itemID);
+
+ // Refresh trash
+ Zotero.Notifier.trigger('refresh', 'trash', this.libraryID);
+ if (this._deleted) {
+ Zotero.Notifier.trigger('trash', 'item', this.id);
+ }
+
+ if (parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
}
-
// Note
- if (this.isNote() || this._changedNote) {
- sql = "INSERT INTO itemNotes "
- + "(itemID, sourceItemID, note, title) VALUES (?,?,?,?)";
- var parent = this.isNote() ? this.getSource() : null;
- var noteText = this._noteText ? this._noteText : '';
+ if ((isNew && this.isNote()) || this._changed.note) {
+ if (!isNew) {
+ if (this._noteText === null || this._noteTitle === null) {
+ throw new Error("Cached note values not set with "
+ + "this._changed.note set to true");
+ }
+ }
+
+ let parent = this.isNote() ? this.parentID : null;
+ let noteText = this._noteText ? this._noteText : '';
// Add <div> wrapper if not present
- if (!noteText.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
+ if (!noteText.match(/^<div class="zotero-note znv[0-9]+">[\s\S]*<\/div>$/)) {
// Keep consistent with getNote()
noteText = '<div class="zotero-note znv1">' + noteText + '</div>';
}
- var bindParams = [
- itemID,
+ let params = [
parent ? parent : null,
noteText,
this._noteTitle ? this._noteTitle : ''
];
- Zotero.DB.query(sql, bindParams);
+ let sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=?";
+ if (yield Zotero.DB.valueQueryAsync(sql, itemID)) {
+ sql = "UPDATE itemNotes SET parentItemID=?, note=?, title=? WHERE itemID=?";
+ params.push(itemID);
+ }
+ else {
+ sql = "INSERT INTO itemNotes "
+ + "(itemID, parentItemID, note, title) VALUES (?,?,?,?)";
+ params.unshift(itemID);
+ }
+ yield Zotero.DB.queryAsync(sql, params);
+
+ if (parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
}
-
+ //
// Attachment
- if (this.isAttachment()) {
- var sql = "INSERT INTO itemAttachments (itemID, sourceItemID, linkMode, "
- + "mimeType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)";
- var parent = this.getSource();
- var linkMode = this.attachmentLinkMode;
- var mimeType = this.attachmentMIMEType;
- var charsetID = Zotero.CharacterSets.getID(this.attachmentCharset);
- var path = this.attachmentPath;
- var syncState = this.attachmentSyncState;
+ //
+ if (!isNew) {
+ // If attachment title changes, update parent attachments
+ if (this._changed.itemData && this._changed.itemData[110] && this.isAttachment() && parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
+ }
+
+ if (this.isAttachment() || this._changed.attachmentData) {
+ let sql = "REPLACE INTO itemAttachments (itemID, parentItemID, linkMode, "
+ + "contentType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)";
+ let parent = this.parentID;
+ let linkMode = this.attachmentLinkMode;
+ let contentType = this.attachmentContentType;
+ let charsetID = Zotero.CharacterSets.getID(this.attachmentCharset);
+ let path = this.attachmentPath;
+ let syncState = this.attachmentSyncState;
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
// Save attachment within attachment base directory as relative path
@@ -1549,661 +1553,200 @@ Zotero.Item.prototype.save = function(options) {
}
}
- var bindParams = [
+ let params = [
itemID,
parent ? parent : null,
{ int: linkMode },
- mimeType ? { string: mimeType } : null,
+ contentType ? { string: contentType } : null,
charsetID ? { int: charsetID } : null,
path ? { string: path } : null,
syncState ? { int: syncState } : 0
];
- Zotero.DB.query(sql, bindParams);
+ yield Zotero.DB.queryAsync(sql, params);
+
+ // Clear cached child attachments of the parent
+ if (!isNew && parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
}
- Zotero.Notifier.trigger('add', 'item', itemID);
-
- // Parent item
- if (this._sourceItem) {
- if (typeof this._sourceItem == 'number') {
- var newSourceItem = Zotero.Items.get(this._sourceItem);
- }
- else {
- var newSourceItem = Zotero.Items.getByLibraryAndKey(this.libraryID, this._sourceItem);
+ // Tags
+ if (this._changed.tags) {
+ let oldTags = this._previousData.tags;
+ let newTags = this._tags;
+
+ // Convert to individual JSON objects, diff, and convert back
+ let oldTagsJSON = oldTags.map(function (x) JSON.stringify(x));
+ let newTagsJSON = newTags.map(function (x) JSON.stringify(x));
+ let toAdd = Zotero.Utilities.arrayDiff(newTagsJSON, oldTagsJSON)
+ .map(function (x) JSON.parse(x));
+ let toRemove = Zotero.Utilities.arrayDiff(oldTagsJSON, newTagsJSON)
+ .map(function (x) JSON.parse(x));;
+
+ for (let i=0; i<toAdd.length; i++) {
+ let tag = toAdd[i];
+ let tagID = yield Zotero.Tags.getIDFromName(this.libraryID, tag.tag, true);
+ let sql = "INSERT INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
+ yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
+ Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + tag.tag);
}
- if (!newSourceItem) {
- // TODO: clear caches?
- var msg = "Cannot set source to invalid item " + this._sourceItem;
- var e = new Zotero.Error(msg, "MISSING_OBJECT");
+ if (toRemove.length) {
+ yield Zotero.Tags.load(this.libraryID);
+ for (let i=0; i<toRemove.length; i++) {
+ let tag = toRemove[i];
+ let tagID = Zotero.Tags.getID(this.libraryID, tag.tag);
+ let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?";
+ yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
+ Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + tag.tag);
+ }
+ Zotero.Prefs.set('purge.tags', true);
}
+ }
+
+ // Collections
+ if (this._changed.collections) {
+ let oldCollections = this._previousData.collections;
+ let newCollections = this._collections;
- var newSourceItemNotifierData = {};
- //newSourceItemNotifierData[newSourceItem.id] = {};
- Zotero.Notifier.trigger('modify', 'item', newSourceItem.id, newSourceItemNotifierData);
+ let toAdd = Zotero.Utilities.arrayDiff(newCollections, oldCollections);
+ let toRemove = Zotero.Utilities.arrayDiff(oldCollections, newCollections);
- switch (Zotero.ItemTypes.getName(this.itemTypeID)) {
- case 'note':
- newSourceItem.incrementNoteCount();
- break;
- case 'attachment':
- newSourceItem.incrementAttachmentCount();
- break;
+ for (let i=0; i<toAdd.length; i++) {
+ let collectionID = toAdd[i];
+
+ let sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) FROM collectionItems "
+ + "WHERE collectionID=?";
+ let orderIndex = yield Zotero.DB.valueQueryAsync(sql, collectionID);
+
+ sql = "INSERT OR IGNORE INTO collectionItems "
+ + "(collectionID, itemID, orderIndex) VALUES (?, ?, ?)";
+ yield Zotero.DB.queryAsync(sql, [collectionID, this.id, orderIndex]);
+
+ Zotero.Collections.refreshChildItems(collectionID);
+ Zotero.Notifier.trigger('add', 'collection-item', collectionID + '-' + this.id);
+ }
+
+ if (toRemove.length) {
+ let sql = "DELETE FROM collectionItems WHERE itemID=? AND collectionID IN ("
+ + toRemove.join(',')
+ + ")";
+ yield Zotero.DB.queryAsync(sql, this.id);
+
+ for (let i=0; i<toRemove.length; i++) {
+ let collectionID = toRemove[i];
+ Zotero.Collections.refreshChildItems(collectionID);
+ Zotero.Notifier.trigger('remove', 'collection-item', collectionID + '-' + this.id);
+ }
}
}
+ // Related items
+ if (this._changed.relatedItems) {
+ var removed = [];
+ var newids = [];
+ var currentIDs = yield this._getRelatedItems(true);
+
+ for each(var id in currentIDs) {
+ newids.push(id);
+ }
+
+ if (newids.length) {
+ var sql = "REPLACE INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
+ var replaceStatement = Zotero.DB.getAsyncStatement(sql);
+
+ for each(var linkedItemID in newids) {
+ replaceStatement.bindInt32Parameter(0, itemID);
+ replaceStatement.bindInt32Parameter(1, linkedItemID);
+
+ yield Zotero.DB.executeAsyncStatement(replaceStatement);
+ }
+ }
+
+ Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
+ }
// Related items
if (this._changed.relatedItems) {
var removed = [];
var newids = [];
- var currentIDs = this._getRelatedItems(true);
+ var currentIDs = yield this._getRelatedItems(true);
+ for each(var id in this._previousData.related) {
+ if (currentIDs.indexOf(id) == -1) {
+ removed.push(id);
+ }
+ }
for each(var id in currentIDs) {
+ if (this._previousData.related.indexOf(id) != -1) {
+ continue;
+ }
newids.push(id);
}
+ if (removed.length) {
+ var sql = "DELETE FROM itemSeeAlso WHERE itemID=? "
+ + "AND linkedItemID IN ("
+ + removed.map(function () '?').join()
+ + ")";
+ yield Zotero.DB.queryAsync(sql, [this.id].concat(removed));
+ }
+
if (newids.length) {
var sql = "INSERT INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
- var insertStatement = Zotero.DB.getStatement(sql);
+ var insertStatement = Zotero.DB.getAsyncStatement(sql);
for each(var linkedItemID in newids) {
- insertStatement.bindInt32Parameter(0, itemID);
+ insertStatement.bindInt32Parameter(0, this.id);
insertStatement.bindInt32Parameter(1, linkedItemID);
- try {
- insertStatement.execute();
- }
- catch (e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
- }
+ yield Zotero.DB.executeAsyncStatement(insertStatement);
}
}
Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
}
- }
-
- //
- // Existing item, update
- //
- else {
- Zotero.debug('Updating database with new item data', 4);
-
- // Begin history transaction
- //Zotero.History.begin('modify-item', this.id);
-
- //
- // Primary fields
- //
- //Zotero.History.modify('items', 'itemID', this.id);
+ // Update child item counts and contents
+ if (reloadParentChildItems) {
+ for (let parentItemID in reloadParentChildItems) {
+ let parentItem = yield Zotero.Items.getAsync(parentItemID);
+ yield parentItem.reload(['primaryData', 'childItems'], true);
+ parentItem.clearBestAttachmentState();
+ }
+ }
- var sql = "UPDATE items SET ";
- var sqlValues = [];
+ // New items have to be reloaded via Zotero.Items.get(), so mark them as disabled
+ if (isNew) {
+ var id = this.id;
+ this._disabled = true;
+ return id;
+ }
- var updateFields = [
- 'itemTypeID',
- 'dateAdded',
- 'dateModified',
- 'clientDateModified',
- 'libraryID',
- 'key'
- ];
+ // Always reload primary data. DataObject.reload() only reloads changed data types, so
+ // it won't reload, say, dateModified and firstCreator if only creator data was changed
+ // and not primaryData.
+ yield this.loadPrimaryData(true);
+ yield this.reload();
+ this._clearChanged();
- for each(var field in updateFields) {
- if (this._changedPrimaryData && this._changedPrimaryData[field]) {
- sql += field + '=?, ';
- sqlValues.push(this.getField(field));
- }
- else if (field == 'dateModified' && !options.skipDateModifiedUpdate) {
- sql += field + '=?, ';
- sqlValues.push(Zotero.DB.transactionDateTime);
- }
- else if (field == 'clientDateModified' && !options.skipClientDateModifiedUpdate) {
- sql += field + '=?, ';
- sqlValues.push(Zotero.DB.transactionDateTime);
- }
- }
-
- sql = sql.substr(0, sql.length-2) + " WHERE itemID=?";
- sqlValues.push({ int: this.id });
-
- if (sqlValues.length > 1) {
- Zotero.DB.query(sql, sqlValues);
- }
-
-
- //
- // ItemData
- //
- if (this._changedItemData) {
- var del = [];
-
- sql = "SELECT valueID FROM itemDataValues WHERE value=?";
- var valueStatement = Zotero.DB.getStatement(sql);
-
- sql = "INSERT INTO itemDataValues VALUES (?,?)";
- var insertStatement = Zotero.DB.getStatement(sql);
-
- sql = "REPLACE INTO itemData VALUES (?,?,?)";
- var replaceStatement = Zotero.DB.getStatement(sql);
-
- for (fieldID in this._changedItemData) {
- var value = this.getField(fieldID, true);
-
- // If field changed and is empty, mark row for deletion
- if (!value) {
- del.push(fieldID);
- continue;
- }
-
- /*
- // Field exists
- if (this._preChangeArray[Zotero.ItemFields.getName(fieldID)]) {
- Zotero.History.modify('itemData', 'itemID-fieldID',
- [this.id, fieldID]);
- }
- // Field is new
- else {
- Zotero.History.add('itemData', 'itemID-fieldID',
- [this.id, fieldID]);
- }
- */
-
- if (Zotero.ItemFields.getID('accessDate') == fieldID
- && this.getField(fieldID) == 'CURRENT_TIMESTAMP') {
- value = Zotero.DB.transactionDateTime;
- }
-
- var dataType = Zotero.DB.getSQLDataType(value);
-
- switch (dataType) {
- case 32:
- valueStatement.bindInt32Parameter(0, value);
- break;
-
- case 64:
- valueStatement.bindInt64Parameter(0, value);
- break;
-
- default:
- valueStatement.bindUTF8StringParameter(0, value);
- }
- if (valueStatement.executeStep()) {
- var valueID = valueStatement.getInt32(0);
- }
- else {
- var valueID = null;
- }
-
- valueStatement.reset();
-
- // Create data row if necessary
- if (!valueID) {
- valueID = Zotero.ID.get('itemDataValues');
- insertStatement.bindInt32Parameter(0, valueID);
-
- // If this is changed, search.js also needs to
- // change
- switch (dataType) {
- case 32:
- insertStatement.
- bindInt32Parameter(1, value);
- break;
-
- case 64:
- insertStatement.
- bindInt64Parameter(1, value);
- break;
-
- default:
- insertStatement.
- bindUTF8StringParameter(1, value);
- }
-
- try {
- insertStatement.execute();
- }
- catch (e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
- }
- }
-
- replaceStatement.bindInt32Parameter(0, this.id);
- replaceStatement.bindInt32Parameter(1, fieldID);
- replaceStatement.bindInt32Parameter(2, valueID);
-
- try {
- replaceStatement.execute();
- }
- catch (e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
- }
- }
-
- // Delete blank fields
- if (del.length) {
- /*
- // Add to history
- for (var i in del) {
- Zotero.History.remove('itemData', 'itemID-fieldID',
- [this.id, del[i]]);
- }
- */
-
- sql = 'DELETE from itemData WHERE itemID=? '
- + 'AND fieldID IN ('
- + del.map(function () '?').join()
- + ')';
- Zotero.DB.query(sql, [this.id].concat(del));
- }
- }
-
- //
- // Creators
- //
- if (this._changedCreators) {
- for (var orderIndex in this._changedCreators) {
- Zotero.debug('Creator ' + orderIndex + ' has changed', 4);
-
- var creator = this.getCreator(orderIndex);
-
- /*
- if (!creator.ref.exists()) {
- throw ("Creator in position " + orderIndex + " doesn't exist");
- }
- */
-
- /*
- // Delete at position
- Zotero.History.remove('itemCreators', 'itemID-orderIndex',
- [this.id, orderIndex]);
- */
-
- var sql2 = 'DELETE FROM itemCreators WHERE itemID=?'
- + ' AND orderIndex=?';
- Zotero.DB.query(sql2, [{ int: this.id }, { int: orderIndex }]);
-
- if (!creator) {
- continue;
- }
-
- if (creator.ref.hasChanged()) {
- Zotero.debug("Auto-saving changed creator " + creator.ref.id);
- creator.ref.save();
- }
-
- sql = "INSERT INTO itemCreators VALUES (?,?,?,?)";
-
- sqlValues = [
- { int: this.id },
- { int: creator.ref.id },
- { int: creator.creatorTypeID },
- { int: orderIndex }
- ];
-
- Zotero.DB.query(sql, sqlValues);
-
- /*
- Zotero.History.add('itemCreators',
- 'itemID-creatorID-creatorTypeID',
- [this.id, creatorID, creator['creatorTypeID']]);
- */
- }
- }
-
-
- let parentItem = this.getSource();
- parentItem = parentItem ? Zotero.Items.get(parentItem) : null;
-
- if (this._changedDeleted) {
- if (this.deleted) {
- sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
- }
- else {
- // If undeleting, remove any merge-tracking relations
- var relations = Zotero.Relations.getByURIs(
- Zotero.URI.getItemURI(this),
- Zotero.Relations.deletedItemPredicate,
- false
- );
- for each(var relation in relations) {
- relation.erase();
- }
-
- sql = "DELETE FROM deletedItems WHERE itemID=?";
- }
- Zotero.DB.query(sql, this.id);
-
- if (parentItem) {
- parentItem.updateNumNotes();
- parentItem.updateBestAttachmentState();
- }
- }
-
-
- // Note
- if (this._changedNote) {
- if (this._noteText === null || this._noteTitle === null) {
- throw ('Cached note values not set with this._changedNote '
- + ' set to true in Item.save()');
- }
-
- var parent = this.isNote() ? this.getSource() : null;
- var noteText = this._noteText;
- // Add <div> wrapper if not present
- if (!noteText.match(/^<div class="zotero-note znv[0-9]+">[\s\S]*<\/div>$/)) {
- noteText = '<div class="zotero-note znv1">' + noteText + '</div>';
- }
-
- var sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=?";
- if (Zotero.DB.valueQuery(sql, this.id)) {
- sql = "UPDATE itemNotes SET sourceItemID=?, note=?, title=? WHERE itemID=?";
- var bindParams = [
- parent ? parent : null,
- noteText,
- this._noteTitle,
- this.id
- ];
- }
- // Row might not yet exist for new embedded attachment notes
- else {
- sql = "INSERT INTO itemNotes "
- + "(itemID, sourceItemID, note, title) VALUES (?,?,?,?)";
- var bindParams = [
- this.id,
- parent ? parent : null,
- noteText,
- this._noteTitle
- ];
- }
- Zotero.DB.query(sql, bindParams);
-
- if (parentItem) {
- // Embedded attachment notes are included in parent note count
- if (this.isAttachment()) {
- parentItem.updateNumNotes();
- }
-
- // Clear cached child notes of the parent. If the note
- // moved between parents, the old one will be cleared
- // when changing the note count below
- parentItem.clearCachedNotes();
- }
- }
-
-
- //
- // Attachment
- //
- // If attachment title changes, update parent attachments
- if (this._changedItemData[110] && this.isAttachment() && parentItem) {
- parentItem.clearCachedAttachments();
- }
- if (this._changedAttachmentData) {
- var sql = "UPDATE itemAttachments SET sourceItemID=?, "
- + "linkMode=?, mimeType=?, charsetID=?, path=?, syncState=? "
- + "WHERE itemID=?";
- let parent = this.getSource();
- var linkMode = this.attachmentLinkMode;
- var mimeType = this.attachmentMIMEType;
- var charsetID = Zotero.CharacterSets.getID(this.attachmentCharset);
- var path = this.attachmentPath;
- var syncState = this.attachmentSyncState;
-
- if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
- // Save attachment within attachment base directory as relative path
- if (Zotero.Prefs.get('saveRelativeAttachmentPath')) {
- path = Zotero.Attachments.getBaseDirectoryRelativePath(path);
- }
- // If possible, convert relative path to absolute
- else {
- let file = Zotero.Attachments.resolveRelativePath(path);
- if (file) {
- path = file.persistentDescriptor;
- }
- }
- }
-
- var bindParams = [
- parent ? parent : null,
- { int: linkMode },
- mimeType ? { string: mimeType } : null,
- charsetID ? { int: charsetID } : null,
- path ? { string: path } : null,
- syncState ? { int: syncState } : 0,
- this.id
- ];
- Zotero.DB.query(sql, bindParams);
-
- // Clear cached child attachments of the parent. If the note
- // moved between parents, the old one will be cleared
- // when changing the note count below
- if (parentItem) {
- parentItem.clearCachedAttachments();
- }
- }
-
- var notifierData = {};
- notifierData[this.id] = { changed: this._previousData };
- Zotero.Notifier.trigger('modify', 'item', this.id, notifierData);
-
- // Parent
- if (this._changedSource) {
- var type = Zotero.ItemTypes.getName(this.itemTypeID);
- var Type = type[0].toUpperCase() + type.substr(1);
-
- if (this._sourceItem) {
- if (typeof this._sourceItem == 'number') {
- var newSourceItem = Zotero.Items.get(this._sourceItem);
- }
- else {
- var newSourceItem = Zotero.Items.getByLibraryAndKey(this.libraryID, this._sourceItem);
- }
-
- if (!newSourceItem) {
- // TODO: clear caches
- var msg = "Cannot set source to invalid item " + this._sourceItem;
- var e = new Zotero.Error(msg, "MISSING_OBJECT");
- throw (e);
- }
-
- var newSourceItemNotifierData = {};
- //newSourceItemNotifierData[newSourceItem.id] = {};
- Zotero.Notifier.trigger('modify', 'item', newSourceItem.id, newSourceItemNotifierData);
- }
-
- var oldSourceItemKey = this._previousData.parentItem;
- if (oldSourceItemKey) {
- var oldSourceItem = Zotero.Items.getByLibraryAndKey(this.libraryID, oldSourceItemKey);
- if (oldSourceItem) {
- var oldSourceItemNotifierData = {};
- //oldSourceItemNotifierData[oldSourceItem.id] = {};
- Zotero.Notifier.trigger('modify', 'item', oldSourceItem.id, oldSourceItemNotifierData);
- }
- else if (oldSourceItemKey) {
- var oldSourceItemNotifierData = null;
- Zotero.debug("Old source item " + oldSourceItemKey
- + " didn't exist in Zotero.Item.save()", 2);
- }
- }
-
-
-
- // If this was an independent item, remove from any collections
- // where it existed previously and add source instead if
- // there is one
- if (!oldSourceItemKey) {
- var sql = "SELECT collectionID FROM collectionItems "
- + "WHERE itemID=?";
- var changedCollections = Zotero.DB.columnQuery(sql, this.id);
- if (changedCollections) {
- sql = "UPDATE collections SET dateModified=CURRENT_TIMESTAMP, clientDateModified=CURRENT_TIMESTAMP "
- + "WHERE collectionID IN (SELECT collectionID FROM collectionItems WHERE itemID=?)";
- Zotero.DB.query(sql, this.id);
-
- if (newSourceItem) {
- sql = "UPDATE OR REPLACE collectionItems "
- + "SET itemID=? WHERE itemID=?";
- Zotero.DB.query(sql, [newSourceItem.id, this.id]);
- }
- else {
- sql = "DELETE FROM collectionItems WHERE itemID=?";
- Zotero.DB.query(sql, this.id);
- }
-
- for each(var c in changedCollections) {
- Zotero.Notifier.trigger('remove', 'collection-item', c + '-' + this.id);
- }
-
- Zotero.Collections.reload(changedCollections);
- }
- }
-
- // Update DB, if not a note or attachment we already changed above
- if (!this._changedAttachmentData &&
- (!this._changedNote || !this.isNote())) {
- var sql = "UPDATE item" + Type + "s SET sourceItemID=? "
- + "WHERE itemID=?";
- var bindParams = [
- newSourceItem ? newSourceItem.id : null, this.id
- ];
- Zotero.DB.query(sql, bindParams);
- }
-
- // Update the counts of the previous and new sources
- if (oldSourceItem) {
- switch (type) {
- case 'note':
- oldSourceItem.decrementNoteCount();
- break;
- case 'attachment':
- oldSourceItem.decrementAttachmentCount();
- break;
- }
- }
-
- if (newSourceItem) {
- switch (type) {
- case 'note':
- newSourceItem.incrementNoteCount();
- break;
- case 'attachment':
- newSourceItem.incrementAttachmentCount();
- break;
- }
- }
- }
-
-
- // Related items
- if (this._changed.relatedItems) {
- var removed = [];
- var newids = [];
- var currentIDs = this._getRelatedItems(true);
-
- for each(var id in this._previousData.related) {
- if (currentIDs.indexOf(id) == -1) {
- removed.push(id);
- }
- }
- for each(var id in currentIDs) {
- if (this._previousData.related.indexOf(id) != -1) {
- continue;
- }
- newids.push(id);
- }
-
- if (removed.length) {
- var sql = "DELETE FROM itemSeeAlso WHERE itemID=? "
- + "AND linkedItemID IN ("
- + removed.map(function () '?').join()
- + ")";
- Zotero.DB.query(sql, [this.id].concat(removed));
- }
-
- if (newids.length) {
- var sql = "INSERT INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
- var insertStatement = Zotero.DB.getStatement(sql);
-
- for each(var linkedItemID in newids) {
- insertStatement.bindInt32Parameter(0, this.id);
- insertStatement.bindInt32Parameter(1, linkedItemID);
-
- try {
- insertStatement.execute();
- }
- catch (e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
- }
- }
- }
-
- Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
- }
- }
-
- if (!this.id) {
- this._id = itemID;
- }
-
- if (!this.key) {
- this._key = key;
+ return true;
+ }.bind(this), transactionOptions);
+ }
+ catch (e) {
+ try {
+ yield this.loadPrimaryData(true);
+ yield this.reload();
+ this._clearChanged();
}
-
- if (this._changedDeleted) {
- // Update child item counts on parent
- var sourceItemID = this.getSource();
- if (sourceItemID) {
- var sourceItem = Zotero.Items.get(sourceItemID);
- if (this._deleted) {
- if (this.isAttachment()) {
- sourceItem.decrementAttachmentCount();
- }
- else {
- sourceItem.decrementNoteCount();
- }
- }
- else {
- if (this.isAttachment()) {
- sourceItem.incrementAttachmentCount();
- }
- else {
- sourceItem.incrementNoteCount();
- }
- }
- }
- // Refresh trash
- Zotero.Notifier.trigger('refresh', 'trash', this.libraryID);
- if (this._deleted) {
- Zotero.Notifier.trigger('trash', 'item', this.id);
- }
+ catch (e2) {
+ Zotero.debug(e2, 1);
}
- Zotero.Items.reload(this.id);
-
- //Zotero.History.commit();
- Zotero.DB.commitTransaction();
+ Zotero.debug(e, 1);
+ throw e;
}
-
- catch (e) {
- //Zotero.History.cancel();
- Zotero.DB.rollbackTransaction();
- Zotero.debug(e);
- throw(e);
- }
-
- // New items have to be reloaded via Zotero.Items.get(),
- // so mark them as disabled
- if (isNew) {
- var id = this.id;
- this._disabled = true;
- return id;
- }
-
- return true;
-}
+});
/**
@@ -2218,167 +1761,33 @@ Zotero.Item.prototype.updateClientDateModified = function () {
}
-Zotero.Item.prototype.isRegularItem = function() {
- return !(this.isNote() || this.isAttachment());
-}
-
-
-Zotero.Item.prototype.isTopLevelItem = function () {
- return this.isRegularItem() || !this.getSourceKey();
-}
-
-
-Zotero.Item.prototype.numChildren = function(includeTrashed) {
- return this.numNotes(includeTrashed) + this.numAttachments(includeTrashed);
-}
-
-
-/**
- * @return {Integer|FALSE} itemID of the parent item for an attachment or note, or FALSE if none
- */
-Zotero.Item.prototype.getSource = function() {
- if (this._sourceItem === false) {
- return false;
- }
-
- if (this._sourceItem !== null) {
- if (typeof this._sourceItem == 'number') {
- return this._sourceItem;
- }
- var sourceItem = Zotero.Items.getByLibraryAndKey(this.libraryID, this._sourceItem);
- if (!sourceItem) {
- var msg = "Source item for keyed source doesn't exist in Zotero.Item.getSource() " + "(" + this._sourceItem + ")";
- var e = new Zotero.Error(msg, "MISSING_OBJECT");
- throw (e);
- }
- // Replace stored key with id
- this._sourceItem = sourceItem.id;
- return sourceItem.id;
- }
-
- if (!this.id) {
- return false;
- }
-
- if (this.isNote()) {
- var Type = 'Note';
- }
- else if (this.isAttachment()) {
- var Type = 'Attachment';
- }
- else {
- return false;
- }
-
- var sql = "SELECT sourceItemID FROM item" + Type + "s WHERE itemID=?";
- var sourceItemID = Zotero.DB.valueQuery(sql, this.id);
- if (!sourceItemID) {
- sourceItemID = false;
- }
- this._sourceItem = sourceItemID;
- return sourceItemID;
-}
-
-
+Zotero.Item.prototype.isRegularItem = function() {
+ return !(this.isNote() || this.isAttachment());
+}
+
+
+Zotero.Item.prototype.isTopLevelItem = function () {
+ return this.isRegularItem() || !this.parentKey;
+}
+
+
+Zotero.Item.prototype.numChildren = function(includeTrashed) {
+ return this.numNotes(includeTrashed) + this.numAttachments(includeTrashed);
+}
+
+
/**
* @return {String|FALSE} Key of the parent item for an attachment or note, or FALSE if none
*/
Zotero.Item.prototype.getSourceKey = function() {
- if (this._sourceItem === false) {
- return false;
- }
-
- if (this._sourceItem !== null) {
- if (typeof this._sourceItem == 'string') {
- return this._sourceItem;
- }
- var sourceItem = Zotero.Items.get(this._sourceItem);
- return sourceItem.key;
- }
-
- if (!this.id) {
- return false;
- }
-
- if (this.isNote()) {
- var Type = 'Note';
- }
- else if (this.isAttachment()) {
- var Type = 'Attachment';
- }
- else {
- return false;
- }
-
- var sql = "SELECT key FROM item" + Type + "s A JOIN items B "
- + "ON (A.sourceItemID=B.itemID) WHERE A.itemID=?";
- var key = Zotero.DB.valueQuery(sql, this.id);
- if (!key) {
- key = null;
- }
- this._sourceItem = key;
- return key;
-}
-
-
-Zotero.Item.prototype.setSource = function(sourceItemID) {
- if (this.isNote()) {
- var type = 'note';
- var Type = 'Note';
- }
- else if (this.isAttachment()) {
- var type = 'attachment';
- var Type = 'Attachment';
- }
- else {
- throw ("setSource() can only be called on items of type 'note' or 'attachment'");
- }
-
- var oldSourceItemID = this.getSource();
- if (oldSourceItemID == sourceItemID) {
- Zotero.debug("Source item has not changed for item " + this.id);
- return false;
- }
-
- this._markFieldChange('parentItem', this.getSourceKey());
- this._changedSource = true;
- this._sourceItem = sourceItemID ? parseInt(sourceItemID) : false;
-
- return true;
+ Zotero.debug("Zotero.Item.prototype.getSource() is deprecated -- use .parentKey");
+ return this._parentKey;
}
Zotero.Item.prototype.setSourceKey = function(sourceItemKey) {
- if (this.isNote()) {
- var type = 'note';
- var Type = 'Note';
- }
- else if (this.isAttachment()) {
- var type = 'attachment';
- var Type = 'Attachment';
- }
- else {
- throw ("setSourceKey() can only be called on items of type 'note' or 'attachment'");
- }
-
- var oldSourceItemID = this.getSource();
- if (oldSourceItemID) {
- var sourceItem = Zotero.Items.get(oldSourceItemID);
- var oldSourceItemKey = sourceItem.key;
- }
- else {
- var oldSourceItemKey = false;
- }
- if (oldSourceItemKey == sourceItemKey) {
- Zotero.debug("Source item has not changed in Zotero.Item.setSourceKey()");
- return false;
- }
-
- this._markFieldChange('parentItem', oldSourceItemKey);
- this._changedSource = true;
- this._sourceItem = sourceItemKey ? sourceItemKey : false;
-
- return true;
+ Zotero.debug("Zotero.Item.prototype.setSourceKey() is deprecated -- use .parentKey");
+ return this.parentKey = sourceItemKey;
}
@@ -2387,20 +1796,36 @@ Zotero.Item.prototype.setSourceKey = function(sourceItemKey) {
// Methods dealing with note items
//
////////////////////////////////////////////////////////
-Zotero.Item.prototype.incrementNoteCount = function() {
+Zotero.Item.prototype.incrementNumNotes = function () {
this._numNotes++;
- this._cachedNotes = null;
}
+Zotero.Item.prototype.incrementNumNotesTrashed = function () {
+ this._numNotesTrashed++;
+}
+
+Zotero.Item.prototype.incrementNumNotesEmbedded = function () {
+ this._numNotesEmbedded++;
+}
+
+Zotero.Item.prototype.incrementNumNotesTrashed = function () {
+ this._numNotesEmbeddedTrashed++;
+}
-Zotero.Item.prototype.decrementNoteCount = function() {
+Zotero.Item.prototype.decrementNumNotes = function () {
this._numNotes--;
- this._cachedNotes = null;
}
+Zotero.Item.prototype.decrementNumNotesTrashed = function () {
+ this._numNotesTrashed--;
+}
+
+Zotero.Item.prototype.decrementNumNotesEmbedded = function () {
+ this._numNotesEmbedded--;
+}
-Zotero.Item.prototype.clearCachedNotes = function () {
- this._cachedNotes = null;
+Zotero.Item.prototype.decrementNumNotesTrashed = function () {
+ this._numNotesEmbeddedTrashed--;
}
@@ -2433,58 +1858,24 @@ Zotero.Item.prototype.numNotes = function(includeTrashed, includeEmbedded) {
if (this.isNote()) {
throw ("numNotes() cannot be called on items of type 'note'");
}
-
- if (!this.id) {
- return 0;
+ var cacheKey = '_numNotes';
+ if (includeTrashed && includeEmbedded) {
+ return this[cacheKey] + this[cacheKey + "EmbeddedTrashed"];
}
-
- if (includeTrashed && this._numNotesTrashed === null) {
- var sql = "SELECT COUNT(*) FROM itemNotes WHERE sourceItemID=? AND "
- + "itemID IN (SELECT itemID FROM deletedItems)";
- this._numNotesTrashed = parseInt(Zotero.DB.valueQuery(sql, this.id));
+ else if (includeTrashed) {
+ return this[cacheKey] + this[cacheKey + "Trashed"];
}
- var embedded = 0;
- if (includeEmbedded) {
- if ((includeTrashed ? this._numNotesEmbeddedIncludingTrashed : this._numNotesEmbedded) === null) {
- var sql = "SELECT COUNT(*) FROM itemAttachments IA JOIN itemNotes USING (itemID) WHERE ";
- // For attachments, include their own embedded notes
- if (this.isAttachment()) {
- sql += "IA.itemID=?";
- }
- else {
- sql += "IA.sourceItemID=?";
- }
- sql += " AND note!='' AND note!=?";
- if (!includeTrashed) {
- sql += " AND itemID NOT IN (SELECT itemID FROM deletedItems)";
- }
- var embedded = parseInt(Zotero.DB.valueQuery(sql, [this.id, Zotero.Notes.defaultNote]));
- if (includeTrashed) {
- this._numNotesEmbeddedIncludingTrashed = embedded;
- }
- else {
- this._numNotesEmbedded = embedded;
- }
- }
+ else if (includeEmbedded) {
+ return this[cacheKey] + this[cacheKey + "Embedded"];
}
-
- return this._numNotes
- + (includeTrashed ? this._numNotesTrashed : 0)
- + (includeTrashed ? this._numNotesEmbeddedIncludingTrashed : this._numNotesEmbedded);
-}
-
-
-Zotero.Item.prototype.updateNumNotes = function () {
- this._numNotesTrashed = null;
- this._numNotesEmbedded = null;
- this._numNotesEmbeddedIncludingTrashed = null;
+ return this[cacheKey];
}
/**
* Get the first line of the note for display in the items list
*
- * Note: Note titles can also come from Zotero.Items.cacheFields()!
+ * Note titles are loaded in loadItemData(), but can also come from Zotero.Items.cacheFields()
*
* @return {String}
*/
@@ -2492,25 +1883,15 @@ Zotero.Item.prototype.getNoteTitle = function() {
if (!this.isNote() && !this.isAttachment()) {
throw ("getNoteTitle() can only be called on notes and attachments");
}
-
if (this._noteTitle !== null) {
return this._noteTitle;
}
-
- if (!this.id) {
- return '';
- }
-
- var sql = "SELECT title FROM itemNotes WHERE itemID=?";
- var title = Zotero.DB.valueQuery(sql, this.id);
-
- this._noteTitle = title ? title : '';
-
- return title ? title : '';
-}
+ this._requireData('itemData');
+ return "";
+};
-Zotero.Item.prototype.hasNote = function () {
+Zotero.Item.prototype.hasNote = Zotero.Promise.coroutine(function* () {
if (!this.isNote() && !this.isAttachment()) {
throw new Error("hasNote() can only be called on notes and attachments");
}
@@ -2519,17 +1900,17 @@ Zotero.Item.prototype.hasNote = function () {
return this._hasNote;
}
- if (!this.id) {
+ if (!this._id) {
return false;
}
var sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=? "
+ "AND note!='' AND note!=?";
- var hasNote = !!Zotero.DB.valueQuery(sql, [this.id, Zotero.Notes.defaultNote]);
+ var hasNote = !!(yield Zotero.DB.valueQueryAsync(sql, [this._id, Zotero.Notes.defaultNote]));
this._hasNote = hasNote;
return hasNote;
-}
+});
/**
@@ -2547,35 +1928,8 @@ Zotero.Item.prototype.getNote = function() {
return this._noteText;
}
- if (!this.id) {
- return '';
- }
-
- var sql = "SELECT note FROM itemNotes WHERE itemID=?";
- var note = Zotero.DB.valueQuery(sql, this.id);
-
- // Convert non-HTML notes on-the-fly
- if (note) {
- if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
- note = Zotero.Utilities.htmlSpecialChars(note);
- note = Zotero.Notes.notePrefix + '<p>'
- + note.replace(/\n/g, '</p><p>')
- .replace(/\t/g, ' ')
- .replace(/ /g, ' ')
- + '</p>' + Zotero.Notes.noteSuffix;
- note = note.replace(/<p>\s*<\/p>/g, '<p> </p>');
- var sql = "UPDATE itemNotes SET note=? WHERE itemID=?";
- Zotero.DB.query(sql, [note, this.id]);
- }
-
- // Don't include <div> wrapper when returning value
- var startLen = note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)[0].length;
- var endLen = 6; // "</div>".length
- note = note.substr(startLen, note.length - startLen - endLen);
- }
-
- this._noteText = note ? note : '';
- return this._noteText;
+ this._requireData('note');
+ return "";
}
@@ -2599,17 +1953,18 @@ Zotero.Item.prototype.setNote = function(text) {
.trim();
var oldText = this.getNote();
- if (text == oldText) {
- Zotero.debug("Note has not changed in Zotero.Item.setNote()");
+ if (text === oldText) {
+ Zotero.debug("Note hasn't changed", 4);
return false;
}
this._hasNote = text !== '';
this._noteText = text;
this._noteTitle = Zotero.Notes.noteToTitle(text);
+ this._displayTitle = this._noteTitle;
this._markFieldChange('note', oldText);
- this._changedNote = true;
+ this._changed.note = true;
return true;
}
@@ -2619,73 +1974,44 @@ Zotero.Item.prototype.setNote = function(text) {
* Returns child notes of this item
*
* @param {Boolean} includeTrashed Include trashed child items
+ * @param {Boolean} includeEmbedded Include embedded attachment notes
* @return {Integer[]} Array of itemIDs
*/
Zotero.Item.prototype.getNotes = function(includeTrashed) {
if (this.isNote()) {
- throw ("getNotes() cannot be called on items of type 'note'");
+ throw new Error("getNotes() cannot be called on items of type 'note'");
}
- if (!this.id) {
- return [];
- }
+ this._requireData('childItems');
- // Get the right cache array
- if (!this._cachedNotes) {
- this._cachedNotes = {
- chronologicalWithTrashed: null,
- chronologicalWithoutTrashed: null,
- alphabeticalWithTrashed: null,
- alphabeticalWithoutTrashed: null
- };
- }
- var cache = this._cachedNotes;
- var cacheKey = (Zotero.Prefs.get('sortNotesChronologically')
- ? 'chronological' : 'alphabetical')
+ var sortChronologically = Zotero.Prefs.get('sortNotesChronologically');
+ var cacheKey = (sortChronologically ? "chronological" : "alphabetical")
+ 'With' + (includeTrashed ? '' : 'out') + 'Trashed';
- if (cache[cacheKey] !== null) {
- return cache[cacheKey];
- }
-
- var sql = "SELECT N.itemID, title FROM itemNotes N NATURAL JOIN items "
- + "WHERE sourceItemID=?";
- if (!includeTrashed) {
- sql += " AND N.itemID NOT IN (SELECT itemID FROM deletedItems)";
- }
-
- if (Zotero.Prefs.get('sortNotesChronologically')) {
- sql += " ORDER BY dateAdded";
- var results = Zotero.DB.columnQuery(sql, this.id);
- results = results ? results : [];
- cache[cacheKey] = results;
- return results;
- }
-
- var notes = Zotero.DB.query(sql, this.id);
- if (!notes) {
- cache[cacheKey] = [];
- return [];
- }
- // Sort by title
- var collation = Zotero.getLocaleCollation();
- var f = function (a, b) {
- var aTitle = Zotero.Items.getSortTitle(a.title);
- var bTitle = Zotero.Items.getSortTitle(b.title);
- return collation.compareString(1, aTitle, bTitle);
+ if (this._notes[cacheKey]) {
+ return this._notes[cacheKey];
}
- var noteIDs = [];
- notes.sort(f);
- for each(var note in notes) {
- noteIDs.push(note.itemID);
+ var rows = this._notes.rows.concat();
+ // Remove trashed items if necessary
+ if (!includeTrashed) {
+ rows = rows.filter(function (row) !row.trashed);
+ }
+ // Sort by title if necessary
+ if (!sortChronologically) {
+ var collation = Zotero.getLocaleCollation();
+ rows.sort(function (a, b) {
+ var aTitle = Zotero.Items.getSortTitle(a.title);
+ var bTitle = Zotero.Items.getSortTitle(b.title);
+ return collation.compareString(1, aTitle, bTitle);
+ });
}
- cache[cacheKey] = noteIDs;
- return noteIDs;
+ var ids = [row.itemID for (row of rows)];
+ this._notes[cacheKey] = ids;
+ return ids;
}
-
////////////////////////////////////////////////////////
//
// Methods dealing with attachments
@@ -2693,23 +2019,6 @@ Zotero.Item.prototype.getNotes = function(includeTrashed) {
// save() is not required for attachment functions
//
///////////////////////////////////////////////////////
-Zotero.Item.prototype.incrementAttachmentCount = function() {
- this._numAttachments++;
- this._cachedAttachments = null;
-}
-
-
-Zotero.Item.prototype.decrementAttachmentCount = function() {
- this._numAttachments--;
- this._cachedAttachments = null;
-}
-
-
-Zotero.Item.prototype.clearCachedAttachments = function () {
- this._cachedAttachments = null;
-}
-
-
/**
* Determine if an item is an attachment
**/
@@ -2718,6 +2027,9 @@ Zotero.Item.prototype.isAttachment = function() {
}
+/**
+ * @return {Promise<Boolean>}
+ */
Zotero.Item.prototype.isImportedAttachment = function() {
if (!this.isAttachment()) {
return false;
@@ -2730,6 +2042,9 @@ Zotero.Item.prototype.isImportedAttachment = function() {
}
+/**
+ * @return {Promise<Boolean>}
+ */
Zotero.Item.prototype.isWebAttachment = function() {
if (!this.isAttachment()) {
return false;
@@ -2742,6 +2057,9 @@ Zotero.Item.prototype.isWebAttachment = function() {
}
+/**
+ * @return {Promise<Boolean>}
+ */
Zotero.Item.prototype.isFileAttachment = function() {
if (!this.isAttachment()) {
return false;
@@ -2754,70 +2072,58 @@ Zotero.Item.prototype.isFileAttachment = function() {
* Returns number of child attachments of item
*
* @param {Boolean} includeTrashed Include trashed child items in count
- * @return {Integer}
+ * @return <Integer>
*/
Zotero.Item.prototype.numAttachments = function(includeTrashed) {
if (this.isAttachment()) {
throw ("numAttachments() cannot be called on attachment items");
}
- if (!this.id) {
- return 0;
- }
-
- var deleted = 0;
+ var cacheKey = '_numAttachments';
if (includeTrashed) {
- var sql = "SELECT COUNT(*) FROM itemAttachments WHERE sourceItemID=? AND "
- + "itemID IN (SELECT itemID FROM deletedItems)";
- deleted = parseInt(Zotero.DB.valueQuery(sql, this.id));
+ return this[cacheKey] + this[cacheKey + "Trashed"];
}
-
- return this._numAttachments + deleted;
+ return this[cacheKey];
}
/**
-* Get an nsILocalFile for the attachment, or false if the associated file
-* doesn't exist
-*
-* _row_ is optional itemAttachments row if available to skip queries
-*
-* Note: Always returns false for items with LINK_MODE_LINKED_URL,
-* since they have no files -- use getField('url') instead
-**/
-Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
- if (!this.isAttachment()) {
- throw ("getFile() can only be called on attachment items");
+ * Get an nsILocalFile for the attachment, or false if it doesn't exist
+ *
+ * @return {nsILocalFile|false} An nsIFile, or false for invalid paths
+ */
+Zotero.Item.prototype.getFile = function (skipExistsCheck) {
+ if (arguments.length > 1) {
+ Zotero.debug("WARNING: Zotero.Item.prototype.getFile() no longer takes two parameters");
}
- if (!row) {
- var row = {
- linkMode: this.attachmentLinkMode,
- path: this.attachmentPath
- };
+ if (!this.isAttachment()) {
+ throw new Error("getFile() can only be called on attachment items");
}
+ var linkMode = this.attachmentLinkMode;
+ var path = this.attachmentPath;
+
// No associated files for linked URLs
- if (row.linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
return false;
}
- if (!row.path) {
+ if (!path) {
Zotero.debug("Attachment path is empty", 2);
- this._updateAttachmentStates(false);
return false;
}
// Imported file with relative path
- if (row.linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
- row.linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
+ if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
+ linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
try {
- if (row.path.indexOf("storage:") == -1) {
- Zotero.debug("Invalid attachment path '" + row.path + "'", 2);
+ if (path.indexOf("storage:") == -1) {
+ Zotero.debug("Invalid attachment path '" + path + "'", 2);
throw ('Invalid path');
}
// Strip "storage:"
- var path = row.path.substr(8);
+ path = path.substr(8);
// setRelativeDescriptor() silently uses the parent directory on Windows
// if the filename contains certain characters, so strip them —
// but don't skip characters outside of XML range, since they may be
@@ -2831,11 +2137,13 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
if (Zotero.isWin) {
path = Zotero.File.getValidFileName(path, true);
}
- var file = Zotero.Attachments.getStorageDirectory(this.id);
+ var file = Zotero.Attachments.getStorageDirectory(this);
file.QueryInterface(Components.interfaces.nsILocalFile);
file.setRelativeDescriptor(file, path);
}
catch (e) {
+ Zotero.debug(e);
+
// See if this is a persistent path
// (deprecated for imported attachments)
Zotero.debug('Trying as persistent descriptor');
@@ -2844,26 +2152,135 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
var file = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
file.persistentDescriptor = row.path;
+ }
+ catch (e) {
+ Zotero.debug('Invalid persistent descriptor', 2);
+ return false;
+ }
+ }
+ }
+ // Linked file with relative path
+ else if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
+ path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
+ var file = Zotero.Attachments.resolveRelativePath(path);
+ }
+ else {
+ var file = Components.classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+
+ try {
+ file.persistentDescriptor = row.path;
+ }
+ catch (e) {
+ // See if this is an old relative path (deprecated)
+ Zotero.debug('Invalid persistent descriptor -- trying relative');
+ try {
+ var refDir = (row.linkMode == this.LINK_MODE_LINKED_FILE)
+ ? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory();
+ file.setRelativeDescriptor(refDir, row.path);
+ }
+ catch (e) {
+ Zotero.debug('Invalid relative descriptor', 2);
+ return false;
+ }
+ }
+ }
+
+ if (!skipExistsCheck && !file.exists()) {
+ return false;
+ }
+
+ return file;
+};
+
+
+/**
+ * Get the absolute file path for the attachment, or false if the associated file doesn't exist
+ *
+ * @return {Promise<string|false>} - A promise for either the absolute file path of the attachment
+ * or false for invalid paths or if the file doesn't exist
+ */
+Zotero.Item.prototype.getFilePath = Zotero.Promise.coroutine(function* (skipExistsCheck) {
+ if (!this.isAttachment()) {
+ throw new Error("getFilePath() can only be called on attachment items");
+ }
+
+ var linkMode = this.attachmentLinkMode;
+ var path = this.attachmentPath;
+
+ // No associated files for linked URLs
+ if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ return false;
+ }
+
+ if (!path) {
+ Zotero.debug("Attachment path is empty", 2);
+ if (!skipExistsCheck) {
+ yield this._updateAttachmentStates(false);
+ }
+ return false;
+ }
+
+ // Imported file with relative path
+ if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
+ linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
+ try {
+ if (path.indexOf("storage:") == -1) {
+ Zotero.debug("Invalid attachment path '" + path + "'", 2);
+ throw ('Invalid path');
+ }
+ // Strip "storage:"
+ var path = path.substr(8);
+ // setRelativeDescriptor() silently uses the parent directory on Windows
+ // if the filename contains certain characters, so strip them —
+ // but don't skip characters outside of XML range, since they may be
+ // correct in the opaque relative descriptor string
+ //
+ // This is a bad place for this, since the change doesn't make it
+ // back up to the sync server, but we do it to make sure we don't
+ // accidentally use the parent dir. Syncing to OS X, which doesn't
+ // exhibit this bug, will properly correct such filenames in
+ // storage.js and propagate the change
+ if (Zotero.isWin) {
+ path = Zotero.File.getValidFileName(path, true);
+ }
+ var file = Zotero.Attachments.getStorageDirectory(this);
+ file.QueryInterface(Components.interfaces.nsILocalFile);
+ file.setRelativeDescriptor(file, path);
+ }
+ catch (e) {
+ Zotero.debug(e);
+
+ // See if this is a persistent path
+ // (deprecated for imported attachments)
+ Zotero.debug('Trying as persistent descriptor');
+
+ try {
+ var file = Components.classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+ file.persistentDescriptor = path;
// If valid, convert this to a relative descriptor
- if (file.exists()) {
- Zotero.DB.query("UPDATE itemAttachments SET path=? WHERE itemID=?",
- ["storage:" + file.leafName, this.id]);
+ if (!skipExistsCheck && file.exists()) {
+ yield Zotero.DB.queryAsync("UPDATE itemAttachments SET path=? WHERE itemID=?",
+ ["storage:" + file.leafName, this._id]);
}
}
catch (e) {
Zotero.debug('Invalid persistent descriptor', 2);
- this._updateAttachmentStates(false);
+ if (!skipExistsCheck) {
+ yield this._updateAttachmentStates(false);
+ }
return false;
}
}
}
// Linked file with relative path
- else if (row.linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
- row.path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
- var file = Zotero.Attachments.resolveRelativePath(row.path);
- if (!file) {
- this._updateAttachmentStates(false);
+ else if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE &&
+ path.indexOf(Zotero.Attachments.BASE_PATH_PLACEHOLDER) == 0) {
+ var file = Zotero.Attachments.resolveRelativePath(path);
+ if (!skipExistsCheck && !file) {
+ yield this._updateAttachmentStates(false);
return false;
}
}
@@ -2872,44 +2289,50 @@ Zotero.Item.prototype.getFile = function(row, skipExistsCheck) {
createInstance(Components.interfaces.nsILocalFile);
try {
- file.persistentDescriptor = row.path;
+ file.persistentDescriptor = path;
}
catch (e) {
// See if this is an old relative path (deprecated)
Zotero.debug('Invalid persistent descriptor -- trying relative');
try {
- var refDir = (row.linkMode == this.LINK_MODE_LINKED_FILE)
+ var refDir = (linkMode == this.LINK_MODE_LINKED_FILE)
? Zotero.getZoteroDirectory() : Zotero.getStorageDirectory();
- file.setRelativeDescriptor(refDir, row.path);
+ file.setRelativeDescriptor(refDir, path);
// If valid, convert this to a persistent descriptor
- if (file.exists()) {
- Zotero.DB.query("UPDATE itemAttachments SET path=? WHERE itemID=?",
- [file.persistentDescriptor, this.id]);
+ if (!skipExistsCheck && file.exists()) {
+ yield Zotero.DB.queryAsync("UPDATE itemAttachments SET path=? WHERE itemID=?",
+ [file.persistentDescriptor, this._id]);
}
}
catch (e) {
Zotero.debug('Invalid relative descriptor', 2);
- this._updateAttachmentStates(false);
+ if (!skipExistsCheck) {
+ yield this._updateAttachmentStates(false);
+ }
return false;
}
}
}
- if (!skipExistsCheck && !file.exists()) {
- Zotero.debug("Attachment file '" + file.path + "' not found", 2);
- this._updateAttachmentStates(false);
+ path = file.path;
+
+ if (!skipExistsCheck && !(yield OS.File.exists(path))) {
+ Zotero.debug("Attachment file '" + path + "' not found", 2);
+ yield this._updateAttachmentStates(false);
return false;
}
- this._updateAttachmentStates(true);
- return file;
-}
+ if (!skipExistsCheck) {
+ yield this._updateAttachmentStates(true);
+ }
+ return path;
+});
/**
* Update file existence state of this item and best attachment state of parent item
*/
-Zotero.Item.prototype._updateAttachmentStates = function (exists) {
+Zotero.Item.prototype._updateAttachmentStates = Zotero.Promise.coroutine(function* (exists) {
this._fileExists = exists;
if (this.isTopLevelItem()) {
@@ -2917,7 +2340,7 @@ Zotero.Item.prototype._updateAttachmentStates = function (exists) {
}
try {
- var parentKey = this.getSource();
+ var parentKey = this.parentKey;
}
// This can happen during classic sync conflict resolution, if a
// standalone attachment was modified locally and remotely was changed
@@ -2928,69 +2351,59 @@ Zotero.Item.prototype._updateAttachmentStates = function (exists) {
return;
}
- Zotero.Items.get(parentKey).updateBestAttachmentState();
-}
+ var item = yield Zotero.Items.getByLibraryAndKey(this.libraryID, parentKey);
+ item.clearBestAttachmentState();
+});
Zotero.Item.prototype.getFilename = function () {
if (!this.isAttachment()) {
- throw ("getFileName() can only be called on attachment items in Zotero.Item.getFilename()");
+ throw new Error("getFileName() can only be called on attachment items");
}
if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
- throw ("getFilename() cannot be called on link attachments in Zotero.Item.getFilename()");
+ throw new Error("getFilename() cannot be called on link attachments");
}
- var file = this.getFile(null, true);
+ var file = this.getFile();
+ // Invalid path
if (!file) {
- return false;
+ return '';
}
-
return file.leafName;
}
/**
- * Cached check for file existence, used for items view
+ * Asynchronous cached check for file existence, used for items view
*
- * This is updated only initially and on subsequent getFile() calls.
+ * This is updated only initially and on subsequent getFilePath() calls.
*/
-Zotero.Item.prototype.fileExists = function (cachedOnly) {
- if (!cachedOnly && this._fileExists === null) {
- this.getFile();
+Zotero.Item.prototype.fileExists = Zotero.Promise.coroutine(function* () {
+ if (this._fileExists !== null) {
+ return this._fileExists;
}
- return this._fileExists;
-};
+
+ if (!this.isAttachment()) {
+ throw new Error("Zotero.Item.fileExists() can only be called on attachment items");
+ }
+
+ if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ throw new Error("Zotero.Item.fileExists() cannot be called on link attachments");
+ }
+
+ var exists = !!(yield this.getFilePath());
+ yield this._updateAttachmentStates(exists);
+ return exists;
+});
/**
- * Asynchronous cached check for file existence, used for items view
- *
- * This is updated only initially and on subsequent getFile() calls.
+ * Synchronous cached check for file existence, used for items view
*/
-Zotero.Item.prototype.fileExistsAsync = function () {
- var self = this;
- return Q.fcall(function () {
- if (self._fileExists !== null) {
- return self._fileExists;
- }
-
- if (!self.isAttachment()) {
- throw new Error("Zotero.Item.fileExistsAsync() can only be called on attachment items");
- }
-
- if (self.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
- throw new Error("Zotero.Item.fileExistsAsync() cannot be called on link attachments");
- }
-
- var nsIFile = self.getFile(null, true);
- return Q(OS.File.exists(nsIFile.path))
- .then(function(exists) {
- self._updateAttachmentStates(exists);
- return exists;
- });
- });
-};
+Zotero.Item.prototype.fileExistsCached = function () {
+ return this._fileExists;
+}
@@ -3001,98 +2414,110 @@ Zotero.Item.prototype.fileExistsAsync = function () {
* -2 Error renaming
* false Attachment file not found
*/
-Zotero.Item.prototype.renameAttachmentFile = function(newName, overwrite) {
- var file = this.getFile();
- if (!file) {
+Zotero.Item.prototype.renameAttachmentFile = Zotero.Promise.coroutine(function* (newName, overwrite) {
+ var origPath = yield this.getFilePath();
+ if (!origPath) {
Zotero.debug("Attachment file not found in renameAttachmentFile()", 2);
return false;
}
- var origModDate = file.lastModifiedTime;
try {
+ var origName = OS.Path.basename(origPath);
+ var origModDate = yield OS.File.stat(origPath).lastModificationDate;
+
newName = Zotero.File.getValidFileName(newName);
- var dest = file.parent;
- dest.append(newName);
+ var destPath = OS.Path.join(OS.Path.dirname(origPath), newName);
+ var destName = OS.Path.basename(destPath);
// Ignore if no change
//
- // Note: Just comparing file.leafName to newName isn't reliable
- if (file.leafName === dest.leafName) {
+ // Note: Just comparing origName to newName isn't reliable
+ if (origFileName === destName) {
return true;
}
- // If old and new names differ only in case, let's bank on this
- // just being a case change and not bother checking for existing
- // files, since dest.exists() will just show true on a case-insensitive
- // filesystem anyway.
- if (file.leafName.toLowerCase() != dest.leafName.toLowerCase()) {
- if (!overwrite && dest.exists()) {
- return -1;
- }
- }
-
// Update mod time and clear hash so the file syncs
// TODO: use an integer counter instead of mod time for change detection
// Update mod time first, because it may fail for read-only files on Windows
- file.lastModifiedTime = new Date();
- file.moveTo(null, newName);
-
- this.relinkAttachmentFile(dest);
-
- Zotero.DB.beginTransaction();
+ yield OS.File.setDates(origPath, null, null);
+ var result = yield OS.File.move(origPath, destPath, { noOverwrite: !overwrite })
+ // If no overwriting and file exists, return -1
+ .catch(OS.File.Error, function (e) {
+ if (e.becauseExists) {
+ return -1;
+ }
+ throw e;
+ });
+ if (result) {
+ return result;
+ }
- Zotero.Sync.Storage.setSyncedHash(this.id, null, false);
- Zotero.Sync.Storage.setSyncState(this.id, Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD);
+ yield this.relinkAttachmentFile(destPath);
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ Zotero.Sync.Storage.setSyncedHash(this.id, null, false);
+ Zotero.Sync.Storage.setSyncState(this.id, Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD);
+ }.bind(this));
return true;
}
catch (e) {
// Restore original modification date in case we managed to change it
- try { file.lastModifiedTime = origModDate } catch (e) {}
+ try {
+ OS.File.setDates(origPath, null, origModDate);
+ } catch (e) {
+ Zotero.debug(e, 2);
+ }
Zotero.debug(e);
Components.utils.reportError(e);
return -2;
}
-}
+});
/**
+ * @param {string} path File path
* @param {Boolean} [skipItemUpdate] Don't update attachment item mod time,
* so that item doesn't sync. Used when a file
* needs to be renamed to be accessible but the
* user doesn't have access to modify the
* attachment metadata
*/
-Zotero.Item.prototype.relinkAttachmentFile = function(file, skipItemUpdate) {
+Zotero.Item.prototype.relinkAttachmentFile = Zotero.Promise.coroutine(function* (path, skipItemUpdate) {
+ if (path instanceof Components.interfaces.nsIFile) {
+ Zotero.debug("WARNING: Zotero.Item.prototype.relinkAttachmentFile() now takes an absolute "
+ + "file path instead of an nsIFile");
+ path = path.path;
+ }
+
var linkMode = this.attachmentLinkMode;
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
- throw('Cannot relink linked URL in Zotero.Items.relinkAttachmentFile()');
+ throw new Error('Cannot relink linked URL');
}
- var newName = Zotero.File.getValidFileName(file.leafName);
+ var fileName = OS.Path.basename(path);
+
+ var newName = Zotero.File.getValidFileName(fileName);
if (!newName) {
- throw ("No valid characters in filename after filtering in Zotero.Item.relinkAttachmentFile()");
+ throw new Error("No valid characters in filename after filtering");
}
// Rename file to filtered name if necessary
- if (file.leafName != newName) {
- Zotero.debug("Renaming file '" + file.leafName + "' to '" + newName + "'");
- file.moveTo(null, newName);
+ if (fileName != newName) {
+ Zotero.debug("Renaming file '" + fileName + "' to '" + newName + "'");
+ OS.File.move(path, OS.Path.join(OS.Path.dirname(path), newName), { noOverwrite: true });
}
- var path = Zotero.Attachments.getPath(file, linkMode);
- this.attachmentPath = path;
+ this.attachmentPath = Zotero.Attachments.getPath(file, linkMode);
- this.save({
+ yield this.save({
skipDateModifiedUpdate: true,
skipClientDateModifiedUpdate: skipItemUpdate
});
return false;
-}
+});
@@ -3130,19 +2555,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentLinkMode', function () {
if (!this.isAttachment()) {
return undefined;
}
-
- if (this._attachmentLinkMode !== null) {
- return this._attachmentLinkMode;
- }
-
- if (!this.id) {
- return null;
- }
-
- var sql = "SELECT linkMode FROM itemAttachments WHERE itemID=?";
- var linkMode = Zotero.DB.valueQuery(sql, this.id);
- this._attachmentLinkMode = linkMode;
- return linkMode;
+ return this._attachmentLinkMode;
});
@@ -3166,63 +2579,53 @@ Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
if (val === this.attachmentLinkMode) {
return;
}
- if (!this._changedAttachmentData) {
- this._changedAttachmentData = {};
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
}
- this._changedAttachmentData.linkMode = true;
+ this._changed.attachmentData.linkMode = true;
this._attachmentLinkMode = val;
});
Zotero.Item.prototype.getAttachmentMIMEType = function() {
- Zotero.debug("getAttachmentMIMEType() deprecated -- use .attachmentMIMEType");
- return this.attachmentMIMEType;
-}
+ Zotero.debug("getAttachmentMIMEType() deprecated -- use .attachmentContentType");
+ return this.attachmentContentType;
+};
+
+Zotero.Item.prototype.__defineGetter__('attachmentMIMEType', function () {
+ Zotero.debug(".attachmentMIMEType deprecated -- use .attachmentContentType");
+ return this.attachmentContentType;
+});
/**
- * MIME type of an attachment (e.g. 'text/plain')
+ * Content type of an attachment (e.g. 'text/plain')
*/
-Zotero.Item.prototype.__defineGetter__('attachmentMIMEType', function () {
+Zotero.Item.prototype.__defineGetter__('attachmentContentType', function () {
if (!this.isAttachment()) {
return undefined;
}
-
- if (this._attachmentMIMEType !== null) {
- return this._attachmentMIMEType;
- }
-
- if (!this.id) {
- return '';
- }
-
- var sql = "SELECT mimeType FROM itemAttachments WHERE itemID=?";
- var mimeType = Zotero.DB.valueQuery(sql, this.id);
- if (!mimeType) {
- mimeType = '';
- }
- this._attachmentMIMEType = mimeType;
- return mimeType;
+ return this._attachmentContentType;
});
-Zotero.Item.prototype.__defineSetter__('attachmentMIMEType', function (val) {
+Zotero.Item.prototype.__defineSetter__('attachmentContentType', function (val) {
if (!this.isAttachment()) {
- throw (".attachmentMIMEType can only be set for attachment items");
+ throw (".attachmentContentType can only be set for attachment items");
}
if (!val) {
val = '';
}
- if (val == this.attachmentMIMEType) {
+ if (val == this.attachmentContentType) {
return;
}
- if (!this._changedAttachmentData) {
- this._changedAttachmentData = {};
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
}
- this._changedAttachmentData.mimeType = true;
- this._attachmentMIMEType = val;
+ this._changed.attachmentData.contentType = true;
+ this._attachmentContentType = val;
});
@@ -3239,22 +2642,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentCharset', function () {
if (!this.isAttachment()) {
return undefined;
}
-
- if (this._attachmentCharset !== undefined) {
- return Zotero.CharacterSets.getName(this._attachmentCharset);
- }
-
- if (!this.id) {
- return null;
- }
-
- var sql = "SELECT charsetID FROM itemAttachments WHERE itemID=?";
- var charset = Zotero.DB.valueQuery(sql, this.id);
- if (!charset) {
- charset = null;
- }
- this._attachmentCharset = charset;
- return Zotero.CharacterSets.getName(charset);
+ return this._attachmentCharset
});
@@ -3282,10 +2670,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentCharset', function (val) {
return;
}
- if (!this._changedAttachmentData) {
- this._changedAttachmentData = {};
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData= {};
}
- this._changedAttachmentData.charset = true;
+ this._changed.attachmentData.charset = true;
this._attachmentCharset = val;
});
@@ -3294,22 +2682,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentPath', function () {
if (!this.isAttachment()) {
return undefined;
}
-
- if (this._attachmentPath !== null) {
- return this._attachmentPath;
- }
-
- if (!this.id) {
- return '';
- }
-
- var sql = "SELECT path FROM itemAttachments WHERE itemID=?";
- var path = Zotero.DB.valueQuery(sql, this.id);
- if (!path) {
- path = '';
- }
- this._attachmentPath = path;
- return path;
+ return this._attachmentPath;
});
@@ -3330,10 +2703,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentPath', function (val) {
return;
}
- if (!this._changedAttachmentData) {
- this._changedAttachmentData = {};
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
}
- this._changedAttachmentData.path = true;
+ this._changed.attachmentData.path = true;
this._attachmentPath = val;
});
@@ -3345,10 +2718,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentPath', function (val) {
* path handling is done on item save.
*/
Zotero.Item.prototype.updateAttachmentPath = function () {
- if (!this._changedAttachmentData) {
- this._changedAttachmentData = {};
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
}
- this._changedAttachmentData.path = true;
+ this._changed.attachmentData.path = true;
this.save({
skipDateModifiedUpdate: true
});
@@ -3359,19 +2732,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentSyncState', function () {
if (!this.isAttachment()) {
return undefined;
}
-
- if (this._attachmentSyncState != undefined) {
- return this._attachmentSyncState;
- }
-
- if (!this.id) {
- return undefined;
- }
-
- var sql = "SELECT syncState FROM itemAttachments WHERE itemID=?";
- var syncState = Zotero.DB.valueQuery(sql, this.id);
- this._attachmentSyncState = syncState;
- return syncState;
+ return this._attachmentSyncState;
});
@@ -3407,10 +2768,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) {
return;
}
- if (!this._changedAttachmentData) {
- this._changedAttachmentData = {};
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
}
- this._changedAttachmentData.syncState = true;
+ this._changed.attachmentData.syncState = true;
this._attachmentSyncState = val;
});
@@ -3421,9 +2782,10 @@ Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) {
* Note: This is the mod time of the file itself, not the last-known mod time
* of the file on the storage server as stored in the database
*
- * @return {Number} File modification time as timestamp in milliseconds
+ * @return {Promise<Number|undefined>} File modification time as timestamp in milliseconds,
+ * or undefined if no file
*/
-Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', function () {
+Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', Zotero.Promise.coroutine(function* () {
if (!this.isAttachment()) {
return undefined;
}
@@ -3432,12 +2794,12 @@ Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', function ()
return undefined;
}
- var file = this.getFile();
- if (!file) {
+ var path = yield this.getFilePath();
+ if (!path) {
return undefined;
}
- var fmtime = file.lastModifiedTime;
+ var fmtime = OS.File.stat(path).lastModificationDate;
if (fmtime < 1) {
Zotero.debug("File mod time " + fmtime + " is less than 1 -- interpreting as 1", 2);
@@ -3445,7 +2807,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', function ()
}
return fmtime;
-});
+}));
/**
@@ -3481,9 +2843,9 @@ Zotero.Item.prototype.__defineGetter__('attachmentHash', function () {
* - Paragraph breaks will be lost in PDF content
* - For PDFs, will return empty string if Zotero.Fulltext.pdfConverterIsRegistered() is false
*
- * @return {String} Attachment text, or empty string if unavailable
+ * @return {Promise<String>} - A promise for attachment text or empty string if unavailable
*/
-Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
+Zotero.Item.prototype.__defineGetter__('attachmentText', Zotero.Promise.coroutine(function* () {
if (!this.isAttachment()) {
return undefined;
}
@@ -3493,27 +2855,27 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
}
var file = this.getFile();
- var cacheFile = Zotero.Fulltext.getItemCacheFile(this.id);
+ var cacheFile = Zotero.Fulltext.getItemCacheFile(this);
if (!file) {
if (cacheFile.exists()) {
- var str = Zotero.File.getContents(cacheFile);
+ var str = yield Zotero.File.getContentsAsync(cacheFile);
return str.trim();
}
return '';
}
- var mimeType = this.attachmentMIMEType;
- if (!mimeType) {
- mimeType = Zotero.MIME.getMIMETypeFromFile(file);
- if (mimeType) {
- this.attachmentMIMEType = mimeType;
- this.save();
+ var contentType = this.attachmentContentType;
+ if (!contentType) {
+ contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
+ if (contentType) {
+ this.attachmentContentType = contentType;
+ yield this.save();
}
}
var str;
- if (Zotero.Fulltext.isCachedMIMEType(mimeType)) {
+ if (Zotero.Fulltext.isCachedMIMEType(contentType)) {
var reindex = false;
if (!cacheFile.exists()) {
@@ -3521,7 +2883,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
reindex = true;
}
// Fully index item if it's not yet
- else if (!Zotero.Fulltext.isFullyIndexed(this.id)) {
+ else if (!(yield Zotero.Fulltext.isFullyIndexed(this))) {
Zotero.debug("Item " + this.id + " is not fully indexed -- caching now");
reindex = true;
}
@@ -3531,23 +2893,23 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3);
return '';
}
- Zotero.Fulltext.indexItems(this.id, false);
+ yield Zotero.Fulltext.indexItems(this.id, false);
}
if (!cacheFile.exists()) {
Zotero.debug("Cache file doesn't exist after indexing -- returning empty .attachmentText");
return '';
}
- str = Zotero.File.getContents(cacheFile);
+ str = yield Zotero.File.getContentsAsync(cacheFile);
}
- else if (mimeType == 'text/html') {
- str = Zotero.File.getContents(file);
+ else if (contentType == 'text/html') {
+ str = yield Zotero.File.getContentsAsync(file);
str = Zotero.Utilities.unescapeHTML(str);
}
- else if (mimeType == 'text/plain') {
- str = Zotero.File.getContents(file);
+ else if (contentType == 'text/plain') {
+ str = yield Zotero.File.getContentsAsync(file);
}
else {
@@ -3555,7 +2917,7 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
}
return str.trim();
-});
+}));
@@ -3567,75 +2929,31 @@ Zotero.Item.prototype.__defineGetter__('attachmentText', function () {
*/
Zotero.Item.prototype.getAttachments = function(includeTrashed) {
if (this.isAttachment()) {
- throw ("getAttachments() cannot be called on attachment items");
+ throw new Error("getAttachments() cannot be called on attachment items");
}
- if (!this.id) {
- return [];
- }
+ this._requireData('childItems');
- // Get the right cache array
- if (!this._cachedAttachments) {
- this._cachedAttachments = {
- chronologicalWithTrashed: null,
- chronologicalWithoutTrashed: null,
- alphabeticalWithTrashed: null,
- alphabeticalWithoutTrashed: null
- };
- }
- var cache = this._cachedAttachments;
- var cacheKey = (Zotero.Prefs.get('sortAttachmentsChronologically')
- ? 'chronological' : 'alphabetical')
+ var cacheKey = (Zotero.Prefs.get('sortAttachmentsChronologically') ? 'chronological' : 'alphabetical')
+ 'With' + (includeTrashed ? '' : 'out') + 'Trashed';
- if (cache[cacheKey] !== null) {
- return cache[cacheKey];
- }
-
- var sql = "SELECT A.itemID, value AS title FROM itemAttachments A "
- + "NATURAL JOIN items I LEFT JOIN itemData ID "
- + "ON (fieldID=110 AND A.itemID=ID.itemID) "
- + "LEFT JOIN itemDataValues IDV "
- + "ON (ID.valueID=IDV.valueID) "
- + "WHERE sourceItemID=?";
- if (!includeTrashed) {
- sql += " AND A.itemID NOT IN (SELECT itemID FROM deletedItems)";
- }
-
- if (Zotero.Prefs.get('sortAttachmentsChronologically')) {
- sql += " ORDER BY dateAdded";
- var results = Zotero.DB.columnQuery(sql, this.id);
- results = results ? results : [];
- cache[cacheKey] = results;
- return results;
- }
- var attachments = Zotero.DB.query(sql, this.id);
- if (!attachments) {
- cache[cacheKey] = [];
- return [];
+ if (this._attachments[cacheKey]) {
+ return this._attachments[cacheKey];
}
- // Sort by title
- var collation = Zotero.getLocaleCollation();
- var f = function (a, b) {
- return collation.compareString(1, a.title, b.title);
+ var rows = this._attachments.rows.concat();
+ // Remove trashed items if necessary
+ if (!includeTrashed) {
+ rows = rows.filter(function (row) !row.trashed);
}
-
- var attachmentIDs = [];
- attachments.sort(f);
- for each(var attachment in attachments) {
- attachmentIDs.push(attachment.itemID);
+ // Sort by title if necessary
+ if (!Zotero.Prefs.get('sortAttachmentsChronologically')) {
+ var collation = Zotero.getLocaleCollation();
+ rows.sort(function (a, b) collation.compareString(1, a.title, b.title));
}
- cache[cacheKey] = attachmentIDs;
- return attachmentIDs;
-}
-
-
-Zotero.Item.prototype.getBestSnapshot = function () {
- var msg = "Zotero.Item.getBestSnapshot() is deprecated -- use getBestAttachment";
- Zotero.debug(msg, 2);
- Components.utils.reportError(msg);
- return this.getBestAttachment();
+ var ids = [row.itemID for (row of rows)];
+ this._attachments[cacheKey] = ids;
+ return ids;
}
@@ -3644,374 +2962,390 @@ Zotero.Item.prototype.getBestSnapshot = function () {
* oldest non-PDF attachment matching parent URL, oldest PDF attachment not matching URL,
* old non-PDF attachment not matching URL
*
- * @return {Integer} itemID for attachment
+ * @return {Promise<Zotero.Item|FALSE>} - A promise for attachment item or FALSE if none
*/
-Zotero.Item.prototype.getBestAttachment = function() {
+Zotero.Item.prototype.getBestAttachment = Zotero.Promise.coroutine(function* () {
if (!this.isRegularItem()) {
throw ("getBestAttachment() can only be called on regular items");
}
- var attachments = this.getBestAttachments();
+ var attachments = yield this.getBestAttachments();
return attachments ? attachments[0] : false;
-}
+});
+
+
+/**
+ * Looks for attachment in the following order: oldest PDF attachment matching parent URL,
+ * oldest PDF attachment not matching parent URL, oldest non-PDF attachment matching parent URL,
+ * old non-PDF attachment not matching parent URL
+ *
+ * @return {Promise<Zotero.Item[]>} - A promise for an array of Zotero items
+ */
+Zotero.Item.prototype.getBestAttachments = Zotero.Promise.coroutine(function* () {
+ if (!this.isRegularItem()) {
+ throw new Error("getBestAttachments() can only be called on regular items");
+ }
+
+ var url = this.getField('url');
+
+ var sql = "SELECT IA.itemID FROM itemAttachments IA NATURAL JOIN items I "
+ + "LEFT JOIN itemData ID ON (IA.itemID=ID.itemID AND fieldID=1) "
+ + "LEFT JOIN itemDataValues IDV ON (ID.valueID=IDV.valueID) "
+ + "WHERE parentItemID=? AND linkMode NOT IN (?) "
+ + "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) "
+ + "ORDER BY contentType='application/pdf' DESC, value=? DESC, dateAdded ASC";
+ var itemIDs = yield Zotero.DB.columnQueryAsync(sql, [this.id, Zotero.Attachments.LINK_MODE_LINKED_URL, url]);
+ return Zotero.Items.get(itemIDs);
+});
+
+
/**
- * Returned cached state of best attachment for use in items view
+ * Return state of best attachment
*
- * @return {Integer} 0 (none), 1 (present), -1 (missing)
+ * @return {Promise<Integer>} Promise for 0 (none), 1 (present), -1 (missing)
*/
-Zotero.Item.prototype.getBestAttachmentState = function (cachedOnly) {
- if (cachedOnly || this._bestAttachmentState !== null) {
+Zotero.Item.prototype.getBestAttachmentState = Zotero.Promise.coroutine(function* () {
+ if (this._bestAttachmentState !== null) {
return this._bestAttachmentState;
}
- var itemID = this.getBestAttachment();
- this._bestAttachmentState = itemID
- ? (Zotero.Items.get(itemID).fileExists() ? 1 : -1)
- : 0;
- return this._bestAttachmentState;
-}
+ var item = yield this.getBestAttachment();
+ if (item) {
+ let exists = yield item.fileExists();
+ return this._bestAttachmentState = exists ? 1 : -1;
+ }
+ return this._bestAttachmentState = 0;
+});
/**
* Return cached state of best attachment for use in items view
*
- * @return {Promise:Integer} Promise with 0 (none), 1 (present), -1 (missing)
+ * @return {Integer|null} 0 (none), 1 (present), -1 (missing), null (unavailable)
*/
-Zotero.Item.prototype.getBestAttachmentStateAsync = function () {
- var self = this;
- return Q.fcall(function() {
- if (self._bestAttachmentState !== null) {
- return self._bestAttachmentState;
- }
- var itemID = self.getBestAttachment();
- if (itemID) {
- return Zotero.Items.get(itemID).fileExistsAsync()
- .then(function (exists) {
- self._bestAttachmentState = exists ? 1 : -1;
- });
- }
- else {
- self._bestAttachmentState = 0;
- }
- })
- .then(function () {
- return self._bestAttachmentState;
- });
+Zotero.Item.prototype.getBestAttachmentStateCached = function () {
+ return this._bestAttachmentState;
}
-Zotero.Item.prototype.updateBestAttachmentState = function () {
+Zotero.Item.prototype.clearBestAttachmentState = function () {
this._bestAttachmentState = null;
}
+//
+// Methods dealing with item tags
+//
+//
+/**
+ * Returns all tags assigned to an item
+ *
+ * @return {Array} Array of tag data in API JSON format
+ */
+Zotero.Item.prototype.getTags = function () {
+ this._requireData('tags');
+ // BETTER DEEP COPY?
+ return JSON.parse(JSON.stringify(this._tags));
+};
+
+
/**
- * Looks for attachment in the following order: oldest PDF attachment matching parent URL,
- * oldest PDF attachment not matching parent URL, oldest non-PDF attachment matching parent URL,
- * old non-PDF attachment not matching parent URL
+ * Check if the item has a given tag
*
- * @return {Array|FALSE} itemIDs for attachments, or FALSE if none
+ * @param {String}
+ * @return {Boolean}
*/
-Zotero.Item.prototype.getBestAttachments = function() {
- if (!this.isRegularItem()) {
- throw ("getBestAttachments() can only be called on regular items");
- }
-
- var url = this.getField('url');
-
- var sql = "SELECT IA.itemID FROM itemAttachments IA NATURAL JOIN items I "
- + "LEFT JOIN itemData ID ON (IA.itemID=ID.itemID AND fieldID=1) "
- + "LEFT JOIN itemDataValues IDV ON (ID.valueID=IDV.valueID) "
- + "WHERE sourceItemID=? AND linkMode NOT IN (?) "
- + "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) "
- + "ORDER BY mimeType='application/pdf' DESC, value=? DESC, dateAdded ASC";
- return Zotero.DB.columnQuery(sql, [this.id, Zotero.Attachments.LINK_MODE_LINKED_URL, url]);
+Zotero.Item.prototype.hasTag = function (tagName) {
+ this._requireData('tags');
+ return this._tags.some(function (tagData) tagData.tag == tagName);
}
-//
-// Methods dealing with item tags
-//
-// save() is not required for tag functions
-//
-Zotero.Item.prototype.addTag = function(name, type) {
- if (!this.id) {
- throw ('Cannot add tag to unsaved item in Item.addTag()');
+/**
+ * Get the assigned type for a given tag of the item
+ */
+Zotero.Item.prototype.getTagType = function (tagName) {
+ this._requireData('tags');
+ for (let i=0; i<this._tags.length; i++) {
+ let tag = this._tags[i];
+ if (tag.tag === tagName) {
+ return tag.type ? tag.type : 0;
+ }
}
-
- name = Zotero.Utilities.trim(name);
-
- if (!name) {
- Zotero.debug('Not saving empty tag in Item.addTag()', 2);
- return false;
+ return null;
+}
+
+
+/**
+ * Set the item's tags
+ *
+ * A separate save() is required to update the database.
+ *
+ * @param {Array} tags Tag data in API JSON format (e.g., [{tag: 'tag', type: 1}])
+ */
+Zotero.Item.prototype.setTags = function (tags) {
+ var oldTags = this.getTags();
+ var newTags = tags.concat();
+ for (let i=0; i<oldTags.length; i++) {
+ oldTags[i] = Zotero.Tags.cleanData(oldTags[i]);
}
-
- if (!type) {
- type = 0;
+ for (let i=0; i<newTags.length; i++) {
+ newTags[i] = Zotero.Tags.cleanData(newTags[i]);
}
- Zotero.DB.beginTransaction();
- try {
-
- var matchingTags = Zotero.Tags.getIDs(name, this.libraryID);
- var itemTags = this.getTags();
- if (matchingTags && itemTags.length) {
- for each(var id in matchingTags) {
- if (itemTags.indexOf(id) != -1) {
- var tag = Zotero.Tags.get(id);
- // If existing automatic and adding identical user,
- // remove automatic
- if (type == 0 && tag.type == 1) {
- this.removeTag(id);
- break;
- }
- // If existing user and adding automatic, skip
- else if (type == 1 && tag.type == 0) {
- Zotero.debug("Identical user tag '" + name
- + "' already exists -- skipping automatic tag");
- Zotero.DB.commitTransaction();
- return false;
- }
- }
- }
- }
+ // Sort to allow comparison with JSON, which maybe we'll stop doing if it's too slow
+ var sorter = function (a, b) {
+ if (a.type < b.type) return -1;
+ if (a.type > b.type) return 1;
+ return a.tag.localeCompare(b.tag);
+ };
+ oldTags.sort(sorter);
+ newTags.sort(sorter);
- var tagID = Zotero.Tags.getID(name, type, this.libraryID);
- if (!tagID) {
- var tag = new Zotero.Tag;
- tag.libraryID = this.libraryID;
- tag.name = name;
- tag.type = type;
- var tagID = tag.save();
+ if (JSON.stringify(oldTags) == JSON.stringify(newTags)) {
+ Zotero.debug("Tags haven't changed", 4);
+ return;
}
- var added = this.addTagByID(tagID);
- Zotero.DB.commitTransaction();
- return added ? tagID : false;
-
- }
- catch (e) {
- Zotero.debug(e);
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
+ this._markFieldChange('tags', this._tags);
+ this._changed.tags = true;
+ this._tags = newTags;
}
-Zotero.Item.prototype.addTags = function (tags, type) {
- Zotero.DB.beginTransaction();
- try {
- var tagIDs = [];
- for (var i = 0; i < tags.length; i++) {
- let id = this.addTag(tags[i], type);
- if (id) {
- tagIDs.push(id);
+/**
+ * Add a single manual tag to the item. If an automatic tag with the same name already exists,
+ * replace it with a manual one.
+ *
+ * A separate save() is required to update the database.
+ *
+ * @param {String} name
+ */
+Zotero.Item.prototype.addTag = function (name) {
+ var changed = false;
+ var tags = this.getTags();
+ for (let i=0; i<tags.length; i++) {
+ let tag = tags[i];
+ if (tag.tag === name) {
+ if (!tag.type) {
+ Zotero.debug("Tag '" + name + "' already exists on item " + this.libraryKey);
+ return false;
}
+ tag.type = 0;
+ changed = true;
+ break;
}
-
- Zotero.DB.commitTransaction();
- return tagIDs.length > 0 ? tagIDs : false;
}
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
+ if (!changed) {
+ tags.push({
+ tag: name
+ });
}
+ this.setTags(tags);
+ return true;
}
-Zotero.Item.prototype.addTagByID = function(tagID) {
- if (!this.id) {
- throw ('Cannot add tag to unsaved item in Zotero.Item.addTagByID()');
- }
+Zotero.Item.prototype.addTags = function (tags) {
+ throw new Error("Unimplemented");
+}
+
+
+/**
+ * Replace an existing tag with a new manual tag
+ *
+ * @param {String} oldTag
+ * @param {String} newTag
+ */
+Zotero.Item.prototype.replaceTag = function (oldTag, newTag) {
+ var tags = this.getTags();
+ newTag = newTag.trim();
- if (!tagID) {
- throw ('tagID not provided in Zotero.Item.addTagByID()');
- }
+ Zotero.debug("REPLACING TAG " + oldTag + " " + newTag);
- var tag = Zotero.Tags.get(tagID);
- if (!tag) {
- throw ('Cannot add invalid tag ' + tagID + ' in Zotero.Item.addTagByID()');
+ if (newTag === "") {
+ Zotero.debug('Not replacing with empty tag', 2);
+ return false;
}
- var added = tag.addItem(this.id);
- if (!added) {
+ var changed = false;
+ for (let i=0; i<tags.length; i++) {
+ let tag = tags[i];
+ if (tag.tag === oldTag) {
+ tag.tag = newTag;
+ tag.type = 0;
+ changed = true;
+ }
+ }
+ if (!changed) {
+ Zotero.debug("Tag '" + oldTag + "' not found on item -- not replacing", 2);
return false;
}
- tag.save();
- return true;
+ this.setTags(tags);
+ return true;
+}
+
+
+/**
+ * Remove a tag from the item
+ *
+ * A separate save() is required to update the database.
+ */
+Zotero.Item.prototype.removeTag = function(tagName) {
+ this._requireData('tags');
+ Zotero.debug(this._tags);
+ var newTags = this._tags.filter(function (tagData) tagData.tag !== tagName);
+ if (newTags.length == this._tags.length) {
+ Zotero.debug('Cannot remove missing tag ' + tagName + ' from item ' + this.libraryKey);
+ return;
+ }
+ Zotero.debug(newTags);
+ this.setTags(newTags);
}
-Zotero.Item.prototype.hasTag = function(tagID) {
- return this.hasTags(tagID);
-}
-/*
- * Returns true if the item has one or more of |tagIDs|
+/**
+ * Remove all tags from the item
*
- * |tagIDs| can be an int or array of ints
+ * A separate save() is required to update the database.
*/
-Zotero.Item.prototype.hasTags = function(tagIDs) {
- var tagIDs = Zotero.flattenArguments(tagIDs);
-
- var sql = "SELECT COUNT(*) FROM itemTags WHERE itemID=? AND tagID IN ("
- + tagIDs.map(function () '?').join() + ")";
- return !!Zotero.DB.valueQuery(sql, [this.id].concat(tagIDs));
+Zotero.Item.prototype.removeAllTags = function() {
+ this._requireData('tags');
+ this.setTags([]);
}
+
+//
+// Methods dealing with collections
+//
/**
- * Returns all tags assigned to an item
+ * Gets the collections the item is in
*
- * @return Array Array of Zotero.Tag objects
+ * @return {Array<Integer>} An array of collectionIDs for all collections the item belongs to
*/
-Zotero.Item.prototype.getTags = function() {
- if (!this.id) {
- return [];
- }
- var sql = "SELECT tagID, name FROM tags WHERE tagID IN "
- + "(SELECT tagID FROM itemTags WHERE itemID=?)";
- var tags = Zotero.DB.query(sql, this.id);
- if (!tags) {
- return [];
- }
-
- var collation = Zotero.getLocaleCollation();
- tags.sort(function(a, b) {
- return collation.compareString(1, a.name, b.name);
- });
-
- var tagObjs = [];
- for (var i=0; i<tags.length; i++) {
- var tag = Zotero.Tags.get(tags[i].tagID);
- tagObjs.push(tag);
- }
- return tagObjs;
-}
+Zotero.Item.prototype.getCollections = function () {
+ this._requireData('collections');
+ return this._collections.concat();
+};
-Zotero.Item.prototype.getTagIDs = function() {
- var sql = "SELECT tagID FROM itemTags WHERE itemID=?";
- return Zotero.DB.columnQuery(sql, this.id);
-}
-Zotero.Item.prototype.replaceTag = function(oldTagID, newTag) {
- if (!this.id) {
- throw ('Cannot replace tag on unsaved item');
- }
-
- newTag = Zotero.Utilities.trim(newTag);
+/**
+ * Sets the collections the item is in
+ *
+ * A separate save() (with options.skipDateModifiedUpdate, possibly) is required to save changes.
+ *
+ * @param {Array<String|Integer>} collectionIDsOrKeys Collection ids or keys
+ */
+Zotero.Item.prototype.setCollections = function (collectionIDsOrKeys) {
+ this._requireData('collections');
- if (!newTag) {
- Zotero.debug('Not replacing with empty tag', 2);
- return false;
+ if (!collectionIDsOrKeys) {
+ collectionIDsOrKeys = [];
}
- Zotero.DB.beginTransaction();
+ // Convert any keys to ids
+ var collectionIDs = collectionIDsOrKeys.map(function (val) {
+ return parseInt(val) == val
+ ? parseInt(val)
+ : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, val);
+ }.bind(this));
+ collectionIDs = Zotero.Utilities.arrayUnique(collectionIDs);
- var oldTag = Zotero.Tags.getName(oldTagID);
- if (oldTag==newTag) {
- Zotero.DB.commitTransaction();
- return false;
+ if (Zotero.Utilities.arrayEquals(this._collections, collectionIDs)) {
+ Zotero.debug("Collections have not changed for item " + this.id);
+ return;
}
- this.removeTag(oldTagID);
- var id = this.addTag(newTag);
- Zotero.DB.commitTransaction();
- Zotero.Notifier.trigger('modify', 'item', this.id);
- Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + oldTagID);
- if (id) {
- Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + id);
- }
- return id;
-}
+ this._markFieldChange("collections", this._collections);
+ this._collections = collectionIDs;
+ this._changed.collections = true;
+};
-Zotero.Item.prototype.removeTag = function(tagID) {
- if (!this.id) {
- throw ('Cannot remove tag on unsaved item in Zotero.Item.removeTag()');
- }
-
- if (!tagID) {
- throw ('tagID not provided in Zotero.Item.removeTag()');
- }
+
+/**
+ * Add this item to a collection
+ *
+ * A separate save() (with options.skipDateModifiedUpdate, possibly) is required to save changes.
+ *
+ * @param {Number} collectionID
+ */
+Zotero.Item.prototype.addToCollection = function (collectionIDOrKey) {
+ var collectionID = parseInt(collectionIDOrKey) == collectionIDOrKey
+ ? parseInt(collectionIDOrKey)
+ : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey)
- var tag = Zotero.Tags.get(tagID);
- if (!tag) {
- throw ('Cannot remove invalid tag ' + tagID + ' in Zotero.Item.removeTag()');
+ if (!collectionID) {
+ throw new Error("Invalid collection '" + collectionIDOrKey + "'");
}
- tag.removeItem(this.id);
- tag.save();
-
- if (!tag.countLinkedItems()) {
- Zotero.Prefs.set('purge.tags', true);
+ this._requireData('collections');
+ if (this._collections.indexOf(collectionID) != -1) {
+ Zotero.debug("Item is already in collection " + collectionID);
+ return;
}
-}
+ this.setCollections(this._collections.concat(collectionID));
+};
-Zotero.Item.prototype.removeAllTags = function() {
- if (!this.id) {
- throw ('Cannot remove tags on unsaved item');
- }
+
+/**
+ * Remove this item from a collection
+ *
+ * A separate save() (with options.skipDateModifiedUpdate, possibly) is required to save changes.
+ *
+ * @param {Number} collectionID
+ */
+Zotero.Item.prototype.removeFromCollection = function (collectionIDOrKey) {
+ var collectionID = parseInt(collectionIDOrKey) == collectionIDOrKey
+ ? parseInt(collectionIDOrKey)
+ : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey)
- Zotero.DB.beginTransaction();
- var tagIDs = this.getTagIDs();
- if (!tagIDs) {
- Zotero.DB.commitTransaction();
- return;
+ if (!collectionID) {
+ throw new Error("Invalid collection '" + collectionIDOrKey + "'");
}
- Zotero.DB.query("DELETE FROM itemTags WHERE itemID=?", this.id);
- Zotero.Tags.purge();
- Zotero.DB.commitTransaction();
- Zotero.Notifier.trigger('modify', 'item', this.id);
+ Zotero.debug("REMOVING FROM COLLECTION");
+ Zotero.debug(this._collections);
- for (var i in tagIDs) {
- tagIDs[i] = this.id + '-' + tagIDs[i];
+ this._requireData('collections');
+ var pos = this._collections.indexOf(collectionID);
+ if (pos == -1) {
+ Zotero.debug("Item is not in collection " + collectionID);
+ return;
}
- Zotero.Notifier.trigger('remove', 'item-tag', tagIDs);
-}
+ this.setCollections(this._collections.slice(0, pos).concat(this._collections.slice(pos + 1)));
+};
+
+
+/**
+* Determine whether the item belongs to a given collectionID
+**/
+Zotero.Item.prototype.inCollection = function (collectionID) {
+ this._requireData('collections');
+ return this._collections.indexOf(collectionID) != -1;
+};
+
+
+//
+// Methods dealing with relations
+//
+
/**
* Return an item in the specified library equivalent to this item
+ *
+ * @return {Promise}
*/
Zotero.Item.prototype.getLinkedItem = function (libraryID) {
- if (libraryID == this.libraryID) {
- throw ("Item is already in library " + libraryID + " in Zotero.Item.getLinkedItem()");
- }
-
- var predicate = Zotero.Relations.linkedObjectPredicate;
- var itemURI = Zotero.URI.getItemURI(this);
- var links = Zotero.Relations.getObject(itemURI, predicate, false).concat(
- Zotero.Relations.getSubject(false, predicate, itemURI)
- );
-
- if (!links.length) {
- return false;
- }
-
- if (libraryID) {
- var libraryItemPrefix = Zotero.URI.getLibraryURI(libraryID) + "/items/";
- }
- else {
- var libraryItemPrefix = Zotero.URI.getCurrentUserURI() + "/items/";
- }
- for each(var link in links) {
- if (link.indexOf(libraryItemPrefix) == 0) {
- var item = Zotero.URI.getURIItem(link);
- if (!item) {
- Zotero.debug("Referenced linked item '" + link + "' not found in Zotero.Item.getLinkedItem()", 2);
- continue;
- }
- return item;
- }
- }
- return false;
+ return this._getLinkedObject(libraryID);
}
-Zotero.Item.prototype.addLinkedItem = function (item) {
+Zotero.Item.prototype.addLinkedItem = Zotero.Promise.coroutine(function* (item) {
var url1 = Zotero.URI.getItemURI(this);
var url2 = Zotero.URI.getItemURI(item);
var predicate = Zotero.Relations.linkedObjectPredicate;
- if (Zotero.Relations.getByURIs(url1, predicate, url2).length
- || Zotero.Relations.getByURIs(url2, predicate, url1).length) {
+ if ((yield Zotero.Relations.getByURIs(url1, predicate, url2)).length
+ || (yield Zotero.Relations.getByURIs(url2, predicate, url1)).length) {
Zotero.debug("Items " + this.key + " and " + item.key + " are already linked");
return false;
}
@@ -4021,8 +3355,8 @@ Zotero.Item.prototype.addLinkedItem = function (item) {
// new, copied item).
var libraryID = (!this.libraryID || !item.libraryID) ? 0 : this.libraryID;
- Zotero.Relations.add(libraryID, url1, predicate, url2);
-}
+ yield Zotero.Relations.add(libraryID, url1, predicate, url2);
+});
@@ -4036,7 +3370,7 @@ Zotero.Item.prototype.getImageSrc = function() {
// extend to support other document types later
if ((linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE ||
linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL) &&
- this.attachmentMIMEType == 'application/pdf') {
+ this.attachmentContentType == 'application/pdf') {
itemType += '-pdf';
}
else if (linkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) {
@@ -4057,6 +3391,37 @@ Zotero.Item.prototype.getImageSrc = function() {
}
+Zotero.Item.prototype.getImageSrcWithTags = Zotero.Promise.coroutine(function* () {
+ //Zotero.debug("Generating tree image for item " + this.id);
+
+ var uri = this.getImageSrc();
+
+ // TODO: Optimize this. Maybe load color/item associations in batch in cacheFields?
+ yield this.loadTags();
+ var tags = this.getTags();
+ if (!tags.length) {
+ return uri;
+ }
+
+ var tagColors = yield Zotero.Tags.getColors(this.libraryID);
+ var colorData = [];
+ for (let i=0; i<tags.length; i++) {
+ let tag = tags[i];
+ if (tagColors[tag.tag]) {
+ colorData.push(tagColors[tag.tag]);
+ }
+ }
+ if (!colorData.length) {
+ return uri;
+ }
+ colorData.sort(function (a, b) {
+ return a.position - b.position;
+ });
+ var colors = colorData.map(function (val) val.color);
+ return Zotero.Tags.generateItemsListImage(colors, uri);
+});
+
+
/**
* Compares this item to another
@@ -4091,10 +3456,10 @@ Zotero.Item.prototype.diff = function (item, includeMatches, ignoreFields) {
var changed = false;
- changed = thisData.sourceItemKey != otherData.sourceItemKey;
+ changed = thisData.parentKey != otherData.parentKey;
if (includeMatches || changed) {
- diff[0].sourceItemKey = thisData.sourceItemKey;
- diff[1].sourceItemKey = otherData.sourceItemKey;
+ diff[0].parentKey = thisData.parentKey;
+ diff[1].parentKey = otherData.parentKey;
if (changed) {
numDiffs++;
@@ -4339,7 +3704,7 @@ Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved, skipTag
}
// Add in-memory creators
newItem.setCreator(
- newIndex, this.getCreator(i).ref, obj.creators[i].creatorType
+ newIndex, this.getCreator(i).ref, obj.creators[i].creatorTypeID
);
}
}
@@ -4383,15 +3748,15 @@ Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved, skipTag
else {
newItem.setNote(this.getNote());
if (sameLibrary) {
- var parent = this.getSourceKey();
+ var parent = this.parentKey;
if (parent) {
- newItem.setSourceKey(parent);
+ newItem.parentKey = parent;
}
}
if (this.isAttachment()) {
newItem.attachmentLinkMode = this.attachmentLinkMode;
- newItem.attachmentMIMEType = this.attachmentMIMEType;
+ newItem.attachmentContentType = this.attachmentContentType;
newItem.attachmentCharset = this.attachmentCharset;
if (sameLibrary) {
if (this.attachmentPath) {
@@ -4429,9 +3794,9 @@ Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved, skipTag
/**
* Delete item from database and clear from Zotero.Items internal array
*
- * Items.erase() should be used instead of this
+ * Items.erase() should be used for multiple items
*/
-Zotero.Item.prototype.erase = function() {
+Zotero.Item.prototype.erase = Zotero.Promise.coroutine(function* () {
if (!this.id) {
return false;
}
@@ -4440,644 +3805,787 @@ Zotero.Item.prototype.erase = function() {
var changedItems = [];
var changedItemsNotifierData = {};
-
- Zotero.DB.beginTransaction();
-
var deletedItemNotifierData = {};
- deletedItemNotifierData[this.id] = { old: this.serialize() };
-
- // Remove group item metadata
- if (this.libraryID) {
- var sql = "DELETE FROM groupItems WHERE itemID=?";
- Zotero.DB.query(sql, this.id);
- }
- // Remove item from parent collections
- var parentCollectionIDs = this.getCollections();
- if (parentCollectionIDs) {
- for (var i=0; i<parentCollectionIDs.length; i++) {
- Zotero.Collections.get(parentCollectionIDs[i]).removeItem(this.id);
- }
- }
-
- // Note
- if (this.isNote()) {
- // Decrement note count of source items
- var sql = "SELECT sourceItemID FROM itemNotes WHERE itemID=" + this.id;
- var sourceItemID = Zotero.DB.valueQuery(sql);
- if (sourceItemID) {
- var sourceItem = Zotero.Items.get(sourceItemID);
- //changedItemsNotifierData[sourceItem.id] = {};
- if (!this.deleted) {
- sourceItem.decrementNoteCount();
- }
- changedItems.push(sourceItemID);
- }
- }
- // Attachment
- else if (this.isAttachment()) {
- // Decrement file count of source items
- var sql = "SELECT sourceItemID FROM itemAttachments WHERE itemID=" + this.id;
- var sourceItemID = Zotero.DB.valueQuery(sql);
- if (sourceItemID) {
- var sourceItem = Zotero.Items.get(sourceItemID);
- //changedItemsNotifierData[sourceItem.id] = {};
- if (!this.deleted) {
- sourceItem.decrementAttachmentCount();
+ yield Zotero.DB.executeTransaction(function* () {
+ deletedItemNotifierData[this.id] = { old: this.toJSON() };
+
+ // Remove item from parent collections
+ var parentCollectionIDs = this.collections;
+ if (parentCollectionIDs) {
+ for (var i=0; i<parentCollectionIDs.length; i++) {
+ let parentCollection = yield Zotero.Collections.getAsync(parentCollectionIDs[i]);
+ yield parentCollection.removeItem(this.id);
}
- changedItems.push(sourceItemID);
}
- // Delete associated files
- var linkMode = this.getAttachmentLinkMode();
- switch (linkMode) {
- // Link only -- nothing to delete
- case Zotero.Attachments.LINK_MODE_LINKED_URL:
- break;
- default:
+ var parentItem = this.parentKey;
+ parentItem = parentItem ? yield Zotero.Items.getByLibraryAndKey(this.libraryID, parentItem) : null;
+
+ // // Delete associated attachment files
+ if (this.isAttachment()) {
+ let linkMode = this.getAttachmentLinkMode();
+ // If link only, nothing to delete
+ if (linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
try {
- var file = Zotero.Attachments.getStorageDirectory(this.id);
- if (file.exists()) {
- file.remove(true);
- }
+ let file = Zotero.Attachments.getStorageDirectory(this);
+ yield OS.File.removeDir(file.path, {
+ ignoreAbsent: true,
+ ignorePermissions: true
+ });
}
catch (e) {
+ Zotero.debug(e, 2);
Components.utils.reportError(e);
}
+ }
}
+ // Regular item
+ else {
+ let sql = "SELECT itemID FROM itemNotes WHERE parentItemID=?1 UNION "
+ + "SELECT itemID FROM itemAttachments WHERE parentItemID=?1";
+ let toDelete = yield Zotero.DB.columnQueryAsync(sql, [this.id]);
+ for (let i=0; i<toDelete.length; i++) {
+ let obj = yield Zotero.Items.getAsync(toDelete[i]);
+ yield obj.erase();
+ }
+ }
+
+ // Flag related items for notification
+ // TEMP: Do something with relations
+ /*var relateds = this._getRelatedItems(true);
+ for each(var id in relateds) {
+ let relatedItem = Zotero.Items.get(id);
+ }*/
+
+ // Clear fulltext cache
+ if (this.isAttachment()) {
+ yield Zotero.Fulltext.clearItemWords(this.id);
+ //Zotero.Fulltext.clearItemContent(this.id);
+ }
+
+ // Remove relations (except for merge tracker)
+ var uri = Zotero.URI.getItemURI(this);
+ yield Zotero.Relations.eraseByURI(uri, [Zotero.Relations.deletedItemPredicate]);
+
+ yield Zotero.DB.queryAsync('DELETE FROM items WHERE itemID=?', this.id);
+
+ if (parentItem) {
+ yield parentItem.reload(['primaryData', 'childItems'], true);
+ parentItem.clearBestAttachmentState();
+ }
+ }.bind(this));
+
+ Zotero.Items.unload(this.id);
+
+ // Send notification of changed items
+ if (changedItems.length) {
+ Zotero.Notifier.trigger('modify', 'item', changedItems, changedItemsNotifierData);
}
- // Regular item
+ Zotero.Notifier.trigger('delete', 'item', this.id, deletedItemNotifierData);
- // If flag given, delete child notes and files
- else {
- var sql = "SELECT itemID FROM itemNotes WHERE sourceItemID=?1 UNION "
- + "SELECT itemID FROM itemAttachments WHERE sourceItemID=?1";
- var toDelete = Zotero.DB.columnQuery(sql, [this.id]);
+ Zotero.Prefs.set('purge.items', true);
+ Zotero.Prefs.set('purge.creators', true);
+ Zotero.Prefs.set('purge.tags', true);
+});
+
+
+Zotero.Item.prototype.isCollection = function() {
+ return false;
+}
+
+
+Zotero.Item.prototype.fromJSON = function (json) {
+ if (json.itemKey) this.key = json.itemKey;
+ if (json.itemType) this.setType(Zotero.ItemTypes.getID(json.itemType));
+
+ var changedFields = {};
+
+ // Primary data
+ for (var field in json) {
+ let val = json[field];
- if (toDelete) {
- for (var i in toDelete) {
- var obj = Zotero.Items.get(toDelete[i]);
- obj.erase();
+ switch (field) {
+ case 'itemKey':
+ case 'itemType':
+ case 'creators':
+ case 'deleted':
+ break;
+
+ case 'dateAdded':
+ case 'dateModified':
+ item[field] = val;
+ break;
+
+ case 'tags':
+ this.setTags(json.tags);
+ break;
+
+ case 'collections':
+ this.setCollections(json.collections);
+ break;
+
+ case 'relations':
+ this.setRelations(json.relations);
+ break;
+
+ //
+ // Attachment metadata
+ //
+ case 'linkMode':
+ this.attachmentLinkMode = Zotero.Attachments["LINK_MODE_" + val.toUpperCase()];
+ break;
+
+ case 'contentType':
+ this.attachmentContentType = val;
+ break;
+
+ case 'charset':
+ this.attachmentCharset = val;
+ break;
+
+ case 'path':
+ this.attachmentPath = val;
+ break;
+
+ // Item fields
+ default:
+ let changed = this.setField(field, json[field]);
+ if (changed) {
+ changedFields[field] = true;
}
}
}
- // Flag related items for notification
- var relateds = this._getRelatedItemsBidirectional();
- for each(var id in relateds) {
- var relatedItem = Zotero.Items.get(id);
- if (changedItems.indexOf(id) != -1) {
- //changedItemsNotifierData[id] = {};
- changedItems.push(id);
+ // Clear existing fields not specified
+ var previousFields = this.getUsedFields(true);
+ for each(let field in previousFields) {
+ if (!changedFields[field] &&
+ // Invalid fields will already have been cleared by the type change
+ Zotero.ItemFields.isValidForType(
+ Zotero.ItemFields.getID(field), data.itemTypeID
+ )) {
+ this.setField(field, false);
}
}
- // Clear fulltext cache
- if (this.isAttachment()) {
- Zotero.Fulltext.clearItemWords(this.id);
- //Zotero.Fulltext.clearItemContent(this.id);
- }
+ // Deleted item flag
+ this.deleted = !!json.deleted;
- // Remove relations (except for merge tracker)
- var uri = Zotero.URI.getItemURI(this);
- Zotero.Relations.eraseByURI(uri, [Zotero.Relations.deletedItemPredicate]);
+ // Creators
+ var numCreators = 0;
+ if (json.creators) {
+ for each (let creator in json.creators) {
+ this.setCreator(pos, creator);
+ numCreators++;
+ }
+ }
+ // Remove item's remaining creators not in JSON
+ var rem = this.numCreators() - numCreators;
+ for (let j = 0; j < rem; j++) {
+ // Keep removing last creator
+ this.removeCreator(numCreators);
+ }
- Zotero.DB.query('DELETE FROM annotations WHERE itemID=?', this.id);
- Zotero.DB.query('DELETE FROM highlights WHERE itemID=?', this.id);
- Zotero.DB.query('DELETE FROM deletedItems WHERE itemID=?', this.id);
- var hasCreators = Zotero.DB.valueQuery(
- "SELECT rowid FROM itemCreators WHERE itemID=? LIMIT 1", this.id
- );
- if (hasCreators) {
- Zotero.DB.query('DELETE FROM itemCreators WHERE itemID=?', this.id);
+ // Both notes and attachments might have parents and notes
+ if (this.isNote() || this.isAttachment()) {
+ let parentKey = json.parentItem;
+ this.parentKey = parentKey ? parentKey : false;
+
+ let note = json.note;
+ this.setNote(note !== undefined ? note : "");
+ }
+}
+
+
+/**
+ * @param {Object} options
+ * @param {Object} patchBase
+ */
+Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patchBase) {
+ if (this.hasChanged()) {
+ throw new Error("Cannot generate JSON from changed item");
}
- Zotero.DB.query('DELETE FROM itemNotes WHERE itemID=?', this.id);
- Zotero.DB.query('DELETE FROM itemAttachments WHERE itemID=?', this.id);
- Zotero.DB.query('DELETE FROM itemSeeAlso WHERE itemID=?', this.id);
- Zotero.DB.query('DELETE FROM itemSeeAlso WHERE linkedItemID=?', this.id);
- var tags = this.getTags();
- if (tags.length) {
- var hasTags = true;
- Zotero.DB.query('DELETE FROM itemTags WHERE itemID=?', this.id);
- // DEBUG: Hack to reload linked items -- replace with something better
- for each(var tag in tags) {
- tag._linkedItemsLoaded = false;
- }
+ if (options) {
+ var mode = options.mode;
}
else {
- var hasTags = false;
+ var mode = 'new';
}
- Zotero.DB.query('DELETE FROM itemData WHERE itemID=?', this.id);
-
- try {
- Zotero.DB.query('DELETE FROM items WHERE itemID=?', this.id);
+ if (mode == 'patch') {
+ if (!patchBase) {
+ throw new Error("Cannot use patch mode if patchBase not provided");
+ }
}
- catch (e) {
- // If deletion fails, try to correct a few things that have come up before
- Zotero.debug("Item deletion failed -- trying to fix", 2);
- Zotero.Fulltext.clearItemWords(this.id);
- Zotero.DB.query('DELETE FROM itemTags WHERE itemID=?', this.id);
-
- // And then try again
- Zotero.DB.query('DELETE FROM items WHERE itemID=?', this.id);
+ else if (patchBase) {
+ Zotero.debug("Zotero.Item.toJSON: ignoring provided patchBase in " + mode + " mode", 2);
}
- try {
- Zotero.DB.commitTransaction();
+ var obj = {};
+ if (options && options.includeKey) {
+ obj.itemKey = this.key;
}
- catch (e) {
- // On failure, reset count of source items
- if (sourceItem) {
- if (this.isNote()) {
- sourceItem.incrementNoteCount();
- }
- else if (this.isAttachment()) {
- sourceItem.incrementAttachmentCount();
- }
- }
- Zotero.DB.rollbackTransaction();
- throw (e);
+ if (options && options.includeVersion) {
+ obj.itemVersion = this.version;
}
+ obj.itemType = Zotero.ItemTypes.getName(this.itemTypeID);
- Zotero.Items.unload(this.id);
-
- // Send notification of changed items
- if (changedItems.length) {
- Zotero.Notifier.trigger('modify', 'item', changedItems, changedItemsNotifierData);
+ // Fields
+ yield this.loadItemData();
+ for (let i in this._itemData) {
+ let val = this.getField(i) + '';
+ if (val !== '' || mode == 'full') {
+ obj[Zotero.ItemFields.getName(i)] = val;
+ }
}
- Zotero.Notifier.trigger('delete', 'item', this.id, deletedItemNotifierData);
-
- Zotero.Prefs.set('purge.items', true);
- if (hasCreators) {
- Zotero.Prefs.set('purge.creators', true);
+ // Creators
+ if (this.isRegularItem()) {
+ yield this.loadCreators()
+ obj.creators = this.getCreators();
}
- if (hasTags) {
- Zotero.Prefs.set('purge.tags', true);
+ else {
+ var parent = this.parentKey;
+ if (parent || mode == 'full') {
+ obj.parentItem = parent ? parent : false;
+ }
+
+ // Attachment fields
+ if (this.isAttachment()) {
+ obj.linkMode = this.attachmentLinkMode;
+ obj.contentType = this.attachmentContentType;
+ obj.charset = Zotero.CharacterSets.getName(this.attachmentCharset);
+ obj.path = this.attachmentPath;
+ }
+
+ // Notes and embedded attachment notes
+ yield this.loadNote();
+ let note = this.getNote();
+ if (note !== "" || mode == 'full') {
+ obj.note = note;
+ }
+
+ // TODO: md5, hash?
}
-}
-
-
-Zotero.Item.prototype.isCollection = function() {
- return false;
-}
-
-
-Zotero.Item.prototype.toArray = function (mode) {
- Zotero.debug('Zotero.Item.toArray() is deprecated -- use Zotero.Item.serialize()');
- if (this.id || this.key) {
- if (!this._primaryDataLoaded) {
- this.loadPrimaryData(true);
- }
- if (!this._itemDataLoaded) {
- this._loadItemData();
+ // Tags
+ obj.tags = [];
+ yield this.loadTags()
+ var tags = yield this.getTags();
+ for each (let tag in tags) {
+ let tagObj = {};
+ tagObj.tag = tag.name;
+ let type = tag.type;
+ if (type != 0 || mode == 'full') {
+ tagObj.type = tag.type;
}
+ obj.tags.push(tagObj);
}
- var arr = {};
+ // Collections
+ yield this.loadCollections();
+ obj.collections = this.getCollections().map(function (id) {
+ return Zotero.Collections.getLibraryAndKeyFromID(id)[1];
+ });
- // Primary fields
- for each(var i in Zotero.Items.primaryFields) {
- switch (i) {
- case 'itemID':
- arr.itemID = this._id;
- continue;
-
- case 'itemTypeID':
- arr.itemType = Zotero.ItemTypes.getName(this.itemTypeID);
- continue;
-
- // Skip virtual fields
- case 'firstCreator':
- case 'numNotes':
- case 'numAttachments':
- case 'sourceItemID':
- continue;
-
- // For the rest, just copy over
- default:
- arr[i] = this['_' + i];
+ // Relations
+ yield this.loadRelations();
+ obj.relations = {};
+ var rels = yield Zotero.Relations.getByURIs(Zotero.URI.getItemURI(this));
+ for each (let rel in rels) {
+ obj.relations[rel.predicate] = rel.object;
+ }
+ var relatedItems = this._getRelatedItems().map(function (key) {
+ return Zotero.Items.getIDFromLibraryAndKey(this.libraryID, key);
+ }.bind(this)).filter(function (val) val !== false);
+ relatedItems = Zotero.Items.get(relatedItems);
+ var pred = Zotero.Relations.relatedItemPredicate;
+ for (let i=0; i<relatedItems.length; i++) {
+ let item = relatedItems[i];
+ let uri = Zotero.URI.getItemURI(item);
+ if (obj.relations[pred]) {
+ if (typeof obj.relations[pred] == 'string') {
+ obj.relations[pred] = [uri];
+ }
+ obj.relations[pred].push(uri)
+ }
+ else {
+ obj.relations[pred] = uri;
}
}
- // Item metadata
- for (var i in this._itemData) {
- arr[Zotero.ItemFields.getName(i)] = this.getField(i) + '';
+ // Deleted
+ let deleted = this.deleted;
+ if (deleted || mode == 'full') {
+ obj.deleted = deleted;
}
- if (mode == 1 || mode == 2) {
- if (!arr.title &&
- (this.itemTypeID == Zotero.ItemTypes.getID('letter') ||
- this.itemTypeID == Zotero.ItemTypes.getID('interview'))) {
- arr.title = this.getDisplayTitle(mode == 2) + '';
- }
+ if (options && options.includeDate) {
+ obj.dateAdded = this.dateAdded;
+ obj.dateModified = this.dateModified;
}
- if (!this.isNote() && !this.isAttachment()) {
- // Creators
- arr.creators = [];
- var creators = this.getCreators();
- for (var i in creators) {
- var creator = {};
- // Convert creatorTypeIDs to text
- creator.creatorType =
- Zotero.CreatorTypes.getName(creators[i].creatorTypeID);
- creator.creatorID = creators[i].ref.id;
- creator.firstName = creators[i].ref.firstName;
- creator.lastName = creators[i].ref.lastName;
- creator.fieldMode = creators[i].ref.fieldMode;
- arr.creators.push(creator);
+ if (mode == 'patch') {
+ for (let i in patchBase) {
+ switch (i) {
+ case 'itemKey':
+ case 'itemVersion':
+ case 'dateModified':
+ continue;
+ }
+
+ if (i in obj) {
+ if (obj[i] === patchBase[i]) {
+ delete obj[i];
+ }
+ }
+ else {
+ obj[i] = '';
+ }
}
}
- // Notes
- if (this.isNote()) {
- arr.note = this.getNote();
- var parent = this.getSourceKey();
- if (parent) {
- arr.sourceItemKey = parent;
- }
+ return obj;
+});
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Asynchronous load methods
+//
+//////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Load in the field data from the database
+ */
+Zotero.Item.prototype.loadItemData = Zotero.Promise.coroutine(function* (reload) {
+ Zotero.debug("Loading item data for item " + this.libraryKey);
+
+ if (this._loaded.itemData && !reload) {
+ return;
}
- // Attachments
- if (this.isAttachment()) {
- // Attachments can have embedded notes
- arr.note = this.getNote();
-
- var parent = this.getSourceKey();
- if (parent) {
- arr.sourceItemKey = parent;
- }
+ if (!this.id) {
+ throw ('ItemID not set for object before attempting to load data');
}
- // Attach children of regular items
- if (this.isRegularItem()) {
- // Append attached notes
- arr.notes = [];
- var notes = this.getNotes();
- for (var i in notes) {
- var note = Zotero.Items.get(notes[i]);
- arr.notes.push(note.toArray());
- }
+ if (!this.isNote()) {
+ var sql = "SELECT fieldID, value FROM itemData NATURAL JOIN itemDataValues WHERE itemID=?";
+ yield Zotero.DB.queryAsync(
+ sql,
+ this.id,
+ {
+ onRow: function (row) {
+ this.setField(row.getResultByIndex(0), row.getResultByIndex(1), true);
+ }.bind(this)
+ }
+ );
- arr.attachments = [];
- var attachments = this.getAttachments();
- for (var i in attachments) {
- var attachment = Zotero.Items.get(attachments[i]);
- arr.attachments.push(attachment.toArray());
+ // Mark nonexistent fields as loaded
+ let itemTypeFields = Zotero.ItemFields.getItemTypeFields(this.itemTypeID);
+ for (let i=0; i<itemTypeFields.length; i++) {
+ let fieldID = itemTypeFields[i];
+ if (this._itemData[fieldID] === null) {
+ this._itemData[fieldID] = false;
+ }
}
}
- arr.tags = [];
- var tags = this.getTags();
- for (var i=0, len=tags.length; i<len; i++) {
- var tag = tags[i].serialize();
- tag.tag = tag.fields.name;
- tag.type = tag.fields.type;
- arr.tags.push(tag);
- }
-
- arr.related = this._getRelatedItemsBidirectional();
-
- return arr;
-}
-
-/*
- * Convert the item object into a persistent form
- * for use by the export functions
- *
- * Modes:
- *
- * 1 == e.g. [Letter to Valee]
- * 2 == e.g. [Stothard; Letter to Valee; May 8, 1928]
- */
-Zotero.Item.prototype.serialize = function(mode) {
- if (this.id || this.key) {
- if (!this._primaryDataLoaded) {
- this.loadPrimaryData(true);
- }
- if (!this._itemDataLoaded && this.id) {
- this._loadItemData();
+ if (this.isNote() || this.isAttachment()) {
+ var sql = "SELECT title FROM itemNotes WHERE itemID=?";
+ var row = yield Zotero.DB.rowQueryAsync(sql, this.id);
+ if (row) {
+ let title = row.title;
+ this._noteTitle = title !== false ? title : '';
}
}
- var arr = {};
- arr.primary = {};
- arr.virtual = {};
- arr.fields = {};
+ this._loaded.itemData = true;
+ this._clearChanged('itemData');
+ this.loadDisplayTitle(reload);
+});
+
+
+Zotero.Item.prototype.loadNote = Zotero.Promise.coroutine(function* (reload) {
+ Zotero.debug("Loading note data for item " + this.libraryKey);
- // Primary and virtual fields
- for each(var i in Zotero.Items.primaryFields) {
- switch (i) {
- case 'itemID':
- arr.primary.itemID = this._id;
- continue;
-
- case 'itemTypeID':
- arr.primary.itemType = Zotero.ItemTypes.getName(this.itemTypeID);
- continue;
-
- case 'firstCreator':
- arr.virtual[i] = this['_' + i] + '';
- continue;
-
- case 'numNotes':
- case 'numAttachments':
- arr.virtual[i] = this['_' + i];
- continue;
-
- case 'sourceItemID':
- continue;
-
- // For the rest, just copy over
- default:
- arr.primary[i] = this['_' + i];
- }
+ if (this._loaded.note && !reload) {
+ return;
}
- // Item metadata
- for (var i in this._itemData) {
- arr.fields[Zotero.ItemFields.getName(i)] = this.getField(i) + '';
+ if (!this.isNote() && !this.isAttachment()) {
+ throw new Error("Can only load note for note or attachment item");
}
- if (mode == 1 || mode == 2) {
- if (!arr.fields.title &&
- (this.itemTypeID == Zotero.ItemTypes.getID('letter') ||
- this.itemTypeID == Zotero.ItemTypes.getID('interview'))) {
- arr.fields.title = this.getDisplayTitle(mode == 2) + '';
+ var sql = "SELECT note FROM itemNotes WHERE itemID=?";
+ var row = yield Zotero.DB.rowQueryAsync(sql, this.id);
+ if (row) {
+ let note = row.note;
+
+ // Convert non-HTML notes on-the-fly
+ if (note !== "") {
+ if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
+ note = Zotero.Utilities.htmlSpecialChars(note);
+ note = Zotero.Notes.notePrefix + '<p>'
+ + note.replace(/\n/g, '</p><p>')
+ .replace(/\t/g, ' ')
+ .replace(/ /g, ' ')
+ + '</p>' + Zotero.Notes.noteSuffix;
+ note = note.replace(/<p>\s*<\/p>/g, '<p> </p>');
+ let sql = "UPDATE itemNotes SET note=? WHERE itemID=?";
+ yield Zotero.DB.queryAsync(sql, [note, this.id]);
+ }
+
+ // Don't include <div> wrapper when returning value
+ let startLen = note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)[0].length;
+ let endLen = 6; // "</div>".length
+ note = note.substr(startLen, note.length - startLen - endLen);
}
+
+ this._noteText = note ? note : '';
}
- // Deleted items flag
- if (this.deleted) {
- arr.deleted = true;
+ this._loaded.note = true;
+ this._clearChanged('note');
+});
+
+
+Zotero.Item.prototype.loadDisplayTitle = Zotero.Promise.coroutine(function* (reload) {
+ if (this._displayTitle !== null && !reload) {
+ return;
}
- if (this.isRegularItem()) {
- // Creators
- arr.creators = [];
- var creators = this.getCreators();
- for (var i in creators) {
- var creator = {};
- // Convert creatorTypeIDs to text
- creator.creatorType = Zotero.CreatorTypes.getName(creators[i].creatorTypeID);
- creator.creatorID = creators[i].ref.id;
- creator.firstName = creators[i].ref.firstName;
- creator.lastName = creators[i].ref.lastName;
- creator.fieldMode = creators[i].ref.fieldMode;
- creator.libraryID = creators[i].ref.libraryID;
- creator.key = creators[i].ref.key;
- arr.creators.push(creator);
+ var title = this.getField('title', false, true);
+ var itemTypeID = this.itemTypeID;
+ 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 = [];
+ for (let j=0; j<creatorsData.length; j++) {
+ let creatorData = creatorsData[j];
+ let creatorTypeID = creatorsData[j].creatorTypeID;
+ if ((itemTypeID == 8 && creatorTypeID == 16) || // 'letter'
+ (itemTypeID == 10 && creatorTypeID == 7)) { // 'interview'
+ participants.push(creatorData);
+ }
+ else if ((itemTypeID == 8 && creatorTypeID == 1) || // 'letter'/'author'
+ (itemTypeID == 10 && creatorTypeID == 6)) { // 'interview'/'interviewee'
+ authors.push(creatorData);
+ }
}
- // Attach children of regular items
-
- // Append attached notes
- arr.notes = [];
- var notes = this.getNotes();
- for (var i in notes) {
- var note = Zotero.Items.get(notes[i]);
- arr.notes.push(note.serialize());
+ var strParts = [];
+ if (participants.length > 0) {
+ let names = [];
+ for (let j=0; j<participants.length; j++) {
+ names.push(
+ participants[j].name !== undefined
+ ? participants[j].name
+ : participants[j].lastName
+ );
+ }
+ switch (names.length) {
+ case 1:
+ var str = 'oneParticipant';
+ break;
+
+ case 2:
+ var str = 'twoParticipants';
+ break;
+
+ case 3:
+ var str = 'threeParticipants';
+ break;
+
+ default:
+ var str = 'manyParticipants';
+ }
+ strParts.push(Zotero.getString('pane.items.' + itemTypeName + '.' + str, names));
}
-
- // Append attachments
- arr.attachments = [];
- var attachments = this.getAttachments();
- for (var i in attachments) {
- var attachment = Zotero.Items.get(attachments[i]);
- arr.attachments.push(attachment.serialize());
+ else {
+ strParts.push(Zotero.ItemTypes.getLocalizedString(itemTypeID));
}
+
+ title = '[' + strParts.join('; ') + ']';
}
- // Notes and embedded attachment notes
- else {
- if (this.isAttachment()) {
- arr.attachment = {};
- arr.attachment.linkMode = this.attachmentLinkMode;
- arr.attachment.mimeType = this.attachmentMIMEType;
- arr.attachment.charset = this.attachmentCharset;
- arr.attachment.path = this.attachmentPath;
+ else if (itemTypeID == 17) { // 'case' itemTypeID
+ if (title) { // common law cases always have case names
+ var reporter = this.getField('reporter');
+ if (reporter) {
+ title = title + ' (' + reporter + ')';
+ } else {
+ var court = this.getField('court');
+ if (court) {
+ title = title + ' (' + court + ')';
+ }
+ }
}
-
- arr.note = this.getNote();
- var parent = this.getSourceKey();
- if (parent) {
- arr.sourceItemKey = parent;
+ else { // civil law cases have only shortTitle as case name
+ var strParts = [];
+ var caseinfo = "";
+
+ var part = this.getField('court');
+ if (part) {
+ strParts.push(part);
+ }
+
+ part = Zotero.Date.multipartToSQL(this.getField('date', true, true));
+ if (part) {
+ strParts.push(part);
+ }
+
+ yield this.loadCreators()
+ var creatorData = this.getCreator(0);
+ if (creatorData && creatorData.creatorTypeID === 1) { // author
+ strParts.push(creatorData.lastName);
+ }
+
+ title = '[' + strParts.join(', ') + ']';
}
}
- arr.tags = [];
- var tags = this.getTags();
- for (var i=0, len=tags.length; i<len; i++) {
- arr.tags.push(tags[i].serialize());
- }
-
- arr.related = this._getRelatedItems(true);
- arr.relatedReverse = this._getRelatedItemsReverse();
-
- return arr;
-}
-
-
+ return this._displayTitle = title;
+});
-//////////////////////////////////////////////////////////////////////////////
-//
-// Private Zotero.Item methods
-//
-//////////////////////////////////////////////////////////////////////////////
/*
* Load in the creators from the database
*/
-Zotero.Item.prototype._loadCreators = function() {
+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 ('ItemID not set for item before attempting to load creators');
+ 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 creators = Zotero.DB.query(sql, this.id);
+ var rows = yield Zotero.DB.queryAsync(sql, this.id);
this._creators = [];
- this._creatorsLoaded = true;
+ this._creatorIDs = [];
+ this._loaded.creators = true;
+ this._clearChanged('creators');
- if (!creators) {
+ if (!rows) {
return true;
}
- for (var i=0; i<creators.length; i++) {
- var creatorObj = Zotero.Creators.get(creators[i].creatorID);
- if (!creatorObj) {
- creatorObj = new Zotero.Creator();
- creatorObj.id = creators[i].creatorID;
+ var maxOrderIndex = -1;
+ for (var i=0; i<rows.length; i++) {
+ let row = rows[i];
+ if (row.orderIndex > 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++;
}
- this._creators[creators[i].orderIndex] = {
- ref: creatorObj,
- creatorTypeID: creators[i].creatorTypeID
- };
}
return true;
-}
+});
-/*
- * Load in the field data from the database
- */
-Zotero.Item.prototype._loadItemData = function() {
- if (!this.id) {
- // Log backtrace and data
- try {
- asfasfsa();
- }
- catch (e) {
- Zotero.debug(e);
- Zotero.debug(this._itemTypeID);
- Zotero.debug(this._libraryID);
- Zotero.debug(this._key);
- Zotero.debug(this._dateAdded);
- Zotero.debug(this._dateModified);
- }
- throw ('ItemID not set for object before attempting to load data');
+Zotero.Item.prototype.loadChildItems = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.childItems && !reload) {
+ return;
}
- var sql = "SELECT fieldID, value FROM itemData NATURAL JOIN itemDataValues "
- + "WHERE itemID=?";
- var fields = Zotero.DB.query(sql, this.id);
-
- var itemTypeFields = Zotero.ItemFields.getItemTypeFields(this.itemTypeID);
-
- for each(var field in fields) {
- this.setField(field.fieldID, field.value, true);
+ // 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);
- // Mark nonexistent fields as loaded
- for each(var fieldID in itemTypeFields) {
- if (this._itemData[fieldID] === null) {
- this._itemData[fieldID] = false;
- }
+ //
+ // 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._itemDataLoaded = true;
-}
+ this._loaded.childItems = true;
+ this._clearChanged('childItems');
+});
-Zotero.Item.prototype._loadRelatedItems = function() {
- if (!this.id) {
+Zotero.Item.prototype.loadTags = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.tags && !reload) {
return;
}
- if (!this._primaryDataLoaded) {
- this.loadPrimaryData(true);
+ 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);
- var sql = "SELECT linkedItemID FROM itemSeeAlso WHERE itemID=?";
- var ids = Zotero.DB.columnQuery(sql, this.id);
-
- this._relatedItems = [];
-
- if (ids) {
- for each(var id in ids) {
- this._relatedItems.push(Zotero.Items.get(id));
- }
+ this._tags = [];
+ for (let i=0; i<rows.length; i++) {
+ let row = rows[i];
+ this._tags.push(Zotero.Tags.cleanData(row));
}
- this._relatedItemsLoaded = true;
-}
+ this._loaded.tags = true;
+ this._clearChanged('tags');
+});
-/**
- * Returns related items this item point to
- *
- * @param bool asIDs Return as itemIDs
- * @return array Array of itemIDs
- */
-Zotero.Item.prototype._getRelatedItems = function (asIDs) {
- if (!this._relatedItemsLoaded) {
- this._loadRelatedItems();
+
+Zotero.Item.prototype.loadCollections = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.collections && !reload) {
+ return;
}
-
- if (this._relatedItems.length == 0) {
- return [];
+ if (!this._id) {
+ return;
+ }
+ var sql = "SELECT collectionID FROM collectionItems WHERE itemID=?";
+ this._collections = yield Zotero.DB.columnQueryAsync(sql, this.id);
+ this._loaded.collections = true;
+ this._clearChanged('collections');
+});
+
+
+Zotero.Item.prototype.loadRelations = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.relations && !reload) {
+ return;
}
- // Return itemIDs
- if (asIDs) {
- var ids = [];
- for each(var item in this._relatedItems) {
- ids.push(item.id);
- }
- return ids;
+ Zotero.debug("Loading relations for item " + this.libraryKey);
+
+ this._requireData('primaryData');
+
+ var itemURI = Zotero.URI.getItemURI(this);
+
+ var relations = yield Zotero.Relations.getByURIs(itemURI);
+ relations = relations.map(function (rel) [rel.predicate, rel.object]);
+
+ // Related items are bidirectional, so include any with this item as the object
+ var reverseRelations = yield Zotero.Relations.getByURIs(
+ false, Zotero.Relations.relatedItemPredicate, itemURI
+ );
+ for (let i=0; i<reverseRelations.length; i++) {
+ let rel = reverseRelations[i];
+ relations.push([rel.predicate, rel.subject]);
}
- // Return Zotero.Item objects
- var objs = [];
- for each(var item in this._relatedItems) {
- objs.push(item);
+ // Also include any owl:sameAs relations with this item as the object
+ reverseRelations = yield Zotero.Relations.getByURIs(
+ false, Zotero.Relations.linkedObjectPredicate, itemURI
+ );
+ for (let i=0; i<reverseRelations.length; i++) {
+ let rel = reverseRelations[i];
+ relations.push([rel.predicate, rel.subject]);
}
- return objs;
-}
+
+ this._relations = relations;
+ this._loaded.relations = true;
+ this._clearChanged('relations');
+});
+//////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+//////////////////////////////////////////////////////////////////////////////
/**
- * Returns related items that point to this item
+ * Returns related items this item point to
*
- * @return array Array of itemIDs
+ * @return {String[]} - An array of item keys
*/
-Zotero.Item.prototype._getRelatedItemsReverse = function () {
- if (!this.id) {
- return [];
- }
+Zotero.Item.prototype._getRelatedItems = function () {
+ this._requireData('relations');
+
+ var predicate = Zotero.Relations.relatedItemPredicate;
- var sql = "SELECT itemID FROM itemSeeAlso WHERE linkedItemID=?";
- var ids = Zotero.DB.columnQuery(sql, this.id);
- if (!ids) {
+ var relations = this.getRelations();
+ if (!relations[predicate]) {
return [];
}
- return ids;
-}
-
-
-/**
- * Returns related items this item points to and that point to this item
- *
- * @return array Array of itemIDs
- */
-Zotero.Item.prototype._getRelatedItemsBidirectional = function () {
- var related = this._getRelatedItems(true);
- var reverse = this._getRelatedItemsReverse();
- if (reverse.length) {
- if (!related.length) {
- return reverse;
- }
-
- for each(var id in reverse) {
- if (related.indexOf(id) == -1) {
- related.push(id);
- }
+ var relatedItemURIs = typeof relations[predicate] == 'string'
+ ? [relations[predicate]]
+ : relations[predicate];
+
+ // Pull out object values from related-item relations, turn into items, and pull out keys
+ var keys = [];
+ for (let i=0; i<relatedItemURIs.length; i++) {
+ item = Zotero.URI.getURIItem(relatedItemURIs[i]);
+ if (item) {
+ keys.push(item.key);
}
}
- else if (!related) {
- return [];
- }
- return related;
+ return keys;
}
-Zotero.Item.prototype._setRelatedItems = function (itemIDs) {
- if (!this._relatedItemsLoaded) {
- this._loadRelatedItems();
+
+Zotero.Item.prototype._setRelatedItems = Zotero.Promise.coroutine(function* (itemIDs) {
+ if (!this._loaded.relatedItems) {
+ yield this._loadRelatedItems();
}
if (itemIDs.constructor.name != 'Array') {
throw ('ids must be an array in Zotero.Items._setRelatedItems()');
}
- var currentIDs = this._getRelatedItems(true);
+ var currentIDs = yield this._getRelatedItems(true);
var oldIDs = []; // children being kept
var newIDs = []; // new children
@@ -5107,7 +4615,7 @@ Zotero.Item.prototype._setRelatedItems = function (itemIDs) {
continue;
}
- var item = Zotero.Items.get(id);
+ var item = yield Zotero.Items.getAsync(id);
if (!item) {
throw ("Can't relate item to invalid item " + id
+ " in Zotero.Item._setRelatedItems()");
@@ -5138,77 +4646,30 @@ Zotero.Item.prototype._setRelatedItems = function (itemIDs) {
newIDs = oldIDs.concat(newIDs);
this._relatedItems = [];
for each(var itemID in newIDs) {
- this._relatedItems.push(Zotero.Items.get(itemID));
+ this._relatedItems.push(yield Zotero.Items.getAsync(itemID));
}
return true;
-}
+});
/**
- * The creator has already been changed in itembox.xml before being passed
- * to setCreator()/removeCreator(), so we have to reach in and get its
- * previousData, and ideally try to detect when this private data structure
- * has changed, which it almost certainly will. I am so sorry.
+ * @return {Object} Return a copy of the creators, with additional 'id' properties
*/
Zotero.Item.prototype._getOldCreators = function () {
- var oldCreators = [];
- for (var i in this._creators) {
- if (this._creators[i].ref._changed) {
- if (!this._creators[i].ref._previousData
- && !this._creators[i].ref._previousData.fields) {
- Components.utils.reportError("Previous creator data not available in expected form");
- oldCreators.push(false);
- continue;
- }
- var c = this._creators[i].ref._previousData.fields;
- }
- else {
- var c = this._creators[i].ref;
- }
-
- var old = {
- // Convert creatorTypeIDs to text
- creatorType: Zotero.CreatorTypes.getName(
- this._creators[i].creatorTypeID
- )
- };
-
- if (c.fieldMode) {
- // In 'fields' there's just 'name' for single-field mode
- old.name = typeof c.name == 'undefined' ? c.lastName : c.name;
- }
- else {
- old.firstName = c.firstName;
- old.lastName = c.lastName;
+ var oldCreators = {};
+ for (i=0; i<this._creators.length; i++) {
+ let old = {};
+ for (let field in this._creators[i]) {
+ old[field] = this._creators[i][field];
}
- oldCreators.push(old);
+ // Add 'id' property for efficient DB updates
+ old.id = this._creatorIDs[i];
+ oldCreators[i] = old;
}
return oldCreators;
}
-/**
- * Save old version of data that's being changed, to pass to the notifier
- */
-Zotero.Item.prototype._markFieldChange = function (field, oldValue) {
- // Only save if item already exists and field not already changed
- if (!this.id || !this.exists() || typeof this._previousData[field] != 'undefined') {
- return;
- }
- this._previousData[field] = oldValue;
-}
-
-
-Zotero.Item.prototype._clearFieldChange = function (field) {
- delete this._previousData[field];
-}
-
-
-Zotero.Item.prototype._generateKey = function () {
- return Zotero.Utilities.generateObjectKey();
-}
-
-
Zotero.Item.prototype._disabledCheck = function () {
if (this._disabled) {
var msg = "New Zotero.Item objects shouldn't be accessed after save -- use Zotero.Items.get()";
diff --git a/chrome/content/zotero/xpcom/data/itemFields.js b/chrome/content/zotero/xpcom/data/itemFields.js
@@ -201,7 +201,7 @@ Zotero.ItemFields = new function() {
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
- throw ("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
+ throw new Error("Invalid field '" + baseField + '" for base field');
}
if (fieldID == baseFieldID) {
@@ -233,12 +233,12 @@ Zotero.ItemFields = new function() {
function getFieldIDFromTypeAndBase(itemType, baseField) {
var itemTypeID = Zotero.ItemTypes.getID(itemType);
if (!itemTypeID) {
- throw new Error("Invalid item type '" + itemType + "' in ItemFields.getFieldIDFromTypeAndBase()");
+ throw new Error("Invalid item type '" + itemType + "'");
}
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
- throw new Error("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
+ throw new Error("Invalid field '" + baseField + '" for base field');
}
return _baseTypeFields[itemTypeID][baseFieldID];
diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js
@@ -32,103 +32,75 @@ Zotero.Items = new function() {
this.constructor.prototype = new Zotero.DataObjects();
// Privileged methods
- this.get = get;
- this.exist = exist;
- this.getAll = getAll;
this.add = add;
- this.cacheFields = cacheFields;
- this.erase = erase;
- this.getFirstCreatorSQL = getFirstCreatorSQL;
this.getSortTitle = getSortTitle;
- this.__defineGetter__('primaryFields', function () {
- if (!_primaryFields.length) {
- _primaryFields = Zotero.DB.getColumns('items');
- _primaryFields.splice(_primaryFields.indexOf('clientDateModified'), 1);
- _primaryFields = _primaryFields.concat(
- ['firstCreator', 'numNotes', 'numAttachments', 'sourceItemID']
- );
+ Object.defineProperty(this, "_primaryDataSQLParts", {
+ get: function () {
+ return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = {
+ itemID: "O.itemID",
+ itemTypeID: "O.itemTypeID",
+ dateAdded: "O.dateAdded",
+ dateModified: "O.dateModified",
+ libraryID: "O.libraryID",
+ key: "O.key",
+ // 'itemVersion' because computerProgram has 'version'
+ itemVersion: "O.version AS itemVersion",
+ synced: "O.synced",
+
+ firstCreator: _getFirstCreatorSQL(),
+ sortCreator: _getSortCreatorSQL(),
+
+ deleted: "DI.itemID IS NOT NULL AS deleted",
+
+ numNotes: "(SELECT COUNT(*) FROM itemNotes INo "
+ + "WHERE parentItemID=O.itemID AND "
+ + "INo.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numNotes",
+
+ numNotesTrashed: "(SELECT COUNT(*) FROM itemNotes INo "
+ + "WHERE parentItemID=O.itemID AND "
+ + "INo.itemID IN (SELECT itemID FROM deletedItems)) AS numNotesTrashed",
+
+ numNotesEmbedded: "(SELECT COUNT(*) FROM itemAttachments IA "
+ + "JOIN itemNotes USING (itemID) "
+ + "WHERE IA.parentItemID=O.itemID AND "
+ + "note!='' AND note!='" + Zotero.Notes.defaultNote + "' AND "
+ + "IA.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numNotesEmbedded",
+
+ numNotesEmbeddedTrashed: "(SELECT COUNT(*) FROM itemAttachments IA "
+ + "JOIN itemNotes USING (itemID) "
+ + "WHERE IA.parentItemID=O.itemID AND "
+ + "note!='' AND note!='" + Zotero.Notes.defaultNote + "' AND "
+ + "IA.itemID IN (SELECT itemID FROM deletedItems)) "
+ + "AS numNotesEmbeddedTrashed",
+
+ numAttachments: "(SELECT COUNT(*) FROM itemAttachments IA WHERE parentItemID=O.itemID AND "
+ + "IA.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numAttachments",
+
+ numAttachmentsTrashed: "(SELECT COUNT(*) FROM itemAttachments IA WHERE parentItemID=O.itemID AND "
+ + "IA.itemID IN (SELECT itemID FROM deletedItems)) AS numAttachmentsTrashed",
+
+ parentID: "(CASE O.itemTypeID WHEN 14 THEN IAP.itemID WHEN 1 THEN INoP.itemID END) AS parentID",
+ parentKey: "(CASE O.itemTypeID WHEN 14 THEN IAP.key WHEN 1 THEN INoP.key END) AS parentKey",
+
+ attachmentCharset: "IA.charsetID AS attachmentCharset",
+ attachmentLinkMode: "IA.linkMode AS attachmentLinkMode",
+ attachmentContentType: "IA.contentType AS attachmentContentType",
+ attachmentPath: "IA.path AS attachmentPath",
+ attachmentSyncState: "IA.syncState AS attachmentSyncState"
+ });
}
-
- // Once primary fields have been cached, get rid of getter for speed purposes
- delete this.primaryFields;
- this.primaryFields = _primaryFields;
-
- return _primaryFields;
});
-
// Private members
- var _cachedFields = [];
+ var _primaryDataSQLParts;
+ var _cachedFields = {};
var _firstCreatorSQL = '';
- var _primaryFields = [];
+ var _sortCreatorSQL = '';
var _emptyTrashIdleObserver = null;
var _emptyTrashTimer = null;
- /*
- * Retrieves (and loads, if necessary) an arbitrary number of items
- *
- * Can be passed ids as individual parameters or as an array of ids, or both
- *
- * If only one argument and it's an id, return object directly;
- * otherwise, return array
- */
- function get() {
- var toLoad = [];
- var loaded = [];
-
- if (!arguments[0]) {
- Zotero.debug('No arguments provided to Items.get()');
- return false;
- }
-
- var ids = Zotero.flattenArguments(arguments);
-
- for (var i=0; i<ids.length; i++) {
- // Check if already loaded
- if (!this._objectCache[ids[i]]) {
- toLoad.push(ids[i]);
- }
- }
-
- // New items to load
- if (toLoad.length) {
- this._load(toLoad);
- }
-
- // If single id, return the object directly
- if (arguments[0] && typeof arguments[0]!='object'
- && typeof arguments[1]=='undefined') {
- if (!this._objectCache[arguments[0]]) {
- Zotero.debug("Item " + arguments[0] + " doesn't exist", 2);
- return false;
- }
- return this._objectCache[arguments[0]];
- }
-
- // Otherwise, build return array
- for (i=0; i<ids.length; i++) {
- if (!this._objectCache[ids[i]]) {
- Zotero.debug("Item " + ids[i] + " doesn't exist", 2);
- continue;
- }
- loaded.push(this._objectCache[ids[i]]);
- }
-
- return loaded;
- }
-
-
- function exist(itemIDs) {
- var sql = "SELECT itemID FROM items WHERE itemID IN ("
- + itemIDs.map(function () '?').join() + ")";
- var exist = Zotero.DB.columnQuery(sql, itemIDs);
- return exist ? exist : [];
- }
-
-
-
/**
* Return items marked as deleted
*
@@ -137,36 +109,38 @@ Zotero.Items = new function() {
* Zotero.Item objects
* @return {Zotero.Item[]|Integer[]}
*/
- this.getDeleted = function (libraryID, asIDs, days) {
+ this.getDeleted = Zotero.Promise.coroutine(function* (libraryID, asIDs, days) {
var sql = "SELECT itemID FROM items JOIN deletedItems USING (itemID) "
+ "WHERE libraryID=?";
-
if (days) {
sql += " AND dateDeleted<=DATE('NOW', '-" + parseInt(days) + " DAYS')";
}
- var ids = Zotero.DB.columnQuery(sql, [libraryID]);
- if (!ids) {
+ var ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);
+ if (!ids.length) {
return [];
}
if (asIDs) {
return ids;
}
- return this.get(ids);
- }
+ return this.getAsync(ids);
+ });
- /*
- * Returns all items in the database
+ /**
+ * Returns all items in a given library
*
- * If |onlyTopLevel|, don't include child items
+ * @param {Integer} libraryID
+ * @param {Boolean} [onlyTopLevel=false] If true, don't include child items
+ * @param {Boolean} [includeDeleted=false] If true, include deleted items
+ * @return {Promise<Array<Zotero.Item>>}
*/
- function getAll(onlyTopLevel, libraryID, includeDeleted) {
+ this.getAll = Zotero.Promise.coroutine(function* (libraryID, onlyTopLevel, includeDeleted) {
var sql = 'SELECT A.itemID FROM items A';
if (onlyTopLevel) {
sql += ' LEFT JOIN itemNotes B USING (itemID) '
+ 'LEFT JOIN itemAttachments C ON (C.itemID=A.itemID) '
- + 'WHERE B.sourceItemID IS NULL AND C.sourceItemID IS NULL';
+ + 'WHERE B.parentItemID IS NULL AND C.parentItemID IS NULL';
}
else {
sql += " WHERE 1";
@@ -175,9 +149,9 @@ Zotero.Items = new function() {
sql += " AND A.itemID NOT IN (SELECT itemID FROM deletedItems)";
}
sql += " AND libraryID=?";
- var ids = Zotero.DB.columnQuery(sql, libraryID);
- return this.get(ids);
- }
+ var ids = yield Zotero.DB.columnQueryAsync(sql, libraryID);
+ return this.getAsync(ids);
+ });
/*
@@ -247,38 +221,47 @@ Zotero.Items = new function() {
}
var id = item.save();
- return this.get(id);
- }
-
-
- this.isPrimaryField = function (field) {
- return this.primaryFields.indexOf(field) != -1;
+ return this.getAsync(id);
}
- function cacheFields(fields, items) {
+ 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" : ''));
+ + (items ? " for " + items.length + " items" : '')
+ + " in library " + libraryID);
+
if (items && items.length > 0) {
- this._load(items);
+ yield this._load(libraryID, items);
}
else {
- this._load();
+ yield this._load(libraryID);
}
var primaryFields = [];
var fieldIDs = [];
for each(var field in fields) {
// Check if field already cached
- if (_cachedFields.indexOf(field) != -1) {
+ if (_cachedFields[libraryID] && _cachedFields[libraryID].indexOf(field) != -1) {
continue;
}
- _cachedFields.push(field);
+ if (!_cachedFields[libraryID]) {
+ _cachedFields[libraryID] = [];
+ }
+ _cachedFields[libraryID].push(field);
if (this.isPrimaryField(field)) {
primaryFields.push(field);
@@ -295,93 +278,145 @@ Zotero.Items = new function() {
}
if (primaryFields.length) {
- var sql = "SELECT itemID, " + primaryFields.join(', ') + " FROM items";
+ var sql = "SELECT O.itemID, "
+ + primaryFields.map((val) => this.getPrimaryDataSQLPart(val)).join(', ')
+ + this.primaryDataSQLFrom + " AND O.libraryID=?";
+ var params = [libraryID];
if (items) {
- sql += " WHERE itemID IN (" + items.join() + ")";
- }
- var rows = Zotero.DB.query(sql);
- for each(var row in rows) {
- //Zotero.debug('Calling loadFromRow for item ' + row.itemID);
- this._objectCache[row.itemID].loadFromRow(row);
+ 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<primaryFields.length; i++) {
+ obj[primaryFields[i]] = row.getResultByIndex(i);
+ }
+ Zotero.debug(obj.itemID);
+ Zotero.debug(Object.keys(this._objectCache));
+ this._objectCache[obj.itemID].loadFromRow(rowObj);
+ }.bind(this)
+ }
+ );
}
// All fields already cached
if (!fieldIDs.length) {
+ Zotero.debug('All fields already cached');
return;
}
- var allItemIDs = Zotero.DB.columnQuery("SELECT itemID FROM items");
+ var sql = "SELECT itemID FROM items WHERE libraryID=?";
+ var params = [libraryID];
+ var allItemIDs = yield Zotero.DB.columnQueryAsync(sql, params);
var itemFieldsCached = {};
- var sql = "SELECT itemID, fieldID, value FROM itemData "
- + "NATURAL JOIN itemDataValues WHERE ";
+ var sql = "SELECT itemID, fieldID, value FROM items JOIN itemData USING (itemID) "
+ + "JOIN itemDataValues USING (valueID) WHERE libraryID=?";
+ var params = [libraryID];
if (items) {
- sql += "itemID IN (" + items.join() + ") AND ";
- }
- sql += "fieldID IN (" + fieldIDs.join() + ")";
-
- var itemDataRows = Zotero.DB.query(sql);
- for each(var row in itemDataRows) {
- //Zotero.debug('Setting field ' + row.fieldID + ' for item ' + row.itemID);
- if (this._objectCache[row.itemID]) {
- this._objectCache[row.itemID].setField(row.fieldID, row.value, true);
+ sql += " AND itemID IN (" + items.join() + ")";
+ }
+ sql += " AND fieldID IN (" + fieldIDs.join() + ")";
+ yield Zotero.DB.queryAsync(
+ sql,
+ params,
+ {
+ onRow: function (row) {
+ let itemID = row.getResultByIndex(0);
+ let fieldID = row.getResultByIndex(1);
+ let value = row.getResultByIndex(2);
+
+ //Zotero.debug('Setting field ' + fieldID + ' for item ' + itemID);
+ if (this._objectCache[itemID]) {
+ this._objectCache[itemID].setField(fieldID, value, true);
+ }
+ else {
+ if (!missingItems) {
+ var missingItems = {};
+ }
+ if (!missingItems[itemID]) {
+ missingItems[itemID] = true;
+ Zotero.debug("itemData row references nonexistent item " + itemID);
+ Components.utils.reportError("itemData row references nonexistent item " + itemID);
+ }
+ }
+
+ if (!itemFieldsCached[itemID]) {
+ itemFieldsCached[itemID] = {};
+ }
+ itemFieldsCached[itemID][fieldID] = true;
+ }.bind(this)
}
- else {
- if (!missingItems) {
- var missingItems = {};
- }
- if (!missingItems[row.itemID]) {
- missingItems[row.itemID] = true;
- Components.utils.reportError("itemData row references nonexistent item " + row.itemID);
+ );
+
+ // Set nonexistent fields in the cache list to false (instead of null)
+ for (let i=0; i<allItemIDs.length; i++) {
+ let itemID = allItemIDs[i];
+ for (let j=0; j<fieldIDs.length; j++) {
+ let fieldID = fieldIDs[j];
+ if (Zotero.ItemFields.isValidForType(fieldID, this._objectCache[itemID].itemTypeID)) {
+ if (!itemFieldsCached[itemID] || !itemFieldsCached[itemID][fieldID]) {
+ //Zotero.debug('Setting field ' + fieldID + ' to false for item ' + itemID);
+ this._objectCache[itemID].setField(fieldID, false, true);
+ }
}
}
-
- if (!itemFieldsCached[row.itemID]) {
- itemFieldsCached[row.itemID] = {};
- }
- itemFieldsCached[row.itemID][row.fieldID] = true;
}
- // If 'title' is one of the fields, load in note titles
+ // If 'title' is one of the fields, load in display titles (note titles, letter titles...)
if (fields.indexOf('title') != -1) {
var titleFieldID = Zotero.ItemFields.getID('title');
- var sql = "SELECT itemID, title FROM itemNotes WHERE itemID"
- + " NOT IN (SELECT itemID FROM itemAttachments)";
+
+ // Note titles
+ var sql = "SELECT itemID, title FROM items JOIN itemNotes USING (itemID) "
+ + "WHERE libraryID=? AND itemID NOT IN (SELECT itemID FROM itemAttachments)";
+ var params = [libraryID];
if (items) {
sql += " AND itemID IN (" + items.join() + ")";
}
- var rows = Zotero.DB.query(sql);
- for each(var row in rows) {
- //Zotero.debug('Setting title for note ' + row.itemID);
- if (this._objectCache[row.itemID]) {
- this._objectCache[row.itemID].setField(titleFieldID, row.title, true);
- }
- else {
- if (!missingItems) {
- var missingItems = {};
- }
- if (!missingItems[row.itemID]) {
- missingItems[row.itemID] = true;
- Components.utils.reportError("itemData row references nonexistent item " + row.itemID);
- }
+ yield Zotero.DB.queryAsync(
+ sql,
+ params,
+ {
+ onRow: function (row) {
+ let itemID = row.getResultByIndex(0);
+ let title = row.getResultByIndex(1);
+
+ //Zotero.debug('Setting title for note ' + row.itemID);
+ if (this._objectCache[itemID]) {
+ this._objectCache[itemID].setField(titleFieldID, title, true);
+ }
+ else {
+ if (!missingItems) {
+ var missingItems = {};
+ }
+ if (!missingItems[itemID]) {
+ missingItems[itemID] = true;
+ Components.utils.reportError(
+ "itemData row references nonexistent item " + itemID
+ );
+ }
+ }
+ }.bind(this)
}
+ );
+
+ // Display titles
+ for (let i=0; i<allItemIDs.length; i++) {
+ let itemID = allItemIDs[i];
+ let item = this._objectCache[itemID];
+ yield this._objectCache[itemID].loadDisplayTitle()
}
}
- // Set nonexistent fields in the cache list to false (instead of null)
- for each(var itemID in allItemIDs) {
- for each(var fieldID in fieldIDs) {
- if (Zotero.ItemFields.isValidForType(fieldID, this._objectCache[itemID].itemTypeID)) {
- if (!itemFieldsCached[itemID] || !itemFieldsCached[itemID][fieldID]) {
- //Zotero.debug('Setting field ' + fieldID + ' to false for item ' + itemID);
- this._objectCache[itemID].setField(fieldID, false, true);
- }
- }
- }
- }
- }
+ Zotero.debug("Cached fields in " + ((new Date) - t) + "ms");
+ });
this.merge = function (item, otherItems) {
@@ -398,7 +433,7 @@ Zotero.Items = new function() {
// TODO: Skip identical children?
- attachment.setSource(item.id);
+ attachment.parentID = item.id;
attachment.save();
}
@@ -419,8 +454,7 @@ Zotero.Items = new function() {
}
// Related items
- var relatedItems = otherItem.relatedItemsBidirectional;
- Zotero.debug(item._getRelatedItems(true));
+ var relatedItems = otherItem.relatedItems;
for each(var relatedItemID in relatedItems) {
item.addRelatedItem(relatedItemID);
}
@@ -450,54 +484,49 @@ Zotero.Items = new function() {
this.trash = function (ids) {
ids = Zotero.flattenArguments(ids);
- Zotero.UnresponsiveScriptIndicator.disable();
- try {
- Zotero.DB.beginTransaction();
- for each(var id in ids) {
- var item = this.get(id);
+ return Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ let item = yield this.getAsync(id);
if (!item) {
Zotero.debug('Item ' + id + ' does not exist in Items.trash()!', 1);
Zotero.Notifier.trigger('delete', 'item', id);
continue;
}
item.deleted = true;
- item.save();
+ yield item.save({
+ skipDateModifiedUpdate: true
+ });
}
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
- finally {
- Zotero.UnresponsiveScriptIndicator.enable();
- }
+ }.bind(this));
}
/**
* @param {Integer} days Only delete items deleted more than this many days ago
*/
- this.emptyTrash = function (libraryID, days, limit) {
+ this.emptyTrash = Zotero.Promise.coroutine(function* (libraryID, days, limit) {
var t = new Date();
- Zotero.DB.beginTransaction();
- var deletedIDs = this.getDeleted(libraryID, true, days);
- if (deletedIDs.length) {
- if (limit) {
- deletedIDs = deletedIDs.slice(0, limit - 1)
- }
- this.erase(deletedIDs);
- Zotero.Notifier.trigger('refresh', 'trash', libraryID);
- }
- Zotero.DB.commitTransaction();
+ var deletedIDs = [];
+ yield Zotero.DB.executeTransaction(function* () {
+ deletedIDs = yield this.getDeleted(libraryID, true, days);
+ if (deletedIDs.length) {
+ if (limit) {
+ deletedIDs = deletedIDs.slice(0, limit - 1)
+ }
+ yield this.erase(deletedIDs);
+ Zotero.Notifier.trigger('refresh', 'trash', libraryID);
+ }
+ }.bind(this));
+
if (deletedIDs.length) {
Zotero.debug("Emptied " + deletedIDs.length + " item(s) from trash in " + (new Date() - t) + " ms");
}
return deletedIDs.length;
- }
+ });
/**
@@ -519,23 +548,24 @@ Zotero.Items = new function() {
// TODO: increase number after dealing with slow
// tag.getLinkedItems() call during deletes
var num = 10;
- var deleted = Zotero.Items.emptyTrash(null, days, num);
-
- if (!deleted) {
- _emptyTrashTimer = null;
- return;
- }
-
- // Set a timer to do more every few seconds
- if (!_emptyTrashTimer) {
- _emptyTrashTimer = Components.classes["@mozilla.org/timer;1"].
- createInstance(Components.interfaces.nsITimer);
- }
- _emptyTrashTimer.init(
- _emptyTrashIdleObserver.observe,
- 5 * 1000,
- Components.interfaces.nsITimer.TYPE_ONE_SHOT
- );
+ Zotero.Items.emptyTrash(null, days, num)
+ .then(function (deleted) {
+ if (!deleted) {
+ _emptyTrashTimer = null;
+ return;
+ }
+
+ // Set a timer to do more every few seconds
+ if (!_emptyTrashTimer) {
+ _emptyTrashTimer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ }
+ _emptyTrashTimer.init(
+ _emptyTrashIdleObserver.observe,
+ 5 * 1000,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
+ );
+ });
}
// When no longer idle, cancel timer
else if (topic == 'back') {
@@ -555,50 +585,66 @@ Zotero.Items = new function() {
/**
* Delete item(s) from database and clear from internal array
*
- * @param {Integer|Integer[]} ids Item ids
+ * @param {Integer|Integer[]} ids - Item ids
+ * @return {Promise}
*/
- function erase(ids) {
- ids = Zotero.flattenArguments(ids);
-
- var usiDisabled = Zotero.UnresponsiveScriptIndicator.disable();
- try {
- Zotero.DB.beginTransaction();
- for each(var id in ids) {
- var item = this.get(id);
+ this.erase = function (ids) {
+ return Zotero.DB.executeTransaction(function* () {
+ ids = Zotero.flattenArguments(ids);
+
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ let item = yield this.getAsync(id);
if (!item) {
Zotero.debug('Item ' + id + ' does not exist in Items.erase()!', 1);
continue;
}
- item.erase(); // calls unload()
- item = undefined;
+ yield item.erase(); // calls unload()
}
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
- finally {
- if (usiDisabled) {
- Zotero.UnresponsiveScriptIndicator.enable();
- }
- }
- }
+ }.bind(this));
+ };
/**
* Purge unused data values
*/
- this.purge = function () {
+ this.purge = Zotero.Promise.coroutine(function* () {
if (!Zotero.Prefs.get('purge.items')) {
return;
}
var sql = "DELETE FROM itemDataValues WHERE valueID NOT IN "
+ "(SELECT valueID FROM itemData)";
- Zotero.DB.query(sql);
+ yield Zotero.DB.queryAsync(sql);
Zotero.Prefs.set('purge.items', false)
+ });
+
+
+ this._getPrimaryDataSQL = function () {
+ // This should match Zotero.Item.loadPrimaryData, but with all possible columns
+ return "SELECT "
+ + Object.keys(this._primaryDataSQLParts).map((val) => this._primaryDataSQLParts[val]).join(', ')
+ + this.primaryDataSQLFrom;
+ };
+
+
+ this.primaryDataSQLFrom = " FROM items O "
+ + "LEFT JOIN itemAttachments IA USING (itemID) "
+ + "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
+ + "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ + "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
+ + "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID) "
+ + "WHERE 1";
+
+
+ this._postLoad = function (libraryID, ids) {
+ if (!ids) {
+ if (!_cachedFields[libraryID]) {
+ _cachedFields[libraryID] = [];
+ }
+ _cachedFields[libraryID] = this.primaryFields.concat();
+ }
}
@@ -607,7 +653,7 @@ Zotero.Items = new function() {
*
* Why do we do this entirely in SQL? Because we're crazy. Crazy like foxes.
*/
- function getFirstCreatorSQL() {
+ function _getFirstCreatorSQL() {
if (_firstCreatorSQL) {
return _firstCreatorSQL;
}
@@ -620,90 +666,84 @@ Zotero.Items = new function() {
"CASE (" +
"SELECT COUNT(*) FROM itemCreators IC " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
- "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
- "WHERE itemID=I.itemID AND primaryField=1" +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1" +
") " +
"WHEN 0 THEN NULL " +
"WHEN 1 THEN (" +
"SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
- "NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
- "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
- "WHERE itemID=I.itemID AND primaryField=1" +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1" +
") " +
"WHEN 2 THEN (" +
"SELECT " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
- "NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
- "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
- "WHERE itemID=I.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedAnd + " ' || " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
- "NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
- "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
- "WHERE itemID=I.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" +
") " +
"ELSE (" +
"SELECT " +
"(SELECT lastName FROM itemCreators IC NATURAL JOIN creators " +
- "NATURAL JOIN creatorData " +
"LEFT JOIN itemTypeCreatorTypes ITCT " +
- "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=I.itemTypeID) " +
- "WHERE itemID=I.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedEtAl + "' " +
") " +
"END, " +
// Then try editors
"CASE (" +
- "SELECT COUNT(*) FROM itemCreators WHERE itemID=I.itemID AND creatorTypeID IN (3)" +
+ "SELECT COUNT(*) FROM itemCreators WHERE itemID=O.itemID AND creatorTypeID IN (3)" +
") " +
"WHEN 0 THEN NULL " +
"WHEN 1 THEN (" +
"SELECT lastName FROM itemCreators NATURAL JOIN creators " +
- "NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (3)" +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3)" +
") " +
"WHEN 2 THEN (" +
"SELECT " +
- "(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
+ "(SELECT lastName FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedAnd + " ' || " +
- "(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1,1) " +
+ "(SELECT lastName FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1,1) " +
") " +
"ELSE (" +
"SELECT " +
- "(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
+ "(SELECT lastName FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedEtAl + "' " +
") " +
"END, " +
// Then try contributors
"CASE (" +
- "SELECT COUNT(*) FROM itemCreators WHERE itemID=I.itemID AND creatorTypeID IN (2)" +
+ "SELECT COUNT(*) FROM itemCreators WHERE itemID=O.itemID AND creatorTypeID IN (2)" +
") " +
"WHEN 0 THEN NULL " +
"WHEN 1 THEN (" +
"SELECT lastName FROM itemCreators NATURAL JOIN creators " +
- "NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (2)" +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2)" +
") " +
"WHEN 2 THEN (" +
"SELECT " +
- "(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
+ "(SELECT lastName FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedAnd + " ' || " +
- "(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1,1) " +
+ "(SELECT lastName FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1,1) " +
") " +
"ELSE (" +
"SELECT " +
- "(SELECT lastName FROM itemCreators NATURAL JOIN creators NATURAL JOIN creatorData " +
- "WHERE itemID=I.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
+ "(SELECT lastName FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
" || ' " + localizedEtAl + "' " +
") " +
"END" +
@@ -714,8 +754,130 @@ Zotero.Items = new function() {
}
+ /*
+ * Generate SQL to retrieve sortCreator field
+ */
+ function _getSortCreatorSQL() {
+ if (_sortCreatorSQL) {
+ return _sortCreatorSQL;
+ }
+
+ var nameSQL = "lastName || ' ' || firstName ";
+
+ var sql = "COALESCE(" +
+ // First try for primary creator types
+ "CASE (" +
+ "SELECT COUNT(*) FROM itemCreators IC " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1" +
+ ") " +
+ "WHEN 0 THEN NULL " +
+ "WHEN 1 THEN (" +
+ "SELECT " + nameSQL + "FROM itemCreators IC NATURAL JOIN creators " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1" +
+ ") " +
+ "WHEN 2 THEN (" +
+ "SELECT " +
+ "(SELECT " + nameSQL + " FROM itemCreators IC NATURAL JOIN creators " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators IC NATURAL JOIN creators " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" +
+ ") " +
+ "ELSE (" +
+ "SELECT " +
+ "(SELECT " + nameSQL + " FROM itemCreators IC NATURAL JOIN creators " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators IC NATURAL JOIN creators " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 1,1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators IC NATURAL JOIN creators " +
+ "LEFT JOIN itemTypeCreatorTypes ITCT " +
+ "ON (IC.creatorTypeID=ITCT.creatorTypeID AND ITCT.itemTypeID=O.itemTypeID) " +
+ "WHERE itemID=O.itemID AND primaryField=1 ORDER BY orderIndex LIMIT 2,1)" +
+ ") " +
+ "END, " +
+
+ // Then try editors
+ "CASE (" +
+ "SELECT COUNT(*) FROM itemCreators WHERE itemID=O.itemID AND creatorTypeID IN (3)" +
+ ") " +
+ "WHEN 0 THEN NULL " +
+ "WHEN 1 THEN (" +
+ "SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3)" +
+ ") " +
+ "WHEN 2 THEN (" +
+ "SELECT " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1,1) " +
+ ") " +
+ "ELSE (" +
+ "SELECT " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 1,1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (3) ORDER BY orderIndex LIMIT 2,1)" +
+ ") " +
+ "END, " +
+
+ // Then try contributors
+ "CASE (" +
+ "SELECT COUNT(*) FROM itemCreators WHERE itemID=O.itemID AND creatorTypeID IN (2)" +
+ ") " +
+ "WHEN 0 THEN NULL " +
+ "WHEN 1 THEN (" +
+ "SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2)" +
+ ") " +
+ "WHEN 2 THEN (" +
+ "SELECT " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1,1) " +
+ ") " +
+ "ELSE (" +
+ "SELECT " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 1,1)" +
+ " || ' ' || " +
+ "(SELECT " + nameSQL + " FROM itemCreators NATURAL JOIN creators " +
+ "WHERE itemID=O.itemID AND creatorTypeID IN (2) ORDER BY orderIndex LIMIT 2,1)" +
+ ") " +
+ "END" +
+ ") AS sortCreator";
+
+ _sortCreatorSQL = sql;
+ return sql;
+ }
+
+
function getSortTitle(title) {
- if (!title) {
+ if (title === false || title === undefined) {
return '';
}
if (typeof title == 'number') {
@@ -723,57 +885,4 @@ Zotero.Items = new function() {
}
return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1')
}
-
-
- this._load = function () {
- if (!arguments[0] && !this._reloadCache) {
- return;
- }
-
- // Should be the same as parts in Zotero.Item.loadPrimaryData
- var sql = 'SELECT I.*, '
- + getFirstCreatorSQL() + ', '
- + "(SELECT COUNT(*) FROM itemNotes INo WHERE sourceItemID=I.itemID AND "
- + "INo.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numNotes, "
- + "(SELECT COUNT(*) FROM itemAttachments IA WHERE sourceItemID=I.itemID AND "
- + "IA.itemID NOT IN (SELECT itemID FROM deletedItems)) AS numAttachments, "
- + "(CASE I.itemTypeID WHEN 14 THEN (SELECT sourceItemID FROM itemAttachments IA WHERE IA.itemID=I.itemID) "
- + "WHEN 1 THEN (SELECT sourceItemID FROM itemNotes INo WHERE INo.itemID=I.itemID) END) AS sourceItemID "
- + 'FROM items I WHERE 1';
- if (arguments[0]) {
- sql += ' AND I.itemID IN (' + Zotero.join(arguments[0], ',') + ')';
- }
- var itemsRows = Zotero.DB.query(sql),
- itemIDs = {};
- for(var i=0, n=itemsRows.length; i<n; i++) {
- var row = itemsRows[i],
- itemID = row.itemID;
- itemIDs[itemID] = true;
-
- // Item doesn't exist -- create new object and stuff in array
- if (!this._objectCache[itemID]) {
- var item = new Zotero.Item();
- item.loadFromRow(row, true);
- this._objectCache[itemID] = item;
- }
- // Existing item -- reload in place
- else {
- this._objectCache[itemID].loadFromRow(row, true);
- }
- }
-
- // If loading all items, remove old items that no longer exist
- if (!arguments[0]) {
- for each(var c in this._objectCache) {
- if (!itemIDs[c.id]) {
- this.unload(c.id);
- }
- }
-
- _cachedFields = ['itemID', 'itemTypeID', 'dateAdded', 'dateModified',
- 'firstCreator', 'numNotes', 'numAttachments', 'sourceItemID', 'numChildren'];
- this._reloadCache = false;
- }
- }
}
-
diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js
@@ -25,8 +25,17 @@
Zotero.Libraries = new function () {
this.exists = function (libraryID) {
- var sql = "SELECT COUNT(*) FROM libraries WHERE libraryID=?";
- return !!Zotero.DB.valueQuery(sql, [libraryID]);
+ // Until there are other library types, this can just check groups,
+ // which already preload ids at startup
+ try {
+ return !!Zotero.Groups.getGroupIDFromLibraryID(libraryID);
+ }
+ catch (e) {
+ if (e.getMessage().indexOf("does not exist") != -1) {
+ return false;
+ }
+ throw e;
+ }
}
@@ -44,6 +53,11 @@ Zotero.Libraries = new function () {
}
+ this.dbLibraryID = function (libraryID) {
+ return (libraryID == Zotero.libraryID) ? 0 : libraryID;
+ }
+
+
this.getName = function (libraryID) {
if (!libraryID) {
return Zotero.getString('pane.collections.library');
@@ -63,7 +77,7 @@ Zotero.Libraries = new function () {
this.getType = function (libraryID) {
- if (libraryID === 0 || !Zotero.libraryID || libraryID == Zotero.libraryID) {
+ if (libraryID === 0 || !Zotero.libraryID || libraryID == Zotero.libraryID) {
return 'user';
}
var sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
@@ -74,6 +88,26 @@ Zotero.Libraries = new function () {
return libraryType;
}
+ /**
+ * @param {Integer} libraryID
+ * @return {Promise:Integer}
+ */
+ this.getVersion = function (libraryID) {
+ var sql = "SELECT version FROM libraries WHERE libraryID=?";
+ return Zotero.DB.valueQueryAsync(sql, libraryID);
+ }
+
+
+ /**
+ * @param {Integer} libraryID
+ * @param {Integer} version
+ * @return {Promise}
+ */
+ this.setVersion = function (libraryID, version) {
+ var sql = "UPDATE libraries SET version=? WHERE libraryID=?";
+ return Zotero.DB.queryAsync(sql, [version, libraryID]);
+ }
+
this.isEditable = function (libraryID) {
var type = this.getType(libraryID);
diff --git a/chrome/content/zotero/xpcom/data/relation.js b/chrome/content/zotero/xpcom/data/relation.js
@@ -106,32 +106,32 @@ Zotero.Relation.prototype._set = function (field, val) {
*
* @return bool TRUE if the relation exists, FALSE if not
*/
-Zotero.Relation.prototype.exists = function () {
+Zotero.Relation.prototype.exists = Zotero.Promise.coroutine(function* () {
if (this.id) {
var sql = "SELECT COUNT(*) FROM relations WHERE relationID=?";
- return !!Zotero.DB.valueQuery(sql, this.id);
+ return !!(yield Zotero.DB.valueQueryAsync(sql, this.id));
}
if (this.libraryID && this.subject && this.predicate && this.object) {
var sql = "SELECT COUNT(*) FROM relations WHERE libraryID=? AND "
+ "subject=? AND predicate=? AND object=?";
var params = [this.libraryID, this.subject, this.predicate, this.object];
- return !!Zotero.DB.valueQuery(sql, params);
+ return !!(yield Zotero.DB.valueQueryAsync(sql, params));
}
throw ("ID or libraryID/subject/predicate/object not set in Zotero.Relation.exists()");
-}
+});
-Zotero.Relation.prototype.load = function () {
+Zotero.Relation.prototype.load = Zotero.Promise.coroutine(function* () {
var id = this._id;
if (!id) {
- throw ("ID not set in Zotero.Relation.load()");
+ throw new Error("ID not set");
}
var sql = "SELECT * FROM relations WHERE ROWID=?";
- var row = Zotero.DB.rowQuery(sql, id);
+ var row = yield Zotero.DB.rowQueryAsync(sql, id);
if (!row) {
return;
}
@@ -144,10 +144,11 @@ Zotero.Relation.prototype.load = function () {
this._loaded = true;
return true;
-}
+});
-Zotero.Relation.prototype.save = function () {
+// TODO: async
+Zotero.Relation.prototype.save = Zotero.Promise.coroutine(function* () {
if (this.id) {
throw ("Existing relations cannot currently be altered in Zotero.Relation.save()");
}
@@ -168,7 +169,7 @@ Zotero.Relation.prototype.save = function () {
+ "(libraryID, subject, predicate, object, clientDateModified) "
+ "VALUES (?, ?, ?, ?, ?)";
try {
- var insertID = Zotero.DB.query(
+ var insertID = yield Zotero.DB.queryAsync(
sql,
[
this.libraryID,
@@ -181,52 +182,46 @@ Zotero.Relation.prototype.save = function () {
}
catch (e) {
// If above failed, try deleting existing row, in case libraryID has changed
- Zotero.DB.beginTransaction();
-
- var sql2 = "SELECT COUNT(*) FROM relations WHERE subject=? AND predicate=? AND object=?";
- if (Zotero.DB.valueQuery(sql2, [this.subject, this.predicate, this.object])) {
- // Delete
- sql2 = "DELETE FROM relations WHERE subject=? AND predicate=? AND object=?";
- Zotero.DB.query(sql2, [this.subject, this.predicate, this.object]);
-
- // Insert with original query
- var insertID = Zotero.DB.query(
- sql,
- [
- this.libraryID,
- this.subject,
- this.predicate,
- this.object,
- Zotero.DB.transactionDateTime
- ]
- );
- }
-
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ var sql2 = "SELECT COUNT(*) FROM relations WHERE subject=? AND predicate=? AND object=?";
+ if (yield Zotero.DB.valueQueryAsync(sql2, [this.subject, this.predicate, this.object])) {
+ // Delete
+ sql2 = "DELETE FROM relations WHERE subject=? AND predicate=? AND object=?";
+ yield Zotero.DB.queryAsync(sql2, [this.subject, this.predicate, this.object]);
+
+ // Insert with original query
+ var insertID = yield Zotero.DB.queryAsync(
+ sql,
+ [
+ this.libraryID,
+ this.subject,
+ this.predicate,
+ this.object,
+ Zotero.DB.transactionDateTime
+ ]
+ );
+ }
+ }.bind(this));
}
return insertID;
-}
+});
-Zotero.Relation.prototype.erase = function () {
+Zotero.Relation.prototype.erase = Zotero.Promise.coroutine(function* () {
if (!this.id) {
throw ("ID not set in Zotero.Relation.erase()");
}
- Zotero.DB.beginTransaction();
-
var deleteData = {};
deleteData[this.id] = {
old: this.serialize()
}
var sql = "DELETE FROM relations WHERE ROWID=?";
- Zotero.DB.query(sql, [this.id]);
-
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.queryAsync(sql, [this.id]);
Zotero.Notifier.trigger('delete', 'relation', [this.id], deleteData);
-}
+});
Zotero.Relation.prototype.toXML = function (doc) {
diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js
@@ -27,6 +27,7 @@ Zotero.Relations = new function () {
Zotero.DataObjects.apply(this, ['relation']);
this.constructor.prototype = new Zotero.DataObjects();
+ this.__defineGetter__('relatedItemPredicate', function () "dc:relation");
this.__defineGetter__('linkedObjectPredicate', function () "owl:sameAs");
this.__defineGetter__('deletedItemPredicate', function () 'dc:isReplacedBy');
@@ -49,13 +50,13 @@ Zotero.Relations = new function () {
/**
* @return {Object[]}
*/
- this.getByURIs = function (subject, predicate, object) {
+ this.getByURIs = Zotero.Promise.coroutine(function* (subject, predicate, object) {
if (predicate) {
predicate = _getPrefixAndValue(predicate).join(':');
}
if (!subject && !predicate && !object) {
- throw ("No values provided in Zotero.Relations.get()");
+ throw new Error("No values provided");
}
var sql = "SELECT ROWID FROM relations WHERE 1";
@@ -72,42 +73,42 @@ Zotero.Relations = new function () {
sql += " AND object=?";
params.push(object);
}
- var rows = Zotero.DB.columnQuery(sql, params);
+ var rows = yield Zotero.DB.columnQueryAsync(sql, params);
if (!rows) {
return [];
}
var toReturn = [];
- for each(var id in rows) {
+ for (let i=0; i<rows.length; i++) {
var relation = new Zotero.Relation;
- relation.id = id;
+ relation.id = rows[i];
toReturn.push(relation);
}
return toReturn;
- }
+ });
- this.getSubject = function (subject, predicate, object) {
+ this.getSubject = Zotero.Promise.coroutine(function* (subject, predicate, object) {
var subjects = [];
- var relations = this.getByURIs(subject, predicate, object);
+ var relations = yield this.getByURIs(subject, predicate, object);
for each(var relation in relations) {
subjects.push(relation.subject);
}
return subjects;
- }
+ });
- this.getObject = function (subject, predicate, object) {
+ this.getObject = Zotero.Promise.coroutine(function* (subject, predicate, object) {
var objects = [];
- var relations = this.getByURIs(subject, predicate, object);
+ var relations = yield this.getByURIs(subject, predicate, object);
for each(var relation in relations) {
objects.push(relation.object);
}
return objects;
- }
+ });
- this.updateUser = function (fromUserID, fromLibraryID, toUserID, toLibraryID) {
+ this.updateUser = Zotero.Promise.coroutine(function* (fromUserID, fromLibraryID, toUserID, toLibraryID) {
if (!fromUserID) {
throw ("Invalid source userID " + fromUserID + " in Zotero.Relations.updateUserID");
}
@@ -121,24 +122,22 @@ Zotero.Relations = new function () {
throw ("Invalid target libraryID " + toLibraryID + " in Zotero.Relations.updateUserID");
}
- Zotero.DB.beginTransaction();
-
- var sql = "UPDATE relations SET libraryID=? WHERE libraryID=?";
- Zotero.DB.query(sql, [toLibraryID, fromLibraryID]);
-
- sql = "UPDATE relations SET "
- + "subject=REPLACE(subject, 'zotero.org/users/" + fromUserID + "', "
- + "'zotero.org/users/" + toUserID + "'), "
- + "object=REPLACE(object, 'zotero.org/users/" + fromUserID + "', "
- + "'zotero.org/users/" + toUserID + "') "
- + "WHERE predicate IN (?, ?)";
- Zotero.DB.query(sql, [this.linkedObjectPredicate, this.deletedItemPredicate]);
-
- Zotero.DB.commitTransaction();
- }
+ yield Zotero.DB.executeTransaction(function* () {
+ var sql = "UPDATE relations SET libraryID=? WHERE libraryID=?";
+ Zotero.DB.query(sql, [toLibraryID, fromLibraryID]);
+
+ sql = "UPDATE relations SET "
+ + "subject=REPLACE(subject, 'zotero.org/users/" + fromUserID + "', "
+ + "'zotero.org/users/" + toUserID + "'), "
+ + "object=REPLACE(object, 'zotero.org/users/" + fromUserID + "', "
+ + "'zotero.org/users/" + toUserID + "') "
+ + "WHERE predicate IN (?, ?)";
+ Zotero.DB.query(sql, [this.linkedObjectPredicate, this.deletedItemPredicate]);
+ }.bind(this));
+ });
- this.add = function (libraryID, subject, predicate, object) {
+ this.add = Zotero.Promise.coroutine(function* (libraryID, subject, predicate, object) {
predicate = _getPrefixAndValue(predicate).join(':');
var relation = new Zotero.Relation;
@@ -154,24 +153,24 @@ Zotero.Relations = new function () {
relation.subject = subject;
relation.predicate = predicate;
relation.object = object;
- relation.save();
- }
+ yield relation.save();
+ });
/**
* Copy relations from one object to another within the same library
*/
- this.copyURIs = function (libraryID, fromURI, toURI) {
- var rels = this.getByURIs(fromURI);
+ this.copyURIs = Zotero.Promise.coroutine(function* (libraryID, fromURI, toURI) {
+ var rels = yield this.getByURIs(fromURI);
for each(var rel in rels) {
- this.add(libraryID, toURI, rel.predicate, rel.object);
+ yield this.add(libraryID, toURI, rel.predicate, rel.object);
}
- var rels = this.getByURIs(false, false, fromURI);
+ var rels = yield this.getByURIs(false, false, fromURI);
for each(var rel in rels) {
- this.add(libraryID, rel.subject, rel.predicate, toURI);
+ yield this.add(libraryID, rel.subject, rel.predicate, toURI);
}
- }
+ });
/**
@@ -179,73 +178,72 @@ Zotero.Relations = new function () {
* @param {String[]} ignorePredicates
*/
this.eraseByURIPrefix = function (prefix, ignorePredicates) {
- Zotero.DB.beginTransaction();
-
- prefix = prefix + '%';
- var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
- var params = [prefix, prefix];
- if (ignorePredicates) {
- for each(var ignorePredicate in ignorePredicates) {
- sql += " AND predicate != ?";
- params.push(ignorePredicate);
+ return Zotero.DB.executeTransaction(function* () {
+ prefix = prefix + '%';
+ var sql = "SELECT ROWID FROM relations WHERE (subject LIKE ? OR object LIKE ?)";
+ var params = [prefix, prefix];
+ if (ignorePredicates) {
+ for each(var ignorePredicate in ignorePredicates) {
+ sql += " AND predicate != ?";
+ params.push(ignorePredicate);
+ }
}
- }
- var ids = Zotero.DB.columnQuery(sql, params);
-
- for each(var id in ids) {
- var relation = this.get(id);
- relation.erase();
- }
-
- Zotero.DB.commitTransaction();
+ var ids = yield Zotero.DB.columnQueryAsync(sql, params);
+
+ for (let i=0; i<ids.length; i++) {
+ let relation = yield this.get(ids[i]);
+ yield relation.erase();
+ }
+ }.bind(this));
}
+ /**
+ * @return {Promise}
+ */
this.eraseByURI = function (uri, ignorePredicates) {
- Zotero.DB.beginTransaction();
-
- var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)";
- var params = [uri, uri];
- if (ignorePredicates) {
- for each(var ignorePredicate in ignorePredicates) {
- sql += " AND predicate != ?";
- params.push(ignorePredicate);
+ return Zotero.DB.executeTransaction(function* () {
+ var sql = "SELECT ROWID FROM relations WHERE (subject=? OR object=?)";
+ var params = [uri, uri];
+ if (ignorePredicates) {
+ for each(var ignorePredicate in ignorePredicates) {
+ sql += " AND predicate != ?";
+ params.push(ignorePredicate);
+ }
}
- }
- var ids = Zotero.DB.columnQuery(sql, params);
-
- for each(var id in ids) {
- var relation = this.get(id);
- relation.erase();
- }
-
- Zotero.DB.commitTransaction();
+ var ids = yield Zotero.DB.columnQueryAsync(sql, params);
+
+ for (let i=0; i<ids.length; i++) {
+ let relation = yield this.get(ids[i]);
+ yield relation.erase();
+ }
+ }.bind(this));
}
- this.purge = function () {
+ this.purge = Zotero.Promise.coroutine(function* () {
var sql = "SELECT subject FROM relations WHERE predicate != ? "
+ "UNION SELECT object FROM relations WHERE predicate != ?";
- var uris = Zotero.DB.columnQuery(sql, [this.deletedItemPredicate, this.deletedItemPredicate]);
+ var uris = yield Zotero.DB.columnQueryAsync(sql, [this.deletedItemPredicate, this.deletedItemPredicate]);
if (uris) {
var prefix = Zotero.URI.defaultPrefix;
- Zotero.DB.beginTransaction();
- for each(var uri in uris) {
- // Skip URIs that don't begin with the default prefix,
- // since they don't correspond to local items
- if (uri.indexOf(prefix) == -1) {
- continue;
- }
- if (uri.indexOf(/\/items\//) != -1 && !Zotero.URI.getURIItem(uri)) {
- this.eraseByURI(uri);
+ yield Zotero.DB.executeTransaction(function* () {
+ for each(var uri in uris) {
+ // Skip URIs that don't begin with the default prefix,
+ // since they don't correspond to local items
+ if (uri.indexOf(prefix) == -1) {
+ continue;
+ }
+ if (uri.indexOf(/\/items\//) != -1 && !Zotero.URI.getURIItem(uri)) {
+ this.eraseByURI(uri);
+ }
+ if (uri.indexOf(/\/collections\//) != -1 && !Zotero.URI.getURICollection(uri)) {
+ this.eraseByURI(uri);
+ }
}
- if (uri.indexOf(/\/collections\//) != -1 && !Zotero.URI.getURICollection(uri)) {
- this.eraseByURI(uri);
- }
- }
- Zotero.DB.commitTransaction();
+ }.bind(this));
}
- }
+ });
this.xmlToRelation = function (relationNode) {
diff --git a/chrome/content/zotero/xpcom/data/tags.js b/chrome/content/zotero/xpcom/data/tags.js
@@ -28,363 +28,413 @@
* Same structure as Zotero.Creators -- make changes in both places if possible
*/
Zotero.Tags = new function() {
- Zotero.DataObjects.apply(this, ['tag']);
- this.constructor.prototype = new Zotero.DataObjects();
-
this.MAX_COLORED_TAGS = 6;
- var _tags = {}; // indexed by tag text
+ var _tagIDsByName = {};
+ var _tagNamesByID = {};
+ var _loaded = {};
- var _libraryColors = [];
+ var _libraryColors = {};
var _libraryColorsByName = {};
var _itemsListImagePromises = {};
var _itemsListExtraImagePromises = {};
- this.get = get;
- this.getName = getName;
- this.getID = getID;
- this.getIDs = getIDs;
- this.getTypes = getTypes;
- this.getAll = getAll;
- this.getAllWithinSearch = getAllWithinSearch;
- this.getTagItems = getTagItems;
- this.search = search;
- this.rename = rename;
- this.erase = erase;
- this.purge = purge;
-
- /*
- * Returns a tag and type for a given tagID
- */
- function get(id, skipCheck) {
- if (this._reloadCache) {
- this.reloadAll();
- }
- return this._objectCache[id] ? this._objectCache[id] : false;
- }
-
-
- /*
+ /**
* Returns a tag for a given tagID
+ *
+ * @param {Number} libraryID
+ * @param {Number} tagID
*/
- function getName(tagID) {
- if (this._objectCache[tagID]) {
- return this._objectCache[tagID].name;
+ this.getName = function (libraryID, tagID) {
+ if (!tagID) {
+ throw new Error("tagID not provided");
}
-
- // Populate cache
- var tag = this.get(tagID);
-
- return this._objectCache[tagID] ? this._objectCache[tagID].name : false;
+ if (_tagNamesByID[tagID]) {
+ return _tagNamesByID[tagID].name;
+ }
+ _requireLoad(libraryID);
+ return false;
}
- /*
- * Returns the tagID matching given tag and type
+ /**
+ * Returns the tagID matching a given tag
+ *
+ * @param {Number} libraryID
+ * @param {String} name
*/
- function getID(name, type, libraryID) {
- name = Zotero.Utilities.trim(name);
- var lcname = name.toLowerCase();
-
- if (!libraryID) {
- libraryID = 0;
+ this.getID = function (libraryID, name) {
+ if (_tagIDsByName[libraryID] && _tagIDsByName[libraryID]['_' + name]) {
+ return _tagIDsByName[libraryID]['_' + name];
}
-
- if (_tags[libraryID] && _tags[libraryID][type] && _tags[libraryID][type]['_' + lcname]) {
- return _tags[libraryID][type]['_' + lcname];
- }
-
- // FIXME: COLLATE NOCASE doesn't work for Unicode characters, so this
- // won't find Äbc if "äbc" is entered and will allow a duplicate tag
- // to be created
- var sql = "SELECT tagID FROM tags WHERE name=? AND type=? AND libraryID=?";
- var tagID = Zotero.DB.valueQuery(sql, [name, type, libraryID]);
- if (tagID) {
- if (!_tags[libraryID]) {
- _tags[libraryID] = {};
- }
- if (!_tags[libraryID][type]) {
- _tags[libraryID][type] = [];
- }
- _tags[libraryID][type]['_' + lcname] = tagID;
- }
-
- return tagID;
- }
+ _requireLoad(libraryID);
+ return false;
+ };
- /*
- * Returns all tagIDs for this tag (of all types)
+ /**
+ * Returns the tagID matching given fields, or creates a new tag and returns its id
+ *
+ * @param {Number} libraryID
+ * @param {String} name - Tag data in API JSON format
+ * @param {Boolean} [create=false] - If no matching tag, create one
+ * @return {Promise<Integer>} tagID
*/
- function getIDs(name, libraryID) {
- name = Zotero.Utilities.trim(name);
- var sql = "SELECT tagID FROM tags WHERE name=? AND libraryID=?";
- return Zotero.DB.columnQuery(sql, [name, libraryID]);
- }
+ this.getIDFromName = Zotero.Promise.method(function (libraryID, name, create) {
+ data = this.cleanData({
+ tag: name
+ });
+ return Zotero.DB.executeTransaction(function* () {
+ var sql = "SELECT tagID FROM tags WHERE libraryID=? AND name=?";
+ var id = yield Zotero.DB.valueQueryAsync(sql, [libraryID, data.tag]);
+ if (!id && create) {
+ id = yield Zotero.ID.get('tags');
+ let sql = "INSERT INTO tags (tagID, libraryID, name) VALUES (?, ?, ?)";
+ let insertID = yield Zotero.DB.queryAsync(sql, [id, libraryID, data.tag]);
+ if (!id) {
+ id = insertID;
+ }
+ _cacheTag(libraryID, id, data.tag);
+ }
+ return id;
+ });
+ });
/*
* Returns an array of tag types for tags matching given tag
*/
- function getTypes(name, libraryID) {
- name = Zotero.Utilities.trim(name);
- var sql = "SELECT type FROM tags WHERE name=? AND libraryID=?";
- return Zotero.DB.columnQuery(sql, [name, libraryID]);
- }
+ this.getTypes = Zotero.Promise.method(function (name, libraryID) {
+ if (libraryID != parseInt(libraryID)) {
+ throw new Error("libraryID must be an integer");
+ }
+
+ var sql = "SELECT type FROM tags WHERE libraryID=? AND name=?";
+ return Zotero.DB.columnQueryAsync(sql, [libraryID, name.trim()]);
+ });
/**
* Get all tags indexed by tagID
*
- * _types_ is an optional array of tag types to fetch
+ * @param {Number} libraryID
+ * @param {Array} [types] Tag types to fetch
+ * @return {Promise<Array>} A promise for an array containing tag objects in API JSON format
+ * [{ { tag: "foo" }, { tag: "bar", type: 1 }]
*/
- function getAll(types, libraryID) {
- var sql = "SELECT tagID, name FROM tags WHERE libraryID=?";
+ this.getAll = Zotero.Promise.coroutine(function* (libraryID, types) {
+ var sql = "SELECT DISTINCT name AS tag, type FROM tags "
+ + "JOIN itemTags USING (tagID) WHERE libraryID=?";
var params = [libraryID];
if (types) {
sql += " AND type IN (" + types.join() + ")";
}
- if (params.length) {
- var tags = Zotero.DB.query(sql, params);
- }
- else {
- var tags = Zotero.DB.query(sql);
- }
- if (!tags) {
- return {};
- }
-
- var indexed = {};
- for (var i=0; i<tags.length; i++) {
- var tag = this.get(tags[i].tagID, true);
- indexed[tags[i].tagID] = tag;
- }
- return indexed;
- }
+ var rows = yield Zotero.DB.queryAsync(sql, params);
+ return rows.map((row) => this.cleanData(row));
+ });
- /*
+ /**
* Get all tags within the items of a Zotero.Search object
*
- * _types_ is an optional array of tag types to fetch
+ * @param {Zotero.Search} search
+ * @param {Array} [types] Array of tag types to fetch
+ * @param {String|Promise<String>} [tmpTable] Temporary table with items to use
*/
- function getAllWithinSearch(search, types, tmpTable) {
- // Save search results to temporary table
- if(!tmpTable) {
- try {
- var tmpTable = search.search(true);
- }
- catch (e) {
- if (typeof e == 'string'
- && e.match(/Saved search [0-9]+ does not exist/)) {
- Zotero.DB.rollbackTransaction();
- Zotero.debug(e, 2);
- }
- else {
- throw (e);
- }
- }
- if (!tmpTable) {
- return {};
- }
+ this.getAllWithinSearch = Zotero.Promise.coroutine(function* (search, types) {
+ // Save search results to temporary table, if one isn't provided
+ var tmpTable = yield search.search(true);
+ if (!tmpTable) {
+ return {};
}
+ return this.getAllWithinSearchResults(tmpTable, types);
+ });
+
+
+ /**
+ * Get all tags within the items of a temporary table of search results
+ *
+ * @param {String|Promise<String>} tmpTable Temporary table with items to use
+ * @param {Array} [types] Array of tag types to fetch
+ * @return {Promise<Object>} Promise for object with tag data in API JSON format, keyed by tagID
+ */
+ this.getAllWithinSearchResults = Zotero.Promise.coroutine(function* (tmpTable, types) {
+ tmpTable = yield Zotero.Promise.resolve(tmpTable);
- var sql = "SELECT DISTINCT tagID, name, type FROM itemTags "
- + "NATURAL JOIN tags WHERE itemID IN "
+ var sql = "SELECT DISTINCT name AS tag, type FROM itemTags "
+ + "JOIN tags USING (tagID) WHERE itemID IN "
+ "(SELECT itemID FROM " + tmpTable + ") ";
if (types) {
sql += "AND type IN (" + types.join() + ") ";
}
- var tags = Zotero.DB.query(sql);
+ var rows = yield Zotero.DB.queryAsync(sql);
if(!tmpTable) {
- Zotero.DB.query("DROP TABLE " + tmpTable);
+ yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable);
}
- if (!tags) {
- return {};
- }
-
- var collation = Zotero.getLocaleCollation();
- tags.sort(function(a, b) {
- return collation.compareString(1, a.name, b.name);
- });
-
- var indexed = {};
- for (var i=0; i<tags.length; i++) {
- var tagID = tags[i].tagID;
- indexed[tagID] = this.get(tagID, true);
- }
- return indexed;
- }
+ return rows.map((row) => this.cleanData(row));
+ });
/**
* Get the items associated with the given saved tag
*
- * @param {Integer} tagID
- * @return {Integer[]|FALSE}
+ * @param {Number} tagID
+ * @return {Promise<Number[]>} A promise for an array of itemIDs
*/
- function getTagItems(tagID) {
+ this.getTagItems = function (tagID) {
var sql = "SELECT itemID FROM itemTags WHERE tagID=?";
- return Zotero.DB.columnQuery(sql, tagID) || [];
+ return Zotero.DB.columnQueryAsync(sql, tagID);
}
- function search(str) {
- var sql = 'SELECT tagID, name, type FROM tags';
+ this.search = Zotero.Promise.coroutine(function* (str) {
+ var sql = 'SELECT name AS tag, type FROM tags';
if (str) {
sql += ' WHERE name LIKE ?';
}
- var tags = Zotero.DB.query(sql, str ? '%' + str + '%' : undefined);
-
- if (!tags) {
- return {};
+ var rows = yield Zotero.DB.queryAsync(sql, str ? '%' + str + '%' : undefined);
+ return rows.map((row) => this.cleanData(row));
+ });
+
+
+ this.load = Zotero.Promise.coroutine(function* (libraryID, reload) {
+ if (_loaded[libraryID] && !reload) {
+ return;
}
- var collation = Zotero.getLocaleCollation();
- tags.sort(function(a, b) {
- return collation.compareString(1, a.name, b.name);
- });
+ Zotero.debug("Loading tags in library " + libraryID);
+
+ var sql = 'SELECT tagID AS id, name FROM tags WHERE libraryID=?';
+ var tags = yield Zotero.DB.queryAsync(sql, libraryID);
+
+ _tagIDsByName[libraryID] = {}
- var indexed = {};
for (var i=0; i<tags.length; i++) {
- var tag = this.get(tags[i].tagID, true);
- indexed[tags[i].tagID] = tag;
+ let tag = tags[i];
+ _tagIDsByName[libraryID]['_' + tag.name] = tag.id;
+ _tagNamesByID[tag.id] = tag.name;
}
- return indexed;
- }
+
+ _loaded[libraryID] = true;
+ });
/**
* Rename a tag and update the tag colors setting accordingly if necessary
*
+ * @param {Number} tagID
+ * @param {String} newName
* @return {Promise}
*/
- function rename(tagID, newName) {
- var tagObj, libraryID, oldName, oldType, notifierData, self = this;
- return Q.fcall(function () {
- Zotero.debug('Renaming tag', 4);
-
- newName = newName.trim();
+ this.rename = Zotero.Promise.coroutine(function* (libraryID, oldName, newName) {
+ Zotero.debug("Renaming tag '" + oldName + "' to '" + newName + "'", 4);
+
+ oldName = oldName.trim();
+ newName = newName.trim();
+
+ if (oldName == newName) {
+ Zotero.debug("Tag name hasn't changed", 2);
+ return;
+ }
+
+ yield Zotero.Tags.load(libraryID);
+ var oldTagID = this.getID(libraryID, oldName);
+
+ // We need to know if the old tag has a color assigned so that
+ // we can assign it to the new name
+ var oldColorData = yield this.getColor(libraryID, oldName);
+
+ yield Zotero.DB.executeTransaction(function* () {
+ var oldItemIDs = yield this.getTagItems(oldTagID);
+ var newTagID = yield this.getIDFromName(libraryID, newName, true);
- tagObj = self.get(tagID);
- libraryID = tagObj.libraryID;
- oldName = tagObj.name;
- oldType = tagObj.type;
- notifierData = {};
- notifierData[tagID] = { old: tagObj.serialize() };
+ yield Zotero.Utilities.Internal.forEachChunkAsync(
+ oldItemIDs,
+ Zotero.DB.MAX_BOUND_PARAMETERS - 2,
+ Zotero.Promise.coroutine(function* (chunk) {
+ let placeholders = chunk.map(function () '?').join(',');
+
+ // This is ugly, but it's much faster than doing replaceTag() for each item
+ let sql = 'UPDATE OR REPLACE itemTags SET tagID=?, type=0 '
+ + 'WHERE tagID=? AND itemID IN (' + placeholders + ')';
+ yield Zotero.DB.queryAsync(sql, [newTagID, oldTagID].concat(chunk));
+
+ sql = 'UPDATE items SET clientDateModified=? '
+ + 'WHERE itemID IN (' + placeholders + ')'
+ yield Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk));
+
+ yield Zotero.Items.reload(oldItemIDs, ['tags'], true);
+ })
+ );
- if (oldName == newName) {
- Zotero.debug("Tag name hasn't changed", 2);
- return;
- }
+ var notifierData = {};
+ notifierData[newName] = {
+ old: {
+ tag: oldName
+ }
+ };
+ Zotero.Notifier.trigger(
+ 'modify',
+ 'item-tag',
+ oldItemIDs.map(function (itemID) itemID + '-' + newName),
+ notifierData
+ );
- // We need to know if the old tag has a color assigned so that
- // we can assign it to the new name
- return self.getColor(libraryID, oldName);
- })
- .then(function (oldColorData) {
- Zotero.DB.beginTransaction();
+ yield this.purge(libraryID, oldTagID);
+ }.bind(this));
+
+ if (oldColorData) {
+ // Remove color from old tag
+ yield this.setColor(libraryID, oldName);
- var sql = "SELECT tagID, name FROM tags WHERE name=? AND type=0 AND libraryID=?";
- var row = Zotero.DB.rowQuery(sql, [newName, libraryID]);
- if (row) {
- var existingTagID = row.tagID;
- var existingName = row.name;
- }
+ // Add color to new tag
+ yield this.setColor(
+ libraryID,
+ newName,
+ oldColorData.color,
+ oldColorData.position
+ );
+ }
+ });
+
+
+ /**
+ * @return {Promise}
+ */
+ this.erase = Zotero.Promise.coroutine(function* (libraryID, tagIDs) {
+ tagIDs = Zotero.flattenArguments(tagIDs);
+
+ var deletedNames = [];
+ var oldItemIDs = [];
+
+ yield Zotero.DB.executeTransaction(function* () {
+ yield Zotero.Tags.load(libraryID);
- // New tag already exists as manual tag
- if (existingTagID
- // Tag check is case-insensitive, so make sure we have a different tag
- && existingTagID != tagID) {
-
- var changed = false;
- var itemsAdded = false;
-
- // Change case of existing manual tag before switching automatic
- if (oldName.toLowerCase() == newName.toLowerCase() || existingName != newName) {
- var sql = "UPDATE tags SET name=? WHERE tagID=?";
- Zotero.DB.query(sql, [newName, existingTagID]);
- changed = true;
+ for (let i=0; i<tagIDs.length; i++) {
+ let tagID = tagIDs[i];
+ let name = this.getName(libraryID, tagID);
+ if (name === false) {
+ continue;
}
+ deletedNames.push(name);
+ oldItemIDs = oldItemIDs.concat(yield this.getTagItems(tagID));
- var itemIDs = self.getTagItems(tagID);
- var existingItemIDs = self.getTagItems(existingTagID);
-
- // Would be easier to just call removeTag(tagID) and addTag(existingID)
- // here, but this is considerably more efficient
- var sql = "UPDATE OR REPLACE itemTags SET tagID=? WHERE tagID=?";
- Zotero.DB.query(sql, [existingTagID, tagID]);
-
- // Manual purge of old tag
- sql = "DELETE FROM tags WHERE tagID=?";
- Zotero.DB.query(sql, tagID);
- if (_tags[libraryID] && _tags[libraryID][oldType]) {
- delete _tags[libraryID][oldType]['_' + oldName];
+ // This causes a cascading delete from itemTags
+ let sql = "DELETE FROM tags WHERE tagID=?";
+ yield Zotero.DB.queryAsync(sql, [tagID]);
+ }
+
+ yield this.purge(libraryID, tagIDs);
+
+ // Update internal timestamps on all items that had these tags
+ yield Zotero.Utilities.Internal.forEachChunkAsync(
+ Zotero.Utilities.arrayUnique(oldItemIDs),
+ Zotero.DB.MAX_BOUND_PARAMETERS - 1,
+ Zotero.Promise.coroutine(function* (chunk) {
+ let placeholders = chunk.map(function () '?').join(',');
+
+ sql = 'UPDATE items SET clientDateModified=? '
+ + 'WHERE itemID IN (' + placeholders + ')'
+ yield Zotero.DB.queryAsync(sql, [Zotero.DB.transactionDateTime].concat(chunk));
+
+ yield Zotero.Items.reload(oldItemIDs, ['tags']);
+ })
+ );
+ }.bind(this));
+
+ // Also delete tag color setting
+ //
+ // Note that this isn't done in purge(), so the setting will not
+ // be removed if the tag is just removed from all items without
+ // being explicitly deleted.
+ for (let i=0; i<deletedNames.length; i++) {
+ this.setColor(libraryID, deletedNames[i], false);
+ }
+ });
+
+
+ /**
+ * Delete obsolete tags from database and clear internal cache entries
+ *
+ * @param {Number} libraryID
+ * @param {Number|Number[]} [tagIDs] - tagID or array of tagIDs to purge
+ * @return {Promise}
+ */
+ this.purge = Zotero.Promise.coroutine(function* (libraryID, tagIDs) {
+ if (!tagIDs && !Zotero.Prefs.get('purge.tags')) {
+ return;
+ }
+
+ if (tagIDs) {
+ tagIDs = Zotero.flattenArguments(tagIDs);
+ }
+
+ yield Zotero.DB.executeTransaction(function* () {
+ yield Zotero.Tags.load(libraryID);
+
+ // Use given tags, as long as they're orphaned
+ if (tagIDs) {
+ let sql = "CREATE TEMPORARY TABLE tagDelete (tagID INT PRIMARY KEY)";
+ yield Zotero.DB.queryAsync(sql);
+ for (let i=0; i<tagIDs.length; i++) {
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tagDelete VALUES (?)", tagIDs[i]);
}
- delete self._objectCache[tagID];
- Zotero.Notifier.trigger('delete', 'tag', tagID, notifierData);
+ sql = "SELECT * FROM tagDelete WHERE tagID NOT IN (SELECT tagID FROM itemTags)";
+ var toDelete = yield Zotero.DB.columnQueryAsync(sql);
+ }
+ // Look for orphaned tags
+ else {
+ var sql = "CREATE TEMPORARY TABLE tagDelete AS "
+ + "SELECT tagID FROM tags WHERE tagID "
+ + "NOT IN (SELECT tagID FROM itemTags)";
+ yield Zotero.DB.queryAsync(sql);
- // Simulate tag removal on items that used old tag
- var itemTags = [];
- for (var i in itemIDs) {
- itemTags.push(itemIDs[i] + '-' + tagID);
- }
- Zotero.Notifier.trigger('remove', 'item-tag', itemTags);
+ sql = "CREATE INDEX tagDelete_tagID ON tagDelete(tagID)";
+ yield Zotero.DB.queryAsync(sql);
- // And send tag add for new tag (except for those that already had it)
- var itemTags = [];
- for (var i in itemIDs) {
- if (existingItemIDs.indexOf(itemIDs[i]) == -1) {
- itemTags.push(itemIDs[i] + '-' + existingTagID);
- itemsAdded = true;
- }
- }
+ sql = "SELECT * FROM tagDelete";
+ var toDelete = yield Zotero.DB.columnQueryAsync(sql);
- if (changed) {
- if (itemsAdded) {
- Zotero.Notifier.trigger('add', 'item-tag', itemTags);
- }
-
- // Mark existing tag as updated
- sql = "UPDATE tags SET dateModified=CURRENT_TIMESTAMP, "
- + "clientDateModified=CURRENT_TIMESTAMP WHERE tagID=?";
- Zotero.DB.query(sql, existingTagID);
- Zotero.Notifier.trigger('modify', 'tag', existingTagID);
- Zotero.Tags.reload(existingTagID);
+ if (!toDelete) {
+ sql = "DROP TABLE tagDelete";
+ return Zotero.DB.queryAsync(sql);
}
-
- // TODO: notify linked items?
- //Zotero.Notifier.trigger('modify', 'item', itemIDs);
}
- else {
- tagObj.name = newName;
- // Set all renamed tags to manual
- tagObj.type = 0;
- tagObj.save();
+
+ notifierData = {};
+ for (let i=0; i<toDelete.length; i++) {
+ let id = toDelete[i];
+ if (_tagNamesByID[id]) {
+ notifierData[id] = {
+ old: {
+ libraryID: libraryID,
+ tag: _tagNamesByID[id]
+ }
+ };
+ }
}
- Zotero.DB.commitTransaction();
+ _unload(libraryID, toDelete);
- if (oldColorData) {
- // Remove color from old tag
- return self.setColor(libraryID, oldName)
- // Add color to new tag
- .then(function () {
- return self.setColor(
- libraryID,
- newName,
- oldColorData.color,
- oldColorData.position
- );
- });
- }
- });
- }
+ sql = "DELETE FROM tags WHERE tagID IN (SELECT tagID FROM tagDelete);";
+ yield Zotero.DB.queryAsync(sql);
+
+ sql = "DROP TABLE tagDelete";
+ yield Zotero.DB.queryAsync(sql);
+
+ Zotero.Notifier.trigger('delete', 'tag', toDelete, notifierData);
+ }.bind(this));
+
+ Zotero.Prefs.set('purge.tags', false);
+ });
+ //
+ // Tag color methods
+ //
/**
*
* @param {Integer} libraryID
@@ -405,7 +455,7 @@ Zotero.Tags = new function() {
*
* @param {Integer} libraryID
* @param {Integer} position The position of the tag, starting at 0
- * @return {Promise} A Q promise for an object containing 'name' and 'color'
+ * @return {Promise} A promise for an object containing 'name' and 'color'
*/
this.getColorByPosition = function (libraryID, position) {
return this.getColors(libraryID)
@@ -418,40 +468,36 @@ Zotero.Tags = new function() {
/**
* @param {Integer} libraryID
- * @return {Promise} A Q promise for an object with tag names as keys and
+ * @return {Promise} A promise for an object with tag names as keys and
* objects containing 'color' and 'position' as values
*/
- this.getColors = function (libraryID) {
- var self = this;
- return Q.fcall(function () {
- if (_libraryColorsByName[libraryID]) {
- return _libraryColorsByName[libraryID];
- }
-
- return Zotero.SyncedSettings.get(libraryID, 'tagColors')
- .then(function (tagColors) {
- // If the colors became available from another run
- if (_libraryColorsByName[libraryID]) {
- return _libraryColorsByName[libraryID];
- }
-
- tagColors = tagColors || [];
-
- _libraryColors[libraryID] = tagColors;
- _libraryColorsByName[libraryID] = {};
-
- // Also create object keyed by name for quick checking for individual tag colors
- for (var i=0; i<tagColors.length; i++) {
- _libraryColorsByName[libraryID][tagColors[i].name] = {
- color: tagColors[i].color,
- position: i
- };
- }
-
- return _libraryColorsByName[libraryID];
- });
- });
- }
+ this.getColors = Zotero.Promise.coroutine(function* (libraryID) {
+ if (_libraryColorsByName[libraryID]) {
+ return _libraryColorsByName[libraryID];
+ }
+
+ var tagColors = yield Zotero.SyncedSettings.get(libraryID, 'tagColors');
+
+ // If the colors became available from another run
+ if (_libraryColorsByName[libraryID]) {
+ return _libraryColorsByName[libraryID];
+ }
+
+ tagColors = tagColors || [];
+
+ _libraryColors[libraryID] = tagColors;
+ _libraryColorsByName[libraryID] = {};
+
+ // Also create object keyed by name for quick checking for individual tag colors
+ for (let i=0; i<tagColors.length; i++) {
+ _libraryColorsByName[libraryID][tagColors[i].name] = {
+ color: tagColors[i].color,
+ position: i
+ };
+ }
+
+ return _libraryColorsByName[libraryID];
+ });
/**
@@ -459,79 +505,75 @@ Zotero.Tags = new function() {
*
* @return {Promise}
*/
- this.setColor = function (libraryID, name, color, position) {
+ this.setColor = Zotero.Promise.coroutine(function* (libraryID, name, color, position) {
if (!Number.isInteger(libraryID)) {
throw new Error("libraryID must be an integer");
}
- var self = this;
- return this.getColors(libraryID)
- .then(function () {
- var tagColors = _libraryColors[libraryID];
- var tagIDs = self.getIDs(name, libraryID);
+ yield this.load(libraryID);
+ yield this.getColors(libraryID);
+
+ var tagColors = _libraryColors[libraryID];
+
+ // Unset
+ if (!color) {
+ // Trying to clear color on tag that doesn't have one
+ if (!_libraryColorsByName[libraryID][name]) {
+ return;
+ }
- // Unset
- if (!color) {
- // Trying to clear color on tag that doesn't have one
- if (!_libraryColorsByName[libraryID][name]) {
- return;
+ tagColors = tagColors.filter(function (val) val.name != name);
+ }
+ else {
+ // Get current position if present
+ var currentPosition = -1;
+ for (let i=0; i<tagColors.length; i++) {
+ if (tagColors[i].name == name) {
+ currentPosition = i;
+ break;
}
-
- tagColors = tagColors.filter(function (val) val.name != name);
}
- else {
- // Get current position if present
- var currentPosition = -1;
- for (var i=0; i<tagColors.length; i++) {
- if (tagColors[i].name == name) {
- currentPosition = i;
- break;
- }
- }
-
- // Remove if present
- if (currentPosition != -1) {
- // If no position was specified, we'll reinsert into the same place
- if (typeof position == 'undefined') {
- position = currentPosition;
- }
- tagColors.splice(currentPosition, 1);
- }
- var newObj = {
- name: name,
- color: color
- };
- // If no position or after end, add at end
- if (typeof position == 'undefined' || position >= tagColors.length) {
- tagColors.push(newObj);
- }
- // Otherwise insert into new position
- else {
- tagColors.splice(position, 0, newObj);
+
+ // Remove if present
+ if (currentPosition != -1) {
+ // If no position was specified, we'll reinsert into the same place
+ if (typeof position == 'undefined') {
+ position = currentPosition;
}
+ tagColors.splice(currentPosition, 1);
}
-
- if (tagColors.length) {
- return Zotero.SyncedSettings.set(libraryID, 'tagColors', tagColors);
+ var newObj = {
+ name: name,
+ color: color
+ };
+ // If no position or after end, add at end
+ if (typeof position == 'undefined' || position >= tagColors.length) {
+ tagColors.push(newObj);
}
+ // Otherwise insert into new position
else {
- return Zotero.SyncedSettings.set(libraryID, 'tagColors');
+ tagColors.splice(position, 0, newObj);
}
- });
- };
+ }
+
+ if (tagColors.length) {
+ return Zotero.SyncedSettings.set(libraryID, 'tagColors', tagColors);
+ }
+ else {
+ return Zotero.SyncedSettings.set(libraryID, 'tagColors');
+ }
+ });
/**
* Update caches and trigger redrawing of items in the items list
* when a 'tagColors' setting is modified
*/
- this.notify = function (event, type, ids, extraData) {
+ this.notify = Zotero.Promise.coroutine(function* (event, type, ids, extraData) {
if (type != 'setting') {
return;
}
- var self = this;
-
for (let i=0; i<ids.length; i++) {
let libraryID, setting;
[libraryID, setting] = ids[i].split("/");
@@ -545,83 +587,77 @@ Zotero.Tags = new function() {
delete _libraryColorsByName[libraryID];
// Get the tag colors for each library in which they were modified
- Zotero.SyncedSettings.get(libraryID, 'tagColors')
- .then(function (tagColors) {
- if (!tagColors) {
- tagColors = [];
- }
-
- let id = libraryID + "/" + setting;
- if ((event == 'modify' || event == 'delete') && extraData[id].changed) {
- var previousTagColors = extraData[id].changed.value;
- }
- else {
- var previousTagColors = [];
- }
-
- var affectedItems = [];
-
- // Get all items linked to previous or current tag colors
- var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
- tagNames = Zotero.Utilities.arrayUnique(tagNames);
- for (let i=0; i<tagNames.length; i++) {
- let tagIDs = self.getIDs(tagNames[i], libraryID) || [];
- for (let i=0; i<tagIDs.length; i++) {
- affectedItems = affectedItems.concat(self.getTagItems(tagIDs[i]));
- }
- };
-
- if (affectedItems.length) {
- Zotero.Notifier.trigger('redraw', 'item', affectedItems, { column: 'title' });
- }
- })
- .done();
- }
- };
-
-
- this.toggleItemsListTags = function (libraryID, items, name) {
- var self = this;
- return Q.fcall(function () {
- var tagIDs = self.getIDs(name, libraryID) || [];
- // If there's a color setting but no matching tag, don't throw
- // an error (though ideally this wouldn't be possible).
- if (!tagIDs.length) {
- return;
+ let tagColors = yield Zotero.SyncedSettings.get(libraryID, 'tagColors');
+ if (!tagColors) {
+ tagColors = [];
}
- var tags = tagIDs.map(function (tagID) {
- return Zotero.Tags.get(tagID, true);
- });
- if (!items.length) {
- return;
+ let id = libraryID + "/" + setting;
+ if ((event == 'modify' || event == 'delete') && extraData[id].changed) {
+ var previousTagColors = extraData[id].changed.value;
+ }
+ else {
+ var previousTagColors = [];
}
- Zotero.DB.beginTransaction();
+ var affectedItems = [];
+ // Get all items linked to previous or current tag colors
+ var tagNames = tagColors.concat(previousTagColors).map(function (val) val.name);
+ tagNames = Zotero.Utilities.arrayUnique(tagNames);
+ for (let i=0; i<tagNames.length; i++) {
+ let tagID = this.getID(libraryID, tagNames[i]);
+ affectedItems = affectedItems.concat(yield this.getTagItems(tagID));
+ };
+
+ if (affectedItems.length) {
+ Zotero.Notifier.trigger('redraw', 'item', affectedItems, { column: 'title' });
+ }
+ }
+ });
+
+
+ this.toggleItemsListTags = Zotero.Promise.coroutine(function* (libraryID, items, tagName) {
+ if (!items.length) {
+ return;
+ }
+
+ yield this.load(libraryID);
+ var tagID = this.getID(libraryID, tagName);
+
+ // If there's a color setting but no matching tag, don't throw
+ // an error (though ideally this wouldn't be possible).
+ if (!tagID) {
+ return;
+ }
+
+ return Zotero.DB.executeTransaction(function* () {
// Base our action on the first item. If it has the tag,
// remove the tag from all items. If it doesn't, add it to all.
var firstItem = items[0];
// Remove from all items
- if (firstItem.hasTags(tagIDs)) {
- for (var i=0; i<items.length; i++) {
- for (var j=0; j<tags.length; j++) {
- tags[j].removeItem(items[i].id);
- }
+ if (firstItem.hasTag(tagName)) {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+ item.removeTag(tagName);
+ yield item.save({
+ skipDateModifiedUpdate: true
+ });
}
- tags.forEach(function (tag) tag.save());
Zotero.Prefs.set('purge.tags', true);
}
// Add to all items
else {
- for (var i=0; i<items.length; i++) {
- items[i].addTag(name);
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+ item.addTag(tagName);
+ yield item.save({
+ skipDateModifiedUpdate: true
+ });
}
}
-
- Zotero.DB.commitTransaction();
- });
- };
+ }.bind(this));
+ });
/**
@@ -676,7 +712,7 @@ Zotero.Tags = new function() {
// If there's no extra iamge, resolve a promise now
if (!extraImage) {
var dataURI = canvas.toDataURL("image/png");
- var dataURIPromise = Q(dataURI);
+ var dataURIPromise = Zotero.Promise.resolve(dataURI);
_itemsListImagePromises[hash] = dataURIPromise;
return dataURIPromise;
}
@@ -702,8 +738,8 @@ Zotero.Tags = new function() {
img.src = uri.spec;
// Mark that we've started loading
- var deferred = Q.defer();
- var extraImageDeferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
+ var extraImageDeferred = Zotero.Promise.defer();
_itemsListExtraImagePromises[extraImage] = extraImageDeferred.promise;
// When extra image has loaded, draw it
@@ -711,7 +747,7 @@ Zotero.Tags = new function() {
ctx.drawImage(img, x, 0);
var dataURI = canvas.toDataURL("image/png");
- var dataURIPromise = Q(dataURI);
+ var dataURIPromise = Zotero.Promise.resolve(dataURI);
_itemsListImagePromises[hash] = dataURIPromise;
// Fulfill the promise for this call
@@ -732,7 +768,7 @@ Zotero.Tags = new function() {
ctx.drawImage(img, x, 0);
var dataURI = canvas.toDataURL("image/png");
- var dataURIPromise = Q(dataURI);
+ var dataURIPromise = Zotero.Promise.resolve(dataURI);
_itemsListImagePromises[hash] = dataURIPromise;
return dataURIPromise;
});
@@ -782,189 +818,73 @@ Zotero.Tags = new function() {
}
- /**
- * @return {Promise}
- */
- function erase(ids) {
- ids = Zotero.flattenArguments(ids);
-
- var deleted = [];
-
- Zotero.DB.beginTransaction();
- for each(var id in ids) {
- var tag = this.get(id);
- if (tag) {
- deleted.push({
- libraryID: tag.libraryID,
- name: tag.name
- });
- tag.erase();
- }
+ this.cleanData = function (data) {
+ // Validate data
+ if (data.tag === undefined) {
+ throw new Error("Tag data must contain 'tag' property");
+ }
+ if (data.type !== undefined && data.type != 0 && data.type != 1) {
+ throw new Error("Tag 'type' must be 0 or 1");
}
- Zotero.DB.commitTransaction();
- // Also delete tag color setting
- //
- // Note that this isn't done in purge(), so the setting will not
- // be removed if the tag is just removed from all items without
- // without being explicitly deleted.
- for (var i in deleted) {
- this.setColor(deleted[i].libraryID, deleted[i].name, false);
+ var cleanedData = {};
+ cleanedData.tag = (data.tag + '').trim();
+ if (data.type) {
+ cleanedData.type = parseInt(data.type);
}
+ return cleanedData;
}
/**
- * Delete obsolete tags from database and clear internal array entries
- *
- * @param [Integer[]|Integer] [tagIDs] tagID or array of tagIDs to purge
+ * Clear cache to reload
*/
- function purge(tagIDs) {
- if (!tagIDs && !Zotero.Prefs.get('purge.tags')) {
- return;
- }
-
- if (tagIDs) {
- tagIDs = Zotero.flattenArguments(tagIDs);
- }
-
- Zotero.UnresponsiveScriptIndicator.disable();
- try {
- Zotero.DB.beginTransaction();
-
- // Use given tags
- if (tagIDs) {
- var sql = "CREATE TEMPORARY TABLE tagDelete (tagID INT PRIMARY KEY)";
- Zotero.DB.query(sql);
- for each(var id in tagIDs) {
- Zotero.DB.query("INSERT OR IGNORE INTO tagDelete VALUES (?)", id);
- }
- // Remove duplicates
- var toDelete = Zotero.DB.columnQuery("SELECT * FROM tagDelete");
- }
- // Look for orphaned tags
- else {
- var sql = "CREATE TEMPORARY TABLE tagDelete AS "
- + "SELECT tagID FROM tags WHERE tagID "
- + "NOT IN (SELECT tagID FROM itemTags)";
- Zotero.DB.query(sql);
-
- sql = "CREATE INDEX tagDelete_tagID ON tagDelete(tagID)";
- Zotero.DB.query(sql);
-
- sql = "SELECT * FROM tagDelete";
- var toDelete = Zotero.DB.columnQuery(sql);
-
- if (!toDelete) {
- sql = "DROP TABLE tagDelete";
- Zotero.DB.query(sql);
- Zotero.DB.commitTransaction();
- Zotero.Prefs.set('purge.tags', false);
- return;
- }
- }
-
- var notifierData = {};
-
- for each(var tagID in toDelete) {
- var tag = Zotero.Tags.get(tagID);
- if (tag) {
- notifierData[tagID] = { old: tag.serialize() }
- }
- }
-
- this.unload(toDelete);
-
- sql = "DELETE FROM tags WHERE tagID IN "
- + "(SELECT tagID FROM tagDelete);";
- Zotero.DB.query(sql);
-
- sql = "DROP TABLE tagDelete";
- Zotero.DB.query(sql);
-
- Zotero.DB.commitTransaction();
-
- Zotero.Notifier.trigger('delete', 'tag', toDelete, notifierData);
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
- finally {
- Zotero.UnresponsiveScriptIndicator.enable();
- }
-
- Zotero.Prefs.set('purge.tags', false);
+ this.reload = function () {
+ _tagNamesByID = {};
+ _tagIDsByName = {};
}
- /**
- * Internal reload hook to clear cache
- */
- this._reload = function (ids) {
- _tags = {};
+ this._getPrimaryDataSQL = function () {
+ // This should be the same as the query in Zotero.Tag.load(),
+ // just without a specific tagID
+ return "SELECT * FROM tags O WHERE 1";
}
- /**
- * Unload tags from caches
- *
- * @param int|array ids One or more tagIDs
- */
- this.unload = function () {
- var ids = Zotero.flattenArguments(arguments);
-
- for each(var id in ids) {
- var tag = this._objectCache[id];
- delete this._objectCache[id];
- var libraryID = tag.libraryID;
- if (tag && _tags[libraryID] && _tags[libraryID][tag.type]) {
- delete _tags[libraryID][tag.type]['_' + tag.name];
- }
+ function _requireLoad(libraryID) {
+ if (!_loaded[libraryID]) {
+ throw new Zotero.DataObjects.UnloadedDataException(
+ "Tag data has not been loaded for library " + libraryID,
+ "tags"
+ );
}
}
- this._load = function () {
- if (!arguments[0]) {
- if (!this._reloadCache) {
- return;
- }
- _tags = {};
- this._reloadCache = false;
- }
-
- // This should be the same as the query in Zotero.Tag.load(),
- // just without a specific tagID
- var sql = "SELECT * FROM tags WHERE 1";
- if (arguments[0]) {
- sql += " AND tagID IN (" + Zotero.join(arguments[0], ",") + ")";
- }
- var rows = Zotero.DB.query(sql);
-
- var ids = [];
- for each(var row in rows) {
- var id = row.tagID;
- ids.push(id);
-
- // Tag doesn't exist -- create new object and stuff in array
- if (!this._objectCache[id]) {
- //this.get(id);
- this._objectCache[id] = new Zotero.Tag;
- this._objectCache[id].loadFromRow(row);
- }
- // Existing tag -- reload in place
- else {
- this._objectCache[id].loadFromRow(row);
- }
+ function _cacheTag(libraryID, tagID, name) {
+ _tagNamesByID[tagID] = name;
+ if (!_tagIDsByName[libraryID]) {
+ _tagIDsByName[libraryID] = {};
}
+ _tagIDsByName[libraryID]['_' + name] = tagID;
+ }
+
+ /**
+ * Unload tags from caches
+ *
+ * @param {Number} libraryID
+ * @param {Number|Array<Number>} ids One or more tagIDs
+ */
+ function _unload(libraryID, ids) {
+ var ids = Zotero.flattenArguments(ids);
- if (!arguments[0]) {
- // If loading all tags, remove old tags that no longer exist
- for each(var c in this._objectCache) {
- if (ids.indexOf(c.id) == -1) {
- this.unload(c.id);
- }
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ let tagName = _tagNamesByID[id];
+ delete _tagNamesByID[id];
+ if (tagName && _tagIDsByName[libraryID]) {
+ delete _tagIDsByName[libraryID]['_' + tagName];
}
}
}
diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js
@@ -34,6 +34,8 @@ Zotero.DBConnection = function(dbName) {
throw ('DB name not provided in Zotero.DBConnection()');
}
+ this.MAX_BOUND_PARAMETERS = 999;
+
Components.utils.import("resource://gre/modules/Task.jsm", this);
// Use the Fx24 Sqlite.jsm, because the Sqlite.jsm in Firefox 25 breaks
// locking_mode=EXCLUSIVE with async DB access. In the Fx24 version,
@@ -74,13 +76,22 @@ Zotero.DBConnection = function(dbName) {
this._dbName = dbName;
this._shutdown = false;
this._connection = null;
+ this._connectionAsync = null;
this._transactionDate = null;
this._lastTransactionDate = null;
this._transactionRollback = false;
this._transactionNestingLevel = 0;
this._transactionWaitLevel = 0;
this._asyncTransactionNestingLevel = 0;
- this._callbacks = { begin: [], commit: [], rollback: [] };
+ this._callbacks = {
+ begin: [],
+ commit: [],
+ rollback: [],
+ current: {
+ commit: [],
+ rollback: []
+ }
+ };
this._dbIsCorrupt = null
this._self = this;
}
@@ -113,6 +124,9 @@ Zotero.DBConnection.prototype.test = function () {
* - FALSE on error
*/
Zotero.DBConnection.prototype.query = function (sql,params) {
+ Zotero.debug("WARNING: Zotero.DBConnection.prototype.query() is deprecated "
+ + "-- use queryAsync() instead [QUERY: " + sql + "]", 2);
+
var db = this._getDBConnection();
try {
@@ -123,17 +137,20 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
}
// If SELECT statement, return result
- if (op == 'select') {
+ if (op == 'select' || op == 'pragma') {
// Until the native dataset methods work (or at least exist),
// we build a multi-dimensional associative array manually
- var statement = this.getStatement(sql, params, true);
+ var statement = this.getStatement(sql, params, {
+ checkParams: true
+ });
// Get column names
var columns = [];
var numCols = statement.columnCount;
for (var i=0; i<numCols; i++) {
- columns.push(statement.getColumnName(i));
+ let colName = statement.getColumnName(i);
+ columns.push(colName);
}
var dataset = [];
@@ -150,12 +167,16 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
}
else {
if (params) {
- var statement = this.getStatement(sql, params, true);
+ var statement = this.getStatement(sql, params, {
+ checkParams: true
+ });
statement.execute();
}
else {
- this._debug(sql,5);
- db.executeSimpleSQL(sql);
+ let sql2;
+ [sql2, ] = this.parseQueryAndParams(sql, params);
+ this._debug(sql2, 5);
+ db.executeSimpleSQL(sql2);
}
if (op == 'insert' || op == 'replace') {
@@ -170,6 +191,11 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
catch (e) {
this.checkException(e);
+ try {
+ [sql, params] = this.parseQueryAndParams(sql, params);
+ }
+ catch (e2) {}
+
var dberr = (db.lastErrorString!='not an error')
? ' [ERROR: ' + db.lastErrorString + ']' : '';
throw new Error(e + ' [QUERY: ' + sql + ']' + dberr);
@@ -181,7 +207,9 @@ Zotero.DBConnection.prototype.query = function (sql,params) {
* Query a single value and return it
*/
Zotero.DBConnection.prototype.valueQuery = function (sql,params) {
- var statement = this.getStatement(sql, params, true);
+ var statement = this.getStatement(sql, params, {
+ checkParams: true
+ });
// No rows
if (!statement.executeStep()) {
@@ -210,7 +238,9 @@ Zotero.DBConnection.prototype.rowQuery = function (sql,params) {
* Run a query and return the first column as a numerically-indexed array
*/
Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
- var statement = this.getStatement(sql, params, true);
+ var statement = this.getStatement(sql, params, {
+ checkParams: true
+ });
if (statement) {
var column = new Array();
@@ -234,7 +264,7 @@ Zotero.DBConnection.prototype.columnQuery = function (sql,params) {
* Optional _params_ is an array of bind parameters in the form
* [1,"hello",3] or [{'int':2},{'string':'foobar'}]
*/
-Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams) {
+Zotero.DBConnection.prototype.getStatement = function (sql, params, options) {
var db = this._getDBConnection();
// TODO: limit to Zotero.DB, not all Zotero.DBConnections?
@@ -242,10 +272,11 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams)
throw ("Cannot access database layer from a higher wait level if a transaction is open");
}
- [sql, params] = this.parseQueryAndParams(sql, params);
+ [sql, params] = this.parseQueryAndParams(sql, params, options);
try {
this._debug(sql,5);
+
var statement = db.createStatement(sql);
}
catch (e) {
@@ -256,84 +287,25 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams)
var numParams = statement.parameterCount;
- if (params) {
- if (checkParams) {
- if (numParams == 0) {
- throw ("Parameters provided for query without placeholders [QUERY: " + sql + "]");
- }
- else if (numParams != params.length) {
- throw ("Incorrect number of parameters provided for query "
- + "(" + params.length + ", expecting " + numParams + ") [QUERY: " + sql + "]");
- }
- }
-
+ if (params.length) {
for (var i=0; i<params.length; i++) {
- if (params[i] === undefined) {
- Zotero.debug(params);
- var msg = 'Parameter ' + i + ' is undefined in Zotero.DB.getStatement() [QUERY: ' + sql + ']';
- Zotero.debug(msg);
- Components.utils.reportError(msg);
- throw (msg);
- }
-
- // Integer
- if (params[i]!==null && typeof params[i]['int'] != 'undefined') {
- var type = 'int';
- var value = params[i]['int'];
- }
- // String
- else if (params[i]!==null && typeof params[i]['string'] != 'undefined') {
- var type = 'string';
- var value = params[i]['string'];
- }
- // Automatic (trust the JS type)
- else {
- switch (typeof params[i]) {
- case 'string':
- var type = 'string';
- break;
- case 'number':
- var type = 'int';
- break;
- // Object
- default:
- if (params[i]===null) {
- var type = 'null';
- }
- else {
- var msg = 'Invalid bound parameter ' + params[i]
- + ' in ' + Zotero.Utilities.varDump(params)
- + ' [QUERY: ' + sql + ']';
- Zotero.debug(msg);
- throw(msg);
- }
- }
- var value = params[i];
- }
+ var value = params[i];
// Bind the parameter as the correct type
- switch (type) {
- case 'int':
- var intVal = parseInt(value);
- if (isNaN(intVal)) {
- throw ("Invalid integer value '" + value + "'")
- }
-
+ switch (typeof value) {
+ case 'number':
// Store as 32-bit signed integer
- if (intVal <= 2147483647) {
+ if (value <= 2147483647) {
this._debug('Binding parameter ' + (i+1)
+ ' of type int: ' + value, 5);
- statement.bindInt32Parameter(i, intVal);
+ statement.bindInt32Parameter(i, value);
}
// Store as 64-bit signed integer
- // 2^53 is JS's upper-bound for decimal integers
- else if (intVal < 9007199254740992) {
- this._debug('Binding parameter ' + (i+1)
- + ' of type int64: ' + value, 5);
- statement.bindInt64Parameter(i, intVal);
- }
+ //
+ // Note: 9007199254740992 (2^53) is JS's upper bound for decimal integers
else {
- throw ("Integer value '" + intVal + "' too large");
+ this._debug('Binding parameter ' + (i + 1) + ' of type int64: ' + value, 5);
+ statement.bindInt64Parameter(i, value);
}
break;
@@ -344,57 +316,131 @@ Zotero.DBConnection.prototype.getStatement = function (sql, params, checkParams)
statement.bindUTF8StringParameter(i, value);
break;
- case 'null':
+ case 'object':
+ if (value !== null) {
+ let msg = 'Invalid bound parameter ' + value
+ + ' in ' + Zotero.Utilities.varDump(params)
+ + ' [QUERY: ' + sql + ']';
+ Zotero.debug(msg);
+ throw new Error(msg);
+ }
+
this._debug('Binding parameter ' + (i+1) + ' of type NULL', 5);
statement.bindNullParameter(i);
break;
}
}
}
- else {
- if (checkParams && numParams > 0) {
- throw ("No parameters provided for query containing placeholders "
- + "[QUERY: " + sql + "]");
- }
- }
return statement;
}
-Zotero.DBConnection.prototype.parseQueryAndParams = function (sql, params) {
- if (params) {
- // First, determine the type of query using first word
- var matches = sql.match(/^[^\s\(]*/);
- var queryMethod = matches[0].toLowerCase();
-
- // If single scalar value or single non-array object, wrap in an array
- if (typeof params != 'object' || params === null ||
- (typeof params == 'object' && !params.length)) {
+Zotero.DBConnection.prototype.getAsyncStatement = Zotero.Promise.coroutine(function* (sql) {
+ var conn = yield this._getConnectionAsync();
+ conn = conn._connection;
+
+ // TODO: limit to Zotero.DB, not all Zotero.DBConnections?
+ if (conn.transactionInProgress && Zotero.waiting > this._transactionWaitLevel) {
+ throw ("Cannot access database layer from a higher wait level if a transaction is open");
+ }
+
+ try {
+ this._debug(sql, 5);
+ return conn.createAsyncStatement(sql);
+ }
+ catch (e) {
+ var dberr = (conn.lastErrorString != 'not an error')
+ ? ' [ERROR: ' + conn.lastErrorString + ']' : '';
+ throw new Error(e + ' [QUERY: ' + sql + ']' + dberr);
+ }
+});
+
+
+Zotero.DBConnection.prototype.parseQueryAndParams = function (sql, params, options) {
+ // If single scalar value, wrap in an array
+ if (!Array.isArray(params)) {
+ if (typeof params == 'string' || typeof params == 'number' || params === null) {
params = [params];
}
- // Since we might make changes, only work on a copy of the array
else {
- params = params.concat();
+ params = [];
+ }
+ }
+ // Otherwise, since we might make changes, only work on a copy of the array
+ else {
+ params = params.concat();
+ }
+
+ // Find placeholders
+ var placeholderRE = /\s*[=,(]\s*\?/g;
+
+ if (params.length) {
+ if (options && options.checkParams) {
+ let matches = sql.match(placeholderRE);
+
+ if (!matches) {
+ throw new Error("Parameters provided for query without placeholders "
+ + "[QUERY: " + sql + "]");
+ }
+ else if (matches.length != params.length) {
+ throw new Error("Incorrect number of parameters provided for query "
+ + "(" + params.length + ", expecting " + matches.length + ") "
+ + "[QUERY: " + sql + "]");
+ }
}
- // Replace NULL bound parameters with hard-coded NULLs
- var nullRE = /\s*=?\s*\?/g;
+ // First, determine the type of query using first word
+ var matches = sql.match(/^[^\s\(]*/);
+ var queryMethod = matches[0].toLowerCase();
+
// Reset lastIndex, since regexp isn't recompiled dynamically
- nullRE.lastIndex = 0;
+ placeholderRE.lastIndex = 0;
var lastNullParamIndex = -1;
for (var i=0; i<params.length; i++) {
+ if (typeof params[i] == 'boolean') {
+ throw new Error("Invalid boolean parameter " + i + " '" + params[i] + "' "
+ + "[QUERY: " + sql + "]");
+ }
+ else if (params[i] === undefined) {
+ throw new Error('Parameter ' + i + ' is undefined [QUERY: ' + sql + ']');
+ }
+
if (params[i] !== null) {
+ // Force parameter type if specified
+
+ // Int
+ if (typeof params[i]['int'] != 'undefined') {
+ params[i] = parseInt(params[i]['int']);
+ if (isNaN(params[i])) {
+ throw new Error("Invalid bound parameter " + i + " integer value '" + params[i] + "' "
+ + "[QUERY: " + sql + "]")
+ }
+ }
+ // String
+ else if (typeof params[i]['string'] != 'undefined') {
+ params[i] = params[i]['string'] + "";
+ }
+
continue;
}
+ //
+ // Replace NULL bound parameters with hard-coded NULLs
+ //
+
// Find index of this parameter, skipping previous ones
do {
- var matches = nullRE.exec(sql);
+ var matches = placeholderRE.exec(sql);
lastNullParamIndex++;
}
while (lastNullParamIndex < i);
lastNullParamIndex = i;
+ if (!matches) {
+ throw new Error("Null parameter provided for a query without placeholders "
+ + "-- use false or undefined [QUERY: " + sql + "]");
+ }
+
if (matches[0].indexOf('=') == -1) {
// mozStorage supports null bound parameters in value lists (e.g., "(?,?)") natively
continue;
@@ -418,14 +464,48 @@ Zotero.DBConnection.prototype.parseQueryAndParams = function (sql, params) {
continue;
}
if (!params.length) {
- params = undefined;
+ params = [];
}
}
+ else if (options && options.checkParams && placeholderRE.test(sql)) {
+ throw new Error("Parameters not provided for query containing placeholders "
+ + "[QUERY: " + sql + "]");
+ }
return [sql, params];
};
+/**
+ * Execute an asynchronous statement with previously bound parameters
+ *
+ * Warning: This will freeze if used with a write statement within executeTransaction()!
+ *
+ * @param {mozIStorageAsyncStatement} statement
+ * @return {Promise} Resolved on completion, rejected with a reason on error,
+ * and progressed with a mozIStorageRow for SELECT queries
+ */
+Zotero.DBConnection.prototype.executeAsyncStatement = function (statement) {
+ var deferred = Zotero.Promise.defer();
+ statement.executeAsync({
+ handleResult: function (resultSet) {
+ deferred.progress(resultSet.getNextRow());
+ },
+
+ handleError: function (e) {
+ deferred.reject(e);
+ },
+
+ handleCompletion: function (reason) {
+ if (reason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
+ deferred.reject(reason);
+ }
+ deferred.resolve();
+ }
+ });
+ return deferred.promise;
+}
+
/*
* Only for use externally with this.getStatement()
*/
@@ -650,17 +730,9 @@ Zotero.DBConnection.prototype.tableExists = function (table) {
Zotero.DBConnection.prototype.getColumns = function (table) {
- var db = this._getDBConnection();
-
try {
- var sql = "SELECT * FROM " + table + " LIMIT 1";
- var statement = this.getStatement(sql);
- var cols = new Array();
- for (var i=0,len=statement.columnCount; i<len; i++) {
- cols.push(statement.getColumnName(i));
- }
- statement.finalize();
- return cols;
+ var rows = this.query("PRAGMA table_info(" + table + ")");
+ return [row.name for each (row in rows)];
}
catch (e) {
this._debug(e,1);
@@ -669,6 +741,18 @@ Zotero.DBConnection.prototype.getColumns = function (table) {
}
+Zotero.DBConnection.prototype.getColumnsAsync = function (table) {
+ return Zotero.DB.queryAsync("PRAGMA table_info(" + table + ")")
+ .then(function (rows) {
+ return [row.name for each (row in rows)];
+ })
+ .catch(function (e) {
+ this._debug(e, 1);
+ return false;
+ });
+}
+
+
Zotero.DBConnection.prototype.getColumnHash = function (table) {
var cols = this.getColumns(table);
var hash = {};
@@ -728,18 +812,10 @@ Zotero.DBConnection.prototype.getNextName = function (libraryID, table, field, n
var sql = "SELECT TRIM(SUBSTR(" + field + ", " + (name.length + 1) + ")) "
+ "FROM " + table + " "
- + "WHERE " + field + " REGEXP '^" + name + "( [0-9]+)?$' ";
- if (!libraryID) {
- // DEBUG: Shouldn't this be replaced automatically with "=?"?
- sql += " AND libraryID IS NULL";
- var params = undefined
- }
- else {
- sql += " AND libraryID=?";
- var params = [libraryID];
- }
- sql += " ORDER BY " + field;
- // TEMP: libraryIDInt
+ + "WHERE " + field + " REGEXP '^" + name + "( [0-9]+)?$' "
+ + "AND libraryID=?"
+ + " ORDER BY " + field;
+ var params = [libraryID];
var suffixes = this.columnQuery(sql, params);
// If none found or first one has a suffix, use default name
if (!suffixes || suffixes[0]) {
@@ -750,8 +826,6 @@ Zotero.DBConnection.prototype.getNextName = function (libraryID, table, field, n
return parseInt(a) - parseInt(b);
});
- Zotero.debug(suffixes);
-
var i = 1;
while (suffixes[i] === "") {
i++;
@@ -772,7 +846,7 @@ Zotero.DBConnection.prototype.getNextName = function (libraryID, table, field, n
// Async methods
//
//
-// Zotero.DB.executeTransaction(function (conn) {
+// Zotero.DB.executeTransaction(function* (conn) {
// var created = yield Zotero.DB.queryAsync("CREATE TEMPORARY TABLE tmpFoo (foo TEXT, bar INT)");
//
// // created == true
@@ -797,29 +871,37 @@ Zotero.DBConnection.prototype.getNextName = function (libraryID, table, field, n
// // '3' => "d"
//
// let rows = yield Zotero.DB.queryAsync("SELECT * FROM tmpFoo");
-// for each(let row in rows) {
+// for (let i=0; i<rows.length; i++) {
+// let row = rows[i];
// // row.foo == 'a', row.bar == 1
// // row.foo == 'b', row.bar == 2
// // row.foo == 'c', row.bar == 3
// // row.foo == 'd', row.bar == 4
// }
//
-// // Optional, but necessary to pass 'rows' on to the next handler
-// Zotero.DB.asyncResult(rows);
+// return rows;
// })
// .then(function (rows) {
// // rows == same as above
-// })
-// .done();
+// });
//
/**
- * @param {Function} func Task.js-style generator function that yields promises,
- * generally from queryAsync() and similar
- * @return {Promise} Q promise for result of generator function, which can
- * pass a result by calling asyncResult(val) at the end
+ * @param {Function} func - Generator function that yields promises,
+ * generally from queryAsync() and similar
+ * @return {Promise} - Promise for result of generator function
*/
-Zotero.DBConnection.prototype.executeTransaction = function (func) {
+Zotero.DBConnection.prototype.executeTransaction = function (func, options) {
var self = this;
+
+ // Set temporary options for this transaction that will be reset at the end
+ var origOptions = {};
+ if (options) {
+ for (let option in options) {
+ origOptions[option] = this[option];
+ this[option] = options[option];
+ }
+ }
+
return this._getConnectionAsync()
.then(function (conn) {
if (conn.transactionInProgress) {
@@ -828,6 +910,15 @@ Zotero.DBConnection.prototype.executeTransaction = function (func) {
return self.Task.spawn(func)
.then(
function (result) {
+ if (options) {
+ if (options.onCommit) {
+ self._callbacks.current.commit.push(options.onCommit);
+ }
+ if (options.onRollback) {
+ self._callbacks.current.rollback.push(options.onRollback);
+ }
+ }
+
Zotero.debug("Decreasing async DB transaction level to "
+ --self._asyncTransactionNestingLevel, 5);
return result;
@@ -841,17 +932,90 @@ Zotero.DBConnection.prototype.executeTransaction = function (func) {
}
else {
Zotero.debug("Beginning async DB transaction", 5);
+
+ // Set a timestamp for this transaction
+ self._transactionDate = new Date(Math.floor(new Date / 1000) * 1000);
+
+ // Run begin callbacks
+ for (var i=0; i<self._callbacks.begin.length; i++) {
+ if (self._callbacks.begin[i]) {
+ self._callbacks.begin[i]();
+ }
+ }
return conn.executeTransaction(func)
- .then(
- function (result) {
- Zotero.debug("Committed async DB transaction", 5);
- return result;
- },
- function (e) {
- Zotero.debug("Rolled back async DB transaction", 5);
- throw e;
+ .then(Zotero.Promise.coroutine(function* (result) {
+ Zotero.debug("Committed async DB transaction", 5);
+
+ // Clear transaction time
+ if (self._transactionDate) {
+ self._transactionDate = null;
}
- );
+
+ if (options) {
+ // Function to run once transaction has been committed but before any
+ // permanent callbacks
+ if (options.onCommit) {
+ self._callbacks.current.commit.push(options.onCommit);
+ }
+ self._callbacks.current.rollback = [];
+
+ if (options.vacuumOnCommit) {
+ Zotero.debug('Vacuuming database');
+ yield Zotero.DB.queryAsync('VACUUM');
+ }
+ }
+
+ // Run temporary commit callbacks
+ var f;
+ while (f = self._callbacks.current.commit.shift()) {
+ yield Zotero.Promise.resolve(f());
+ }
+
+ // Run commit callbacks
+ for (var i=0; i<self._callbacks.commit.length; i++) {
+ if (self._callbacks.commit[i]) {
+ yield self._callbacks.commit[i]();
+ }
+ }
+
+ return result;
+ }))
+ .catch(Zotero.Promise.coroutine(function* (e) {
+ Zotero.debug("Rolled back async DB transaction", 5);
+
+ Zotero.debug(e, 1);
+
+ if (options) {
+ // Function to run once transaction has been committed but before any
+ // permanent callbacks
+ if (options.onRollback) {
+ self._callbacks.current.rollback.push(options.onRollback);
+ }
+ }
+
+ // Run temporary commit callbacks
+ var f;
+ while (f = self._callbacks.current.rollback.shift()) {
+ yield Zotero.Promise.resolve(f());
+ }
+
+ // Run rollback callbacks
+ for (var i=0; i<self._callbacks.rollback.length; i++) {
+ if (self._callbacks.rollback[i]) {
+ yield Zotero.Promise.resolve(self._callbacks.rollback[i]());
+ }
+ }
+
+ throw e;
+ }));
+ }
+ })
+ .finally(function () {
+ // Reset options back to their previous values
+ if (options) {
+ for (let option in options) {
+ self[option] = origOptions[option];
+ }
}
});
};
@@ -860,18 +1024,19 @@ Zotero.DBConnection.prototype.executeTransaction = function (func) {
/**
* @param {String} sql SQL statement to run
* @param {Array|String|Integer} [params] SQL parameters to bind
- * @return {Promise|Array} A Q promise for an array of rows. The individual
+ * @return {Promise|Array} A promise for an array of rows. The individual
* rows are Proxy objects that return values from the
* underlying mozIStorageRows based on column names.
*/
-Zotero.DBConnection.prototype.queryAsync = function (sql, params) {
+Zotero.DBConnection.prototype.queryAsync = function (sql, params, options) {
let conn;
let self = this;
+ let onRow = null;
return this._getConnectionAsync().
then(function (c) {
conn = c;
[sql, params] = self.parseQueryAndParams(sql, params);
- if (Zotero.Debug.enabled) {
+ if (Zotero.Debug.enabled && (!options || options.debug === undefined || options.debug === true)) {
Zotero.debug(sql, 5);
for each(let param in params) {
let paramType = typeof param;
@@ -880,7 +1045,20 @@ Zotero.DBConnection.prototype.queryAsync = function (sql, params) {
Zotero.debug(msg, 5);
}
}
- return conn.executeCached(sql, params);
+ if (options && options.onRow) {
+ // Errors in onRow aren't shown by default, so we wrap them in a try/catch
+ onRow = function (row) {
+ try {
+ options.onRow(row);
+ }
+ catch (e) {
+ Zotero.debug(e, 1);
+ Components.utils.reportError(e);
+ throw e;
+ }
+ }
+ }
+ return conn.executeCached(sql, params, onRow);
})
.then(function (rows) {
// Parse out the SQL command being used
@@ -890,11 +1068,25 @@ Zotero.DBConnection.prototype.queryAsync = function (sql, params) {
}
// If SELECT statement, return result
- if (op == 'select') {
+ if (op == 'select' || op == 'pragma') {
+ if (onRow) {
+ return;
+ }
// Fake an associative array with a proxy
let handler = {
get: function(target, name) {
- return target.getResultByName(name);
+ // Ignore promise check
+ if (name == 'then') {
+ return undefined;
+ }
+
+ try {
+ return target.getResultByName(name);
+ }
+ catch (e) {
+ Zotero.debug("DB column '" + name + "' not found");
+ return undefined;
+ }
}
};
for (let i=0, len=rows.length; i<len; i++) {
@@ -928,14 +1120,14 @@ Zotero.DBConnection.prototype.queryAsync = function (sql, params) {
/**
- * @param {String} sql SQL statement to run
- * @param {Array|String|Integer} [params] SQL parameters to bind
- * @return {Promise:Array|Boolean} A Q promise for either the value or FALSE if no result
+ * @param {String} sql SQL statement to run
+ * @param {Array|String|Integer} [params] SQL parameters to bind
+ * @return {Promise<Array|Boolean>} A Q promise for either the value or FALSE if no result
*/
Zotero.DBConnection.prototype.valueQueryAsync = function (sql, params) {
let self = this;
- return this._getConnectionAsync().
- then(function (conn) {
+ return this._getConnectionAsync()
+ .then(function (conn) {
[sql, params] = self.parseQueryAndParams(sql, params);
if (Zotero.Debug.enabled) {
Zotero.debug(sql, 5);
@@ -949,7 +1141,7 @@ Zotero.DBConnection.prototype.valueQueryAsync = function (sql, params) {
return conn.executeCached(sql, params);
})
.then(function (rows) {
- return rows.length ? self._getTypedValue(rows[0], 0) : false;
+ return rows.length ? rows[0].getResultByIndex(0) : false;
})
.catch(function (e) {
if (e.errors && e.errors[0]) {
@@ -965,18 +1157,14 @@ Zotero.DBConnection.prototype.valueQueryAsync = function (sql, params) {
/**
- * DEBUG: This doesn't work -- returning the mozIStorageValueArray Proxy
- * seems to break things
- *
* @param {String} sql SQL statement to run
* @param {Array|String|Integer} [params] SQL parameters to bind
- * @return {Promise:Array} A Q promise for an array of row values
+ * @return {Promise<Object>} A promise for a proxied storage row
*/
Zotero.DBConnection.prototype.rowQueryAsync = function (sql, params) {
- let self = this;
return this.queryAsync(sql, params)
.then(function (rows) {
- return rows.length ? rows[0] : [];
+ return rows.length ? rows[0] : false;
});
};
@@ -984,7 +1172,7 @@ Zotero.DBConnection.prototype.rowQueryAsync = function (sql, params) {
/**
* @param {String} sql SQL statement to run
* @param {Array|String|Integer} [params] SQL parameters to bind
- * @return {Promise:Array} A Q promise for an array of values in the column
+ * @return {Promise<Array>} A Q promise for an array of values in the column
*/
Zotero.DBConnection.prototype.columnQueryAsync = function (sql, params) {
let conn;
@@ -1007,7 +1195,7 @@ Zotero.DBConnection.prototype.columnQueryAsync = function (sql, params) {
.then(function (rows) {
var column = [];
for (let i=0, len=rows.length; i<len; i++) {
- column.push(self._getTypedValue(rows[i], 0));
+ column.push(rows[i].getResultByIndex(0));
}
return column;
})
@@ -1081,20 +1269,6 @@ Zotero.DBConnection.prototype.asyncResult = function (val) {
};
-/**
- * Asynchronously return a connection object for the current DB
- */
-Zotero.DBConnection.prototype._getConnectionAsync = Zotero.lazy(function() {
- var db = this._getDBConnection();
- var options = {
- path: db.databaseFile.path
- };
- var self = this;
- Zotero.debug("Asynchronously opening DB connection");
- return Q(this.Sqlite.openConnection(options));
-});
-
-
/*
* Implements nsIObserver
*/
@@ -1155,14 +1329,23 @@ Zotero.DBConnection.prototype.checkException = function (e) {
* allowing code to re-open the database again
*/
Zotero.DBConnection.prototype.closeDatabase = function (permanent) {
- if(this._connection) {
- var deferred = Q.defer();
- this._connection.asyncClose(deferred.resolve);
- this._connection = permanent ? false : null;
+ if (this._connection || this._connectionAsync) {
+ var deferred = Zotero.Promise.defer();
+
+ Zotero.Promise.all([this._connection.asyncClose, this._connectionAsync.asyncClose])
+ .then(function () {
+ this._connection = undefined;
+ this._connection = permanent ? false : null;
+ this._connectionAsync = undefined;
+ this._connectionAsync = permanent ? false : null;
+ }.bind(this))
+ .then(function () {
+ deferred.resolve();
+ });
+
return deferred.promise;
- } else {
- return Q();
}
+ return Zotero.Promise.resolve();
}
@@ -1505,6 +1688,9 @@ Zotero.DBConnection.prototype._getDBConnection = function () {
var cacheSize = 8192000 / pageSize;
Zotero.DB.query("PRAGMA cache_size=" + cacheSize);
+ // Enable foreign key checks
+ Zotero.DB.query("PRAGMA foreign_keys=1");
+
// Register idle and shutdown handlers to call this.observe() for DB backup
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"]
.getService(Components.interfaces.nsIIdleService);
@@ -1544,7 +1730,188 @@ Zotero.DBConnection.prototype._getDBConnection = function () {
this._connection.createFunction('text2html', 1, rx);
return this._connection;
-}
+};
+
+
+/*
+ * Retrieve a link to the data store asynchronously
+ */
+Zotero.DBConnection.prototype._getConnectionAsync = Zotero.Promise.coroutine(function* () {
+ if (this._connectionAsync) {
+ return this._connectionAsync;
+ }
+ else if (this._connectionAsync === false) {
+ throw new Error("Database permanently closed; not re-opening");
+ }
+
+ this._debug("Asynchronously opening database '" + this._dbName + "'");
+
+ // Get the storage service
+ var store = Components.classes["@mozilla.org/storage/service;1"].
+ getService(Components.interfaces.mozIStorageService);
+
+ var file = Zotero.getZoteroDatabase(this._dbName);
+ var backupFile = Zotero.getZoteroDatabase(this._dbName, 'bak');
+
+ var fileName = this._dbName + '.sqlite';
+
+ catchBlock: try {
+ var corruptMarker = Zotero.getZoteroDatabase(this._dbName, 'is.corrupt');
+ if (corruptMarker.exists()) {
+ throw {
+ name: 'NS_ERROR_FILE_CORRUPTED'
+ };
+ }
+ this._connectionAsync = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
+ path: file.path
+ }));
+ }
+ catch (e) {
+ if (e.name=='NS_ERROR_FILE_CORRUPTED') {
+ this._debug("Database file '" + file.leafName + "' corrupted", 1);
+
+ // No backup file! Eek!
+ if (!backupFile.exists()) {
+ this._debug("No backup file for DB '" + this._dbName + "' exists", 1);
+
+ // Save damaged filed
+ this._debug('Saving damaged DB file with .damaged extension', 1);
+ var damagedFile = Zotero.getZoteroDatabase(this._dbName, 'damaged');
+ Zotero.moveToUnique(file, damagedFile);
+
+ // Create new main database
+ var file = Zotero.getZoteroDatabase(this._dbName);
+ this._connectionAsync = store.openDatabase(file);
+
+ if (corruptMarker.exists()) {
+ corruptMarker.remove(null);
+ }
+
+ alert(Zotero.getString('db.dbCorruptedNoBackup', fileName));
+ break catchBlock;
+ }
+
+ // Save damaged file
+ this._debug('Saving damaged DB file with .damaged extension', 1);
+ var damagedFile = Zotero.getZoteroDatabase(this._dbName, 'damaged');
+ Zotero.moveToUnique(file, damagedFile);
+
+ // Test the backup file
+ try {
+ Zotero.debug("Asynchronously opening DB connection");
+ this._connectionAsync = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
+ path: backupFile.path
+ }));
+ }
+ // Can't open backup either
+ catch (e) {
+ // Create new main database
+ var file = Zotero.getZoteroDatabase(this._dbName);
+ this._connectionAsync = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
+ path: file.path
+ }));
+
+ alert(Zotero.getString('db.dbRestoreFailed', fileName));
+
+ if (corruptMarker.exists()) {
+ corruptMarker.remove(null);
+ }
+
+ break catchBlock;
+ }
+
+ this._connectionAsync = undefined;
+
+ // Copy backup file to main DB file
+ this._debug("Restoring database '" + this._dbName + "' from backup file", 1);
+ try {
+ backupFile.copyTo(backupFile.parent, fileName);
+ }
+ catch (e) {
+ // TODO: deal with low disk space
+ throw (e);
+ }
+
+ // Open restored database
+ var file = Zotero.getZoteroDirectory();
+ file.append(fileName);
+ this._connectionAsync = yield Zotero.Promise.resolve(this.Sqlite.openConnection({
+ path: file.path
+ }));
+ this._debug('Database restored', 1);
+ var msg = Zotero.getString('db.dbRestored', [
+ fileName,
+ Zotero.Date.getFileDateString(backupFile),
+ Zotero.Date.getFileTimeString(backupFile)
+ ]);
+ alert(msg);
+
+ if (corruptMarker.exists()) {
+ corruptMarker.remove(null);
+ }
+
+ break catchBlock;
+ }
+
+ // Some other error that we don't yet know how to deal with
+ throw (e);
+ }
+
+ if (DB_LOCK_EXCLUSIVE) {
+ yield Zotero.DB.queryAsync("PRAGMA locking_mode=EXCLUSIVE");
+ }
+ else {
+ yield Zotero.DB.queryAsync("PRAGMA locking_mode=NORMAL");
+ }
+
+ // Set page cache size to 8MB
+ var pageSize = yield Zotero.DB.valueQueryAsync("PRAGMA page_size");
+ var cacheSize = 8192000 / pageSize;
+ yield Zotero.DB.queryAsync("PRAGMA cache_size=" + cacheSize);
+
+ // Enable foreign key checks
+ yield Zotero.DB.queryAsync("PRAGMA foreign_keys=true");
+
+ // Register idle and shutdown handlers to call this.observe() for DB backup
+ var idleService = Components.classes["@mozilla.org/widget/idleservice;1"]
+ .getService(Components.interfaces.nsIIdleService);
+ idleService.addIdleObserver(this, 60);
+ idleService = null;
+
+ /*// User-defined functions
+ // TODO: move somewhere else?
+
+ // Levenshtein distance UDF
+ var lev = {
+ onFunctionCall: function (arg) {
+ var a = arg.getUTF8String(0);
+ var b = arg.getUTF8String(1);
+ return Zotero.Utilities.levenshtein(a, b);
+ }
+ };
+ this._connection.createFunction('levenshtein', 2, lev);
+
+ // Regexp UDF
+ var rx = {
+ onFunctionCall: function (arg) {
+ var re = new RegExp(arg.getUTF8String(0));
+ var str = arg.getUTF8String(1);
+ return re.test(str);
+ }
+ };
+ this._connection.createFunction('regexp', 2, rx);
+
+ // text2html UDF
+ var rx = {
+ onFunctionCall: function (arg) {
+ var str = arg.getUTF8String(0);
+ return Zotero.Utilities.text2html(str, true);
+ }
+ };
+ this._connection.createFunction('text2html', 1, rx);*/
+
+ return this._connectionAsync;
+});
Zotero.DBConnection.prototype._debug = function (str, level) {
diff --git a/chrome/content/zotero/xpcom/duplicates.js b/chrome/content/zotero/xpcom/duplicates.js
@@ -39,47 +39,35 @@ Zotero.Duplicates = function (libraryID) {
Zotero.Duplicates.prototype.__defineGetter__('name', function () Zotero.getString('pane.collections.duplicate'));
Zotero.Duplicates.prototype.__defineGetter__('libraryID', function () this._libraryID);
-
/**
* Get duplicates, populate a temporary table, and return a search based
* on that table
*
* @return {Zotero.Search}
*/
-Zotero.Duplicates.prototype.getSearchObject = function () {
- Zotero.DB.beginTransaction();
-
- var sql = "DROP TABLE IF EXISTS tmpDuplicates";
- Zotero.DB.query(sql);
-
- var sql = "CREATE TEMPORARY TABLE tmpDuplicates "
- + "(id INTEGER PRIMARY KEY)";
- Zotero.DB.query(sql);
-
- this._findDuplicates();
- var ids = this._sets.findAll(true);
-
- sql = "INSERT INTO tmpDuplicates VALUES (?)";
- var insertStatement = Zotero.DB.getStatement(sql);
-
- for each(var id in ids) {
- insertStatement.bindInt32Parameter(0, id);
+Zotero.Duplicates.prototype.getSearchObject = Zotero.Promise.coroutine(function* () {
+ yield Zotero.DB.executeTransaction(function* () {
+ var sql = "DROP TABLE IF EXISTS tmpDuplicates";
+ yield Zotero.DB.queryAsync(sql);
- try {
- insertStatement.execute();
- }
- catch(e) {
- throw (e + ' [ERROR: ' + Zotero.DB.getLastErrorString() + ']');
+ var sql = "CREATE TEMPORARY TABLE tmpDuplicates "
+ + "(id INTEGER PRIMARY KEY)";
+ yield Zotero.DB.queryAsync(sql);
+
+ this._findDuplicates();
+ var ids = this._sets.findAll(true);
+
+ sql = "INSERT INTO tmpDuplicates VALUES (?)";
+ for (let i=0; i<ids.length; i++) {
+ yield Zotero.DB.queryAsync(sql, [ids[i]], { debug: false })
}
- }
-
- Zotero.DB.commitTransaction();
+ }.bind(this));
var s = new Zotero.Search;
s.libraryID = this._libraryID;
- s.addCondition('tempTable', 'is', 'tmpDuplicates');
+ yield s.addCondition('tempTable', 'is', 'tmpDuplicates');
return s;
-}
+});
/**
@@ -308,7 +296,6 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
else {
var sql = "SELECT lastName, firstName, fieldMode FROM itemCreators "
+ "JOIN creators USING (creatorID) "
- + "JOIN creatorData USING (creatorDataID) "
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
aCreatorRows = Zotero.DB.query(sql, a.itemID);
creatorRowsCache[a.itemID] = aCreatorRows;
@@ -321,7 +308,6 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
else {
var sql = "SELECT lastName, firstName, fieldMode FROM itemCreators "
+ "JOIN creators USING (creatorID) "
- + "JOIN creatorData USING (creatorDataID) "
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
bCreatorRows = Zotero.DB.query(sql, b.itemID);
creatorRowsCache[b.itemID] = bCreatorRows;
diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js
@@ -28,13 +28,12 @@
* @namespace
*/
Zotero.File = new function(){
- Components.utils.import("resource://zotero/q.js");
+ //Components.utils.import("resource://zotero/bluebird.js");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
this.getExtension = getExtension;
this.getClosestDirectory = getClosestDirectory;
- this.getSample = getSample;
this.getContentsFromURL = getContentsFromURL;
this.putContents = putContents;
this.getValidFileName = getValidFileName;
@@ -42,6 +41,21 @@ Zotero.File = new function(){
this.getCharsetFromFile = getCharsetFromFile;
this.addCharsetListener = addCharsetListener;
+
+ this.pathToFile = function (pathOrFile) {
+ if (typeof pathOrFile == 'string') {
+ let nsIFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ nsIFile.initWithPath(pathOrFile);
+ return nsIFile;
+ }
+ else if (pathOrFile instanceof Ci.nsIFile) {
+ return pathOrFile;
+ }
+
+ throw new Error('Unexpected value provided to Zotero.MIME.pathToFile() (' + pathOrFile + ')');
+ }
+
+
/**
* Encode special characters in file paths that might cause problems,
* like # (but preserve slashes or colons)
@@ -82,11 +96,22 @@ Zotero.File = new function(){
}
- /*
- * Get the first 200 bytes of the file as a string (multibyte-safe)
+ /**
+ * Get the first 200 bytes of a source as a string (multibyte-safe)
+ *
+ * @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source - The source to read
+ * @return {Promise}
*/
- function getSample(file) {
- return this.getContents(file, null, 200);
+ this.getSample = function (file) {
+ var bytes = 200;
+ return this.getContentsAsync(file, null, bytes)
+ .catch(function (e) {
+ if (e.name == 'NS_ERROR_ILLEGAL_INPUT') {
+ Zotero.debug("Falling back to raw bytes");
+ return this.getBinaryContentsAsync(file, bytes);
+ }
+ throw e;
+ }.bind(this));
}
@@ -114,7 +139,7 @@ Zotero.File = new function(){
* @return {String} The contents of the file
* @deprecated Use {@link Zotero.File.getContentsAsync} when possible
*/
- this.getContents = function getContents(file, charset, maxLength){
+ this.getContents = function (file, charset, maxLength){
var fis;
if(file instanceof Components.interfaces.nsIInputStream) {
fis = file;
@@ -163,21 +188,36 @@ Zotero.File = new function(){
*
* @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source The source to read
* @param {String} [charset] The character set; defaults to UTF-8
- * @param {Integer} [maxLength] Maximum length to fetch, in bytes (unimplemented)
+ * @param {Integer} [maxLength] Maximum length to fetch, in bytes
* @return {Promise} A Q promise that is resolved with the contents of the file
*/
- this.getContentsAsync = function getContentsAsync(source, charset, maxLength) {
+ this.getContentsAsync = function (source, charset, maxLength) {
+ Zotero.debug("Getting contents of " + source);
+
var options = {
- charset: charset ? Zotero.CharacterSets.getName(charset) : "UTF-8"
+ charset: charset ? Zotero.CharacterSets.getName(charset) : "UTF-8",
+ // This doesn't seem to work -- reading an image file still throws NS_ERROR_ILLEGAL_INPUT
+ replacement: "\uFFFD"
};
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
NetUtil.asyncFetch(source, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
deferred.reject(new Components.Exception("File read operation failed", status));
return;
}
- deferred.resolve(NetUtil.readInputStreamToString(inputStream, inputStream.available(), options));
+ try {
+ deferred.resolve(
+ NetUtil.readInputStreamToString(
+ inputStream,
+ Math.min(maxLength, inputStream.available()),
+ options
+ )
+ );
+ }
+ catch (e) {
+ deferred.reject(e);
+ }
});
return deferred.promise;
};
@@ -187,17 +227,22 @@ Zotero.File = new function(){
* Get the contents of a binary source asynchronously
*
* @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source The source to read
+ * @param {Integer} [maxLength] Maximum length to fetch, in bytes (unimplemented)
* @return {Promise} A Q promise that is resolved with the contents of the source
*/
- this.getBinaryContentsAsync = function (source) {
- var deferred = Q.defer();
+ this.getBinaryContentsAsync = function (source, maxLength) {
+ var deferred = Zotero.Promise.defer();
NetUtil.asyncFetch(source, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
deferred.reject(new Components.Exception("Source read operation failed", status));
return;
}
-
- deferred.resolve(NetUtil.readInputStreamToString(inputStream, inputStream.available()));
+ deferred.resolve(
+ NetUtil.readInputStreamToString(
+ inputStream,
+ Math.min(maxLength, inputStream.available())
+ )
+ );
});
return deferred.promise;
}
@@ -251,20 +296,17 @@ Zotero.File = new function(){
/**
* Write data to a file asynchronously
- * @param {nsIFile} The file to write to
- * @param {String|nsIInputStream} data The string or nsIInputStream to write to the
- * file
- * @param {String} [charset] The character set; defaults to UTF-8
- * @return {Promise} A Q promise that is resolved when the file has been written
+ *
+ * @param {nsIFile} - The file to write to
+ * @param {String|nsIInputStream} data - The string or nsIInputStream to write to the file
+ * @param {String} [charset] - The character set; defaults to UTF-8
+ * @return {Promise} - A promise that is resolved when the file has been written
*/
this.putContentsAsync = function putContentsAsync(file, data, charset) {
- if (typeof data == 'string'
- && Zotero.platformMajorVersion >= 19
- && (!charset || charset.toLowerCase() == 'utf-8')) {
+ if (typeof data == 'string' && (!charset || charset.toLowerCase() == 'utf-8')) {
let encoder = new TextEncoder();
let array = encoder.encode(data);
- Components.utils.import("resource://gre/modules/osfile.jsm");
- return Q(OS.File.writeAtomic(
+ return Zotero.Promise.resolve(OS.File.writeAtomic(
file.path,
array,
{
@@ -290,7 +332,7 @@ Zotero.File = new function(){
data = converter.convertToInputStream(data);
}
- var deferred = Q.defer(),
+ var deferred = Zotero.Promise.defer(),
ostream = FileUtils.openSafeFileOutputStream(file);
NetUtil.asyncCopy(data, ostream, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
@@ -311,7 +353,7 @@ Zotero.File = new function(){
* FALSE if missing
*/
this.deleteIfExists = function deleteIfExists(path) {
- return Q(OS.File.remove(path))
+ return Zotero.Promise.resolve(OS.File.remove(path))
.thenResolve(true)
.catch(function (e) {
if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
@@ -329,7 +371,7 @@ Zotero.File = new function(){
* The DirectoryInterator is passed as the first parameter to the generator.
* A StopIteration error will be caught automatically.
*
- * Zotero.File.iterateDirectory(path, function (iterator) {
+ * Zotero.File.iterateDirectory(path, function* (iterator) {
* while (true) {
* var entry = yield iterator.next();
* [...]
@@ -340,7 +382,7 @@ Zotero.File = new function(){
*/
this.iterateDirectory = function iterateDirectory(path, generator) {
var iterator = new OS.File.DirectoryIterator(path);
- return Q.async(generator)(iterator)
+ return Zotero.Promise.coroutine(generator)(iterator)
.catch(function (e) {
if (e != StopIteration) {
throw e;
diff --git a/chrome/content/zotero/xpcom/fulltext.js b/chrome/content/zotero/xpcom/fulltext.js
@@ -26,34 +26,9 @@
Zotero.Fulltext = new function(){
const CACHE_FILE = '.zotero-ft-cache';
- this.init = init;
- this.registerPDFTool = registerPDFTool;
this.pdfConverterIsRegistered = pdfConverterIsRegistered;
this.pdfInfoIsRegistered = pdfInfoIsRegistered;
this.isCachedMIMEType = isCachedMIMEType;
- this.indexWords = indexWords;
- this.indexDocument = indexDocument;
- this.indexString = indexString;
- this.indexFile = indexFile;
- this.indexPDF = indexPDF;
- this.indexItems = indexItems;
- this.findTextInItems = findTextInItems;
- this.clearItemWords = clearItemWords;
- this.getPages = getPages;
- this.getTotalPagesFromFile = getTotalPagesFromFile;
- this.getChars = getChars;
- this.getTotalCharsFromFile = getTotalCharsFromFile;
- this.setChars = setChars;
- this.setPages = setPages;
- this.getIndexedState = getIndexedState;
- this.getIndexStats = getIndexStats;
- this.canReindex = canReindex;
- this.rebuildIndex = rebuildIndex;
- this.clearIndex = clearIndex;
- this.clearCacheFile = clearCacheFile;
- this.clearCacheFiles = clearCacheFiles;
- //this.clearItemContent = clearItemContent;
- this.purgeUnusedWords = purgeUnusedWords;
this.__defineGetter__("pdfToolsDownloadBaseURL", function() { return 'http://www.zotero.org/download/xpdf/'; });
this.__defineGetter__("pdfToolsName", function() { return 'Xpdf'; });
@@ -70,7 +45,6 @@ Zotero.Fulltext = new function(){
const _processorCacheFile = '.zotero-ft-unprocessed';
-
const kWbClassSpace = 0;
const kWbClassAlphaLetter = 1;
const kWbClassPunct = 2;
@@ -98,11 +72,9 @@ Zotero.Fulltext = new function(){
const SYNC_STATE_TO_PROCESS = 2;
const SYNC_STATE_TO_DOWNLOAD = 3;
- var self = this;
-
- function init() {
- Zotero.DB.query("ATTACH ':memory:' AS 'indexing'");
- Zotero.DB.query('CREATE TABLE indexing.fulltextWords (word NOT NULL)');
+ this.init = Zotero.Promise.coroutine(function* () {
+ yield Zotero.DB.queryAsync("ATTACH ':memory:' AS 'indexing'");
+ yield Zotero.DB.queryAsync('CREATE TABLE indexing.fulltextWords (word NOT NULL)');
this.decoder = Components.classes["@mozilla.org/intl/utf8converterservice;1"].
getService(Components.interfaces.nsIUTF8ConverterService);
@@ -120,21 +92,12 @@ Zotero.Fulltext = new function(){
this.__defineGetter__("pdfInfoFileName", function() { return _pdfInfoFileName; });
this.__defineGetter__("pdfInfoVersion", function() { return _pdfInfoVersion; });
- this.registerPDFTool('converter');
- this.registerPDFTool('info');
-
- // TEMP: Remove after 4.1 DB schema change
- var cols = Zotero.DB.getColumns('fulltextItems');
- if (cols.indexOf("synced") == -1) {
- Zotero.DB.beginTransaction();
- Zotero.DB.query("ALTER TABLE fulltextItems ADD COLUMN synced INT DEFAULT 0");
- Zotero.DB.query("REPLACE INTO settings (setting, key, value) VALUES ('fulltext', 'downloadAll', 1)");
- Zotero.DB.commitTransaction();
- }
+ yield this.registerPDFTool('converter');
+ yield this.registerPDFTool('info');
this.startContentProcessor();
Zotero.addShutdownListener(this.stopContentProcessor.bind(this));
- }
+ });
// this is a port from http://mxr.mozilla.org/mozilla-central/source/intl/lwbrk/src/nsSampleWordBreaker.cpp to
@@ -170,7 +133,7 @@ Zotero.Fulltext = new function(){
* {platform} is navigator.platform, with spaces replaced by hyphens
* e.g. "Win32", "Linux-i686", "MacPPC", "MacIntel", etc.
*/
- function registerPDFTool(tool) {
+ this.registerPDFTool = Zotero.Promise.coroutine(function* (tool) {
var errMsg = false;
var exec = Zotero.getZoteroDirectory();
@@ -205,7 +168,7 @@ Zotero.Fulltext = new function(){
var versionFile = exec.parent;
versionFile.append(fileName + '.version');
if (versionFile.exists()) {
- var version = Zotero.File.getSample(versionFile).split(/[\r\n\s]/)[0];
+ var version = (yield Zotero.File.getSample(versionFile)).split(/[\r\n\s]/)[0];
}
if (!version) {
var version = 'UNKNOWN';
@@ -226,7 +189,7 @@ Zotero.Fulltext = new function(){
Zotero.debug(toolName + ' version ' + version + ' registered at ' + exec.path);
return true;
- }
+ });
function pdfConverterIsRegistered() {
@@ -252,38 +215,39 @@ Zotero.Fulltext = new function(){
}
- /*
+ /**
* Index multiple words at once
+ *
+ * @param {Number} itemID
+ * @param {Array<string>} words
+ * @return {Promise}
*/
function indexWords(itemID, words) {
let chunk;
- Zotero.DB.beginTransaction();
- Zotero.DB.query("DELETE FROM indexing.fulltextWords");
- while (words.length > 0) {
- chunk = words.splice(0, 100);
- Zotero.DB.query('INSERT INTO indexing.fulltextWords (word) ' + ['SELECT ?' for (word of chunk)].join(' UNION '), chunk);
- }
- Zotero.DB.query('INSERT OR IGNORE INTO fulltextWords (word) SELECT word FROM indexing.fulltextWords');
- Zotero.DB.query('DELETE FROM fulltextItemWords WHERE itemID = ?', [itemID]);
- Zotero.DB.query('INSERT OR IGNORE INTO fulltextItemWords (wordID, itemID) SELECT wordID, ? FROM fulltextWords JOIN indexing.fulltextWords USING(word)', [itemID]);
- Zotero.DB.query("REPLACE INTO fulltextItems (itemID, version) VALUES (?,?)", [itemID, 0]);
- Zotero.DB.query("DELETE FROM indexing.fulltextWords");
-
- Zotero.DB.commitTransaction();
- return true;
+ return Zotero.DB.executeTransaction(function* () {
+ yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords");
+ while (words.length > 0) {
+ chunk = words.splice(0, 100);
+ Zotero.DB.queryAsync('INSERT INTO indexing.fulltextWords (word) ' + ['SELECT ?' for (word of chunk)].join(' UNION '), chunk);
+ }
+ yield Zotero.DB.queryAsync('INSERT OR IGNORE INTO fulltextWords (word) SELECT word FROM indexing.fulltextWords');
+ yield Zotero.DB.queryAsync('DELETE FROM fulltextItemWords WHERE itemID = ?', [itemID]);
+ yield Zotero.DB.queryAsync('INSERT OR IGNORE INTO fulltextItemWords (wordID, itemID) SELECT wordID, ? FROM fulltextWords JOIN indexing.fulltextWords USING(word)', [itemID]);
+ yield Zotero.DB.queryAsync("REPLACE INTO fulltextItems (itemID, version) VALUES (?,?)", [itemID, 0]);
+ yield Zotero.DB.queryAsync("DELETE FROM indexing.fulltextWords");
+ }.bind(this));
}
- function indexString(text, charset, itemID, stats, version, synced) {
- try {
- Zotero.UnresponsiveScriptIndicator.disable();
-
- var words = this.semanticSplitter(text, charset);
-
- Zotero.DB.beginTransaction();
-
+ /**
+ * @return {Promise}
+ */
+ var indexString = Zotero.Promise.coroutine(function* (text, charset, itemID, stats, version, synced) {
+ var words = this.semanticSplitter(text, charset);
+
+ yield Zotero.DB.executeTransaction(function* () {
this.clearItemWords(itemID, true);
- this.indexWords(itemID, words, stats, version, synced);
+ yield indexWords(itemID, words, stats, version, synced);
var sql = "UPDATE fulltextItems SET synced=?";
var params = [synced ? parseInt(synced) : SYNC_STATE_UNSYNCED];
@@ -299,30 +263,31 @@ Zotero.Fulltext = new function(){
}
sql += " WHERE itemID=?";
params.push(itemID);
- Zotero.DB.query(sql, params);
+ yield Zotero.DB.queryAsync(sql, params);
/*
var sql = "REPLACE INTO fulltextContent (itemID, textContent) VALUES (?,?)";
Zotero.DB.query(sql, [itemID, {string:text}]);
*/
-
- Zotero.DB.commitTransaction();
-
- // If there's a processor cache file, delete it (whether or not we just used it)
- var cacheFile = this.getItemProcessorCacheFile(itemID);
- if (cacheFile.exists()) {
- cacheFile.remove(false);
- }
-
- Zotero.Notifier.trigger('refresh', 'item', itemID);
- }
- finally {
- Zotero.UnresponsiveScriptIndicator.enable();
+ }.bind(this));
+
+ // If there's a processor cache file, delete it (whether or not we just used it)
+ var item = yield Zotero.Items.getAsync(itemID);
+ var cacheFile = this.getItemProcessorCacheFile(item);
+ if (cacheFile.exists()) {
+ cacheFile.remove(false);
}
- }
+
+ Zotero.Notifier.trigger('refresh', 'item', itemID);
+ }.bind(this));
- function indexDocument(document, itemID){
+ /**
+ * @param {Document} document
+ * @param {Number} itemID
+ * @return {Promise}
+ */
+ this.indexDocument = Zotero.Promise.coroutine(function* (document, itemID) {
if (!itemID){
throw ('Item ID not provided to indexDocument()');
}
@@ -345,7 +310,7 @@ Zotero.Fulltext = new function(){
}
var maxLength = Zotero.Prefs.get('fulltext.textMaxLength');
- var obj = convertItemHTMLToText(itemID, document.body.innerHTML, maxLength);
+ var obj = yield convertItemHTMLToText(itemID, document.body.innerHTML, maxLength);
var text = obj.text;
var totalChars = obj.totalChars;
@@ -354,15 +319,15 @@ Zotero.Fulltext = new function(){
+ itemID + ' in indexDocument()');
}
- this.indexString(text, document.characterSet, itemID);
- this.setChars(itemID, { indexed: text.length, total: totalChars });
- }
+ yield indexString(text, document.characterSet, itemID);
+ yield setChars(itemID, { indexed: text.length, total: totalChars });
+ });
/**
* @param {Boolean} [complete=FALSE] Index the file in its entirety, ignoring maxLength
*/
- function indexFile(file, mimeType, charset, itemID, complete, isCacheFile) {
+ var indexFile = Zotero.Promise.coroutine(function* (file, mimeType, charset, itemID, complete, isCacheFile) {
if (!file.exists()){
Zotero.debug('File not found in indexFile()', 2);
return false;
@@ -376,13 +341,7 @@ Zotero.Fulltext = new function(){
}
if (mimeType == 'application/pdf') {
- try {
- Zotero.UnresponsiveScriptIndicator.disable();
- return this.indexPDF(file, itemID, complete);
- }
- finally {
- Zotero.UnresponsiveScriptIndicator.enable();
- }
+ return this.indexPDF(file, itemID, complete);
}
if (!Zotero.MIME.isTextType(mimeType)) {
@@ -397,12 +356,12 @@ Zotero.Fulltext = new function(){
Zotero.debug('Indexing file ' + file.path);
- var text = Zotero.File.getContents(file, charset);
+ var text = yield Zotero.File.getContentsAsync(file, charset);
var totalChars = text.length;
var maxLength = complete ? false : Zotero.Prefs.get('fulltext.textMaxLength');
if (mimeType == 'text/html') {
- let obj = convertItemHTMLToText(itemID, text, maxLength);
+ let obj = yield convertItemHTMLToText(itemID, text, maxLength);
text = obj.text;
totalChars = obj.totalChars;
}
@@ -412,29 +371,30 @@ Zotero.Fulltext = new function(){
}
}
- Zotero.DB.beginTransaction();
-
- this.indexString(text, charset, itemID);
-
- // Record the number of characters indexed (unless we're indexing a (PDF) cache file,
- // in which case the stats are coming from elsewhere)
- if (!isCacheFile) {
- this.setChars(itemID, { indexed: text.length, total: totalChars });
- }
-
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ yield indexString(text, charset, itemID);
+
+ // Record the number of characters indexed (unless we're indexing a (PDF) cache file,
+ // in which case the stats are coming from elsewhere)
+ if (!isCacheFile) {
+ yield setChars(itemID, { indexed: text.length, total: totalChars });
+ }
+ });
return true;
- }
+ }.bind(this));
- /*
+ /**
* Run PDF through pdfinfo and pdftotext to generate .zotero-ft-info
* and .zotero-ft-cache, and pass the text file back to indexFile()
*
- * @param allPages If true, index all pages rather than pdfMaxPages
+ * @param {nsIFile} file
+ * @param {Number} itemID
+ * @param {Boolean} [allPages] - If true, index all pages rather than pdfMaxPages
+ * @return {Promise}
*/
- function indexPDF(file, itemID, allPages) {
+ this.indexPDF = Zotero.Promise.coroutine(function* (file, itemID, allPages) {
if (!_pdfConverter) {
Zotero.debug("PDF tools are not installed -- skipping indexing");
return false;
@@ -445,12 +405,12 @@ Zotero.Fulltext = new function(){
return false;
}
- var item = Zotero.Items.get(itemID);
+ var item = yield Zotero.Items.getAsync(itemID);
var linkMode = item.attachmentLinkMode;
// If file is stored outside of Zotero, create a directory for the item
// in the storage directory and save the cache file there
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
- var cacheFile = Zotero.Attachments.createDirectoryForItem(itemID);
+ var cacheFile = yield Zotero.Attachments.createDirectoryForItem(item);
}
else {
var cacheFile = file.parent;
@@ -469,7 +429,7 @@ Zotero.Fulltext = new function(){
var args = [file.path, infoFile.path];
try {
proc.runw(true, args, args.length);
- var totalPages = this.getTotalPagesFromFile(itemID);
+ var totalPages = yield getTotalPagesFromFile(itemID);
}
catch (e) {
Zotero.debug("Error running pdfinfo");
@@ -516,29 +476,31 @@ Zotero.Fulltext = new function(){
return false;
}
- Zotero.DB.beginTransaction();
- this.indexFile(cacheFile, 'text/plain', 'utf-8', itemID, true, true);
- this.setPages(itemID, { indexed: pagesIndexed, total: totalPages });
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ yield indexFile(cacheFile, 'text/plain', 'utf-8', itemID, true, true);
+ yield setPages(itemID, { indexed: pagesIndexed, total: totalPages });
+ });
+
return true;
- }
+ });
- function indexItems(items, complete, ignoreErrors) {
+ this.indexItems = Zotero.Promise.coroutine(function* (items, complete, ignoreErrors) {
if (!Array.isArray(items)) {
items = [items];
}
- var items = Zotero.Items.get(items);
+ var items = yield Zotero.Items.getAsync(items);
var found = [];
- for each (let item in items) {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
if (!item.isAttachment()) {
continue;
}
let itemID = item.id;
- var file = item.getFile();
+ var file = yield item.getFile();
if (!file){
Zotero.debug("No file to index for item " + itemID + " in Fulltext.indexItems()");
continue;
@@ -546,7 +508,7 @@ Zotero.Fulltext = new function(){
if (ignoreErrors) {
try {
- this.indexFile(file, item.attachmentMIMEType, item.attachmentCharset, itemID, complete);
+ yield indexFile(file, item.attachmentMIMEType, item.attachmentCharset, itemID, complete);
}
catch (e) {
Zotero.debug(e, 1);
@@ -555,10 +517,10 @@ Zotero.Fulltext = new function(){
}
}
else {
- this.indexFile(file, item.attachmentMIMEType, item.attachmentCharset, itemID, complete);
+ yield indexFile(file, item.attachmentMIMEType, item.attachmentCharset, itemID, complete);
}
}
- }
+ });
//
@@ -570,9 +532,9 @@ Zotero.Fulltext = new function(){
* @param {Integer} maxChars Maximum total characters to include.
* The total can go over this if there's a
* single large item.
- * @return {Array<Object>}
+ * @return {Promise<Array<Object>>}
*/
- this.getUnsyncedContent = function (maxChars) {
+ this.getUnsyncedContent = Zotero.Promise.coroutine(function* (maxChars) {
var maxLength = Zotero.Prefs.get('fulltext.textMaxLength');
var first = true;
var chars = 0;
@@ -580,14 +542,14 @@ Zotero.Fulltext = new function(){
var sql = "SELECT itemID, indexedChars, totalChars, indexedPages, totalPages "
+ "FROM fulltextItems JOIN items USING (itemID) WHERE synced=" + SYNC_STATE_UNSYNCED
+ " ORDER BY clientDateModified DESC";
- var rows = Zotero.DB.query(sql) || [];
+ var rows = yield Zotero.DB.queryAsync(sql) || [];
var libraryIsEditable = {};
var skips = 0;
var maxSkips = 5;
for each (let row in rows) {
let text;
let itemID = row.itemID;
- let item = Zotero.Items.get(itemID);
+ let item = yield Zotero.Items.getAsync(itemID);
let libraryID = item.libraryID;
// Don't send full-text in read-only libraries
if (libraryID && libraryIsEditable[libraryID] === undefined) {
@@ -600,11 +562,11 @@ Zotero.Fulltext = new function(){
let mimeType = item.attachmentMIMEType;
if (isCachedMIMEType(mimeType) || Zotero.MIME.isTextType(mimeType)) {
try {
- let cacheFile = this.getItemCacheFile(itemID);
+ let cacheFile = this.getItemCacheFile(item);
if (cacheFile.exists()) {
Zotero.debug("Adding full-text content from cache "
+ "file for item " + libraryKey);
- text = Zotero.File.getContents(cacheFile);
+ text = yield Zotero.File.getContentsAsync(cacheFile);
}
else {
if (!Zotero.MIME.isTextType(mimeType)) {
@@ -621,11 +583,11 @@ Zotero.Fulltext = new function(){
}
Zotero.debug("Adding full-text content from file for item " + libraryKey);
- text = Zotero.File.getContents(file, item.attachmentCharset);
+ text = yield Zotero.File.getContentsAsync(file, item.attachmentCharset);
// If HTML, convert to plain text first, and cache the result
if (item.attachmentMIMEType == 'text/html') {
- let obj = convertItemHTMLToText(
+ let obj = yield convertItemHTMLToText(
itemID,
text,
// Include in the cache file only as many characters as we
@@ -651,7 +613,7 @@ Zotero.Fulltext = new function(){
+ libraryKey, 2);
// Delete rows for items that weren't supposed to be indexed
- this.clearItemWords(itemID);
+ yield this.clearItemWords(itemID);
continue;
}
@@ -683,28 +645,29 @@ Zotero.Fulltext = new function(){
}
}
return contentItems;
- }
+ });
/**
* @return {String} PHP-formatted POST data for items not yet downloaded
*/
- this.getUndownloadedPostData = function () {
+ this.getUndownloadedPostData = Zotero.Promise.coroutine(function* () {
// On upgrade, get all content
var sql = "SELECT value FROM settings WHERE setting='fulltext' AND key='downloadAll'";
- if (Zotero.DB.valueQuery(sql)) {
+ if (yield Zotero.DB.valueQueryAsync(sql)) {
return "&ftkeys=all";
}
var sql = "SELECT itemID FROM fulltextItems WHERE synced="
+ SYNC_STATE_TO_DOWNLOAD;
- var itemIDs = Zotero.DB.columnQuery(sql);
+ var itemIDs = yield Zotero.DB.columnQueryAsync(sql);
if (!itemIDs) {
return "";
}
var undownloaded = {};
- for each (let itemID in itemIDs) {
- let item = Zotero.Items.get(itemID);
+ for (let i=0; i<itemIDs.length; i++) {
+ let itemID = itemIDs[i];
+ let item = yield Zotero.Items.getAsync(itemID);
let libraryID = item.libraryID
libraryID = libraryID ? libraryID : Zotero.libraryID;
if (!undownloaded[libraryID]) {
@@ -720,15 +683,17 @@ Zotero.Fulltext = new function(){
}
}
return data;
- }
+ });
/**
* Save full-text content and stats to a cache file
+ *
+ * @return {Promise}
*/
- this.setItemContent = function (libraryID, key, text, stats, version) {
+ this.setItemContent = Zotero.Promise.coroutine(function* (libraryID, key, text, stats, version) {
var libraryKey = libraryID + "/" + key;
- var item = Zotero.Items.getByLibraryAndKey(libraryID, key);
+ var item = yield Zotero.Items.getByLibraryAndKey(libraryID, key);
if (!item) {
let msg = "Item " + libraryKey + " not found setting full-text content";
Zotero.debug(msg, 1);
@@ -737,17 +702,17 @@ Zotero.Fulltext = new function(){
}
var itemID = item.id;
- var currentVersion = Zotero.DB.valueQuery(
+ var currentVersion = yield Zotero.DB.valueQueryAsync(
"SELECT version FROM fulltextItems WHERE itemID=?", itemID
);
if (text !== '') {
- var processorCacheFile = this.getItemProcessorCacheFile(itemID);
- var itemCacheFile = this.getItemCacheFile(itemID);
+ var processorCacheFile = this.getItemProcessorCacheFile(item);
+ var itemCacheFile = this.getItemCacheFile(item);
// If a storage directory doesn't exist, create it
if (!processorCacheFile.parent.exists()) {
- Zotero.Attachments.createDirectoryForItem(itemID);
+ yield Zotero.Attachments.createDirectoryForItem(item);
}
// If the local version of the content is already up to date and cached, skip
@@ -758,16 +723,16 @@ Zotero.Fulltext = new function(){
}
// If the local version is 0 but the text matches, just update the version
else if (currentVersion == 0 && itemCacheFile.exists()
- && Zotero.File.getContents(itemCacheFile) == text) {
+ && (yield Zotero.File.getContentsAsync(itemCacheFile)) == text) {
Zotero.debug("Current full-text content matches remote for item "
+ libraryKey + " -- updating version");
var synced = SYNC_STATE_IN_SYNC;
- Zotero.DB.query("UPDATE fulltextItems SET version=? WHERE itemID=?", [version, itemID]);
+ yield Zotero.DB.queryAsync("UPDATE fulltextItems SET version=? WHERE itemID=?", [version, itemID]);
}
else {
Zotero.debug("Writing full-text content and data for item " + libraryKey
+ " to " + processorCacheFile.path);
- Zotero.File.putContents(processorCacheFile, JSON.stringify({
+ yield Zotero.File.putContentsAsync(processorCacheFile, JSON.stringify({
indexedChars: stats.indexedChars,
totalChars: stats.totalChars,
indexedPages: stats.indexedPages,
@@ -785,23 +750,23 @@ Zotero.Fulltext = new function(){
// If indexed previously, update the sync state
if (currentVersion !== false) {
- Zotero.DB.query("UPDATE fulltextItems SET synced=? WHERE itemID=?", [synced, itemID]);
+ yield Zotero.DB.queryAsync("UPDATE fulltextItems SET synced=? WHERE itemID=?", [synced, itemID]);
}
// If not yet indexed, add an empty row
else {
- Zotero.DB.query(
+ yield Zotero.DB.queryAsync(
"REPLACE INTO fulltextItems (itemID, version, synced) VALUES (?, 0, ?)",
[itemID, synced]
);
}
if (_upgradeCheck) {
- Zotero.DB.query("DELETE FROM settings WHERE setting='fulltext' AND key='downloadAll'");
+ yield Zotero.DB.queryAsync("DELETE FROM settings WHERE setting='fulltext' AND key='downloadAll'");
_upgradeCheck = false;
}
this.startContentProcessor();
- }
+ });
/**
@@ -841,12 +806,12 @@ Zotero.Fulltext = new function(){
* to find unprocessed content
* @return {Boolean} TRUE if there's more content to process; FALSE otherwise
*/
- this.processUnprocessedContent = function (itemIDs) {
+ this.processUnprocessedContent = Zotero.Promise.coroutine(function* (itemIDs) {
if (!itemIDs) {
Zotero.debug("Checking for unprocessed full-text content");
let sql = "SELECT itemID FROM fulltextItems WHERE synced="
+ SYNC_STATE_TO_PROCESS;
- itemIDs = Zotero.DB.columnQuery(sql) || [];
+ itemIDs = yield Zotero.DB.columnQueryAsync(sql);
}
var origLen = itemIDs.length;
@@ -867,28 +832,26 @@ Zotero.Fulltext = new function(){
}
let itemID = itemIDs.shift();
- let item = Zotero.Items.get(itemID);
+ let item = yield Zotero.Items.getAsync(itemID);
Zotero.debug("Processing full-text content for item " + item.libraryKey);
- Zotero.Fulltext.indexFromProcessorCache(itemID)
- .then(function () {
- if (itemIDs.length) {
- if (!_processorTimer) {
- _processorTimer = Components.classes["@mozilla.org/timer;1"]
- .createInstance(Components.interfaces.nsITimer);
- }
- _processorTimer.initWithCallback(
- function () {
- Zotero.Fulltext.processUnprocessedContent(itemIDs);
- },
- 100,
- Components.interfaces.nsITimer.TYPE_ONE_SHOT
- );
+ yield Zotero.Fulltext.indexFromProcessorCache(itemID);
+
+ if (itemIDs.length) {
+ if (!_processorTimer) {
+ _processorTimer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
}
- })
- .done();
- }
+ _processorTimer.initWithCallback(
+ function () {
+ Zotero.Fulltext.processUnprocessedContent(itemIDs);
+ },
+ 100,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT
+ );
+ }
+ });
this.idleObserver = {
observe: function (subject, topic, data) {
@@ -908,51 +871,50 @@ Zotero.Fulltext = new function(){
};
- this.indexFromProcessorCache = function (itemID) {
- var self = this;
- return Q.fcall(function () {
- var cacheFile = self.getItemProcessorCacheFile(itemID);
+ /**
+ * @param {Number} itemID
+ * @return {Promise<Boolean>}
+ */
+ this.indexFromProcessorCache = Zotero.Promise.coroutine(function* (itemID) {
+ try {
+ var item = yield Zotero.Items.getAsync(itemID);
+ var cacheFile = this.getItemProcessorCacheFile(item);
if (!cacheFile.exists()) {
Zotero.debug("Full-text content processor cache file doesn't exist for item " + itemID);
return false;
}
- let data;
+ var json = yield Zotero.File.getContentsAsync(cacheFile);
+ var data = JSON.parse(json);
- return Zotero.File.getContentsAsync(cacheFile)
- .then(function (json) {
- data = JSON.parse(json);
-
- // Write the text content to the regular cache file
- cacheFile = self.getItemCacheFile(itemID);
-
- Zotero.debug("Writing full-text content to " + cacheFile.path);
- return Zotero.File.putContentsAsync(cacheFile, data.text).thenResolve(true);
- })
- .then(function (index) {
- if (index) {
- Zotero.Fulltext.indexString(
- data.text,
- "UTF-8",
- itemID,
- {
- indexedChars: data.indexedChars,
- totalChars: data.totalChars,
- indexedPages: data.indexedPages,
- totalPages: data.totalPages
- },
- data.version,
- 1
- );
- }
- });
- })
- .catch(function (e) {
+ // Write the text content to the regular cache file
+ var item = yield Zotero.Items.getAsync(itemID);
+ cacheFile = this.getItemCacheFile(item);
+ Zotero.debug("Writing full-text content to " + cacheFile.path);
+ yield Zotero.File.putContentsAsync(cacheFile, data.text);
+
+ yield indexString(
+ data.text,
+ "UTF-8",
+ itemID,
+ {
+ indexedChars: data.indexedChars,
+ totalChars: data.totalChars,
+ indexedPages: data.indexedPages,
+ totalPages: data.totalPages
+ },
+ data.version,
+ 1
+ );
+
+ return true;
+ }
+ catch (e) {
Components.utils.reportError(e);
Zotero.debug(e, 1);
return false;
- });
- }
+ };
+ });
//
// End full-text content syncing
@@ -970,7 +932,7 @@ Zotero.Fulltext = new function(){
*
* - Slashes in regex are optional
*/
- this.findTextInString = function (content, searchText, mode) {
+ function findTextInString(content, searchText, mode) {
switch (mode){
case 'regexp':
case 'regexpCS':
@@ -1019,7 +981,7 @@ Zotero.Fulltext = new function(){
return -1;
}
- /*
+ /**
* Scan item files for a text string
*
* _items_ -- one or more attachment items to search
@@ -1032,16 +994,20 @@ Zotero.Fulltext = new function(){
* Note:
* - Slashes in regex are optional
* - Add 'Binary' to the mode to search all files, not just text files
+ *
+ * @return {Promise<Array<Object>>} A promise for an array of match objects, with 'id' containing
+ * an itemID and 'match' containing a string snippet
*/
- function findTextInItems(items, searchText, mode){
+ this.findTextInItems = Zotero.Promise.coroutine(function* (items, searchText, mode){
if (!searchText){
return [];
}
- var items = Zotero.Items.get(items);
+ var items = yield Zotero.Items.getAsync(items);
var found = [];
- for each (let item in items) {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
if (!item.isAttachment()) {
continue;
}
@@ -1053,13 +1019,13 @@ Zotero.Fulltext = new function(){
let binaryMode = mode && mode.indexOf('Binary') != -1;
if (isCachedMIMEType(mimeType)) {
- let file = this.getItemCacheFile(itemID);
+ let file = this.getItemCacheFile(item);
if (!file.exists()) {
continue;
}
Zotero.debug("Searching for text '" + searchText + "' in " + file.path);
- content = Zotero.File.getContents(file, 'utf-8', maxLength);
+ content = yield Zotero.File.getContentsAsync(file, 'utf-8', maxLength);
}
else {
// If not binary mode, only scan plaintext files
@@ -1071,10 +1037,10 @@ Zotero.Fulltext = new function(){
}
// Check for a cache file
- let cacheFile = this.getItemCacheFile(itemID);
+ let cacheFile = this.getItemCacheFile(item);
if (cacheFile.exists()) {
Zotero.debug("Searching for text '" + searchText + "' in " + cacheFile.path);
- content = Zotero.File.getContents(cacheFile, 'utf-8', maxLength);
+ content = yield Zotero.File.getContentsAsync(cacheFile, 'utf-8', maxLength);
}
else {
// If that doesn't exist, check for the actual file
@@ -1084,14 +1050,14 @@ Zotero.Fulltext = new function(){
}
Zotero.debug("Searching for text '" + searchText + "' in " + file.path);
- content = Zotero.File.getContents(file, item.attachmentCharset);
+ content = yield Zotero.File.getContentsAsync(file, item.attachmentCharset);
// If HTML and not binary mode, convert to text
if (mimeType == 'text/html' && !binaryMode) {
// Include in the cache file only as many characters as we've indexed
- let chars = this.getChars(itemID);
+ let chars = yield getChars(itemID);
- let obj = convertItemHTMLToText(
+ let obj = yield convertItemHTMLToText(
itemID, content, chars ? chars.indexedChars : null
);
content = obj.text;
@@ -1099,7 +1065,7 @@ Zotero.Fulltext = new function(){
}
}
- let match = this.findTextInString(content, searchText, mode);
+ let match = findTextInString(content, searchText, mode);
if (match != -1) {
found.push({
id: itemID,
@@ -1109,18 +1075,19 @@ Zotero.Fulltext = new function(){
}
return found;
- }
+ });
- function clearItemWords(itemID, skipCacheClear) {
- Zotero.DB.beginTransaction();
- var sql = "SELECT rowid FROM fulltextItems WHERE itemID=? LIMIT 1";
- var indexed = Zotero.DB.valueQuery(sql, itemID);
- if (indexed) {
- Zotero.DB.query("DELETE FROM fulltextItemWords WHERE itemID=?", itemID);
- Zotero.DB.query("DELETE FROM fulltextItems WHERE itemID=?", itemID);
- }
- Zotero.DB.commitTransaction();
+ this.clearItemWords = Zotero.Promise.coroutine(function* (itemID, skipCacheClear) {
+ var indexed = yield Zotero.DB.executeTransaction(function* () {
+ var sql = "SELECT rowid FROM fulltextItems WHERE itemID=? LIMIT 1";
+ var indexed = yield Zotero.DB.valueQueryAsync(sql, itemID);
+ if (indexed) {
+ yield Zotero.DB.queryAsync("DELETE FROM fulltextItemWords WHERE itemID=?", itemID);
+ yield Zotero.DB.queryAsync("DELETE FROM fulltextItems WHERE itemID=?", itemID);
+ }
+ return indexed;
+ }.bind(this));
if (indexed) {
Zotero.Prefs.set('purge.fulltext', true);
@@ -1128,28 +1095,33 @@ Zotero.Fulltext = new function(){
if (!skipCacheClear) {
// Delete fulltext cache file if there is one
- this.clearCacheFile(itemID);
+ yield clearCacheFile(itemID);
}
- }
+ });
- function getPages(itemID, force) {
+ /**
+ * @return {Promise}
+ */
+ this.getPages = function (itemID, force) {
var sql = "SELECT indexedPages, totalPages AS total "
+ "FROM fulltextItems WHERE itemID=?";
- return Zotero.DB.rowQuery(sql, itemID);
+ return Zotero.DB.rowQueryAsync(sql, itemID);
}
- /*
+ /**
* Gets the number of pages from the PDF info cache file
+ *
+ * @return {Promise}
*/
- function getTotalPagesFromFile(itemID) {
- var file = Zotero.Attachments.getStorageDirectory(itemID);
+ var getTotalPagesFromFile = Zotero.Promise.coroutine(function* (itemID) {
+ var file = Zotero.Attachments.getStorageDirectoryByID(itemID);
file.append(this.pdfInfoCacheFile);
if (!file.exists()) {
return false;
}
- var contents = Zotero.File.getContents(file);
+ var contents = yield Zotero.File.getContentsAsync(file);
try {
// Parse pdfinfo output
var pages = contents.match('Pages:[^0-9]+([0-9]+)')[1];
@@ -1159,24 +1131,29 @@ Zotero.Fulltext = new function(){
return false;
}
return pages;
- }
+ });
+ /**
+ * @return {Promise}
+ */
function getChars(itemID) {
var sql = "SELECT indexedChars, totalChars AS total "
+ "FROM fulltextItems WHERE itemID=?";
- return Zotero.DB.rowQuery(sql, itemID);
+ return Zotero.DB.rowQueryAsync(sql, itemID);
}
- /*
+ /**
* Gets the number of characters from the PDF converter cache file
+ *
+ * @return {Promise}
*/
- function getTotalCharsFromFile(itemID) {
- var item = Zotero.Items.get(itemID);
+ var getTotalCharsFromFile = Zotero.Promise.coroutine(function* (itemID) {
+ var item = yield Zotero.Items.getAsync(itemID);
switch (item.attachmentMIMEType) {
case 'application/pdf':
- var file = Zotero.Attachments.getStorageDirectory(itemID);
+ var file = Zotero.Attachments.getStorageDirectory(item);
file.append(this.pdfConverterCacheFile);
if (!file.exists()) {
return false;
@@ -1190,13 +1167,17 @@ Zotero.Fulltext = new function(){
}
}
- return Zotero.File.getContents(file).length;
- }
+ var contents = yield Zotero.File.getContentsAsync(file);
+ return contents.length;
+ });
+ /**
+ * @return {Promise}
+ */
function setPages(itemID, obj) {
var sql = "UPDATE fulltextItems SET indexedPages=?, totalPages=? WHERE itemID=?";
- Zotero.DB.query(
+ return Zotero.DB.queryAsync(
sql,
[
obj.indexed ? parseInt(obj.indexed) : null,
@@ -1207,9 +1188,14 @@ Zotero.Fulltext = new function(){
}
+ /**
+ * @param {Number} itemID
+ * @param {Object} obj
+ * @return {Promise}
+ */
function setChars(itemID, obj) {
var sql = "UPDATE fulltextItems SET indexedChars=?, totalChars=? WHERE itemID=?";
- Zotero.DB.query(
+ return Zotero.DB.queryAsync(
sql,
[
obj.indexed ? parseInt(obj.indexed) : null,
@@ -1223,20 +1209,16 @@ Zotero.Fulltext = new function(){
/*
* Gets the indexed state of an item,
*/
- function getIndexedState(itemID) {
- var item = Zotero.Items.get(itemID);
- if (!item) {
- throw ("Invalid item " + itemID + " in Zotero.Fulltext.getIndexedState()");
- }
-
+ this.getIndexedState = Zotero.Promise.coroutine(function* (item) {
if (!item.isAttachment()) {
- throw ('Item ' + itemID + ' is not an attachment in Zotero.Fulltext.getIndexedState()');
+ throw new Error('Item is not an attachment');
}
+ var itemID = item.id;
switch (item.attachmentMIMEType) {
// Use pages for PDFs
case 'application/pdf':
- var pages = this.getPages(itemID);
+ var pages = yield this.getPages(itemID);
if (pages) {
var indexedPages = pages.indexedPages;
var totalPages = pages.total;
@@ -1261,7 +1243,7 @@ Zotero.Fulltext = new function(){
// Use chars
default:
- var chars = this.getChars(itemID);
+ var chars = yield getChars(itemID);
if (chars) {
var indexedChars = chars.indexedChars;
var totalChars = chars.total;
@@ -1284,50 +1266,54 @@ Zotero.Fulltext = new function(){
}
}
return status;
- }
+ });
- this.isFullyIndexed = function (itemID) {
- if (!itemID) {
- throw ("itemID not provided in Zotero.Fulltext.isFullyIndexed()");
- }
- return this.getIndexedState(itemID) == this.INDEX_STATE_INDEXED;
- }
+ this.isFullyIndexed = Zotero.Promise.coroutine(function* (item) {
+ return (yield this.getIndexedState(item)) == this.INDEX_STATE_INDEXED;
+ });
- function getIndexStats() {
+ /**
+ * @return {Promise}
+ */
+ this.getIndexStats = Zotero.Promise.coroutine(function* () {
var sql = "SELECT COUNT(*) FROM fulltextItems WHERE "
+ "(indexedPages IS NOT NULL AND indexedPages=totalPages) OR "
+ "(indexedChars IS NOT NULL AND indexedChars=totalChars)"
- var indexed = Zotero.DB.valueQuery(sql);
+ var indexed = yield Zotero.DB.valueQueryAsync(sql);
var sql = "SELECT COUNT(*) FROM fulltextItems WHERE "
+ "(indexedPages IS NOT NULL AND indexedPages<totalPages) OR "
+ "(indexedChars IS NOT NULL AND indexedChars<totalChars)"
- var partial = Zotero.DB.valueQuery(sql);
+ var partial = yield Zotero.DB.valueQueryAsync(sql);
var sql = "SELECT COUNT(*) FROM itemAttachments WHERE itemID NOT IN "
+ "(SELECT itemID FROM fulltextItems WHERE "
+ "indexedPages IS NOT NULL OR indexedChars IS NOT NULL)";
- var unindexed = Zotero.DB.valueQuery(sql);
+ var unindexed = yield Zotero.DB.valueQueryAsync(sql);
var sql = "SELECT COUNT(*) FROM fulltextWords";
- var words = Zotero.DB.valueQuery(sql);
+ var words = yield Zotero.DB.valueQueryAsync(sql);
- return { indexed: indexed, partial: partial, unindexed: unindexed,
- words: words };
- }
+ return {
+ indexed: indexed,
+ partial: partial,
+ unindexed: unindexed,
+ words: words
+ };
+ });
- this.getItemCacheFile = function (itemID) {
- var cacheFile = Zotero.Attachments.getStorageDirectory(itemID);
- cacheFile.append(self.pdfConverterCacheFile);
+ this.getItemCacheFile = function (item) {
+ var cacheFile = Zotero.Attachments.getStorageDirectory(item);
+ cacheFile.append(this.pdfConverterCacheFile);
return cacheFile;
}
- this.getItemProcessorCacheFile = function (itemID) {
- var cacheFile = Zotero.Attachments.getStorageDirectory(itemID);
+ this.getItemProcessorCacheFile = function (item) {
+ var cacheFile = Zotero.Attachments.getStorageDirectory(item);
cacheFile.append(_processorCacheFile);
return cacheFile;
}
@@ -1338,11 +1324,9 @@ Zotero.Fulltext = new function(){
*
* Item must be a non-web-link attachment that isn't already fully indexed
*/
- function canReindex(itemID) {
- var item = Zotero.Items.get(itemID);
- if (item && item.isAttachment() && item.attachmentLinkMode !=
- Zotero.Attachments.LINK_MODE_LINKED_URL) {
- switch (this.getIndexedState(itemID)) {
+ this.canReindex = Zotero.Promise.coroutine(function* (item) {
+ if (item.isAttachment() && item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ switch (yield this.getIndexedState(item)) {
case this.INDEX_STATE_UNAVAILABLE:
case this.INDEX_STATE_UNINDEXED:
case this.INDEX_STATE_PARTIAL:
@@ -1352,12 +1336,14 @@ Zotero.Fulltext = new function(){
return true;
}
}
-
return false;
- }
+ });
- function rebuildIndex(unindexedOnly){
+ /**
+ * @return {Promise}
+ */
+ this.rebuildIndex = Zotero.Promise.coroutine(function* (unindexedOnly) {
// Get all attachments other than web links
var sql = "SELECT itemID FROM itemAttachments WHERE linkMode!="
+ Zotero.Attachments.LINK_MODE_LINKED_URL;
@@ -1365,57 +1351,57 @@ Zotero.Fulltext = new function(){
sql += " AND itemID NOT IN (SELECT itemID FROM fulltextItems "
+ "WHERE indexedChars IS NOT NULL OR indexedPages IS NOT NULL)";
}
- var items = Zotero.DB.columnQuery(sql);
+ var items = yield Zotero.DB.columnQueryAsync(sql);
if (items) {
- Zotero.DB.beginTransaction();
- Zotero.DB.query("DELETE FROM fulltextItemWords WHERE itemID IN (" + sql + ")");
- Zotero.DB.query("DELETE FROM fulltextItems WHERE itemID IN (" + sql + ")");
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ yield Zotero.DB.queryAsync("DELETE FROM fulltextItemWords WHERE itemID IN (" + sql + ")");
+ yield Zotero.DB.queryAsync("DELETE FROM fulltextItems WHERE itemID IN (" + sql + ")");
+ });
- this.indexItems(items, false, true);
+ yield this.indexItems(items, false, true);
}
- }
+ });
- /*
+ /**
* Clears full-text word index and all full-text cache files
+ *
+ * @return {Promise}
*/
- function clearIndex(skipLinkedURLs) {
- Zotero.DB.beginTransaction();
-
- var sql = "DELETE FROM fulltextItems";
- if (skipLinkedURLs) {
- var linkSQL = "SELECT itemID FROM itemAttachments WHERE linkMode ="
- + Zotero.Attachments.LINK_MODE_LINKED_URL;
+ this.clearIndex = function (skipLinkedURLs) {
+ return Zotero.DB.executeTransaction(function* () {
+ var sql = "DELETE FROM fulltextItems";
+ if (skipLinkedURLs) {
+ var linkSQL = "SELECT itemID FROM itemAttachments WHERE linkMode ="
+ + Zotero.Attachments.LINK_MODE_LINKED_URL;
+
+ sql += " WHERE itemID NOT IN (" + linkSQL + ")";
+ }
+ yield Zotero.DB.queryAsync(sql);
- sql += " WHERE itemID NOT IN (" + linkSQL + ")";
- }
- Zotero.DB.query(sql);
-
- sql = "DELETE FROM fulltextItemWords";
- if (skipLinkedURLs) {
- sql += " WHERE itemID NOT IN (" + linkSQL + ")";
- }
- Zotero.DB.query(sql);
-
- if (skipLinkedURLs) {
- this.purgeUnusedWords();
- }
- else {
- Zotero.DB.query("DELETE FROM fulltextWords");
- }
-
- this.clearCacheFiles();
-
- Zotero.DB.commitTransaction();
+ sql = "DELETE FROM fulltextItemWords";
+ if (skipLinkedURLs) {
+ sql += " WHERE itemID NOT IN (" + linkSQL + ")";
+ }
+ yield Zotero.DB.queryAsync(sql);
+
+ if (skipLinkedURLs) {
+ yield this.purgeUnusedWords();
+ }
+ else {
+ yield Zotero.DB.queryAsync("DELETE FROM fulltextWords");
+ }
+
+ yield clearCacheFiles();
+ }.bind(this));
}
/*
* Clears cache file for an item
*/
- function clearCacheFile(itemID) {
- var item = Zotero.Items.get(itemID);
+ var clearCacheFile = Zotero.Promise.coroutine(function* (itemID) {
+ var item = yield Zotero.Items.getAsync(itemID);
if (!item) {
return;
}
@@ -1426,7 +1412,7 @@ Zotero.Fulltext = new function(){
}
Zotero.debug('Clearing full-text cache file for item ' + itemID);
- var cacheFile = this.getItemCacheFile(itemID);
+ var cacheFile = Zotero.Fulltext.getItemCacheFile(item);
if (cacheFile.exists()) {
try {
cacheFile.remove(false);
@@ -1435,22 +1421,22 @@ Zotero.Fulltext = new function(){
Zotero.File.checkFileAccessError(e, cacheFile, 'delete');
}
}
- }
+ });
/*
* Clear cache files for all attachments
*/
- function clearCacheFiles(skipLinkedURLs) {
+ var clearCacheFiles = Zotero.Promise.coroutine(function* (skipLinkedURLs) {
var sql = "SELECT itemID FROM itemAttachments";
if (skipLinkedURLs) {
sql += " WHERE linkMode != " + Zotero.Attachments.LINK_MODE_LINKED_URL;
}
- var items = Zotero.DB.columnQuery(sql);
+ var items = yield Zotero.DB.columnQueryAsync(sql);
for (var i=0; i<items.length; i++) {
- this.clearCacheFile(items[i]);
+ yield clearCacheFile(items[i]);
}
- }
+ });
/*
@@ -1460,23 +1446,28 @@ Zotero.Fulltext = new function(){
*/
- function purgeUnusedWords() {
+ /**
+ * @return {Promise}
+ */
+ this.purgeUnusedWords = Zotero.Promise.coroutine(function* () {
if (!Zotero.Prefs.get('purge.fulltext')) {
return;
}
var sql = "DELETE FROM fulltextWords WHERE wordID NOT IN "
+ "(SELECT wordID FROM fulltextItemWords)";
- Zotero.DB.query(sql);
+ yield Zotero.DB.queryAsync(sql);
Zotero.Prefs.set('purge.fulltext', false)
- }
+ });
/**
* Convert HTML to text for an item and cache the result
+ *
+ * @return {Promise}
*/
- function convertItemHTMLToText(itemID, html, maxLength) {
+ var convertItemHTMLToText = Zotero.Promise.coroutine(function* (itemID, html, maxLength) {
// Split elements to avoid word concatentation
html = html.replace(/>/g, '> ');
@@ -1488,12 +1479,13 @@ Zotero.Fulltext = new function(){
}
// Write the converted text to a cache file
- var cacheFile = Zotero.Fulltext.getItemCacheFile(itemID);
+ var item = yield Zotero.Items.getAsync(itemID);
+ var cacheFile = Zotero.Fulltext.getItemCacheFile(item);
Zotero.debug("Writing converted full-text HTML content to " + cacheFile.path);
if (!cacheFile.parent.exists()) {
- Zotero.Attachments.createDirectoryForItem(itemID);
+ yield Zotero.Attachments.createDirectoryForItem(item);
}
- Zotero.File.putContentsAsync(cacheFile, text)
+ yield Zotero.File.putContentsAsync(cacheFile, text)
.catch(function (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
@@ -1503,7 +1495,7 @@ Zotero.Fulltext = new function(){
text: text,
totalChars: totalChars
};
- }
+ });
function HTMLToText(html) {
var nsIFC = Components.classes['@mozilla.org/widget/htmlformatconverter;1']
diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js
@@ -22,6 +22,9 @@ Zotero.HTTP = new function() {
if (xmlhttp.channel.URI.password) {
xmlhttp.channel.URI.password = "********";
}
+ if (xmlhttp.channel.URI.spec) {
+ xmlhttp.channel.URI.spec = xmlhttp.channel.URI.spec.replace(/key=[^&]+&?/, "key=********");
+ }
}
catch (e) {
Zotero.debug(e, 1);
@@ -76,6 +79,9 @@ Zotero.HTTP = new function() {
var dispURL = url;
}
+ // Don't display API key in console
+ dispURL = dispURL.replace(/key=[^&]+&?/, "").replace(/\?$/, "");
+
if(options && options.body) {
var bodyStart = options.body.substr(0, 1024);
// Don't display sync password or session id in console
@@ -91,14 +97,14 @@ Zotero.HTTP = new function() {
}
if (this.browserIsOffline()) {
- return Q.fcall(function() {
+ return Zotero.Promise.try(function() {
Zotero.debug("HTTP " + method + " " + dispURL + " failed: "
+ "Browser is offline");
throw new this.BrowserOfflineException();
});
}
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance();
@@ -451,19 +457,19 @@ Zotero.HTTP = new function() {
if (!Zotero.isStandalone
|| !Zotero.Prefs.get("triggerProxyAuthentication")
|| Zotero.HTTP.browserIsOffline()) {
- Zotero.proxyAuthComplete = Q();
+ Zotero.proxyAuthComplete = Zotero.Promise.resolve();
return false;
}
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
Zotero.proxyAuthComplete = deferred.promise;
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
var uris = Zotero.Prefs.get('proxyAuthenticationURLs').split(',');
uris = Zotero.Utilities.arrayShuffle(uris);
uris.unshift(ZOTERO_CONFIG.PROXY_AUTH_URL);
- return Q.async(function () {
+ return Zotero.spawn(function* () {
let max = 3; // how many URIs to try after the general Zotero one
for (let i = 0; i <= max; i++) {
let uri = uris.shift();
@@ -474,7 +480,7 @@ Zotero.HTTP = new function() {
// For non-Zotero URLs, wait for PAC initialization,
// in a rather ugly and inefficient manner
if (i == 1) {
- let installed = yield Q.fcall(_pacInstalled)
+ let installed = yield Zotero.Promise.try(_pacInstalled)
.then(function (installed) {
if (installed) throw true;
})
@@ -519,7 +525,7 @@ Zotero.HTTP = new function() {
}
}
deferred.resolve();
- })();
+ });
})
.catch(function (e) {
Components.utils.reportError(e);
@@ -544,7 +550,7 @@ Zotero.HTTP = new function() {
Components.utils.import("resource://gre/modules/NetUtil.jsm");
var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
.getService(Components.interfaces.nsIProtocolProxyService);
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
pps.asyncResolve(
NetUtil.newURI(uri),
0,
diff --git a/chrome/content/zotero/xpcom/id.js b/chrome/content/zotero/xpcom/id.js
@@ -24,7 +24,6 @@
*/
Zotero.ID_Tracker = function () {
- this.get = get;
this.getBigInt = getBigInt;
this.skip = skip;
this.getTableName = getTableName;
@@ -46,7 +45,7 @@ Zotero.ID_Tracker = function () {
/*
* Gets an unused primary key id for a DB table
*/
- function get(table, notNull) {
+ this.get = Zotero.Promise.coroutine(function* (table, notNull) {
table = this.getTableName(table);
switch (table) {
@@ -62,7 +61,7 @@ Zotero.ID_Tracker = function () {
case 'tags':
case 'customItemTypes':
case 'customFields':
- var id = _getNextAvailable(table);
+ var id = yield _getNextAvailable(table);
if (!id && notNull) {
return _getNext(table);
}
@@ -72,7 +71,7 @@ Zotero.ID_Tracker = function () {
//
// TODO: use autoincrement instead where available in 1.5
case 'itemDataValues':
- var id = _getNextAvailable(table);
+ var id = yield _getNextAvailable(table);
if (!id) {
// If we can't find an empty id quickly, just use MAX() + 1
return _getNext(table);
@@ -82,7 +81,7 @@ Zotero.ID_Tracker = function () {
default:
throw ("Unsupported table '" + table + "' in Zotero.ID.get()");
}
- }
+ });
this.isValidKey = function (value) {
@@ -162,9 +161,9 @@ Zotero.ID_Tracker = function () {
* Returns the lowest available unused primary key id for table,
* or NULL if none could be loaded in _loadAvailable()
*/
- function _getNextAvailable(table) {
+ var _getNextAvailable = Zotero.Promise.coroutine(function* (table) {
if (!_available[table]) {
- _loadAvailable(table);
+ yield _loadAvailable(table);
}
var arr = _available[table];
@@ -189,7 +188,7 @@ Zotero.ID_Tracker = function () {
if (_skip[table] && _skip[table][id]) {
Zotero.debug("Skipping " + table + " id " + id);
if (!_available[table]) {
- _loadAvailable(table);
+ yield _loadAvailable(table);
}
continue;
}
@@ -198,13 +197,13 @@ Zotero.ID_Tracker = function () {
return id;
}
return null;
- }
+ });
/*
* Get MAX(id) + 1 from table
*/
- function _getNext(table) {
+ var _getNext = Zotero.Promise.coroutine(function* (table) {
var column = _getTableColumn(table);
var sql = 'SELECT MAX(';
@@ -216,7 +215,7 @@ Zotero.ID_Tracker = function () {
}
}
if (!max) {
- throw ("_skip['" + table + "'] must contain positive values in Zotero.ID._getNext()");
+ throw new Error("_skip['" + table + "'] must contain positive values");
}
sql += 'MAX(' + column + ', ' + max + ')';
}
@@ -224,14 +223,14 @@ Zotero.ID_Tracker = function () {
sql += column;
}
sql += ')+1 FROM ' + table;
- return Zotero.DB.valueQuery(sql);
- }
+ return Zotero.DB.valueQueryAsync(sql);
+ });
/*
* Loads available ids for table into memory
*/
- function _loadAvailable(table) {
+ var _loadAvailable = Zotero.Promise.coroutine(function* (table) {
Zotero.debug("Loading available ids for table '" + table + "'");
var minID = _min[table] ? _min[table] + 1 : 1;
@@ -263,7 +262,7 @@ Zotero.ID_Tracker = function () {
var maxID = minID + numIDs - 1;
var sql = "SELECT " + column + " FROM " + table
+ " WHERE " + column + " BETWEEN ? AND ? ORDER BY " + column;
- var ids = Zotero.DB.columnQuery(sql, [minID, maxID]);
+ var ids = yield Zotero.DB.columnQueryAsync(sql, [minID, maxID]);
// If no ids found, we have numIDs unused ids
if (!ids) {
maxID = Math.min(maxID, minID + (maxToFind - 1));
@@ -277,7 +276,7 @@ Zotero.ID_Tracker = function () {
Zotero.debug('No available ids found between ' + minID + ' and ' + maxID + '; trying next ' + numIDs);
minID = maxID + 1;
maxID = minID + numIDs - 1;
- ids = Zotero.DB.columnQuery(sql, [minID, maxID]);
+ ids = yield Zotero.DB.columnQueryAsync(sql, [minID, maxID]);
maxTries--;
}
@@ -329,37 +328,7 @@ Zotero.ID_Tracker = function () {
Zotero.debug("Found " + found + " available ids in table '" + table + "'");
_available[table] = available;
- }
-
-
- /**
- * Find a unique random id for use in a DB table
- *
- * (No longer used)
- **/
- function _getRandomID(table, max){
- var column = _getTableColumn(table);
-
- var sql = 'SELECT COUNT(*) FROM ' + table + ' WHERE ' + column + '= ?';
-
- if (!max){
- max = 16383;
- }
-
- max--; // since we use ceil(), decrement max by 1
- var tries = 3; // # of tries to find a unique id
- for (var i=0; i<tries; i++) {
- var rnd = Math.ceil(Math.random() * max);
- var exists = Zotero.DB.valueQuery(sql, { int: rnd });
- if (!exists) {
- return rnd;
- }
- }
-
- // If no luck after number of tries, try a larger range
- var sql = 'SELECT MAX(' + column + ') + 1 FROM ' + table;
- return Zotero.valueQuery(sql);
- }
+ });
function _getTableColumn(table) {
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
@@ -127,7 +127,7 @@ Zotero.Integration = new function() {
Zotero.logError(e);
}
- Q.delay(1000).then(_checkPluginVersions);
+ Zotero.Promise.delay(1000).then(_checkPluginVersions);
}
/**
@@ -177,13 +177,13 @@ Zotero.Integration = new function() {
return function _checkPluginVersions() {
if(integrationVersionsOK) {
if(integrationVersionsOK === true) {
- return Q.resolve(integrationVersionsOK);
+ return Zotero.Promise.resolve(integrationVersionsOK);
} else {
- return Q.reject(integrationVersionsOK);
+ return Zotero.Promise.reject(integrationVersionsOK);
}
}
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
AddonManager.getAddonsByIDs(INTEGRATION_PLUGINS, function(addons) {
for(var i in addons) {
var addon = addons[i];
@@ -237,8 +237,8 @@ Zotero.Integration = new function() {
// Try to execute the command; otherwise display an error in alert service or word processor
// (depending on what is possible)
document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
- return Q.resolve((new Zotero.Integration.Document(application, document))[command]());
- }).fail(function(e) {
+ return Zotero.Promise.resolve((new Zotero.Integration.Document(application, document))[command]());
+ }).catch(function(e) {
if(!(e instanceof Zotero.Exception.UserCancelled)) {
try {
var displayError = null;
@@ -295,7 +295,7 @@ Zotero.Integration = new function() {
if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
var oldWindow = Zotero.Integration.currentWindow;
- Q.delay(100).then(function() {
+ Zotero.Promise.delay(100).then(function() {
oldWindow.close();
});
}
@@ -689,7 +689,7 @@ Zotero.Integration = new function() {
Zotero.Integration.currentWindow = window;
Zotero.Integration.activate(window);
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var listener = function() {
if(window.location.toString() === "about:blank") return;
@@ -760,7 +760,7 @@ Zotero.Integration.MissingItemException.prototype = {
this.fieldGetter._doc.activate();
var result = this.fieldGetter._doc.displayAlert(msg, 1, 3);
if(result == 0) { // Cancel
- return Q.reject(new Zotero.Exception.UserCancelled("document update"));
+ return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
} else if(result == 1) { // No
for each(var reselectKey in this.reselectKeys) {
this.fieldGetter._removeCodeKeys[reselectKey] = true;
@@ -817,7 +817,7 @@ Zotero.Integration.CorruptFieldException.prototype = {
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
if(result == 0) {
- return Q.reject(new Zotero.Exception.UserCancelled("document update"));
+ return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
} else if(result == 1) { // No
this.fieldGetter._removeCodeFields[this.fieldIndex] = true;
return this.fieldGetter._processFields(this.fieldIndex+1);
@@ -873,12 +873,12 @@ Zotero.Integration.CorruptBibliographyException.prototype = {
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result == 0) {
- return Q.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
+ return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
} else {
this.fieldGetter._bibliographyData = "";
this.fieldGetter._session.bibliographyHasChanged = true;
this.fieldGetter._session.bibliographyDataHasChanged = true;
- return Q.resolve(true);
+ return Zotero.Promise.resolve(true);
}
}
};
@@ -947,7 +947,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
// if no fields, throw an error
if(!haveFields) {
- return Q.reject(new Zotero.Exception.Alert(
+ return Zotero.Promise.reject(new Zotero.Exception.Alert(
"integration.error.mustInsertCitation",
[], "integration.error.title"));
} else {
@@ -958,7 +958,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
// Set doc prefs if no data string yet
this._session = this._createNewSession(data);
this._session.setData(data);
- if(dontRunSetDocPrefs) return Q.resolve(false);
+ if(dontRunSetDocPrefs) return Zotero.Promise.resolve(false);
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
this._app.secondaryFieldType).then(function(status) {
@@ -984,16 +984,16 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(!warning) {
- return Q.reject(new Zotero.Exception.UserCancelled("document upgrade"));
+ return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document upgrade"));
}
} else if(data.dataVersion > DATA_VERSION) {
- return Q.reject(new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
+ return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
[data.zoteroVersion, Zotero.version], "integration.error.title"));
}
if(data.prefs.fieldType !== this._app.primaryFieldType
&& data.prefs.fieldType !== this._app.secondaryFieldType) {
- return Q.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
+ return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
[], "integration.error.title"));
}
@@ -1013,14 +1013,14 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
return me._session;
});
} else {
- return Q.reject(e);
+ return Zotero.Promise.reject(e);
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true;
}
- return Q.resolve(this._session);
+ return Zotero.Promise.resolve(this._session);
}
};
@@ -1165,7 +1165,7 @@ Zotero.Integration.Document.prototype.setDocPrefs = function() {
return fieldGetter.updateSession().then(setDocPrefs);
} else {
// Can get fields while dialog is open
- return Q.all([
+ return Zotero.Promise.all([
fieldGetter.get(),
setDocPrefs()
]).spread(function (fields, setDocPrefs) {
@@ -1272,7 +1272,7 @@ Zotero.Integration.Fields = function(session, doc, fieldErrorHandler) {
Zotero.Integration.Fields.prototype.addField = function(note) {
// Get citation types if necessary
if(!this._doc.canInsertField(this._session.data.prefs['fieldType'])) {
- return Q.reject(new Zotero.Exception.Alert("integration.error.cannotInsertHere",
+ return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.cannotInsertHere",
[], "integration.error.title"));
}
@@ -1281,7 +1281,7 @@ Zotero.Integration.Fields.prototype.addField = function(note) {
if(!this._doc.displayAlert(Zotero.getString("integration.replace"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) {
- return Q.reject(new Zotero.Exception.UserCancelled("inserting citation"));
+ return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("inserting citation"));
}
}
@@ -1290,7 +1290,7 @@ Zotero.Integration.Fields.prototype.addField = function(note) {
(note ? this._session.data.prefs["noteType"] : 0));
}
- return Q.resolve(field);
+ return Zotero.Promise.resolve(field);
}
/**
@@ -1321,11 +1321,11 @@ Zotero.Integration.Fields.prototype.getCodeTypeAndContent = function(rawCode) {
Zotero.Integration.Fields.prototype.get = function get() {
// If we already have fields, just return them
if(this._fields) {
- return Q.resolve(this._fields);
+ return Zotero.Promise.resolve(this._fields);
}
// Create a new promise and add it to promise list
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
// If already getting fields, just return the promise
if(this._deferreds) {
@@ -1737,7 +1737,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = function(field) {
}
var me = this;
- return Q(field).then(function(field) {
+ return Zotero.Promise.resolve(field).then(function(field) {
if(!citation) {
field.setCode("TEMP");
citation = {"citationItems":[], "properties":{}};
@@ -1757,7 +1757,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = function(field) {
}
if(newField) {
- return io.promise.fail(function(e) {
+ return io.promise.catch(function(e) {
// Try to delete new field on failure
try {
field.delete();
@@ -1792,7 +1792,7 @@ Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter
this.style = session.style;
// Start getting citation data
- this._acceptDeferred = Q.defer();
+ this._acceptDeferred = Zotero.Promise.defer();
this._fieldIndexPromise = fieldGetter.get().then(function(fields) {
for(var i=0, n=fields.length; i<n; i++) {
if(fields[i].equals(field)) {
@@ -1802,7 +1802,7 @@ Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter
});
var me = this;
- this.promise = this._fieldIndexPromise.then(function(fieldIndex) {
+ this.promise = this._fieldIndexZotero.Promise.then(function(fieldIndex) {
me._fieldIndex = fieldIndex;
return me._acceptDeferred.promise;
}).then(function(progressCallback) {
@@ -1843,7 +1843,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
*/
"_updateSession":function _updateSession(resolveErrors) {
var me = this;
- if(this._sessionUpdatePromise && this._sessionUpdatePromise.isFulfilled()) {
+ if(this._sessionUpdatePromise && this._sessionUpdateZotero.Promise.isFulfilled()) {
// Session has already been updated. If we were deferring resolving an error,
// and we are supposed to resolve it now, then do that
if(this._sessionUpdateError) {
@@ -1852,13 +1852,13 @@ Zotero.Integration.CitationEditInterface.prototype = {
delete me._sessionUpdateError;
});
} else {
- return Q.reject(this._sessionUpdateError);
+ return Zotero.Promise.reject(this._sessionUpdateError);
}
} else {
- return Q.resolve(true);
+ return Zotero.Promise.resolve(true);
}
} else {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
this._sessionUpdateResolveErrors = this._sessionUpdateResolveErrors || resolveErrors;
this._sessionUpdateDeferreds.push(deferred);
@@ -1937,7 +1937,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
* @return {Promise} A promise resolved by the items
*/
"getItems":function() {
- if(this._fieldIndexPromise.isFulfilled()
+ if(this._fieldIndexZotero.Promise.isFulfilled()
|| Zotero.Utilities.isEmpty(this._session.citationsByItemID)) {
// Either we already have field data for this run or we have no item data at all.
// Update session before continuing.
@@ -1950,7 +1950,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
} else {
// We have item data left over from a previous run with this document, so we don't need
// to wait.
- return Q.resolve(this._getItems());
+ return Zotero.Promise.resolve(this._getItems());
}
},
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -34,14 +34,10 @@
/*
* Constructor for the ItemTreeView object
*/
-Zotero.ItemTreeView = function(itemGroup, sourcesOnly)
-{
+Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) {
this.wrappedJSObject = this;
this.rowCount = 0;
- this.itemGroup = itemGroup;
- // Deprecated
- // TODO: remove
- this._itemGroup = itemGroup;
+ this.collectionTreeRow = collectionTreeRow;
this._initialized = false;
this._skipKeypress = false;
@@ -54,11 +50,12 @@ Zotero.ItemTreeView = function(itemGroup, sourcesOnly)
this._ownerDocument = null;
this._needsSort = false;
- this._dataItems = [];
+ this._rows = [];
+ this._cellTextCache = {};
this._itemImages = {};
this._unregisterID = Zotero.Notifier.registerObserver(
- this, ['item', 'collection-item', 'item-tag', 'share-items', 'bucket']
+ this, ['item', 'collection-item', 'item-tag', 'share-items', 'bucket'], 'itemTreeView'
);
}
@@ -70,28 +67,17 @@ Zotero.ItemTreeView.prototype.addCallback = function(callback) {
}
-Zotero.ItemTreeView.prototype._runCallbacks = function() {
+Zotero.ItemTreeView.prototype._runCallbacks = Zotero.Promise.coroutine(function* () {
for each(var cb in this._callbacks) {
- cb();
+ yield Zotero.Promise.resolve(cb());
}
-}
+});
/**
* Called by the tree itself
*/
-Zotero.ItemTreeView.prototype.setTree = function(treebox)
-{
- var generator = this._setTreeGenerator(treebox);
- if(generator.next()) {
- Zotero.pumpGenerator(generator);
- }
-}
-
-/**
- * Generator used internally for setting the tree
- */
-Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
+Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (treebox)
{
try {
//Zotero.debug("Calling setTree()");
@@ -106,9 +92,9 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
if (this._treebox) {
if (this._needsSort) {
- this.sort();
+ yield this.sort();
}
- yield false;
+ return;
}
if (!treebox) {
@@ -123,25 +109,20 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
}
if (Zotero.locked) {
- var msg = "Zotero is locked -- not loading items tree";
- Zotero.debug(msg, 2);
+ Zotero.debug("Zotero is locked -- not loading items tree", 2);
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
}
- yield false;
+ return;
}
// If a DB transaction is open, display error message and bail
if (!Zotero.stateCheck()) {
- if (this._ownerDocument.defaultView.ZoteroPane_Local) {
- this._ownerDocument.defaultView.ZoteroPane_Local.displayErrorMessage();
- }
- yield false;
+ throw new Error();
}
- var generator = this._refreshGenerator();
- while(generator.next()) yield true;
+ yield this.refresh();
// Add a keypress listener for expand/collapse
var tree = this._treebox.treeBody.parentNode;
@@ -159,11 +140,11 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
if (self._treebox.view.selection.count > 1) {
switch (event.keyCode) {
case 39:
- self.expandSelectedRows();
+ self.expandSelectedRows().done();
break;
case 37:
- self.collapseSelectedRows();
+ self.collapseSelectedRows().done();
break;
}
@@ -174,12 +155,12 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
var key = String.fromCharCode(event.which);
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
- self.expandAllRows();
+ self.expandAllRows().done();
event.preventDefault();
return;
}
else if (key == '-' && !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)) {
- self.collapseAllRows();
+ self.collapseAllRows().done();
event.preventDefault();
return;
}
@@ -192,9 +173,9 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
event.preventDefault();
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
if (coloredTagsRE.test(key)) {
- let libraryID = self._itemGroup.ref.libraryID;
+ let libraryID = self.collectionTreeRow.ref.libraryID;
let position = parseInt(key) - 1;
return Zotero.Tags.getColorByPosition(libraryID, position)
.then(function (colorData) {
@@ -270,72 +251,48 @@ Zotero.ItemTreeView.prototype._setTreeGenerator = function(treebox)
// handleKeyPress() in zoteroPane.js.
tree._handleEnter = function () {};
- this.sort();
+ yield this.sort();
// Only yield if there are callbacks; otherwise, we're almost done
- if(this._callbacks.length && this._waitAfter && Date.now() > this._waitAfter) yield true;
+ if(this._callbacks.length && this._waitAfter && Date.now() > this._waitAfter) yield Zotero.Promise.resolve();
- this.expandMatchParents();
+ yield this.expandMatchParents();
//Zotero.debug('Running callbacks in itemTreeView.setTree()', 4);
- this._runCallbacks();
+ yield this._runCallbacks();
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
}
// Select a queued item from selectItem()
- if (this._itemGroup && this._itemGroup.itemToSelect) {
- var item = this._itemGroup.itemToSelect;
- this.selectItem(item['id'], item['expand']);
- this._itemGroup.itemToSelect = null;
+ if (this.collectionTreeRow && this.collectionTreeRow.itemToSelect) {
+ var item = this.collectionTreeRow.itemToSelect;
+ yield this.selectItem(item['id'], item['expand']);
+ this.collectionTreeRow.itemToSelect = null;
}
delete this._waitAfter;
Zotero.debug("Set tree in "+(Date.now()-start)+" ms");
- } catch(e) {
- Zotero.logError(e);
}
- yield false;
-}
+ catch (e) {
+ Components.utils.reportError(e);
+ if (this.onError) {
+ this.onError(e);
+ }
+ throw e;
+ };
+});
+
/**
* Reload the rows from the data access methods
* (doesn't call the tree.invalidate methods, etc.)
*/
-Zotero.ItemTreeView.prototype.refresh = function()
-{
- var generator = this._refreshGenerator();
- while(generator.next()) {};
-}
-
-/**
- * Generator used internally for refresh
- */
-Zotero.ItemTreeView._haveCachedFields = false;
-Zotero.ItemTreeView.prototype._refreshGenerator = function()
-{
+Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(function* () {
Zotero.debug('Refreshing items list');
- if(!Zotero.ItemTreeView._haveCachedFields) yield true;
-
- var usiDisabled = Zotero.UnresponsiveScriptIndicator.disable();
+ //if(!Zotero.ItemTreeView._haveCachedFields) yield Zotero.Promise.resolve();
- Zotero.ItemGroupCache.clear();
-
- this._searchMode = this._itemGroup.isSearchMode();
-
- if (!this.selection.selectEventsSuppressed) {
- var unsuppress = this.selection.selectEventsSuppressed = true;
- //this._treebox.beginUpdateBatch();
- }
- var savedSelection = this.saveSelection();
- var savedOpenState = this.saveOpenState();
-
- var oldRows = this.rowCount;
- this._dataItems = [];
- this._searchItemIDs = {}; // items matching the search
- this._searchParentIDs = {};
- this.rowCount = 0;
var cacheFields = ['title', 'date'];
// Cache the visible fields so they don't load individually
@@ -344,13 +301,17 @@ Zotero.ItemTreeView.prototype._refreshGenerator = function()
}
// If treebox isn't ready, skip refresh
catch (e) {
- yield false;
+ return false;
}
- for (var i=0; i<visibleFields.length; i++) {
- var field = visibleFields[i];
+ for (let i=0; i<visibleFields.length; i++) {
+ let field = visibleFields[i];
switch (field) {
case 'hasAttachment':
+ // Needed by item.getBestAttachments(), called by getBestAttachmentStateAsync()
+ field = 'url';
+ break;
+
case 'numNotes':
continue;
@@ -363,77 +324,100 @@ Zotero.ItemTreeView.prototype._refreshGenerator = function()
}
}
- Zotero.DB.beginTransaction();
- Zotero.Items.cacheFields(cacheFields);
+ yield Zotero.Items.cacheFields(this.collectionTreeRow.ref.libraryID, cacheFields);
Zotero.ItemTreeView._haveCachedFields = true;
- var newRows = this._itemGroup.getItems();
+ Zotero.CollectionTreeCache.clear();
+
+ if (!this.selection.selectEventsSuppressed) {
+ var unsuppress = this.selection.selectEventsSuppressed = true;
+ //this._treebox.beginUpdateBatch();
+ }
+ var savedSelection = this.getSelectedItems(true);
+ var savedOpenState = yield this.saveOpenState();
+
+ var oldCount = this.rowCount;
+ var newSearchItemIDs = {};
+ var newSearchParentIDs = {};
+ var newCellTextCache = {};
+ var newSearchMode = this.collectionTreeRow.isSearchMode();
+ var newRows = [];
+ var newItems = yield this.collectionTreeRow.getItems();
var added = 0;
- for (var i=0, len=newRows.length; i < len; i++) {
+ for (let i=0, len=newItems.length; i < len; i++) {
// Only add regular items if sourcesOnly is set
- if (this._sourcesOnly && !newRows[i].isRegularItem()) {
+ if (this._sourcesOnly && !newItems[i].isRegularItem()) {
continue;
}
// Don't add child items directly (instead mark their parents for
// inclusion below)
- var sourceItemID = newRows[i].getSource();
- if (sourceItemID) {
- this._searchParentIDs[sourceItemID] = true;
+ let parentItemID = newItems[i].parentItemID;
+ if (parentItemID) {
+ newSearchParentIDs[parentItemID] = true;
}
// Add top-level items
else {
- this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], 0, false), added + 1); //item ref, before row
+ this._addRow(
+ newRows,
+ new Zotero.ItemTreeRow(newItems[i], 0, false),
+ added + 1
+ );
added++;
}
- this._searchItemIDs[newRows[i].id] = true;
+ newSearchItemIDs[newItems[i].id] = true;
}
// Add parents of matches if not matches themselves
- for (var id in this._searchParentIDs) {
- if (!this._searchItemIDs[id]) {
- var item = Zotero.Items.get(id);
- this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), added + 1); //item ref, before row
+ for (let id in newSearchParentIDs) {
+ if (!newSearchItemIDs[id]) {
+ let item = yield Zotero.Items.getAsync(id);
+ this._addRow(
+ newRows,
+ new Zotero.ItemTreeRow(item, 0, false),
+ added + 1
+ );
added++;
}
}
- Zotero.DB.commitTransaction();
-
- if(this._waitAfter && Date.now() > this._waitAfter) yield true;
-
- this._refreshHashMap();
+ if(this._waitAfter && Date.now() > this._waitAfter) yield Zotero.Promise.resolve();
- // Update the treebox's row count
- // this.rowCount isn't always up-to-date, so use the view's count
- var diff = this._treebox.view.rowCount - oldRows;
+ this._rows = newRows;
+ this.rowCount = this._rows.length;
+ var diff = this.rowCount - oldCount;
if (diff != 0) {
this._treebox.rowCountChanged(0, diff);
}
+ this._refreshItemRowMap();
- if (usiDisabled) {
- Zotero.UnresponsiveScriptIndicator.enable();
- }
+ this._searchMode = newSearchMode;
+ this._searchItemIDs = newSearchItemIDs; // items matching the search
+ this._searchParentIDs = newSearchParentIDs;
+ this._cellTextCache = {};
- this.rememberOpenState(savedOpenState);
- this.rememberSelection(savedSelection);
- this.expandMatchParents();
+ yield this.rememberOpenState(savedOpenState);
+ yield this.rememberSelection(savedSelection);
+ yield this.expandMatchParents();
if (unsuppress) {
// This causes a problem with the row count being wrong between views
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
-
- yield false;
-}
+}));
+
+/**
+ * Generator used internally for refresh
+ */
+Zotero.ItemTreeView._haveCachedFields = false;
/*
* Called by Zotero.Notifier on any changes to items in the data layer
*/
-Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
+Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData)
{
if (!this._treebox || !this._treebox.treeBody) {
Components.utils.reportError("Treebox didn't exist in itemTreeView.notify()");
@@ -445,20 +429,21 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
return;
}
- // Clear item type icon and tag colors
+ // Clear item type icon and tag colors when a tag is added to or removed from an item
if (type == 'item-tag') {
+ // TODO: Only update if colored tag changed?
ids.map(function (val) val.split("-")[0]).forEach(function (val) {
delete this._itemImages[val];
}.bind(this));
return;
}
- var itemGroup = this._itemGroup;
+ var collectionTreeRow = this.collectionTreeRow;
var madeChanges = false;
var sort = false;
- var savedSelection = this.saveSelection();
+ var savedSelection = this.getSelectedItems(true);
var previousRow = false;
// Redraw the tree (for tag color and progress changes)
@@ -492,27 +477,33 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
return;
}
- // If refreshing a single item, just unselect and reselect it
if (action == 'refresh') {
if (type == 'share-items') {
- if (itemGroup.isShare()) {
- this.refresh();
+ if (collectionTreeRow.isShare()) {
+ yield this.refresh();
}
}
else if (type == 'bucket') {
- if (itemGroup.isBucket()) {
- this.refresh();
+ if (collectionTreeRow.isBucket()) {
+ yield this.refresh();
}
}
+ // If refreshing a single item, clear caches and then unselect and reselect row
else if (savedSelection.length == 1 && savedSelection[0] == ids[0]) {
+ let row = this._itemRowMap[ids[0]];
+ delete this._cellTextCache[row];
+
this.selection.clearSelection();
- this.rememberSelection(savedSelection);
+ yield this.rememberSelection(savedSelection);
+ }
+ else {
+ this._cellTextCache = {};
}
return;
}
- if (itemGroup.isShare()) {
+ if (collectionTreeRow.isShare()) {
return;
}
@@ -524,7 +515,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// 'collection-item' ids are in the form collectionID-itemID
if (type == 'collection-item') {
- if (!itemGroup.isCollection()) {
+ if (!collectionTreeRow.isCollection()) {
return;
}
@@ -532,7 +523,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
for each(var id in ids) {
var split = id.split('-');
// Skip if not an item in this collection
- if (split[0] != itemGroup.ref.id) {
+ if (split[0] != collectionTreeRow.ref.id) {
continue;
}
splitIDs.push(split[1]);
@@ -541,22 +532,22 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// Select the last item even if there are no changes (e.g. if the tag
// selector is open and already refreshed the pane)
- if (splitIDs.length > 0 && (action == 'add' || action == 'modify')) {
+ /*if (splitIDs.length > 0 && (action == 'add' || action == 'modify')) {
var selectItem = splitIDs[splitIDs.length - 1];
- }
+ }*/
}
this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
- if ((action == 'remove' && !itemGroup.isLibrary(true))
+ if ((action == 'remove' && !collectionTreeRow.isLibrary(true))
|| action == 'delete' || action == 'trash') {
// On a delete in duplicates mode, just refresh rather than figuring
// out what to remove
- if (itemGroup.isDuplicates()) {
+ if (collectionTreeRow.isDuplicates()) {
previousRow = this._itemRowMap[ids[0]];
- this.refresh();
+ yield this.refresh();
madeChanges = true;
sort = true;
}
@@ -565,13 +556,23 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// so sort the ids by row
var rows = [];
for (var i=0, len=ids.length; i<len; i++) {
- if (action == 'delete' || action == 'trash' ||
- !itemGroup.ref.hasItem(ids[i])) {
- // Row might already be gone (e.g. if this is a child and
- // 'modify' was sent to parent)
- if (this._itemRowMap[ids[i]] != undefined) {
- rows.push(this._itemRowMap[ids[i]]);
+ let push = false;
+ if (action == 'delete' && action == 'trash') {
+ push = true;
+ }
+ else {
+ yield collectionTreeRow.ref.loadChildItems();
+ push = !collectionTreeRow.ref.hasItem(ids[i]);
+ }
+ // Row might already be gone (e.g. if this is a child and
+ // 'modify' was sent to parent)
+ let row = this._itemRowMap[ids[i]];
+ if (push && row != undefined) {
+ // Don't remove child items from collections, because it's handled by 'modify'
+ if (action == 'remove' && this.getParentIndex(row) != -1) {
+ continue;
}
+ rows.push(row);
}
}
@@ -583,7 +584,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
var row = rows[i];
if(row != null)
{
- this._hideItem(row-i);
+ this._removeRow(row-i);
this._treebox.rowCountChanged(row-i,-1);
}
}
@@ -595,17 +596,18 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
else if (action == 'modify')
{
+ // Clear row caches
+ var items = yield Zotero.Items.getAsync(ids);
+ for (let i=0; i<items.length; i++) {
+ let id = items[i].id;
+ delete this._itemImages[id];
+ delete this._cellTextCache[id];
+ }
+
// If trash or saved search, just re-run search
- if (itemGroup.isTrash() || itemGroup.isSearch())
+ if (collectionTreeRow.isTrash() || collectionTreeRow.isSearch())
{
- // Clear item type icons
- var items = Zotero.Items.get(ids);
- for (let i=0; i<items.length; i++) {
- let id = items[i].id;
- delete this._itemImages[id];
- }
-
- this.refresh();
+ yield this.refresh();
madeChanges = true;
sort = true;
}
@@ -613,7 +615,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// If no quicksearch, process modifications manually
else if (!quicksearch || quicksearch.value == '')
{
- var items = Zotero.Items.get(ids);
+ var items = yield Zotero.Items.getAsync(ids);
for each(var item in items) {
let id = item.id;
@@ -621,11 +623,9 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// Make sure row map is up to date
// if we made changes in a previous loop
if (madeChanges) {
- this._refreshHashMap();
+ this._refreshItemRowMap();
}
var row = this._itemRowMap[id];
- // Clear item type icon
- delete this._itemImages[id];
// Deleted items get a modify that we have to ignore when
// not viewing the trash
@@ -636,46 +636,61 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// Item already exists in this view
if( row != null)
{
- var sourceItemID = this._getItemAtRow(row).ref.getSource();
+ var parentItemID = this.getRow(row).ref.parentItemID;
var parentIndex = this.getParentIndex(row);
- if (this.isContainer(row) && this.isContainerOpen(row))
+ if (this.isContainer(row))
{
- this.toggleOpenState(row);
- this.toggleOpenState(row);
+ //yield this.toggleOpenState(row);
+ //yield this.toggleOpenState(row);
sort = id;
}
- // If item moved from top-level to under another item,
- // remove the old row -- the container refresh above
- // takes care of adding the new row
- else if (!this.isContainer(row) && parentIndex == -1
- && sourceItemID)
- {
- this._hideItem(row);
- this._treebox.rowCountChanged(row+1, -1)
+ // If item moved from top-level to under another item, remove the old row.
+ // The container refresh above takes care of adding the new row.
+ else if (!this.isContainer(row) && parentIndex == -1 && parentItemID) {
+ this._removeRow(row);
+ this._treebox.rowCountChanged(row + 1, -1)
}
- // If moved from under another item to top level, add row
- else if (!this.isContainer(row) && parentIndex != -1
- && !sourceItemID)
- {
- this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), this.rowCount);
- this._treebox.rowCountChanged(this.rowCount-1, 1);
+ // If moved from under another item to top level, remove old row and add new one
+ else if (!this.isContainer(row) && parentIndex != -1 && !parentItemID) {
+ this._removeRow(row);
+ this._treebox.rowCountChanged(row + 1, -1)
+
+ this._addRow(
+ this._rows,
+ new Zotero.ItemTreeRow(item, 0, false),
+ this.rowCount
+ );
+ this.rowCount++;
+ this._treebox.rowCountChanged(this.rowCount - 1, 1);
sort = id;
}
- // If not moved from under one item to another, resort the row
- else if (!(sourceItemID && parentIndex != -1 && this._itemRowMap[sourceItemID] != parentIndex)) {
+ // If not moved from under one item to another, just resort the row,
+ // which also invalidates it and refreshes it
+ else if (!(parentItemID && parentIndex != -1 && this._itemRowMap[parentItemID] != parentIndex)) {
sort = id;
}
madeChanges = true;
}
-
- else if (((itemGroup.isLibrary() || itemGroup.isGroup()) && itemGroup.ref.libraryID == item.libraryID)
- || (itemGroup.isCollection() && item.inCollection(itemGroup.ref.id))) {
- // Otherwise the item has to be added
- if(item.isRegularItem() || !item.getSource())
- {
+ // Otherwise, for a top-level item in a root view or a collection
+ // containing the item, the item has to be added
+ else if (item.isTopLevelItem()) {
+ // Root view
+ let add = (collectionTreeRow.isLibrary() || collectionTreeRow.isGroup())
+ && collectionTreeRow.ref.libraryID == item.libraryID;
+ // Collection containing item
+ if (!add && collectionTreeRow.isCollection()) {
+ yield item.loadCollections();
+ add = item.inCollection(collectionTreeRow.ref.id);
+ }
+ if (add) {
//most likely, the note or attachment's parent was removed.
- this._showItem(new Zotero.ItemTreeView.TreeRow(item,0,false),this.rowCount);
+ this._addRow(
+ this._rows,
+ new Zotero.ItemTreeRow(item, 0, false),
+ this.rowCount
+ );
+ this.rowCount++;
this._treebox.rowCountChanged(this.rowCount-1,1);
madeChanges = true;
sort = true;
@@ -692,13 +707,9 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
else
{
var allDeleted = true;
- var isTrash = itemGroup.isTrash();
- var items = Zotero.Items.get(ids);
+ var isTrash = collectionTreeRow.isTrash();
+ var items = yield Zotero.Items.getAsync(ids);
for each(var item in items) {
- let id = item.id;
- // Clear item type icon
- delete this._itemImages[id];
-
// If not viewing trash and all items were deleted, ignore modify
if (allDeleted && !isTrash && !item.deleted) {
allDeleted = false;
@@ -714,9 +725,18 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
else if(action == 'add')
{
+ // New items need their item data and collections loaded
+ // before they're inserted into the tree
+ let items = yield Zotero.Items.getAsync(ids);
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+ yield item.loadItemData();
+ yield item.loadCollections();
+ }
+
// In some modes, just re-run search
- if (itemGroup.isSearch() || itemGroup.isTrash() || itemGroup.isUnfiled()) {
- this.refresh();
+ if (collectionTreeRow.isSearch() || collectionTreeRow.isTrash() || collectionTreeRow.isUnfiled()) {
+ yield this.refresh();
madeChanges = true;
sort = true;
}
@@ -724,16 +744,22 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// If not a quicksearch, process new items manually
else if (!quicksearch || quicksearch.value == '')
{
- var items = Zotero.Items.get(ids);
- for each(var item in items) {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
// if the item belongs in this collection
- if ((((itemGroup.isLibrary() || itemGroup.isGroup()) && itemGroup.ref.libraryID == item.libraryID)
- || (itemGroup.isCollection() && item.inCollection(itemGroup.ref.id)))
+ if ((((collectionTreeRow.isLibrary() || collectionTreeRow.isGroup())
+ && collectionTreeRow.ref.libraryID == item.libraryID)
+ || (collectionTreeRow.isCollection() && item.inCollection(collectionTreeRow.ref.id)))
// if we haven't already added it to our hash map
&& this._itemRowMap[item.id] == null
// Regular item or standalone note/attachment
- && (item.isRegularItem() || !item.getSource())) {
- this._showItem(new Zotero.ItemTreeView.TreeRow(item, 0, false), this.rowCount);
+ && item.isTopLevelItem()) {
+ this._addRow(
+ this._rows,
+ new Zotero.ItemTreeRow(item, 0, false),
+ this.rowCount
+ );
+ this.rowCount++;
this._treebox.rowCountChanged(this.rowCount-1,1);
madeChanges = true;
}
@@ -748,10 +774,9 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// For item adds, clear the quicksearch, unless all the new items
// are child items
if (activeWindow && type == 'item') {
- var clear = false;
- var items = Zotero.Items.get(ids);
- for each(var item in items) {
- if (!item.getSource()) {
+ let clear = false;
+ for (let i=0; i<items.length; i++) {
+ if (items[i].isTopLevelItem()) {
clear = true;
break;
}
@@ -780,7 +805,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// Only bother checking for single parent item if 1-5 total items,
// since a translator is unlikely to save more than 4 child items
else if (ids.length <= 5) {
- var items = Zotero.Items.get(ids);
+ var items = yield Zotero.Items.getAsync(ids);
if (items) {
var found = false;
for each(var item in items) {
@@ -805,16 +830,15 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
if (singleSelect) {
if (sort) {
- this.sort(typeof sort == 'number' ? sort : false);
+ yield this.sort(typeof sort == 'number' ? sort : false);
}
else {
- this._refreshHashMap();
+ this._refreshItemRowMap();
}
// Reset to Info tab
this._ownerDocument.getElementById('zotero-view-tabbox').selectedIndex = 0;
-
- this.selectItem(singleSelect);
+ yield this.selectItem(singleSelect);
}
// If single item is selected and was modified
else if (action == 'modify' && ids.length == 1 &&
@@ -827,17 +851,17 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
if (sort) {
- this.sort(typeof sort == 'number' ? sort : false);
+ yield this.sort(typeof sort == 'number' ? sort : false);
}
else {
- this._refreshHashMap();
+ this._refreshItemRowMap();
}
if (activeWindow) {
- this.selectItem(ids[0]);
+ yield this.selectItem(ids[0]);
}
else {
- this.rememberSelection(savedSelection);
+ yield this.rememberSelection(savedSelection);
}
}
else
@@ -847,21 +871,21 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
}
if (sort) {
- this.sort(typeof sort == 'number' ? sort : false);
+ yield this.sort(typeof sort == 'number' ? sort : false);
}
else {
- this._refreshHashMap();
+ this._refreshItemRowMap();
}
// On removal of a row, select item at previous position
if (action == 'remove' || action == 'trash' || action == 'delete') {
// In duplicates view, select the next set on delete
- if (itemGroup.isDuplicates()) {
- if (this._dataItems[previousRow]) {
+ if (collectionTreeRow.isDuplicates()) {
+ if (this._rows[previousRow]) {
// Mirror ZoteroPane.onTreeMouseDown behavior
- var itemID = this._dataItems[previousRow].ref.id;
- var setItemIDs = itemGroup.ref.getSetItemsByItemID(itemID);
- this.selectItems(setItemIDs);
+ var itemID = this._rows[previousRow].ref.id;
+ var setItemIDs = collectionTreeRow.ref.getSetItemsByItemID(itemID);
+ yield this.selectItems(setItemIDs);
}
}
else {
@@ -869,25 +893,25 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// position is a top-level item, move selection one row
// up to select a sibling or parent
if (ids.length == 1 && previousRow > 0) {
- var previousItem = Zotero.Items.get(ids[0]);
- if (previousItem && previousItem.getSource()) {
- if (this._dataItems[previousRow] && this.getLevel(previousRow) == 0) {
+ var previousItem = yield Zotero.Items.getAsync(ids[0]);
+ if (previousItem && !previousItem.isTopLevelItem()) {
+ if (this._rows[previousRow] && this.getLevel(previousRow) == 0) {
previousRow--;
}
}
}
- if (this._dataItems[previousRow]) {
+ if (this._rows[previousRow]) {
this.selection.select(previousRow);
}
// If no item at previous position, select last item in list
- else if (this._dataItems[this._dataItems.length - 1]) {
- this.selection.select(this._dataItems.length - 1);
+ else if (this._rows[this._rows.length - 1]) {
+ this.selection.select(this._rows.length - 1);
}
}
}
else {
- this.rememberSelection(savedSelection);
+ yield this.rememberSelection(savedSelection);
}
}
@@ -896,17 +920,17 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData)
// For special case in which an item needs to be selected without changes
// necessarily having been made
// ('collection-item' add with tag selector open)
- else if (selectItem) {
- this.selectItem(selectItem);
- }
+ /*else if (selectItem) {
+ yield this.selectItem(selectItem);
+ }*/
if (Zotero.suppressUIUpdates) {
- this.rememberSelection(savedSelection);
+ yield this.rememberSelection(savedSelection);
}
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
-}
+});
/*
* Unregisters view from Zotero.Notifier (called on window close)
@@ -927,9 +951,19 @@ Zotero.ItemTreeView.prototype.unregister = function()
///
////////////////////////////////////////////////////////////////////////////////
-Zotero.ItemTreeView.prototype.getCellText = function(row, column)
+Zotero.ItemTreeView.prototype.getCellText = function (row, column)
{
- var obj = this._getItemAtRow(row);
+ var obj = this.getRow(row);
+ var itemID = obj.id;
+
+ // If value is available, retrieve synchronously
+ if (this._cellTextCache[itemID] && this._cellTextCache[itemID][column.id] !== undefined) {
+ return this._cellTextCache[itemID][column.id];
+ }
+
+ if (!this._cellTextCache[itemID]) {
+ this._cellTextCache[itemID] = {}
+ }
var val;
@@ -937,7 +971,8 @@ Zotero.ItemTreeView.prototype.getCellText = function(row, column)
if (column.id === "zotero-items-column-hasAttachment") {
return;
}
- else if (column.id == "zotero-items-column-itemType") {
+ else if(column.id == "zotero-items-column-itemType")
+ {
val = Zotero.ItemTypes.getLocalizedString(obj.ref.itemTypeID);
}
// Year column is just date field truncated
@@ -1013,62 +1048,32 @@ Zotero.ItemTreeView.prototype.getCellText = function(row, column)
}
}
- return val;
+ return this._cellTextCache[itemID][column.id] = val;
}
Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
{
+ var self = this;
+
if(col.id == 'zotero-items-column-title')
{
// Get item type icon and tag swatches
- var item = this._getItemAtRow(row).ref;
+ var item = this.getRow(row).ref;
var itemID = item.id;
if (this._itemImages[itemID]) {
return this._itemImages[itemID];
}
- var uri = item.getImageSrc();
- var tags = item.getTags();
- if (!tags.length) {
- this._itemImages[itemID] = uri;
- return uri;
- }
-
- //Zotero.debug("Generating tree image for item " + itemID);
-
- var colorData = [];
- for (let i=0, len=tags.length; i<len; i++) {
- colorData.push(Zotero.Tags.getColor(item.libraryID, tags[i].name));
- }
- var self = this;
- Q.all(colorData)
- .then(function (colorData) {
- colorData = colorData.filter(function (val) val !== false);
- if (!colorData.length) {
- return false;
- }
- colorData.sort(function (a, b) {
- return a.position - b.position;
- });
- var colors = colorData.map(function (val) val.color);
- return Zotero.Tags.generateItemsListImage(colors, uri);
- })
- // When the promise is fulfilled, the data URL is ready, so invalidate
- // the cell to force requesting it again
- .then(function (dataURL) {
- self._itemImages[itemID] = dataURL ? dataURL : uri;
- if (dataURL) {
- self._treebox.invalidateCell(row, col);
- }
- })
- .done();
-
- this._itemImages[itemID] = uri;
- return uri;
+ item.getImageSrcWithTags()
+ .then(function (uriWithTags) {
+ this._itemImages[itemID] = uriWithTags;
+ this._treebox.invalidateCell(row, col);
+ }.bind(this));
+ return item.getImageSrc();
}
else if (col.id == 'zotero-items-column-hasAttachment') {
- if (this._itemGroup.isTrash()) return false;
+ if (this.collectionTreeRow.isTrash()) return false;
- var treerow = this._getItemAtRow(row);
+ var treerow = this.getRow(row);
var item = treerow.ref;
if ((!this.isContainer(row) || !this.isContainerOpen(row))
@@ -1080,7 +1085,7 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
if (treerow.level === 0) {
if (item.isRegularItem()) {
- let state = item.getBestAttachmentState(true);
+ let state = item.getBestAttachmentStateCached();
if (state !== null) {
switch (state) {
case 1:
@@ -1094,40 +1099,40 @@ Zotero.ItemTreeView.prototype.getImageSrc = function(row, col)
}
}
- item.getBestAttachmentStateAsync()
+ item.getBestAttachmentState()
// Refresh cell when promise is fulfilled
.then(function (state) {
- this._treebox.invalidateCell(row, col);
- }.bind(this))
+ self._treebox.invalidateCell(row, col);
+ })
.done();
}
}
if (item.isFileAttachment()) {
- let exists = item.fileExists(true);
+ let exists = item.fileExistsCached();
if (exists !== null) {
return exists
? "chrome://zotero/skin/bullet_blue.png"
: "chrome://zotero/skin/bullet_blue_empty.png";
}
- item.fileExistsAsync()
+ item.fileExists()
// Refresh cell when promise is fulfilled
.then(function (exists) {
- this._treebox.invalidateCell(row, col);
- }.bind(this));
+ self._treebox.invalidateCell(row, col);
+ });
}
}
}
Zotero.ItemTreeView.prototype.isContainer = function(row)
{
- return this._getItemAtRow(row).ref.isRegularItem();
+ return this.getRow(row).ref.isRegularItem();
}
Zotero.ItemTreeView.prototype.isContainerOpen = function(row)
{
- return this._dataItems[row].isOpen;
+ return this._rows[row].isOpen;
}
Zotero.ItemTreeView.prototype.isContainerEmpty = function(row)
@@ -1136,17 +1141,17 @@ Zotero.ItemTreeView.prototype.isContainerEmpty = function(row)
return true;
}
- var item = this._getItemAtRow(row).ref;
+ var item = this.getRow(row).ref;
if (!item.isRegularItem()) {
return false;
}
- var includeTrashed = this._itemGroup.isTrash();
+ var includeTrashed = this.collectionTreeRow.isTrash();
return item.numNotes(includeTrashed) === 0 && item.numAttachments(includeTrashed) == 0;
}
Zotero.ItemTreeView.prototype.getLevel = function(row)
{
- return this._getItemAtRow(row).level;
+ return this.getRow(row).level;
}
// Gets the index of the row's container, or -1 if none (top-level)
@@ -1175,7 +1180,7 @@ Zotero.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex)
}
}
-Zotero.ItemTreeView.prototype.toggleOpenState = function(row, skipItemMapRefresh)
+Zotero.ItemTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(function* (row, skipItemMapRefresh)
{
// Shouldn't happen but does if an item is dragged over a closed
// container until it opens and then released, since the container
@@ -1184,22 +1189,26 @@ Zotero.ItemTreeView.prototype.toggleOpenState = function(row, skipItemMapRefresh
return;
}
- var count = 0; //used to tell the tree how many rows were added/removed
+ var count = 0;
var thisLevel = this.getLevel(row);
// Close
if (this.isContainerOpen(row)) {
- while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel))
+ while((row + 1 < this._rows.length) && (this.getLevel(row + 1) > thisLevel))
{
- this._hideItem(row+1);
- count--; //count is negative when closing a container because we are removing rows
+ this._removeRow(row+1);
+ count--;
}
+ // Remove from the end of the row's children
+ this._treebox.rowCountChanged(row + 1 + Math.abs(count), count);
}
// Open
else {
- var item = this._getItemAtRow(row).ref;
+ var item = this.getRow(row).ref;
+ yield item.loadChildItems();
+
//Get children
- var includeTrashed = this._itemGroup.isTrash();
+ var includeTrashed = this.collectionTreeRow.isTrash();
var attachments = item.getAttachments(includeTrashed);
var notes = item.getNotes(includeTrashed);
@@ -1212,30 +1221,36 @@ Zotero.ItemTreeView.prototype.toggleOpenState = function(row, skipItemMapRefresh
newRows = notes;
if (newRows) {
- newRows = Zotero.Items.get(newRows);
+ newRows = yield Zotero.Items.getAsync(newRows);
for(var i = 0; i < newRows.length; i++)
{
count++;
- this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], thisLevel + 1, false), row + i + 1); // item ref, before row
+ this._addRow(
+ this._rows,
+ new Zotero.ItemTreeRow(newRows[i], thisLevel + 1, false),
+ row + i + 1
+ );
}
+ this.rowCount += count;
+ this._treebox.rowCountChanged(row + 1, count);
}
}
- this._dataItems[row].isOpen = !this._dataItems[row].isOpen;
+ // Toggle container open value
+ this._rows[row].isOpen = !this._rows[row].isOpen;
if (!count) {
return;
}
- this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these
this._treebox.invalidateRow(row);
if (!skipItemMapRefresh) {
Zotero.debug('Refreshing hash map');
- this._refreshHashMap();
+ this._refreshItemRowMap();
}
-}
+});
Zotero.ItemTreeView.prototype.isSorted = function()
@@ -1244,7 +1259,7 @@ Zotero.ItemTreeView.prototype.isSorted = function()
return true;
}
-Zotero.ItemTreeView.prototype.cycleHeader = function(column)
+Zotero.ItemTreeView.prototype.cycleHeader = Zotero.Promise.coroutine(function* (column)
{
for(var i=0, len=this._treebox.columns.count; i<len; i++)
{
@@ -1268,30 +1283,29 @@ Zotero.ItemTreeView.prototype.cycleHeader = function(column)
}
this.selection.selectEventsSuppressed = true;
- var savedSelection = this.saveSelection();
+ var savedSelection = this.getSelectedItems(true);
if (savedSelection.length == 1) {
var pos = this._itemRowMap[savedSelection[0]] - this._treebox.getFirstVisibleRow();
}
- this.sort();
- this.rememberSelection(savedSelection);
+ yield this.sort();
+ yield this.rememberSelection(savedSelection);
// If single row was selected, try to keep it in the same place
if (savedSelection.length == 1) {
var newRow = this._itemRowMap[savedSelection[0]];
// Calculate the last row that would give us a full view
- var fullTop = Math.max(0, this._dataItems.length - this._treebox.getPageLength());
+ var fullTop = Math.max(0, this._rows.length - this._treebox.getPageLength());
// Calculate the row that would give us the same position
var consistentTop = Math.max(0, newRow - pos);
this._treebox.scrollToRow(Math.min(fullTop, consistentTop));
}
this._treebox.invalidate();
this.selection.selectEventsSuppressed = false;
-}
+});
/*
* Sort the items by the currently sorted column.
*/
-Zotero.ItemTreeView.prototype.sort = function(itemID)
-{
+Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID) {
var t = new Date;
// If Zotero pane is hidden, mark tree for sorting later in setTree()
@@ -1303,10 +1317,10 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
// Single child item sort -- just toggle parent open and closed
if (itemID && this._itemRowMap[itemID] &&
- this._getItemAtRow(this._itemRowMap[itemID]).ref.getSource()) {
- var parentIndex = this.getParentIndex(this._itemRowMap[itemID]);
- this.toggleOpenState(parentIndex);
- this.toggleOpenState(parentIndex);
+ this.getRow(this._itemRowMap[itemID]).ref.parentKey) {
+ let parentIndex = this.getParentIndex(this._itemRowMap[itemID]);
+ yield this.toggleOpenState(parentIndex);
+ yield this.toggleOpenState(parentIndex);
return;
}
@@ -1392,7 +1406,7 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
}
}
- var includeTrashed = this._itemGroup.isTrash();
+ var includeTrashed = this.collectionTreeRow.isTrash();
function fieldCompare(a, b, sortField) {
var aItemID = a.id;
@@ -1428,7 +1442,7 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
}
}
- function rowSort(a, b) {
+ var rowSort = function (a, b) {
var sortFields = Array.slice(arguments, 2);
var sortField;
while (sortField = sortFields.shift()) {
@@ -1438,257 +1452,99 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
}
}
return 0;
- }
+ };
- var firstCreatorSortCache = {};
+ var creatorSortCache = {};
function creatorSort(a, b) {
- //
- // Try sorting by first word in firstCreator field, since we already have it
- //
+ var itemA = a.ref;
+ var itemB = b.ref;
var aItemID = a.id,
bItemID = b.id,
- fieldA = firstCreatorSortCache[aItemID],
- fieldB = firstCreatorSortCache[bItemID];
+ fieldA = creatorSortCache[aItemID],
+ fieldB = creatorSortCache[bItemID];
+ var prop = sortCreatorAsString ? 'firstCreator' : 'sortCreator';
+ var sortStringA = itemA[prop];
+ var sortStringB = itemB[prop];
if (fieldA === undefined) {
- var matches = Zotero.Items.getSortTitle(a.getField('firstCreator')).match(/^[^\s]+/);
+ var matches = Zotero.Items.getSortTitle(sortStringA).match(/^[^\s]+/);
var fieldA = matches ? matches[0] : '';
- firstCreatorSortCache[aItemID] = fieldA;
+ creatorSortCache[aItemID] = fieldA;
}
if (fieldB === undefined) {
- var matches = Zotero.Items.getSortTitle(b.getField('firstCreator')).match(/^[^\s]+/);
+ var matches = Zotero.Items.getSortTitle(sortStringB).match(/^[^\s]+/);
var fieldB = matches ? matches[0] : '';
- firstCreatorSortCache[bItemID] = fieldB;
+ creatorSortCache[bItemID] = fieldB;
}
if (fieldA === "" && fieldB === "") {
return 0;
}
- var cmp = strcmp(fieldA, fieldB);
- if (cmp !== 0 || sortCreatorAsString) {
- return cmp;
- }
-
- //
- // If first word is the same, compare actual creators
- //
- var aRef = a.ref,
- bRef = b.ref,
- aCreators = aRef.getCreators(),
- bCreators = bRef.getCreators(),
- aNumCreators = aCreators.length,
- bNumCreators = bCreators.length,
- aPrimary = Zotero.CreatorTypes.getPrimaryIDForType(aRef.itemTypeID),
- bPrimary = Zotero.CreatorTypes.getPrimaryIDForType(bRef.itemTypeID);
- const editorTypeID = 3,
- contributorTypeID = 2;
-
- // Find the first position of each possible creator type
- var aPrimaryFoundAt = false;
- var aEditorFoundAt = false;
- var aContributorFoundAt = false;
- loop:
- for (var orderIndex in aCreators) {
- switch (aCreators[orderIndex].creatorTypeID) {
- case aPrimary:
- aPrimaryFoundAt = orderIndex;
- // If we find a primary, no need to continue looking
- break loop;
-
- case editorTypeID:
- if (aEditorFoundAt === false) {
- aEditorFoundAt = orderIndex;
- }
- break;
-
- case contributorTypeID:
- if (aContributorFoundAt === false) {
- aContributorFoundAt = orderIndex;
- }
- break;
- }
- }
- if (aPrimaryFoundAt !== false) {
- var aFirstCreatorTypeID = aPrimary;
- var aPos = aPrimaryFoundAt;
- }
- else if (aEditorFoundAt !== false) {
- var aFirstCreatorTypeID = editorTypeID;
- var aPos = aEditorFoundAt;
- }
- else {
- var aFirstCreatorTypeID = contributorTypeID;
- var aPos = aContributorFoundAt;
- }
-
- // Same for b
- var bPrimaryFoundAt = false;
- var bEditorFoundAt = false;
- var bContributorFoundAt = false;
- loop:
- for (var orderIndex in bCreators) {
- switch (bCreators[orderIndex].creatorTypeID) {
- case bPrimary:
- bPrimaryFoundAt = orderIndex;
- break loop;
-
- case 3:
- if (bEditorFoundAt === false) {
- bEditorFoundAt = orderIndex;
- }
- break;
-
- case 2:
- if (bContributorFoundAt === false) {
- bContributorFoundAt = orderIndex;
- }
- break;
- }
- }
- if (bPrimaryFoundAt !== false) {
- var bFirstCreatorTypeID = bPrimary;
- var bPos = bPrimaryFoundAt;
- }
- else if (bEditorFoundAt !== false) {
- var bFirstCreatorTypeID = editorTypeID;
- var bPos = bEditorFoundAt;
- }
- else {
- var bFirstCreatorTypeID = contributorTypeID;
- var bPos = bContributorFoundAt;
- }
-
- while (true) {
- // Compare names
- fieldA = Zotero.Items.getSortTitle(aCreators[aPos].ref.lastName);
- fieldB = Zotero.Items.getSortTitle(bCreators[bPos].ref.lastName);
- cmp = strcmp(fieldA, fieldB);
- if (cmp) {
- return cmp;
- }
-
- fieldA = Zotero.Items.getSortTitle(aCreators[aPos].ref.firstName);
- fieldB = Zotero.Items.getSortTitle(bCreators[bPos].ref.firstName);
- cmp = strcmp(fieldA, fieldB);
- if (cmp) {
- return cmp;
- }
-
- // If names match, find next creator of the relevant type
- aPos++;
- var aFound = false;
- while (aPos < aNumCreators) {
- // Don't die if there's no creator at an index
- if (!aCreators[aPos]) {
- Components.utils.reportError(
- "Creator is missing at position " + aPos
- + " for item " + aRef.libraryID + "/" + aRef.key
- );
- return -1;
- }
-
- if (aCreators[aPos].creatorTypeID == aFirstCreatorTypeID) {
- aFound = true;
- break;
- }
- aPos++;
- }
-
- bPos++;
- var bFound = false;
- while (bPos < bNumCreators) {
- // Don't die if there's no creator at an index
- if (!bCreators[bPos]) {
- Components.utils.reportError(
- "Creator is missing at position " + bPos
- + " for item " + bRef.libraryID + "/" + bRef.key
- );
- return -1;
- }
-
- if (bCreators[bPos].creatorTypeID == bFirstCreatorTypeID) {
- bFound = true;
- break;
- }
- bPos++;
- }
-
- if (aFound && !bFound) {
- return -1;
- }
- if (bFound && !aFound) {
- return 1;
- }
- if (!aFound && !bFound) {
- return 0;
- }
- }
- }
-
- function strcmp(a, b, collationSort) {
// Display rows with empty values last
- if (a === '' && b !== '') return 1;
- if (a !== '' && b === '') return -1;
+ if (fieldA === '' && fieldB !== '') return 1;
+ if (fieldA !== '' && fieldB === '') return -1;
- return collation.compareString(1, a, b);
+ return collation.compareString(1, fieldA, fieldB);
}
// Need to close all containers before sorting
if (!this.selection.selectEventsSuppressed) {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
}
- var savedSelection = this.saveSelection();
- var openItemIDs = this.saveOpenState(true);
+ var savedSelection = this.getSelectedItems(true);
+ var openItemIDs = yield this.saveOpenState(true);
// Single-row sort
if (itemID) {
let row = this._itemRowMap[itemID];
- for (let i=0, len=this._dataItems.length; i<len; i++) {
+ for (let i=0, len=this._rows.length; i<len; i++) {
if (i === row) {
continue;
}
- let cmp = rowSort.apply(this,
- [this._dataItems[i], this._dataItems[row]].concat(sortFields)) * order;
+ let cmp = rowSort.apply(this, [this._rows[i], this._rows[row]].concat(sortFields)) * order;
// As soon as we find a value greater (or smaller if reverse sort),
// insert row at that position
if (cmp > 0) {
- let rowItem = this._dataItems.splice(row, 1);
- this._dataItems.splice(row < i ? i-1 : i, 0, rowItem[0]);
+ let rowItem = this._rows.splice(row, 1);
+ this._rows.splice(row < i ? i-1 : i, 0, rowItem[0]);
this._treebox.invalidate();
break;
}
// If greater than last row, move to end
if (i == len-1) {
- let rowItem = this._dataItems.splice(row, 1);
- this._dataItems.splice(i, 0, rowItem[0]);
+ let rowItem = this._rows.splice(row, 1);
+ this._rows.splice(i, 0, rowItem[0]);
this._treebox.invalidate();
}
}
}
// Full sort
else {
- this._dataItems.sort(function (a, b) {
+ this._rows.sort(function (a, b) {
return rowSort.apply(this, [a, b].concat(sortFields)) * order;
}.bind(this));
+
+ Zotero.debug("Sorted items list without creators in " + (new Date - t) + " ms");
}
- this._refreshHashMap();
+ this._refreshItemRowMap();
- this.rememberOpenState(openItemIDs);
- this.rememberSelection(savedSelection);
+ yield this.rememberOpenState(openItemIDs);
+ yield this.rememberSelection(savedSelection);
if (unsuppress) {
this.selection.selectEventsSuppressed = false;
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
}
Zotero.debug("Sorted items list in " + (new Date - t) + " ms");
-}
+});
+
////////////////////////////////////////////////////////////////////////////////
///
@@ -1700,8 +1556,10 @@ Zotero.ItemTreeView.prototype.sort = function(itemID)
/*
* Select an item
*/
-Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
-{
+Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (id, expand, noRecurse) {
+ var selected = this.getSelectedItems(true);
+ var alreadySelected = selected.length == 1 && selected[0] == id;
+
// Don't change selection if UI updates are disabled (e.g., during sync)
if (Zotero.suppressUIUpdates) {
Zotero.debug("Sync is running; not selecting item");
@@ -1711,8 +1569,8 @@ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
// If no row map, we're probably in the process of switching collections,
// so store the item to select on the item group for later
if (!this._itemRowMap) {
- if (this._itemGroup) {
- this._itemGroup.itemToSelect = { id: id, expand: expand };
+ if (this.collectionTreeRow) {
+ this.collectionTreeRow.itemToSelect = { id: id, expand: expand };
Zotero.debug("_itemRowMap not yet set; not selecting item");
return false;
}
@@ -1725,8 +1583,8 @@ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
// Get the row of the parent, if there is one
var parentRow = null;
- var item = Zotero.Items.get(id);
- var parent = item.getSource();
+ var item = yield Zotero.Items.getAsync(id);
+ var parent = item.parentItemID;
if (parent && this._itemRowMap[parent] != undefined) {
parentRow = this._itemRowMap[parent];
}
@@ -1738,12 +1596,12 @@ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
// No parent -- it's not here
// Clear the quicksearch and tag selection and try again (once)
- if (!noRecurse) {
- if (this._ownerDocument.defaultView.ZoteroPane_Local) {
- this._ownerDocument.defaultView.ZoteroPane_Local.clearQuicksearch();
- this._ownerDocument.defaultView.ZoteroPane_Local.clearTagSelection();
+ if (!noRecurse && this._ownerDocument.defaultView.ZoteroPane_Local) {
+ let cleared1 = yield this._ownerDocument.defaultView.ZoteroPane_Local.clearQuicksearch();
+ let cleared2 = yield this._ownerDocument.defaultView.ZoteroPane_Local.clearTagSelection();
+ if (cleared1 || cleared2) {
+ return this.selectItem(id, expand, true);
}
- return this.selectItem(id, expand, true);
}
Zotero.debug("Could not find row for item; not selecting item");
@@ -1753,24 +1611,45 @@ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
// If parent is already open and we haven't found the item, the child
// hasn't yet been added to the view, so close parent to allow refresh
if (this.isContainerOpen(parentRow)) {
- this.toggleOpenState(parentRow);
+ yield this.toggleOpenState(parentRow);
}
// Open the parent
- this.toggleOpenState(parentRow);
+ yield this.toggleOpenState(parentRow);
row = this._itemRowMap[id];
}
- this.selection.select(row);
+
+ if (!alreadySelected) {
+ // This function calls nsITreeSelection.select(), which triggers the <tree>'s 'onselect'
+ // attribute, which calls ZoteroPane.itemSelected(), which calls ZoteroItemPane.viewItem(),
+ // which refreshes the itembox. But since the 'onselect' doesn't handle promises,
+ // itemSelected() isn't waited for and 'yield selectItem(itemID)' continues before the
+ // itembox has been refreshed. To get around this, we make a promise resolver that's
+ // triggered by itemSelected() when it's done.
+ var itemSelectedPromise = new Zotero.Promise(function () {
+ this._itemSelectedPromiseResolver = {
+ resolve: arguments[0],
+ reject: arguments[1]
+ };
+ }.bind(this));
+
+ this.selection.select(row);
+ }
+
// If |expand|, open row if container
if (expand && this.isContainer(row) && !this.isContainerOpen(row)) {
- this.toggleOpenState(row);
+ yield this.toggleOpenState(row);
}
this.selection.select(row);
+ if (!alreadySelected && !this.selection.selectEventsSuppressed) {
+ yield itemSelectedPromise;
+ }
+
// We aim for a row 5 below the target row, since ensureRowIsVisible() does
// the bare minimum to get the row in view
for (var v = row + 5; v>=row; v--) {
- if (this._dataItems[v]) {
+ if (this._rows[v]) {
this._treebox.ensureRowIsVisible(v);
if (this._treebox.getFirstVisibleRow() <= row) {
break;
@@ -1786,7 +1665,7 @@ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse)
}
return true;
-}
+});
/**
@@ -1836,10 +1715,10 @@ Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
this.selection.getRangeAt(i,start,end);
for (var j=start.value; j<=end.value; j++) {
if (asIDs) {
- items.push(this._getItemAtRow(j).id);
+ items.push(this.getRow(j).id);
}
else {
- items.push(this._getItemAtRow(j).ref);
+ items.push(this.getRow(j).ref);
}
}
}
@@ -1852,7 +1731,7 @@ Zotero.ItemTreeView.prototype.getSelectedItems = function(asIDs)
*
* @param {Boolean} [force=false] Delete item even if removing from a collection
*/
-Zotero.ItemTreeView.prototype.deleteSelection = function (force)
+Zotero.ItemTreeView.prototype.deleteSelection = Zotero.Promise.coroutine(function* (force)
{
if (arguments.length > 1) {
throw ("deleteSelection() no longer takes two parameters");
@@ -1862,15 +1741,15 @@ Zotero.ItemTreeView.prototype.deleteSelection = function (force)
return;
}
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
// Collapse open items
for (var i=0; i<this.rowCount; i++) {
if (this.selection.isSelected(i) && this.isContainer(i) && this.isContainerOpen(i)) {
- this.toggleOpenState(i, true);
+ yield this.toggleOpenState(i, true);
}
}
- this._refreshHashMap();
+ this._refreshItemRowMap();
// Create an array of selected items
var ids = [];
@@ -1880,139 +1759,130 @@ Zotero.ItemTreeView.prototype.deleteSelection = function (force)
{
this.selection.getRangeAt(i,start,end);
for (var j=start.value; j<=end.value; j++)
- ids.push(this._getItemAtRow(j).id);
+ ids.push(this.getRow(j).id);
}
- Zotero.ItemGroupCache.clear();
- var itemGroup = this._itemGroup;
+ Zotero.CollectionTreeCache.clear();
+ var collectionTreeRow = this.collectionTreeRow;
- if (itemGroup.isBucket()) {
- itemGroup.ref.deleteItems(ids);
+ if (collectionTreeRow.isBucket()) {
+ collectionTreeRow.ref.deleteItems(ids);
}
- else if (itemGroup.isTrash()) {
+ else if (collectionTreeRow.isTrash()) {
Zotero.Items.erase(ids);
}
- else if (itemGroup.isLibrary(true) || force) {
+ else if (collectionTreeRow.isLibrary(true) || force) {
Zotero.Items.trash(ids);
}
- else if (itemGroup.isCollection()) {
- itemGroup.ref.removeItems(ids);
+ else if (collectionTreeRow.isCollection()) {
+ collectionTreeRow.ref.removeItems(ids);
}
- this._treebox.endUpdateBatch();
-}
+ //this._treebox.endUpdateBatch();
+});
/*
* Set the search/tags filter on the view
*/
-Zotero.ItemTreeView.prototype.setFilter = function(type, data) {
+Zotero.ItemTreeView.prototype.setFilter = Zotero.Promise.coroutine(function* (type, data) {
if (!this._treebox || !this._treebox.treeBody) {
Components.utils.reportError("Treebox didn't exist in itemTreeView.setFilter()");
return;
}
this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
switch (type) {
case 'search':
- this._itemGroup.setSearch(data);
+ this.collectionTreeRow.setSearch(data);
break;
case 'tags':
- this._itemGroup.setTags(data);
+ this.collectionTreeRow.setTags(data);
break;
default:
throw ('Invalid filter type in setFilter');
}
var oldCount = this.rowCount;
- this.refresh();
+ yield this.refresh();
- this.sort();
+ yield this.sort();
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
//Zotero.debug('Running callbacks in itemTreeView.setFilter()', 4);
- this._runCallbacks();
-}
+ yield this._runCallbacks();
+});
-/*
- * Called by various view functions to show a row
- *
- * item: reference to the Item
- * beforeRow: row index to insert new row before
+/**
+ * Add a tree row into a given array
+ *
+ * @param {Array} newRows - Array to operate on
+ * @param {Zotero.ItemTreeRow} itemTreeRow
+ * @param {Number} beforeRow - Row index to insert new row before
*/
-Zotero.ItemTreeView.prototype._showItem = function(item, beforeRow)
-{
- this._dataItems.splice(beforeRow, 0, item);
- this.rowCount++;
+Zotero.ItemTreeView.prototype._addRow = function (newRows, itemTreeRow, beforeRow) {
+ newRows.splice(beforeRow, 0, itemTreeRow);
}
-/*
- * Called by view to hide specified row
+
+/**
+ * Remove a row from the main rows array
*/
-Zotero.ItemTreeView.prototype._hideItem = function(row)
-{
- this._dataItems.splice(row,1);
+Zotero.ItemTreeView.prototype._removeRow = function (row) {
+ this._rows.splice(row, 1);
this.rowCount--;
+ if (this.selection.isSelected(row)) {
+ this.selection.toggleSelect(row);
+ }
}
/*
* Returns a reference to the item at row (see Zotero.Item in data_access.js)
*/
-Zotero.ItemTreeView.prototype._getItemAtRow = function(row)
-{
- return this._dataItems[row];
+Zotero.ItemTreeView.prototype.getRow = function(row) {
+ return this._rows[row];
}
/*
- * Create hash map of item ids to row indexes
+ * Create map of item ids to row indexes
*/
-Zotero.ItemTreeView.prototype._refreshHashMap = function()
+Zotero.ItemTreeView.prototype._refreshItemRowMap = function()
{
var rowMap = {};
for (var i=0, len=this.rowCount; i<len; i++) {
- var row = this._getItemAtRow(i);
- rowMap[row.ref.id] = i;
+ let row = this.getRow(i);
+ let id = row.ref.id;
+ if (rowMap[id] !== undefined) {
+ Zotero.debug("WARNING: Item row already found", 2);
+ }
+ rowMap[id] = i;
}
this._itemRowMap = rowMap;
}
-/*
- * Saves the ids of currently selected items for later
- */
-Zotero.ItemTreeView.prototype.saveSelection = function()
-{
- var savedSelection = new Array();
-
- var start = new Object();
- var end = new Object();
- for (var i=0, len=this.selection.getRangeCount(); i<len; i++)
- {
- this.selection.getRangeAt(i,start,end);
- for (var j=start.value; j<=end.value; j++)
- {
- var item = this._getItemAtRow(j);
- if (!item) {
- continue;
- }
- savedSelection.push(item.ref.id);
- }
- }
- return savedSelection;
+
+Zotero.ItemTreeView.prototype.saveSelection = function () {
+ return this.getSelectedItems(true);
}
+
/*
- * Sets the selection based on saved selection ids (see above)
+ * Sets the selection based on saved selection ids
*/
-Zotero.ItemTreeView.prototype.rememberSelection = function(selection)
-{
+Zotero.ItemTreeView.prototype.rememberSelection = Zotero.Promise.coroutine(function* (selection)
+{
+ if (!selection.length) {
+ return;
+ }
+
this.selection.clearSelection();
if (!this.selection.selectEventsSuppressed) {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
}
for(var i=0; i < selection.length; i++)
{
@@ -2021,74 +1891,74 @@ Zotero.ItemTreeView.prototype.rememberSelection = function(selection)
}
// Try the parent
else {
- var item = Zotero.Items.get(selection[i]);
+ var item = yield Zotero.Items.getAsync(selection[i]);
if (!item) {
continue;
}
- var parent = item.getSource();
+ var parent = item.parentItemID;
if (!parent) {
continue;
}
if (this._itemRowMap[parent] != null) {
if (this.isContainerOpen(this._itemRowMap[parent])) {
- this.toggleOpenState(this._itemRowMap[parent]);
+ yield this.toggleOpenState(this._itemRowMap[parent]);
}
- this.toggleOpenState(this._itemRowMap[parent]);
+ yield this.toggleOpenState(this._itemRowMap[parent]);
this.selection.toggleSelect(this._itemRowMap[selection[i]]);
}
}
}
if (unsuppress) {
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
-}
+});
-Zotero.ItemTreeView.prototype.selectSearchMatches = function () {
+Zotero.ItemTreeView.prototype.selectSearchMatches = Zotero.Promise.coroutine(function* () {
if (this._searchMode) {
var ids = [];
for (var id in this._searchItemIDs) {
ids.push(id);
}
- this.rememberSelection(ids);
+ yield this.rememberSelection(ids);
}
else {
this.selection.clearSelection();
}
-}
+});
-Zotero.ItemTreeView.prototype.saveOpenState = function(close) {
+Zotero.ItemTreeView.prototype.saveOpenState = Zotero.Promise.coroutine(function* (close) {
var itemIDs = [];
if (close) {
if (!this.selection.selectEventsSuppressed) {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
}
}
- for (var i=0; i<this._dataItems.length; i++) {
+ for (var i=0; i<this._rows.length; i++) {
if (this.isContainer(i) && this.isContainerOpen(i)) {
- itemIDs.push(this._getItemAtRow(i).ref.id);
+ itemIDs.push(this.getRow(i).ref.id);
if (close) {
- this.toggleOpenState(i, true);
+ yield this.toggleOpenState(i, true);
}
}
}
if (close) {
- this._refreshHashMap();
+ this._refreshItemRowMap();
if (unsuppress) {
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
}
return itemIDs;
-}
+});
-Zotero.ItemTreeView.prototype.rememberOpenState = function(itemIDs) {
+Zotero.ItemTreeView.prototype.rememberOpenState = Zotero.Promise.coroutine(function* (itemIDs) {
var rowsToOpen = [];
for each(var id in itemIDs) {
var row = this._itemRowMap[id];
@@ -2104,21 +1974,21 @@ Zotero.ItemTreeView.prototype.rememberOpenState = function(itemIDs) {
if (!this.selection.selectEventsSuppressed) {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
}
// Reopen from bottom up
for (var i=rowsToOpen.length-1; i>=0; i--) {
- this.toggleOpenState(rowsToOpen[i], true);
+ yield this.toggleOpenState(rowsToOpen[i], true);
}
- this._refreshHashMap();
+ this._refreshItemRowMap();
if (unsuppress) {
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
-}
+});
-Zotero.ItemTreeView.prototype.expandMatchParents = function () {
+Zotero.ItemTreeView.prototype.expandMatchParents = Zotero.Promise.coroutine(function* () {
// Expand parents of child matches
if (!this._searchMode) {
return;
@@ -2131,26 +2001,26 @@ Zotero.ItemTreeView.prototype.expandMatchParents = function () {
if (!this.selection.selectEventsSuppressed) {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
}
for (var i=0; i<this.rowCount; i++) {
- var id = this._getItemAtRow(i).ref.id;
+ var id = this.getRow(i).ref.id;
if (hash[id] && this.isContainer(i) && !this.isContainerOpen(i)) {
- this.toggleOpenState(i, true);
+ yield this.toggleOpenState(i, true);
}
}
- this._refreshHashMap();
+ this._refreshItemRowMap();
if (unsuppress) {
- this._treebox.endUpdateBatch();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
-}
+});
Zotero.ItemTreeView.prototype.saveFirstRow = function() {
var row = this._treebox.getFirstVisibleRow();
if (row) {
- return this._getItemAtRow(row).ref.id;
+ return this.getRow(row).ref.id;
}
return false;
}
@@ -2163,68 +2033,68 @@ Zotero.ItemTreeView.prototype.rememberFirstRow = function(firstRow) {
}
-Zotero.ItemTreeView.prototype.expandAllRows = function() {
+Zotero.ItemTreeView.prototype.expandAllRows = Zotero.Promise.coroutine(function* () {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
if (this.isContainer(i) && !this.isContainerOpen(i)) {
- this.toggleOpenState(i, true);
+ yield this.toggleOpenState(i, true);
}
}
- this._refreshHashMap();
- this._treebox.endUpdateBatch();
+ this._refreshItemRowMap();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
-}
+});
-Zotero.ItemTreeView.prototype.collapseAllRows = function() {
+Zotero.ItemTreeView.prototype.collapseAllRows = Zotero.Promise.coroutine(function* () {
var unsuppress = this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
if (this.isContainer(i) && this.isContainerOpen(i)) {
- this.toggleOpenState(i, true);
+ yield this.toggleOpenState(i, true);
}
}
- this._refreshHashMap();
- this._treebox.endUpdateBatch();
+ this._refreshItemRowMap();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
-}
+});
-Zotero.ItemTreeView.prototype.expandSelectedRows = function() {
+Zotero.ItemTreeView.prototype.expandSelectedRows = Zotero.Promise.coroutine(function* () {
var start = {}, end = {};
this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
for (var i = 0, len = this.selection.getRangeCount(); i<len; i++) {
this.selection.getRangeAt(i, start, end);
for (var j = start.value; j <= end.value; j++) {
if (this.isContainer(j) && !this.isContainerOpen(j)) {
- this.toggleOpenState(j, true);
+ yield this.toggleOpenState(j, true);
}
}
}
- this._refreshHashMap();
- this._treebox.endUpdateBatch();
+ this._refreshItemRowMap();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
-}
+});
-Zotero.ItemTreeView.prototype.collapseSelectedRows = function() {
+Zotero.ItemTreeView.prototype.collapseSelectedRows = Zotero.Promise.coroutine(function* () {
var start = {}, end = {};
this.selection.selectEventsSuppressed = true;
- this._treebox.beginUpdateBatch();
+ //this._treebox.beginUpdateBatch();
for (var i = 0, len = this.selection.getRangeCount(); i<len; i++) {
this.selection.getRangeAt(i, start, end);
for (var j = start.value; j <= end.value; j++) {
if (this.isContainer(j) && this.isContainerOpen(j)) {
- this.toggleOpenState(j, true);
+ yield this.toggleOpenState(j, true);
}
}
}
- this._refreshHashMap();
- this._treebox.endUpdateBatch();
+ this._refreshItemRowMap();
+ //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
-}
+});
Zotero.ItemTreeView.prototype.getVisibleFields = function() {
@@ -2247,7 +2117,7 @@ Zotero.ItemTreeView.prototype.getVisibleFields = function() {
*/
Zotero.ItemTreeView.prototype.getSortedItems = function(asIDs) {
var items = [];
- for each(var item in this._dataItems) {
+ for each(var item in this._rows) {
if (asIDs) {
items.push(item.ref.id);
}
@@ -2527,17 +2397,17 @@ Zotero.ItemTreeCommandController.prototype.isCommandEnabled = function(cmd)
return (cmd == 'cmd_selectAll');
}
-Zotero.ItemTreeCommandController.prototype.doCommand = function(cmd)
+Zotero.ItemTreeCommandController.prototype.doCommand = Zotero.Promise.coroutine(function* (cmd)
{
if (cmd == 'cmd_selectAll') {
- if (this.tree.view.wrappedJSObject._itemGroup.isSearchMode()) {
- this.tree.view.wrappedJSObject.selectSearchMatches();
+ if (this.tree.view.wrappedJSObject.collectionTreeRow.isSearchMode()) {
+ yield this.tree.view.wrappedJSObject.selectSearchMatches();
}
else {
this.tree.view.selection.selectAll();
}
}
-}
+});
Zotero.ItemTreeCommandController.prototype.onEvent = function(evt)
{
@@ -2559,10 +2429,10 @@ Zotero.ItemTreeView.prototype.onDragStart = function (event) {
event.dataTransfer.effectAllowed = 'copy';
}
- var itemIDs = this.saveSelection();
- var items = Zotero.Items.get(itemIDs);
+ var itemIDs = this.getSelectedItems(true);
+ event.dataTransfer.setData("zotero/item", itemIDs);
- event.dataTransfer.setData("zotero/item", itemIDs.join());
+ var items = Zotero.Items.get(itemIDs);
// Multi-file drag
// - Doesn't work on Windows
@@ -2609,45 +2479,50 @@ Zotero.ItemTreeView.prototype.onDragStart = function (event) {
}
}
- // Get Quick Copy format for current URL
- var url = this._ownerDocument.defaultView.content && this._ownerDocument.defaultView.content.location ?
- this._ownerDocument.defaultView.content.location.href : null;
- var format = Zotero.QuickCopy.getFormatFromURL(url);
-
- Zotero.debug("Dragging with format " + Zotero.QuickCopy.getFormattedNameFromSetting(format));
-
- var exportCallback = function(obj, worked) {
- if (!worked) {
- Zotero.log(Zotero.getString("fileInterface.exportError"), 'warning');
- return;
- }
+ // Quick Copy is asynchronous
+ Zotero.spawn(function* () {
+ // Get Quick Copy format for current URL
+ var url = this._ownerDocument.defaultView.content && this._ownerDocument.defaultView.content.location ?
+ this._ownerDocument.defaultView.content.location.href : null;
+ //var format = yield Zotero.QuickCopy.getFormatFromURL(url);
+ var format = 'bibliography=http://www.zotero.org/styles/chicago-note-bibliography';
- var text = obj.string.replace(/\r\n/g, "\n");
- event.dataTransfer.setData("text/plain", text);
- }
-
- try {
- var [mode, ] = format.split('=');
- if (mode == 'export') {
- Zotero.QuickCopy.getContentFromItems(items, format, exportCallback);
+ Zotero.debug("Dragging with format " + (yield Zotero.QuickCopy.getFormattedNameFromSetting(format)));
+
+ var exportCallback = function(obj, worked) {
+ if (!worked) {
+ Zotero.log(Zotero.getString("fileInterface.exportError"), 'warning');
+ return;
+ }
+
+ var text = obj.string.replace(/\r\n/g, "\n");
+ event.dataTransfer.setData("text/plain", text);
}
- else if (mode.indexOf('bibliography') == 0) {
- var content = Zotero.QuickCopy.getContentFromItems(items, format, null, event.shiftKey);
- if (content) {
- if (content.html) {
- event.dataTransfer.setData("text/html", content.html);
+
+ try {
+ var [mode, ] = format.split('=');
+ if (mode == 'export') {
+ Zotero.QuickCopy.getContentFromItems(items, format, exportCallback);
+ }
+ else if (mode.indexOf('bibliography') == 0) {
+ var content = Zotero.QuickCopy.getContentFromItems(items, format, null, event.shiftKey);
+ if (content) {
+ if (content.html) {
+ event.dataTransfer.setData("text/html", content.html);
+ }
+ event.dataTransfer.setData("text/plain", content.text);
}
- event.dataTransfer.setData("text/plain", content.text);
+ }
+ else {
+ Components.utils.reportError("Invalid Quick Copy mode '" + mode + "'");
}
}
- else {
- Components.utils.reportError("Invalid Quick Copy mode '" + mode + "'");
+ catch (e) {
+ Zotero.debug(e);
+ Components.utils.reportError(e + " with format '" + format + "'");
}
- }
- catch (e) {
- Components.utils.reportError(e + " with format '" + format + "'");
- }
-}
+ }.bind(this));
+};
// Implements nsIFlavorDataProvider for dragging attachment files to OS
@@ -2744,7 +2619,7 @@ Zotero.ItemTreeView.fileDragDataProvider.prototype = {
// Create folder if multiple files
if (numFiles > 1) {
- var dirName = Zotero.Attachments.getFileBaseNameFromItem(items[i].id);
+ var dirName = Zotero.Attachments.getFileBaseNameFromItem(items[i]);
try {
if (useTemp) {
var copiedFile = destDir.clone();
@@ -2888,18 +2763,14 @@ Zotero.ItemTreeView.prototype.canDropCheck = function (row, orient, dataTransfer
var dataType = dragData.dataType;
var data = dragData.data;
- if (dataType == 'zotero/item') {
- var ids = data;
- }
-
- var itemGroup = this._itemGroup;
+ var collectionTreeRow = this.collectionTreeRow;
if (row != -1 && orient == 0) {
- var rowItem = this._getItemAtRow(row).ref; // the item we are dragging over
+ var rowItem = this.getRow(row).ref; // the item we are dragging over
}
if (dataType == 'zotero/item') {
- var items = Zotero.Items.get(ids);
+ let items = Zotero.Items.get(data);
// Directly on a row
if (rowItem) {
@@ -2912,22 +2783,21 @@ Zotero.ItemTreeView.prototype.canDropCheck = function (row, orient, dataTransfer
}
// Disallow cross-library child drag
- if (item.libraryID != itemGroup.ref.libraryID) {
+ if (item.libraryID != collectionTreeRow.ref.libraryID) {
return false;
}
// Only allow dragging of notes and attachments
// that aren't already children of the item
- if (item.getSource() != rowItem.id) {
+ if (item.parentItemID != rowItem.id) {
canDrop = true;
}
}
-
return canDrop;
}
// In library, allow children to be dragged out of parent
- else if (itemGroup.isLibrary(true) || itemGroup.isCollection()) {
+ else if (collectionTreeRow.isLibrary(true) || collectionTreeRow.isCollection()) {
for each(var item in items) {
// Don't allow drag if any top-level items
if (item.isTopLevelItem()) {
@@ -2936,15 +2806,15 @@ Zotero.ItemTreeView.prototype.canDropCheck = function (row, orient, dataTransfer
// Don't allow web attachments to be dragged out of parents,
// but do allow PDFs for now so they can be recognized
- if (item.isWebAttachment() && item.attachmentMIMEType != 'application/pdf') {
+ if (item.isWebAttachment() && item.attachmentContentType != 'application/pdf') {
return false;
}
// Don't allow children to be dragged within their own parents
- var parentItemID = item.getSource();
+ var parentItemID = item.parentItemID;
var parentIndex = this._itemRowMap[parentItemID];
- if (this.getLevel(row) > 0) {
- if (this._getItemAtRow(this.getParentIndex(row)).ref.id == parentItemID) {
+ if (row != -1 && this.getLevel(row) > 0) {
+ if (this.getRow(this.getParentIndex(row)).ref.id == parentItemID) {
return false;
}
}
@@ -2969,7 +2839,7 @@ Zotero.ItemTreeView.prototype.canDropCheck = function (row, orient, dataTransfer
}
// Disallow cross-library child drag
- if (item.libraryID != itemGroup.ref.libraryID) {
+ if (item.libraryID != collectionTreeRow.ref.libraryID) {
return false;
}
}
@@ -2985,7 +2855,7 @@ Zotero.ItemTreeView.prototype.canDropCheck = function (row, orient, dataTransfer
}
}
// Don't allow drop into searches
- else if (itemGroup.isSearch()) {
+ else if (collectionTreeRow.isSearch()) {
return false;
}
@@ -2993,13 +2863,13 @@ Zotero.ItemTreeView.prototype.canDropCheck = function (row, orient, dataTransfer
}
return false;
-}
+};
/*
* Called when something's been dropped on or next to a row
*/
-Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
- if (!this.canDropCheck(row, orient, dataTransfer)) {
+Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, orient, dataTransfer) {
+ if (!this.canDrop(row, orient, dataTransfer)) {
return false;
}
@@ -3011,8 +2881,9 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
var dropEffect = dragData.dropEffect;
var dataType = dragData.dataType;
var data = dragData.data;
- var itemGroup = this.itemGroup;
- var targetLibraryID = itemGroup.ref.libraryID;
+ var sourceCollectionTreeRow = Zotero.DragDrop.getDragSource(dataTransfer);
+ var collectionTreeRow = this.collectionTreeRow;
+ var targetLibraryID = collectionTreeRow.ref.libraryID;
if (dataType == 'zotero/item') {
var ids = data;
@@ -3039,40 +2910,52 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
// Set drop target as the parent item for dragged items
//
// canDrop() limits this to child items
- var rowItem = this._getItemAtRow(row).ref; // the item we are dragging over
- for each(var item in items) {
- item.setSource(rowItem.id);
- item.save();
- }
+ var rowItem = this.getRow(row).ref; // the item we are dragging over
+ yield Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+ item.parentID = rowItem.id;
+ yield item.save();
+ }
+ });
}
// Dropped outside of a row
else
{
// Remove from parent and make top-level
- if (itemGroup.isLibrary(true)) {
- for each(var item in items) {
- if (!item.isRegularItem())
- {
- item.setSource();
- item.save()
+ if (collectionTreeRow.isLibrary(true)) {
+ yield Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+ if (!item.isRegularItem()) {
+ item.parentID = false;
+ yield item.save()
+ }
}
- }
+ });
}
// Add to collection
else
{
- for each(var item in items)
- {
- var source = item.isRegularItem() ? false : item.getSource();
- // Top-level item
- if (source) {
- item.setSource();
- item.save()
+ yield Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<items.length; i++) {
+ let item = items[i];
+ var source = item.isRegularItem() ? false : item.parentItemID;
+ yield item.loadCollections();
+ // Top-level item
+ if (source) {
+ item.parentID = false;
+ item.addToCollection(collectionTreeRow.ref.id);
+ yield item.save();
+ }
+ else {
+ item.addToCollection(collectionTreeRow.ref.id);
+ yield item.save();
+ }
+ toMove.push(item.id);
}
- itemGroup.ref.addItem(item.id);
- toMove.push(item.id);
- }
+ });
}
}
@@ -3081,18 +2964,17 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
if (!sameLibrary) {
throw new Error("Cannot move items between libraries");
}
- let targetItemGroup = Zotero.DragDrop.getDragSource();
- if (!targetItemGroup || !targetItemGroup.isCollection()) {
+ if (!sourceCollectionTreeRow || !sourceCollectionTreeRow.isCollection()) {
throw new Error("Drag source must be a collection");
}
- if (itemGroup.id != targetItemGroup.id) {
- itemGroup.ref.removeItems(toMove);
+ if (collectionTreeRow.id != sourceCollectionTreeRow.id) {
+ yield collectionTreeRow.ref.removeItems(toMove);
}
}
}
else if (dataType == 'text/x-moz-url' || dataType == 'application/x-moz-file') {
// Disallow drop into read-only libraries
- if (!itemGroup.editable) {
+ if (!collectionTreeRow.editable) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
@@ -3100,17 +2982,17 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
return;
}
- var targetLibraryID = itemGroup.ref.libraryID;
+ var targetLibraryID = collectionTreeRow.ref.libraryID;
- var sourceItemID = false;
+ var parentItemID = false;
var parentCollectionID = false;
- var treerow = this._getItemAtRow(row);
+ var treerow = this.getRow(row);
if (orient == 0) {
- sourceItemID = treerow.ref.id
+ parentItemID = treerow.ref.id
}
- else if (itemGroup.isCollection()) {
- var parentCollectionID = itemGroup.ref.id;
+ else if (collectionTreeRow.isCollection()) {
+ var parentCollectionID = collectionTreeRow.ref.id;
}
var unlock = Zotero.Notifier.begin(true);
@@ -3142,15 +3024,15 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
// Still string, so remote URL
if (typeof file == 'string') {
- if (sourceItemID) {
- if (!itemGroup.filesEditable) {
+ if (parentItemID) {
+ if (!collectionTreeRow.filesEditable) {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
win.ZoteroPane.displayCannotEditLibraryFilesMessage();
return;
}
- Zotero.Attachments.importFromURL(url, sourceItemID, false, false, null, null, targetLibraryID);
+ Zotero.Attachments.importFromURL(url, parentItemID, false, false, null, null, targetLibraryID);
}
else {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
@@ -3164,13 +3046,12 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
// Otherwise file, so fall through
}
- try {
- Zotero.DB.beginTransaction();
+ yield Zotero.DB.executeTransaction(function () {
if (dropEffect == 'link') {
- var itemID = Zotero.Attachments.linkFromFile(file, sourceItemID);
+ var itemID = Zotero.Attachments.linkFromFile(file, parentItemID);
}
else {
- var itemID = Zotero.Attachments.importFromFile(file, sourceItemID, targetLibraryID);
+ var itemID = Zotero.Attachments.importFromFile(file, parentItemID, targetLibraryID);
// If moving, delete original file
if (dragData.dropEffect == 'move') {
try {
@@ -3182,24 +3063,19 @@ Zotero.ItemTreeView.prototype.drop = function(row, orient, dataTransfer) {
}
}
if (parentCollectionID) {
- var col = Zotero.Collections.get(parentCollectionID);
+ var col = yield Zotero.Collections.getAsync(parentCollectionID);
if (col) {
- col.addItem(itemID);
+ yield col.addItem(itemID);
}
}
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
+ });
}
}
finally {
Zotero.Notifier.commit(unlock);
}
}
-}
+});
////////////////////////////////////////////////////////////////////////////////
@@ -3212,7 +3088,7 @@ Zotero.ItemTreeView.prototype.isSeparator = function(row) { return false;
Zotero.ItemTreeView.prototype.getRowProperties = function(row, prop) {}
Zotero.ItemTreeView.prototype.getColumnProperties = function(col, prop) {}
Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
- var treeRow = this._getItemAtRow(row);
+ var treeRow = this.getRow(row);
var itemID = treeRow.ref.id;
var props = [];
@@ -3272,7 +3148,7 @@ Zotero.ItemTreeView.prototype.getCellProperties = function(row, col, prop) {
return props.join(" ");
}
-Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)
+Zotero.ItemTreeRow = function(ref, level, isOpen)
{
this.ref = ref; //the item associated with this
this.level = level;
@@ -3280,12 +3156,12 @@ Zotero.ItemTreeView.TreeRow = function(ref, level, isOpen)
this.id = ref.id;
}
-Zotero.ItemTreeView.TreeRow.prototype.getField = function(field, unformatted)
+Zotero.ItemTreeRow.prototype.getField = function(field, unformatted)
{
return this.ref.getField(field, unformatted, true);
}
-Zotero.ItemTreeView.TreeRow.prototype.numNotes = function() {
+Zotero.ItemTreeRow.prototype.numNotes = function() {
if (this.ref.isNote()) {
return '';
}
diff --git a/chrome/content/zotero/xpcom/libraryTreeView.js b/chrome/content/zotero/xpcom/libraryTreeView.js
@@ -45,7 +45,7 @@ Zotero.LibraryTreeView.prototype = {
* Called by HTML 5 Drag and Drop when dragging over the tree
*/
onDragEnter: function (event) {
- Zotero.DragDrop.currentDragEvent = event;
+ Zotero.DragDrop.currentEvent = event;
return false;
},
@@ -60,7 +60,7 @@ Zotero.LibraryTreeView.prototype = {
// Prevent modifier keys from doing their normal things
event.preventDefault();
- Zotero.DragDrop.currentDragEvent = event;
+ Zotero.DragDrop.currentEvent = event;
var target = event.target;
if (target.tagName != 'treechildren') {
@@ -83,28 +83,25 @@ Zotero.LibraryTreeView.prototype = {
return;
}
- if (event.dataTransfer.getData("zotero/collection")) {
- this._setDropEffect(event, "move");
- }
- else if (event.dataTransfer.getData("zotero/item")) {
- var sourceItemGroup = Zotero.DragDrop.getDragSource();
- if (sourceItemGroup) {
+ if (event.dataTransfer.getData("zotero/item")) {
+ var sourceCollectionTreeRow = Zotero.DragDrop.getDragSource();
+ if (sourceCollectionTreeRow) {
if (this.type == 'collection') {
- var targetItemGroup = Zotero.DragDrop.getDragTarget();
+ var targetCollectionTreeRow = Zotero.DragDrop.getDragTarget();
}
else if (this.type == 'item') {
- var targetItemGroup = this.itemGroup;
+ var targetCollectionTreeRow = this.collectionTreeRow;
}
else {
throw new Error("Invalid type '" + this.type + "'");
}
- if (!targetItemGroup) {
+ if (!targetCollectionTreeRow) {
this._setDropEffect(event, "none");
return false;
}
- if (sourceItemGroup.id == targetItemGroup.id) {
+ if (sourceCollectionTreeRow.id == targetCollectionTreeRow.id) {
// Ignore drag into the same collection
if (this.type == 'collection') {
this._setDropEffect(event, "none");
@@ -116,12 +113,12 @@ Zotero.LibraryTreeView.prototype = {
return false;
}
// If the source isn't a collection, the action has to be a copy
- if (!sourceItemGroup.isCollection()) {
+ if (!sourceCollectionTreeRow.isCollection()) {
this._setDropEffect(event, "copy");
return false;
}
// For now, all cross-library drags are copies
- if (sourceItemGroup.ref.libraryID != targetItemGroup.ref.libraryID) {
+ if (sourceCollectionTreeRow.ref.libraryID != targetCollectionTreeRow.ref.libraryID) {
this._setDropEffect(event, "copy");
return false;
}
@@ -172,7 +169,7 @@ Zotero.LibraryTreeView.prototype = {
// See note above
if (event.dataTransfer.types.contains("application/x-moz-file")) {
if (Zotero.isMac) {
- Zotero.DragDrop.currentDragEvent = event;
+ Zotero.DragDrop.currentEvent = event;
if (event.metaKey) {
if (event.altKey) {
event.dataTransfer.dropEffect = 'link';
@@ -192,7 +189,7 @@ Zotero.LibraryTreeView.prototype = {
onDragExit: function (event) {
//Zotero.debug("Clearing drag data");
- Zotero.DragDrop.currentDragEvent = null;
+ Zotero.DragDrop.currentEvent = null;
},
diff --git a/chrome/content/zotero/xpcom/mime.js b/chrome/content/zotero/xpcom/mime.js
@@ -311,16 +311,22 @@ Zotero.MIME = new function(){
* Try to determine the MIME type of the file, using a few different
* techniques
*/
- this.getMIMETypeFromFile = function (file) {
- var str = Zotero.File.getSample(file);
+ this.getMIMETypeFromFile = Zotero.Promise.coroutine(function* (file) {
+ var str = yield Zotero.File.getSample(file);
var ext = Zotero.File.getExtension(file);
return this.getMIMETypeFromData(str, ext);
- }
+ });
- this.getMIMETypeFromURL = function (url, callback, cookieSandbox) {
- Zotero.HTTP.doHead(url, function(xmlhttp) {
+ /**
+ * @param {String} url
+ * @param {Zotero.CookieSandbox} [cookieSandbox]
+ * @return {Promise}
+ */
+ this.getMIMETypeFromURL = function (url, cookieSandbox) {
+ return Zotero.HTTP.promise("HEAD", url, { cookieSandbox: cookieSandbox, successCodes: false })
+ .then(function (xmlhttp) {
if (xmlhttp.status != 200 && xmlhttp.status != 204) {
Zotero.debug("Attachment HEAD request returned with status code "
+ xmlhttp.status + " in Zotero.MIME.getMIMETypeFromURL()", 2);
@@ -331,7 +337,7 @@ Zotero.MIME = new function(){
}
var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"]
- .createInstance(Components.interfaces.nsIURL);
+ .createInstance(Components.interfaces.nsIURL);
nsIURL.spec = url;
// Override MIME type to application/pdf if extension is .pdf --
@@ -344,10 +350,10 @@ Zotero.MIME = new function(){
}
var ext = nsIURL.fileExtension;
- var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext)
+ var hasNativeHandler = Zotero.MIME.hasNativeHandler(mimeType, ext);
- callback(mimeType, hasNativeHandler);
- }, undefined, cookieSandbox);
+ return [mimeType, hasNativeHandler];
+ });
}
@@ -407,11 +413,11 @@ Zotero.MIME = new function(){
}
- this.fileHasInternalHandler = function (file){
- var mimeType = this.getMIMETypeFromFile(file);
+ this.fileHasInternalHandler = Zotero.Promise.coroutine(function* (file){
+ var mimeType = yield this.getMIMETypeFromFile(file);
var ext = Zotero.File.getExtension(file);
return hasInternalHandler(mimeType, ext);
- }
+ });
/*
diff --git a/chrome/content/zotero/xpcom/mimeTypeHandler.js b/chrome/content/zotero/xpcom/mimeTypeHandler.js
@@ -302,8 +302,8 @@ Zotero.MIMETypeHandler = new function () {
inputStream.close();
var me = this;
- Q(_typeHandlers[this._contentType](readString, (this._request.name ? this._request.name : null),
- this._contentType, channel)).fail(function(e) {
+ Zotero.Promise.resolve(_typeHandlers[this._contentType](readString, (this._request.name ? this._request.name : null),
+ this._contentType, channel)).catch(function(e) {
// if there was an error, handle using nsIExternalHelperAppService
var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].
getService(Components.interfaces.nsIExternalHelperAppService);
diff --git a/chrome/content/zotero/xpcom/notifier.js b/chrome/content/zotero/xpcom/notifier.js
@@ -27,7 +27,7 @@ Zotero.Notifier = new function(){
var _observers = {};
var _disabled = false;
var _types = [
- 'collection', 'creator', 'search', 'share', 'share-items', 'item', 'file',
+ 'collection', 'search', 'share', 'share-items', 'item', 'file',
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash', 'bucket', 'relation'
];
var _inTransaction;
@@ -36,17 +36,15 @@ Zotero.Notifier = new function(){
this.registerObserver = registerObserver;
this.unregisterObserver = unregisterObserver;
- this.trigger = trigger;
this.untrigger = untrigger;
this.begin = begin;
- this.commit = commit;
this.reset = reset;
this.disable = disable;
this.enable = enable;
this.isEnabled = isEnabled;
- function registerObserver(ref, types){
+ function registerObserver(ref, types, id) {
if (types){
types = Zotero.flattenArguments(types);
@@ -66,7 +64,7 @@ Zotero.Notifier = new function(){
tries = 10;
}
- var hash = Zotero.randomString(len);
+ var hash = (id ? id + '_' : '') + Zotero.randomString(len);
tries--;
}
while (_observers[hash]);
@@ -100,7 +98,7 @@ Zotero.Notifier = new function(){
*
* - New events and types should be added to the order arrays in commit()
**/
- function trigger(event, type, ids, extraData, force){
+ this.trigger = Zotero.Promise.coroutine(function* (event, type, ids, extraData, force) {
if (_disabled){
Zotero.debug("Notifications are disabled");
return false;
@@ -114,8 +112,12 @@ Zotero.Notifier = new function(){
var queue = _inTransaction && !force;
- Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', " + '[' + ids.join() + '])'
+ Zotero.debug("Notifier.trigger('" + event + "', '" + type + "', " + '[' + ids.join() + '], ' + extraData + ')'
+ (queue ? " queued" : " called " + "[observers: " + Object.keys(_observers).length + "]"));
+ if (extraData) {
+ Zotero.debug("EXTRA DATA:");
+ Zotero.debug(extraData);
+ }
// Merge with existing queue
if (queue) {
@@ -135,18 +137,22 @@ Zotero.Notifier = new function(){
// Merge extraData keys
if (extraData) {
+ Zotero.debug("ADDING EXTRA DATA");
for (var dataID in extraData) {
+ Zotero.debug(dataID);
if (extraData[dataID]) {
+ Zotero.debug("YES");
_queue[type][event].data[dataID] = extraData[dataID];
}
}
}
+ Zotero.debug(_queue[type][event]);
return true;
}
for (var i in _observers){
- Zotero.debug("Calling notify('" + event + "') on observer with hash '" + i + "'", 4);
+ Zotero.debug("Calling notify() with " + event + "/" + type + " on observer with hash '" + i + "'", 4);
if (!_observers[i]) {
Zotero.debug("Observer no longer exists");
@@ -158,7 +164,7 @@ Zotero.Notifier = new function(){
// Catch exceptions so all observers get notified even if
// one throws an error
try {
- _observers[i].ref.notify(event, type, ids, extraData);
+ yield Zotero.Promise.resolve(_observers[i].ref.notify(event, type, ids, extraData));
}
catch (e) {
Zotero.debug(e);
@@ -168,7 +174,7 @@ Zotero.Notifier = new function(){
}
return true;
- }
+ });
function untrigger(event, type, ids) {
@@ -226,7 +232,7 @@ Zotero.Notifier = new function(){
*
* If the queue is locked, notifications will only run if _unlock_ is true
*/
- function commit(unlock) {
+ this.commit = Zotero.Promise.coroutine(function* (unlock) {
// If there's a lock on the event queue and _unlock_ isn't given, don't commit
if ((unlock == undefined && _locked) || (unlock != undefined && !unlock)) {
//Zotero.debug("Keeping Notifier event queue open", 4);
@@ -253,29 +259,25 @@ Zotero.Notifier = new function(){
for (var event in _queue[type]) {
runQueue[type][event] = {
ids: [],
- data: {}
+ data: _queue[type][event].data
};
// Remove redundant ids
for (var i=0; i<_queue[type][event].ids.length; i++) {
var id = _queue[type][event].ids[i];
- var data = _queue[type][event].data[id];
// Don't send modify on nonexistent items or tags
if (event == 'modify') {
- if (type == 'item' && !Zotero.Items.get(id)) {
+ if (type == 'item' && !(yield Zotero.Items.getAsync(id))) {
continue;
}
- else if (type == 'tag' && !Zotero.Tags.get(id)) {
+ else if (type == 'tag' && !(yield Zotero.Tags.getAsync(id))) {
continue;
}
}
if (runQueue[type][event].ids.indexOf(id) == -1) {
runQueue[type][event].ids.push(id);
- if (data) {
- runQueue[type][event].data[id] = data;
- }
}
}
@@ -293,13 +295,18 @@ Zotero.Notifier = new function(){
for (var type in runQueue) {
for (var event in runQueue[type]) {
if (runQueue[type][event].ids.length || event == 'refresh') {
- trigger(event, type, runQueue[type][event].ids,
- runQueue[type][event].data, true);
+ yield this.trigger(
+ event,
+ type,
+ runQueue[type][event].ids,
+ runQueue[type][event].data,
+ true
+ );
}
}
}
}
- }
+ });
/*
diff --git a/chrome/content/zotero/xpcom/quickCopy.js b/chrome/content/zotero/xpcom/quickCopy.js
@@ -27,7 +27,6 @@
Zotero.QuickCopy = new function() {
this.getContentType = getContentType;
this.stripContentType = stripContentType;
- this.getFormatFromURL = getFormatFromURL;
this.getContentFromItems = getContentFromItems;
var _formattedNames = {};
@@ -96,7 +95,7 @@ Zotero.QuickCopy = new function() {
}
- function getFormatFromURL(url) {
+ this.getFormatFromURL = Zotero.Promise.coroutine(function* (url) {
if (!url) {
return Zotero.Prefs.get("export.quickCopy.setting");
}
@@ -118,7 +117,7 @@ Zotero.QuickCopy = new function() {
var sql = "SELECT key AS domainPath, value AS format FROM settings "
+ "WHERE setting='quickCopySite' AND (key LIKE ? OR key LIKE ?)";
var urlDomain = urlHostPort.match(/[^\.]+\.[^\.]+$/);
- var rows = Zotero.DB.query(sql, ['%' + urlDomain + '%', '/%']);
+ var rows = yield Zotero.DB.queryAsync(sql, ['%' + urlDomain + '%', '/%']);
for each(var row in rows) {
var [domain, path] = row.domainPath.split(/\//);
path = '/' + (path ? path : '');
@@ -157,7 +156,7 @@ Zotero.QuickCopy = new function() {
}
return Zotero.Prefs.get("export.quickCopy.setting");
- }
+ });
/*
diff --git a/chrome/content/zotero/xpcom/report.js b/chrome/content/zotero/xpcom/report.js
@@ -220,7 +220,7 @@ Zotero.Report = new function() {
case 'key':
case 'itemType':
case 'itemID':
- case 'sourceItemID':
+ case 'parentItemID':
case 'title':
case 'firstCreator':
case 'creators':
diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js
@@ -30,6 +30,7 @@ Zotero.Schema = new function(){
var _dbVersions = [];
var _schemaVersions = [];
+ var _maxCompatibility = 1;
var _repositoryTimer;
var _remoteUpdateInProgress = false, _localUpdateInProgress = false;
@@ -40,11 +41,7 @@ Zotero.Schema = new function(){
*/
this.getDBVersion = function (schema) {
if (_dbVersions[schema]){
- return Q(_dbVersions[schema]);
- }
-
- if (!Zotero.DB.tableExists('version')) {
- return Q(false)
+ return Zotero.Promise.resolve(_dbVersions[schema]);
}
var sql = "SELECT version FROM version WHERE schema='" + schema + "'";
@@ -55,6 +52,15 @@ Zotero.Schema = new function(){
_dbVersions[schema] = dbVersion;
}
return dbVersion;
+ })
+ .catch(function (e) {
+ return Zotero.DB.tableExistsAsync('version')
+ .then(function (exists) {
+ if (exists) {
+ throw e;
+ }
+ return false;
+ });
});
}
@@ -63,42 +69,50 @@ Zotero.Schema = new function(){
* Checks if the DB schema exists and is up-to-date, updating if necessary
*/
this.updateSchema = function () {
- return Q.all([this.getDBVersion('userdata'), this.getDBVersion('userdata3')])
- .spread(function (oldDBVersion, dbVersion) {
- if (!oldDBVersion) {
+ // 'userdata' is the last upgrade step run in _migrateUserDataSchema() based on the
+ // version in the schema file. Upgrade steps may or may not break DB compatibility.
+ //
+ // 'compatibility' is incremented manually by upgrade steps in order to break DB
+ // compatibility with older versions.
+ return Zotero.Promise.all([this.getDBVersion('userdata'), this.getDBVersion('compatibility')])
+ .spread(function (userdata, compatibility) {
+ if (!userdata) {
Zotero.debug('Database does not exist -- creating\n');
- return _initializeSchema().thenResolve(true);
+ return _initializeSchema().return(true);
}
- if (oldDBVersion < 76) {
- // TODO: localize
- let msg = "Zotero found a pre–Zotero 2.1 database that cannot be upgraded to "
- + "work with this version of Zotero. To continue, either upgrade your "
- + "database using an earlier version of Zotero or delete your "
- + "Zotero data directory to start with a new database."
+ // We don't handle upgrades from pre-Zotero 2.1 databases
+ if (userdata < 76) {
+ let msg = Zotero.getString('upgrade.nonupgradeableDB1')
+ + "\n\n" + Zotero.getString('upgrade.nonupgradeableDB2', "4.0");
throw new Error(msg);
}
- return _getSchemaSQLVersion('userdata3')
+ if (compatibility > _maxCompatibility) {
+ throw new Error("Database is incompatible this Zotero version "
+ + "(" + compatibility + " > " + _maxCompatibility + ")");
+ }
+
+ return _getSchemaSQLVersion('userdata')
// If upgrading userdata, make backup of database first
.then(function (schemaVersion) {
- if (dbVersion < schemaVersion) {
- return Zotero.DB.backupDatabase(dbVersion);
+ if (userdata < schemaVersion) {
+ return Zotero.DB.backupDatabase(userdata);
}
})
.then(function () {
return Zotero.DB.executeTransaction(function (conn) {
- var up1 = yield _updateSchema('system');
+ var updated = yield _updateSchema('system');
// Update custom tables if they exist so that changes are in
// place before user data migration
if (Zotero.DB.tableExists('customItemTypes')) {
- yield Zotero.Schema.updateCustomTables(up1);
+ yield Zotero.Schema.updateCustomTables(updated);
}
- var up2 = yield _migrateUserDataSchema(dbVersion);
+ updated = yield _migrateUserDataSchema(userdata);
yield _updateSchema('triggers');
- Zotero.DB.asyncResult(up2);
+ Zotero.DB.asyncResult(updated);
})
.then(function (updated) {
// Populate combined tables for custom types and fields
@@ -110,7 +124,7 @@ Zotero.Schema = new function(){
.then(function () {
if (updated) {
// Upgrade seems to have been a success -- delete any previous backups
- var maxPrevious = dbVersion - 1;
+ var maxPrevious = userdata - 1;
var file = Zotero.getZoteroDirectory();
var toDelete = [];
try {
@@ -140,10 +154,15 @@ Zotero.Schema = new function(){
}
// After a delay, start update of bundled files and repo updates
- Zotero.initializationPromise
+ //
+ // **************
+ // TEMP TEMP TEMP
+ // **************
+ //
+ /*Zotero.initializationPromise
.delay(5000)
.then(function () Zotero.Schema.updateBundledFiles(null, false, true))
- .done();
+ .done();*/
return updated;
});
@@ -155,7 +174,8 @@ Zotero.Schema = new function(){
// This is mostly temporary
// TEMP - NSF
- this.importSchema = function (str, uri) {
+ // TODO: async
+ this.importSchema = Zotero.Promise.coroutine(function* (str, uri) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
@@ -277,7 +297,7 @@ Zotero.Schema = new function(){
for each(var search in searches) {
if (search.name == 'Overdue NSF Reviewers') {
var id = search.id;
- Zotero.Searches.erase(id);
+ yield Zotero.Searches.erase(id);
}
}
@@ -285,7 +305,7 @@ Zotero.Schema = new function(){
ps.alert(null, "Zotero Item Type Removed", "The 'NSF Reviewer' item type has been uninstalled.");
}
- }
+ });
function _reloadSchema() {
Zotero.Schema.updateCustomTables()
@@ -314,10 +334,11 @@ Zotero.Schema = new function(){
if (!skipDelete) {
yield Zotero.DB.queryAsync("DELETE FROM itemTypesCombined");
- yield Zotero.DB.queryAsync("DELETE FROM fieldsCombined");
+ yield Zotero.DB.queryAsync("DELETE FROM fieldsCombined WHERE fieldID NOT IN (SELECT fieldID FROM itemData)");
yield Zotero.DB.queryAsync("DELETE FROM itemTypeFieldsCombined");
yield Zotero.DB.queryAsync("DELETE FROM baseFieldMappingsCombined");
}
+
var offset = Zotero.ItemTypes.customIDOffset;
yield Zotero.DB.queryAsync(
"INSERT INTO itemTypesCombined "
@@ -329,7 +350,7 @@ Zotero.Schema = new function(){
+ "SELECT customItemTypeID + " + offset + " AS itemTypeID, typeName, display, 1 AS custom FROM customItemTypes"
);
yield Zotero.DB.queryAsync(
- "INSERT INTO fieldsCombined "
+ "INSERT OR IGNORE INTO fieldsCombined "
+ (
skipSystem
? ""
@@ -372,9 +393,9 @@ Zotero.Schema = new function(){
* @return {Promise}
*/
this.updateBundledFiles = function(mode, skipDeleteUpdate, runRemoteUpdateWhenComplete) {
- if (_localUpdateInProgress) return Q();
+ if (_localUpdateInProgress) return Zotero.Promise.resolve();
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
_localUpdateInProgress = true;
// Get path to addon and then call updateBundledFilesCallback
@@ -389,7 +410,7 @@ Zotero.Schema = new function(){
}
// Asynchronous in Firefox
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(
ZOTERO_CONFIG['GUID'],
@@ -906,7 +927,7 @@ Zotero.Schema = new function(){
* long it's been since the last check
*/
this.updateFromRepository = function (force) {
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
if (force) return true;
if (_remoteUpdateInProgress) {
@@ -1214,12 +1235,12 @@ Zotero.Schema = new function(){
],
// Note/child parents
[
- "SELECT COUNT(*) FROM itemAttachments WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
- "UPDATE itemAttachments SET sourceItemID=NULL WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
+ "SELECT COUNT(*) FROM itemAttachments WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
+ "UPDATE itemAttachments SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
],
[
- "SELECT COUNT(*) FROM itemNotes WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
- "UPDATE itemNotes SET sourceItemID=NULL WHERE sourceItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
+ "SELECT COUNT(*) FROM itemNotes WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
+ "UPDATE itemNotes SET parentItemID=NULL WHERE parentItemID IN (SELECT itemID FROM items WHERE itemTypeID IN (1,14))",
],
// Wrong library tags
@@ -1324,22 +1345,10 @@ Zotero.Schema = new function(){
* @return {Promise:String} A promise for the SQL file's version number
*/
function _getSchemaSQLVersion(schema){
- // TEMP
- if (schema == 'userdata3') {
- schema = 'userdata';
- var newUserdata = true;
- }
return _getSchemaSQL(schema)
.then(function (sql) {
// Fetch the schema version from the first line of the file
var schemaVersion = parseInt(sql.match(/^-- ([0-9]+)/)[1]);
-
- // TEMP: For 'userdata', cap the version at 76
- // For 'userdata3', versions > 76 are allowed.
- if (schema == 'userdata' && !newUserdata) {
- schemaVersion = Math.min(76, schemaVersion);
- }
-
_schemaVersions[schema] = schemaVersion;
return schemaVersion;
});
@@ -1405,22 +1414,17 @@ Zotero.Schema = new function(){
yield _getSchemaSQLVersion('system').then(function (version) {
return _updateDBVersion('system', version);
});
- // TEMP: 77 is for full-text syncing. New users don't need the
- // prompt, so initialize new databases to 77.
- //yield _getSchemaSQLVersion('userdata').then(function (version) {
- // return _updateDBVersion('userdata', version);
- //});
- yield _updateDBVersion('userdata', 77);
- yield _getSchemaSQLVersion('userdata3').then(function (version) {
- return _updateDBVersion('userdata3', version);
+ yield _getSchemaSQLVersion('userdata').then(function (version) {
+ return _updateDBVersion('userdata', version);
});
yield _getSchemaSQLVersion('triggers').then(function (version) {
return _updateDBVersion('triggers', version);
});
+ yield _updateDBVersion('compatibility', _maxCompatibility);
if (!Zotero.Schema.skipDefaultData) {
// Quick Start Guide web page item
- var sql = "INSERT INTO items VALUES(1, 13, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, 'ABCD2345')";
+ var sql = "INSERT INTO items VALUES(1, 13, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0, 'ABCD2345', 0, 0)";
yield Zotero.DB.queryAsync(sql);
var sql = "INSERT INTO itemDataValues VALUES (1, ?)";
yield Zotero.DB.queryAsync(sql, Zotero.getString('install.quickStartGuide'));
@@ -1432,15 +1436,13 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync(sql);
// CHNM as creator
- var sql = "INSERT INTO creatorData VALUES (1, '', 'Center for History and New Media', '', 1, NULL)";
- yield Zotero.DB.queryAsync(sql);
- var sql = "INSERT INTO creators VALUES (1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, 'ABCD2345')";
+ var sql = "INSERT INTO creators VALUES (1, '', 'Center for History and New Media', 1)";
yield Zotero.DB.queryAsync(sql);
var sql = "INSERT INTO itemCreators VALUES (1, 1, 1, 0)";
yield Zotero.DB.queryAsync(sql);
// Welcome note
- var sql = "INSERT INTO items VALUES(2, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL, 'ABCD3456')";
+ var sql = "INSERT INTO items VALUES(2, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0, 'ABCD3456', 0, 0)";
yield Zotero.DB.queryAsync(sql);
var welcomeTitle = Zotero.getString('install.quickStartGuide.message.welcome');
var welcomeMsg = '<div class="zotero-note znv1"><p><strong>' + welcomeTitle + '</strong></p>'
@@ -1480,7 +1482,7 @@ Zotero.Schema = new function(){
function _updateSchema(schema){
- return Q.all([Zotero.Schema.getDBVersion(schema), _getSchemaSQLVersion(schema)])
+ return Zotero.Promise.all([Zotero.Schema.getDBVersion(schema), _getSchemaSQLVersion(schema)])
.spread(function (dbVersion, schemaVersion) {
if (dbVersion == schemaVersion) {
return false;
@@ -1488,7 +1490,7 @@ Zotero.Schema = new function(){
else if (dbVersion < schemaVersion) {
return _getSchemaSQL(schema)
.then(function (sql) {
- return Zotero.DB.queryAsync(sql);
+ return Zotero.DB.executeSQLFile(sql);
})
.then(function () {
return _updateDBVersion(schema, schemaVersion);
@@ -1524,7 +1526,7 @@ Zotero.Schema = new function(){
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
}
- return Q(false);
+ return Zotero.Promise.resolve(false);
}
var currentTime = xmlhttp.responseXML.
@@ -1546,11 +1548,11 @@ Zotero.Schema = new function(){
if (!manual) {
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_CHECK_INTERVAL']);
}
- return Q(true);
+ return Zotero.Promise.resolve(true);
});
}
- return Q.async(function () {
+ return Zotero.spawn(function* () {
try {
for (var i=0, len=translatorUpdates.length; i<len; i++){
yield _translatorXMLToFile(translatorUpdates[i]);
@@ -1569,11 +1571,11 @@ Zotero.Schema = new function(){
if (!manual){
_setRepositoryTimer(ZOTERO_CONFIG['REPOSITORY_RETRY_INTERVAL']);
}
- Q.return(false);
+ return false;
}
- Q.return(true);
- })()
+ return true;
+ })
.then(function (update) {
if (!update) return false;
@@ -1753,17 +1755,12 @@ Zotero.Schema = new function(){
// TODO
//
- // Replace customBaseFieldMappings to fix FK fields/customField -> customFields->customFieldID
// If libraryID set, make sure no relations still use a local user key, and then remove on-error code in sync.js
function _migrateUserDataSchema(fromVersion) {
- return _getSchemaSQLVersion('userdata3')
+ return _getSchemaSQLVersion('userdata')
.then(function (toVersion) {
- if (!fromVersion) {
- fromVersion = 76;
- }
-
- if (fromVersion > toVersion) {
+ if (fromVersion >= toVersion) {
return false;
}
@@ -1774,24 +1771,337 @@ Zotero.Schema = new function(){
//
// Each block performs the changes necessary to move from the
// previous revision to that one.
- for (var i=fromVersion + 1; i<=toVersion; i++) {
- if (i == 77) {
+ for (let i = fromVersion + 1; i <= toVersion; i++) {
+ if (i == 80) {
+ yield _updateDBVersion('compatibility', 1);
+
yield Zotero.DB.queryAsync("CREATE TABLE IF NOT EXISTS syncedSettings (\n setting TEXT NOT NULL,\n libraryID INT NOT NULL,\n value NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n PRIMARY KEY (setting, libraryID)\n)");
yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO syncObjectTypes VALUES (7, 'setting')");
- }
-
- if (i == 78) {
- yield Zotero.DB.queryAsync("CREATE INDEX IF NOT EXISTS creatorData_name ON creatorData(lastName, firstName)");
- }
-
- if (i == 79) {
- yield Zotero.DB.queryAsync("DELETE FROM version WHERE schema='userdata2'");
+ yield Zotero.DB.queryAsync("DELETE FROM version WHERE schema IN ('userdata2', 'userdata3')");
+
+ yield Zotero.DB.queryAsync("INSERT INTO libraries VALUES (0, 'user')");
+ yield Zotero.DB.queryAsync("ALTER TABLE libraries ADD COLUMN version INT NOT NULL DEFAULT 0");
+ yield Zotero.DB.queryAsync("CREATE TABLE syncCache (\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n syncObjectTypeID INT NOT NULL,\n version INT NOT NULL,\n data TEXT,\n PRIMARY KEY (libraryID, key, syncObjectTypeID),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)\n)");
+
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_annotations_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_annotations_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_annotations_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_annotations_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_collections_parentCollectionID_collections_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collections_parentCollectionID_collections_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_collections_parentCollectionID_collections_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collections_collectionID_collections_parentCollectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_collectionItems_collectionID_collections_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collectionItems_collectionID_collections_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_collectionItems_collectionID_collections_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collections_collectionID_collectionItems_collectionID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_collectionItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_collectionItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_collectionItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_collectionItems_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_creators_creatorDataID_creatorData_creatorDataID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creators_creatorDataID_creatorData_creatorDataID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_creators_creatorDataID_creatorData_creatorDataID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creatorData_creatorDataID_creators_creatorDataID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypes_customItemTypeID_customBaseFieldMappings_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_baseFieldID_fields_fieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_baseFieldID_fields_fieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_customFieldID_customFields_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_customFieldID_customFields_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customBaseFieldMappings_customFieldID_customFields_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customFields_customFieldID_customBaseFieldMappings_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypes_customItemTypeID_customItemTypeFields_customItemTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_customItemTypeFields_fieldID_fields_fieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypeFields_fieldID_fields_fieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customItemTypeFields_customFieldID_customFields_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_customItemTypeFields_customFieldID_customFields_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_customFields_customFieldID_customItemTypeFields_customFieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_fulltextItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_fulltextItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_fulltextItems_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_fulltextItemWords_wordID_fulltextWords_wordID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextItemWords_wordID_fulltextWords_wordID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_fulltextItemWords_wordID_fulltextWords_wordID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextWords_wordID_fulltextItemWords_wordID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_fulltextItemWords_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_fulltextItemWords_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_fulltextItemWords_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_fulltextItemWords_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_groups_libraryID_libraries_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_groups_libraryID_libraries_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_groups_libraryID_libraries_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_libraries_libraryID_groups_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_groupItems_createdByUserID_users_userID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_groupItems_createdByUserID_users_userID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_groupItems_createdByUserID_users_userID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_users_userID_groupItems_createdByUserID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_groupItems_lastModifiedByUserID_users_userID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_groupItems_lastModifiedByUserID_users_userID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_groupItems_lastModifiedByUserID_users_userID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_users_userID_groupItems_lastModifiedByUserID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_highlights_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_highlights_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_highlights_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_highlights_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemAttachments_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemAttachments_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemAttachments_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemAttachments_sourceItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemAttachments_sourceItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemAttachments_sourceItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemAttachments_sourceItemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemCreators_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemCreators_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_creatorID_creators_creatorID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_creatorID_creators_creatorID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemCreators_creatorID_creators_creatorID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creators_creatorID_itemCreators_creatorID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_creatorTypes_creatorTypeID_itemCreators_creatorTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemCreators_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemCreators_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemData_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemData_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemData_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemData_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemData_fieldID_fields_fieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemData_fieldID_fields_fieldID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemData_valueID_itemDataValues_valueID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemData_valueID_itemDataValues_valueID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemData_valueID_itemDataValues_valueID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemDataValues_valueID_itemData_valueID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemNotes_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemNotes_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemNotes_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemNotes_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemNotes_sourceItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemNotes_sourceItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemNotes_sourceItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemNotes_sourceItemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_items_libraryID_libraries_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_libraryID_libraries_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_items_libraryID_libraries_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_libraries_libraryID_items_libraryID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemSeeAlso_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemSeeAlso_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemSeeAlso_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemSeeAlso_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemSeeAlso_linkedItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemSeeAlso_linkedItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemSeeAlso_linkedItemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_itemSeeAlso_linkedItemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemTags_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemTags_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemTags_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_items_itemID_itemTags_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_itemTags_tagID_tags_tagID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_itemTags_tagID_tags_tagID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_itemTags_tagID_tags_tagID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_tags_tagID_itemTags_tagID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_savedSearches_savedSearchID_savedSearchConditions_savedSearchID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_deletedItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_deletedItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_deletedItems_itemID_items_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_items_itemID_deletedItems_itemID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_syncObjectTypes_syncObjectTypeID_syncDeleteLog_syncObjectTypeID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fki_proxyHosts_proxyID_proxies_proxyID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_proxyHosts_proxyID_proxies_proxyID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fkd_proxyHosts_proxyID_proxies_proxyID");
+ yield Zotero.DB.queryAsync("DROP TRIGGER IF EXISTS fku_proxies_proxyID_proxyHosts_proxyID");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE collections RENAME TO collectionsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE collections (\n collectionID INTEGER PRIMARY KEY,\n collectionName TEXT NOT NULL,\n parentCollectionID INT DEFAULT NULL,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n UNIQUE (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,\n FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO collections SELECT collectionID, collectionName, parentCollectionID, clientDateModified, IFNULL(libraryID, 0), key, 0, 0 FROM collectionsOld ORDER BY collectionID DESC");
+ yield Zotero.DB.queryAsync("CREATE INDEX collections_synced ON collections(synced)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE items RENAME TO itemsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE items (\n itemID INTEGER PRIMARY KEY,\n itemTypeID INT NOT NULL,\n dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n UNIQUE (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO items SELECT itemID, itemTypeID, dateAdded, dateModified, clientDateModified, IFNULL(libraryID, 0), key, 0, 0 FROM itemsOld ORDER BY dateAdded DESC");
+ yield Zotero.DB.queryAsync("CREATE INDEX items_synced ON items(synced)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE creators RENAME TO creatorsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE creators (\n creatorID INTEGER PRIMARY KEY,\n firstName TEXT,\n lastName TEXT,\n fieldMode INT,\n UNIQUE (lastName, firstName, fieldMode)\n)");
+ yield Zotero.DB.queryAsync("INSERT INTO creators SELECT creatorDataID, firstName, lastName, fieldMode FROM creatorData");
+ yield Zotero.DB.queryAsync("ALTER TABLE itemCreators RENAME TO itemCreatorsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE itemCreators (\n itemID INT NOT NULL,\n creatorID INT NOT NULL,\n creatorTypeID INT NOT NULL DEFAULT 1,\n orderIndex INT NOT NULL DEFAULT 0,\n PRIMARY KEY (itemID, creatorID, creatorTypeID, orderIndex),\n UNIQUE (itemID, orderIndex),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (creatorID) REFERENCES creators(creatorID) ON DELETE CASCADE,\n FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)\n)");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemCreators_creatorTypeID ON itemCreators(creatorTypeID)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemCreators SELECT itemID, C.creatorID, creatorTypeID, orderIndex FROM itemCreatorsOld ICO JOIN creatorsOld CO USING (creatorID) JOIN creators C ON (CO.creatorDataID=C.creatorID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE savedSearches RENAME TO savedSearchesOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE savedSearches (\n savedSearchID INTEGER PRIMARY KEY,\n savedSearchName TEXT NOT NULL,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n UNIQUE (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO savedSearches SELECT savedSearchID, savedSearchName, clientDateModified, IFNULL(libraryID, 0), key, 0, 0 FROM savedSearchesOld ORDER BY savedSearchID DESC");
+ yield Zotero.DB.queryAsync("CREATE INDEX savedSearches_synced ON savedSearches(synced)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE tags RENAME TO tagsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE tags (\n tagID INTEGER PRIMARY KEY,\n libraryID INT NOT NULL,\n name TEXT NOT NULL,\n UNIQUE (libraryID, name)\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO tags SELECT tagID, IFNULL(libraryID, 0), name FROM tagsOld");
+ yield Zotero.DB.queryAsync("ALTER TABLE itemTags RENAME TO itemTagsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE itemTags (\n itemID INT NOT NULL,\n tagID INT NOT NULL,\n type INT NOT NULL,\n PRIMARY KEY (itemID, tagID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (tagID) REFERENCES tags(tagID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemTags SELECT itemID, T.tagID, TOld.type FROM itemTagsOld ITO JOIN tagsOld TOld USING (tagID) JOIN tags T ON (IFNULL(TOld.libraryID, 0)=T.libraryID AND TOld.name=T.name COLLATE BINARY)");
+ yield Zotero.DB.queryAsync("DROP INDEX itemTags_tagID");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemTags_tagID ON itemTags(tagID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE syncedSettings RENAME TO syncedSettingsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE syncedSettings (\n setting TEXT NOT NULL,\n libraryID INT NOT NULL,\n value NOT NULL,\n version INT NOT NULL DEFAULT 0,\n synced INT NOT NULL DEFAULT 0,\n PRIMARY KEY (setting, libraryID),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO syncedSettings SELECT * FROM syncedSettingsOld");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE itemData RENAME TO itemDataOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE itemData (\n itemID INT,\n fieldID INT,\n valueID,\n PRIMARY KEY (itemID, fieldID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (fieldID) REFERENCES fieldsCombined(fieldID),\n FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemData SELECT * FROM itemDataOld");
+ yield Zotero.DB.queryAsync("DROP INDEX itemData_fieldID");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemData_fieldID ON itemData(fieldID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE itemNotes RENAME TO itemNotesOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE itemNotes (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT,\n note TEXT,\n title TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemNotes SELECT * FROM itemNotesOld");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemNotes_parentItemID ON itemNotes(parentItemID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE itemAttachments RENAME TO itemAttachmentsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE itemAttachments (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT,\n linkMode INT,\n contentType TEXT,\n charsetID INT,\n path TEXT,\n originalPath TEXT,\n syncState INT DEFAULT 0,\n storageModTime INT,\n storageHash TEXT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemAttachments SELECT * FROM itemAttachmentsOld");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_parentItemID ON itemAttachments(parentItemID)");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_contentType ON itemAttachments(contentType)");
+ yield Zotero.DB.queryAsync("DROP INDEX itemAttachments_syncState");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE itemSeeAlso RENAME TO itemSeeAlsoOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE itemSeeAlso (\n itemID INT NOT NULL,\n linkedItemID INT NOT NULL,\n PRIMARY KEY (itemID, linkedItemID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (linkedItemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO itemSeeAlso SELECT * FROM itemSeeAlsoOld");
+ yield Zotero.DB.queryAsync("DROP INDEX itemSeeAlso_linkedItemID");
+ yield Zotero.DB.queryAsync("CREATE INDEX itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE collectionItems RENAME TO collectionItemsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE collectionItems (\n collectionID INT NOT NULL,\n itemID INT NOT NULL,\n orderIndex INT NOT NULL DEFAULT 0,\n PRIMARY KEY (collectionID, itemID),\n FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO collectionItems SELECT * FROM collectionItemsOld");
+ yield Zotero.DB.queryAsync("DROP INDEX IF EXISTS itemID"); // incorrect old name
+ yield Zotero.DB.queryAsync("CREATE INDEX collectionItems_itemID ON collectionItems(itemID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE savedSearchConditions RENAME TO savedSearchConditionsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE savedSearchConditions (\n savedSearchID INT NOT NULL,\n searchConditionID INT NOT NULL,\n condition TEXT NOT NULL,\n operator TEXT,\n value TEXT,\n required NONE,\n PRIMARY KEY (savedSearchID, searchConditionID),\n FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO savedSearchConditions SELECT * FROM savedSearchConditionsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE savedSearchConditionsOld");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE deletedItems RENAME TO deletedItemsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE deletedItems (\n itemID INTEGER PRIMARY KEY,\n dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO deletedItems SELECT * FROM deletedItemsOld");
+ yield Zotero.DB.queryAsync("DROP INDEX deletedItems_dateDeleted");
+ yield Zotero.DB.queryAsync("CREATE INDEX deletedItems_dateDeleted ON deletedItems(dateDeleted)");
+
+ yield Zotero.DB.queryAsync("UPDATE relations SET libraryID=0 WHERE libraryID=(SELECT value FROM settings WHERE setting='account' AND key='libraryID')");
+ yield Zotero.DB.queryAsync("ALTER TABLE relations RENAME TO relationsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE relations (\n libraryID INT NOT NULL,\n subject TEXT NOT NULL,\n predicate TEXT NOT NULL,\n object TEXT NOT NULL,\n clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (subject, predicate, object),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO relations SELECT * FROM relationsOld");
+ yield Zotero.DB.queryAsync("DROP INDEX relations_object");
+ yield Zotero.DB.queryAsync("CREATE INDEX relations_object ON relations(object)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE groups RENAME TO groupsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE groups (\n groupID INTEGER PRIMARY KEY,\n libraryID INT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n description TEXT NOT NULL,\n editable INT NOT NULL,\n filesEditable INT NOT NULL,\n etag TEXT NOT NULL DEFAULT '',\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO groups SELECT groupID, libraryID, name, description, editable, filesEditable, '' FROM groupsOld");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE groupItems RENAME TO groupItemsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE groupItems (\n itemID INTEGER PRIMARY KEY,\n createdByUserID INT,\n lastModifiedByUserID INT,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (createdByUserID) REFERENCES users(userID) ON DELETE SET NULL,\n FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID) ON DELETE SET NULL\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO groupItems SELECT * FROM groupItemsOld");
+
+ let cols = yield Zotero.DB.getColumnsAsync('fulltextItems');
+ if (cols.indexOf("synced") == -1) {
+ Zotero.DB.queryAsync("ALTER TABLE fulltextItems ADD COLUMN synced INT DEFAULT 0");
+ Zotero.DB.queryAsync("REPLACE INTO settings (setting, key, value) VALUES ('fulltext', 'downloadAll', 1)");
+ }
+ yield Zotero.DB.queryAsync("ALTER TABLE fulltextItems RENAME TO fulltextItemsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE fulltextItems (\n itemID INTEGER PRIMARY KEY,\n version INT,\n indexedPages INT,\n totalPages INT,\n indexedChars INT,\n totalChars INT,\n synced INT DEFAULT 0,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO fulltextItems SELECT * FROM fulltextItemsOld");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE fulltextItemWords RENAME TO fulltextItemWordsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE fulltextItemWords (\n wordID INT,\n itemID INT,\n PRIMARY KEY (wordID, itemID),\n FOREIGN KEY (wordID) REFERENCES fulltextWords(wordID),\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO fulltextItemWords SELECT * FROM fulltextItemWordsOld");
+
+ yield Zotero.DB.queryAsync("DROP INDEX fulltextItems_version");
+ yield Zotero.DB.queryAsync("DROP INDEX fulltextItemWords_itemID");
+ yield Zotero.DB.queryAsync("CREATE INDEX fulltextItems_version ON fulltextItems(version)");
+ yield Zotero.DB.queryAsync("CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID)");
+
+ yield Zotero.DB.queryAsync("UPDATE syncDeleteLog SET libraryID=0 WHERE libraryID=(SELECT value FROM settings WHERE setting='account' AND key='libraryID')");
+ yield Zotero.DB.queryAsync("ALTER TABLE syncDeleteLog RENAME TO syncDeleteLogOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE syncDeleteLog (\n syncObjectTypeID INT NOT NULL,\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n UNIQUE (syncObjectTypeID, libraryID, key),\n FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO syncDeleteLog SELECT * FROM syncDeleteLogOld");
+ yield Zotero.DB.queryAsync("DROP INDEX syncDeleteLog_timestamp");
+ yield Zotero.DB.queryAsync("CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE storageDeleteLog RENAME TO storageDeleteLogOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE storageDeleteLog (\n libraryID INT NOT NULL,\n key TEXT NOT NULL,\n timestamp INT NOT NULL,\n PRIMARY KEY (libraryID, key),\n FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO storageDeleteLog SELECT * FROM storageDeleteLogOld");
+ yield Zotero.DB.queryAsync("DROP INDEX storageDeleteLog_timestamp");
+ yield Zotero.DB.queryAsync("CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE annotations RENAME TO annotationsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE annotations (\n annotationID INTEGER PRIMARY KEY,\n itemID INT NOT NULL,\n parent TEXT,\n textNode INT,\n offset INT,\n x INT,\n y INT,\n cols INT,\n rows INT,\n text TEXT,\n collapsed BOOL,\n dateModified DATE,\n FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO annotations SELECT * FROM annotationsOld");
+ yield Zotero.DB.queryAsync("DROP INDEX annotations_itemID");
+ yield Zotero.DB.queryAsync("CREATE INDEX annotations_itemID ON annotations(itemID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE highlights RENAME TO highlightsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE highlights (\n highlightID INTEGER PRIMARY KEY,\n itemID INT NOT NULL,\n startParent TEXT,\n startTextNode INT,\n startOffset INT,\n endParent TEXT,\n endTextNode INT,\n endOffset INT,\n dateModified DATE,\n FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO highlights SELECT * FROM highlightsOld");
+ yield Zotero.DB.queryAsync("DROP INDEX highlights_itemID");
+ yield Zotero.DB.queryAsync("CREATE INDEX highlights_itemID ON highlights(itemID)");
+
+ yield Zotero.DB.queryAsync("ALTER TABLE customBaseFieldMappings RENAME TO customBaseFieldMappingsOld");
+ yield Zotero.DB.queryAsync("CREATE TABLE customBaseFieldMappings (\n customItemTypeID INT,\n baseFieldID INT,\n customFieldID INT,\n PRIMARY KEY (customItemTypeID, baseFieldID, customFieldID),\n FOREIGN KEY (customItemTypeID) REFERENCES customItemTypes(customItemTypeID),\n FOREIGN KEY (baseFieldID) REFERENCES fields(fieldID),\n FOREIGN KEY (customFieldID) REFERENCES customFields(customFieldID)\n)");
+ yield Zotero.DB.queryAsync("INSERT OR IGNORE INTO customBaseFieldMappings SELECT * FROM customBaseFieldMappingsOld");
+ yield Zotero.DB.queryAsync("DROP INDEX customBaseFieldMappings_baseFieldID");
+ yield Zotero.DB.queryAsync("DROP INDEX customBaseFieldMappings_customFieldID");
+ yield Zotero.DB.queryAsync("CREATE INDEX customBaseFieldMappings_baseFieldID ON customBaseFieldMappings(baseFieldID)");
+ yield Zotero.DB.queryAsync("CREATE INDEX customBaseFieldMappings_customFieldID ON customBaseFieldMappings(customFieldID)");
+
+ yield Zotero.DB.queryAsync("DROP TABLE annotationsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE collectionItemsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE customBaseFieldMappingsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE deletedItemsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE fulltextItemWordsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE fulltextItemsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE groupItemsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE groupsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE highlightsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE itemAttachmentsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE itemCreatorsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE itemDataOld");
+ yield Zotero.DB.queryAsync("DROP TABLE itemNotesOld");
+ yield Zotero.DB.queryAsync("DROP TABLE itemSeeAlsoOld");
+ yield Zotero.DB.queryAsync("DROP TABLE itemTagsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE relationsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE savedSearchesOld");
+ yield Zotero.DB.queryAsync("DROP TABLE storageDeleteLogOld");
+ yield Zotero.DB.queryAsync("DROP TABLE syncDeleteLogOld");
+ yield Zotero.DB.queryAsync("DROP TABLE syncedSettingsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE collectionsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE creatorsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE creatorData");
+ yield Zotero.DB.queryAsync("DROP TABLE itemsOld");
+ yield Zotero.DB.queryAsync("DROP TABLE tagsOld");
}
}
- yield _updateDBVersion('userdata3', toVersion);
+ yield _updateDBVersion('userdata', toVersion);
})
- .then(function () true);
+ .return(true);
})
}
}
diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js
@@ -24,37 +24,25 @@
*/
Zotero.Search = function() {
- if (arguments[0]) {
- throw ("Zotero.Search constructor doesn't take any parameters");
- }
+ var dataTypes = [
+ 'primaryData',
+ 'conditions'
+ ];
+ Zotero.DataObject.apply(this, ['search', 'searches', dataTypes]);
- this._loaded = false;
- this._init();
-}
-
-
-Zotero.Search.prototype._init = function () {
- // Public members for access by public methods -- do not access directly
- this._id = null;
- this._libraryID = null;
- this._key = null;
this._name = null;
- this._dateAdded = null;
- this._dateModified = null;
-
- this._changed = false;
- this._previousData = false;
this._scope = null;
this._scopeIncludeChildren = null;
this._sql = null;
- this._sqlParams = null;
+ this._sqlParams = false;
this._maxSearchConditionID = 0;
this._conditions = [];
this._hasPrimaryConditions = false;
}
-
+Zotero.Search.prototype = Object.create(Zotero.DataObject.prototype);
+Zotero.Search.constructor = Zotero.Search;
Zotero.Search.prototype.getID = function(){
Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.id');
@@ -72,7 +60,6 @@ Zotero.Search.prototype.setName = function(val) {
}
-Zotero.Search.prototype.__defineGetter__('objectType', function () { return 'search'; });
Zotero.Search.prototype.__defineGetter__('id', function () { return this._get('id'); });
Zotero.Search.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
Zotero.Search.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
@@ -81,102 +68,57 @@ Zotero.Search.prototype.__defineGetter__('key', function () { return this._get('
Zotero.Search.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
Zotero.Search.prototype.__defineGetter__('name', function () { return this._get('name'); });
Zotero.Search.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
-Zotero.Search.prototype.__defineGetter__('dateAdded', function () { return this._get('dateAdded'); });
-Zotero.Search.prototype.__defineSetter__('dateAdded', function (val) { this._set('dateAdded', val); });
-Zotero.Search.prototype.__defineGetter__('dateModified', function () { return this._get('dateModified'); });
-Zotero.Search.prototype.__defineSetter__('dateModified', function (val) { this._set('dateModified', val); });
+Zotero.Search.prototype.__defineGetter__('version', function () { return this._get('version'); });
+Zotero.Search.prototype.__defineSetter__('version', function (val) { this._set('version', val); });
+Zotero.Search.prototype.__defineGetter__('synced', function () { return this._get('synced'); });
+Zotero.Search.prototype.__defineSetter__('synced', function (val) { this._set('synced', val); });
Zotero.Search.prototype.__defineGetter__('conditions', function (arr) { this.getSearchConditions(); });
-
-Zotero.Search.prototype._get = function (field) {
- if ((this._id || this._key) && !this._loaded) {
- this.load();
+Zotero.Search.prototype._set = function (field, value) {
+ if (field == 'id' || field == 'libraryID' || field == 'key') {
+ return this._setIdentifier(field, value);
}
- return this['_' + field];
-}
-
-
-Zotero.Search.prototype._set = function (field, val) {
+
switch (field) {
- case 'id':
- case 'libraryID':
- case 'key':
- if (field == 'libraryID') {
- val = Zotero.DataObjectUtilities.checkLibraryID(val);
- }
-
- if (val == this['_' + field]) {
- return;
- }
-
- if (this._loaded) {
- throw new Error("Cannot set " + field + " after object is already loaded");
- }
-
- //this._checkValue(field, val);
-
- this['_' + field] = val;
- return;
-
- case 'name':
- val = Zotero.Utilities.trim(val);
- break;
- }
+ case 'name':
+ value = value.trim();
+ break;
- if (this.id || this.key) {
- if (!this._loaded) {
- this.load();
- }
- }
- else {
- this._loaded = true;
+ case 'version':
+ value = parseInt(value);
+ break;
+
+ case 'synced':
+ value = !!value;
+ break;
}
- if (this['_' + field] != val) {
+ this._requireData('primaryData');
+
+ if (this['_' + field] != value) {
this._prepFieldChange(field);
switch (field) {
default:
- this['_' + field] = val;
+ this['_' + field] = value;
}
}
}
-/**
- * Check if saved search exists in the database
- *
- * @return bool TRUE if the search exists, FALSE if not
- */
-Zotero.Search.prototype.exists = function() {
- if (!this.id) {
- throw ('searchID not set in Zotero.Search.exists()');
- }
-
- var sql = "SELECT COUNT(*) FROM savedSearches WHERE savedSearchID=?";
- return !!Zotero.DB.valueQuery(sql, this.id);
-}
-
-
/*
* Load a saved search from the DB
*/
-Zotero.Search.prototype.load = function() {
- // Changed in 1.5
- if (arguments[0]) {
- throw ('Parameter no longer allowed in Zotero.Search.load()');
- }
+Zotero.Search.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload) {
+ if (this._loaded.primaryData && !reload) return;
var id = this._id;
var key = this._key;
var libraryID = this._libraryID;
var desc = id ? id : libraryID + "/" + key;
- var sql = "SELECT S.*, "
- + "MAX(searchConditionID) AS maxID "
- + "FROM savedSearches S LEFT JOIN savedSearchConditions "
- + "USING (savedSearchID) WHERE ";
+ var sql = "SELECT * FROM savedSearches WHERE ";
if (id) {
sql += "savedSearchID=?";
var params = id;
@@ -185,61 +127,30 @@ Zotero.Search.prototype.load = function() {
sql += "key=? AND libraryID=?";
var params = [key, libraryID];
}
- sql += " GROUP BY savedSearchID";
- var data = Zotero.DB.rowQuery(sql, params);
+ var data = yield Zotero.DB.rowQueryAsync(sql, params);
- this._loaded = true;
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
if (!data) {
return;
}
- this._init();
- this._id = data.savedSearchID;
- this._libraryID = data.libraryID;
- this._key = data.key;
- this._name = data.savedSearchName;
- this._dateAdded = data.dateAdded;
- this._dateModified = data.dateModified;
- this._maxSearchConditionID = data.maxID;
-
- var sql = "SELECT * FROM savedSearchConditions "
- + "WHERE savedSearchID=? ORDER BY searchConditionID";
- var conditions = Zotero.DB.query(sql, this._id);
-
- // Reindex conditions, in case they're not contiguous in the DB
- var conditionID = 1;
-
- for (var i in conditions) {
- // Parse "condition[/mode]"
- var [condition, mode] =
- Zotero.SearchConditions.parseCondition(conditions[i]['condition']);
-
- var cond = Zotero.SearchConditions.get(condition);
- if (!cond || cond.noLoad) {
- Zotero.debug("Invalid saved search condition '" + condition + "' -- skipping", 2);
- continue;
- }
-
- // Convert itemTypeID to itemType
- //
- // TEMP: This can be removed at some point
- if (condition == 'itemTypeID') {
- condition = 'itemType';
- conditions[i].value = Zotero.ItemTypes.getName(conditions[i].value);
- }
-
- this._conditions[conditionID] = {
- id: conditionID,
- condition: condition,
- mode: mode,
- operator: conditions[i]['operator'],
- value: conditions[i]['value'],
- required: conditions[i]['required']
- };
-
- conditionID++;
- }
+ this.loadFromRow(data);
+});
+
+
+Zotero.Search.prototype.loadFromRow = function (row) {
+ this._id = row.savedSearchID;
+ this._libraryID = row.libraryID;
+ this._key = row.key;
+ this._name = row.savedSearchName;
+ this._version = row.version;
+ this._synced = !!row.synced;
+
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
+ this._identified = true;
}
@@ -252,133 +163,148 @@ Zotero.Search.prototype.load = function() {
*
* For new searches, name must be set called before saving
*/
-Zotero.Search.prototype.save = function(fixGaps) {
- Zotero.Searches.editCheck(this);
-
- if (!this.name) {
- throw('Name not provided for saved search');
- }
-
- Zotero.DB.beginTransaction();
-
- var isNew = !this.id || !this.exists();
-
+Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
try {
- var searchID = this.id ? this.id : Zotero.ID.get('savedSearches');
-
- Zotero.debug("Saving " + (isNew ? 'new ' : '') + "search " + this.id);
-
- var key = this.key ? this.key : this._generateKey();
-
- var columns = [
- 'savedSearchID',
- 'savedSearchName',
- 'dateAdded',
- 'dateModified',
- 'clientDateModified',
- 'libraryID',
- 'key'
- ];
- var placeholders = ['?', '?', '?', '?', '?', '?', '?'];
- var sqlValues = [
- searchID ? { int: searchID } : null,
- { string: this.name },
- // If date added isn't set, use current timestamp
- this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
- // If date modified hasn't changed, use current timestamp
- this._changed.dateModified ?
- this.dateModified : Zotero.DB.transactionDateTime,
- Zotero.DB.transactionDateTime,
- this.libraryID ? this.libraryID : 0,
- key
- ];
+ Zotero.Searches.editCheck(this);
- var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") "
- + "VALUES (" + placeholders + ")";
- var insertID = Zotero.DB.query(sql, sqlValues);
- if (!searchID) {
- searchID = insertID;
+ if (!this.name) {
+ throw('Name not provided for saved search');
}
- if (!isNew) {
- var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
- Zotero.DB.query(sql, this.id);
- }
+ var isNew = !this.id;
- // Close gaps in savedSearchIDs
- var saveConditions = {};
- var i = 1;
- for (var id in this._conditions) {
- if (!fixGaps && id != i) {
- Zotero.DB.rollbackTransaction();
- throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
- }
- saveConditions[i] = this._conditions[id];
- i++;
+ // Register this item's identifiers in Zotero.DataObjects on transaction commit,
+ // before other callbacks run
+ var searchID, libraryID, key;
+ if (isNew) {
+ var transactionOptions = {
+ onCommit: function () {
+ Zotero.Searches.registerIdentifiers(searchID, libraryID, key);
+ }
+ };
+ }
+ else {
+ var transactionOptions = null;
}
- this._conditions = saveConditions;
-
- // TODO: use proper bound parameters once DB class is updated
- for (var i in this._conditions){
- var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
- + "searchConditionID, condition, operator, value, required) "
- + "VALUES (?,?,?,?,?,?)";
-
- // Convert condition and mode to "condition[/mode]"
- var condition = this._conditions[i].mode ?
- this._conditions[i].condition + '/' + this._conditions[i].mode :
- this._conditions[i].condition
-
- var sqlParams = [
- searchID, i, condition,
- this._conditions[i].operator
- ? this._conditions[i].operator : null,
- this._conditions[i].value
- ? this._conditions[i].value : null,
- this._conditions[i].required
- ? 1 : null
+ return Zotero.DB.executeTransaction(function* () {
+ searchID = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
+ libraryID = this.libraryID;
+ key = this._key = this.key ? this.key : this._generateKey();
+
+ Zotero.debug("Saving " + (isNew ? 'new ' : '') + "search " + this.id);
+
+ var columns = [
+ 'savedSearchID',
+ 'savedSearchName',
+ 'clientDateModified',
+ 'libraryID',
+ 'key',
+ 'version',
+ 'synced'
+ ];
+ var placeholders = columns.map(function () '?').join();
+ var sqlValues = [
+ searchID ? { int: searchID } : null,
+ { string: this.name },
+ Zotero.DB.transactionDateTime,
+ this.libraryID ? this.libraryID : 0,
+ key,
+ this.version ? this.version : 0,
+ this.synced ? 1 : 0
];
- Zotero.DB.query(sql, sqlParams);
+
+ var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") "
+ + "VALUES (" + placeholders + ")";
+ var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
+ if (!searchID) {
+ searchID = insertID;
+ }
+
+ if (!isNew) {
+ var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
+ Zotero.DB.query(sql, this.id);
+ }
+
+ // Close gaps in savedSearchIDs
+ var saveConditions = {};
+ var i = 1;
+ for (var id in this._conditions) {
+ if (!fixGaps && id != i) {
+ Zotero.DB.rollbackTransaction();
+ throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
+ }
+ saveConditions[i] = this._conditions[id];
+ i++;
+ }
+
+ this._conditions = saveConditions;
+
+ for (var i in this._conditions){
+ var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
+ + "searchConditionID, condition, operator, value, required) "
+ + "VALUES (?,?,?,?,?,?)";
+
+ // Convert condition and mode to "condition[/mode]"
+ var condition = this._conditions[i].mode ?
+ this._conditions[i].condition + '/' + this._conditions[i].mode :
+ this._conditions[i].condition
+
+ var sqlParams = [
+ searchID,
+ i,
+ condition,
+ this._conditions[i].operator ? this._conditions[i].operator : null,
+ this._conditions[i].value ? this._conditions[i].value : null,
+ this._conditions[i].required ? 1 : null
+ ];
+ Zotero.DB.query(sql, sqlParams);
+ }
+
+
+ if (isNew) {
+ Zotero.Notifier.trigger('add', 'search', this.id);
+ }
+ else {
+ Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
+ }
+
+ if (isNew && this.libraryID) {
+ var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
+ var group = Zotero.Groups.get(groupID);
+ group.clearSearchCache();
+ }
+
+ if (isNew) {
+ var id = this.id;
+ this._disabled = true;
+ return id;
+ }
+
+ yield this.reload();
+ this._clearChanged();
+
+ return isNew ? this.id : true;
+ }.bind(this), transactionOptions);
+ }
+ catch (e) {
+ try {
+ yield this.reload();
+ this._clearChanged();
}
-
-
- if (isNew && this.libraryID) {
- var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
- var group = Zotero.Groups.get(groupID);
- group.clearSearchCache();
+ catch (e2) {
+ Zotero.debug(e2, 1);
}
- Zotero.DB.commitTransaction();
- }
- catch (e) {
- Zotero.DB.rollbackTransaction();
- throw (e);
- }
-
- // If successful, set values in object
- if (!this.id) {
- this._id = searchID;
- }
-
- if (!this.key) {
- this._key = key;
+ Zotero.debug(e, 1);
+ throw e;
}
-
- if (isNew) {
- Zotero.Notifier.trigger('add', 'search', this.id);
- }
- else {
- Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
- }
-
- return this._id;
-}
+});
-Zotero.Search.prototype.clone = function() {
+Zotero.Search.prototype.clone = Zotero.Promise.coroutine(function* (libraryID) {
var s = new Zotero.Search();
- s.libraryID = this.libraryID;
+ s.libraryID = libraryID === undefined ? this.libraryID : libraryID;
var conditions = this.getSearchConditions();
@@ -387,18 +313,16 @@ Zotero.Search.prototype.clone = function() {
condition.condition + '/' + condition.mode :
condition.condition
- s.addCondition(name, condition.operator, condition.value,
+ yield s.addCondition(name, condition.operator, condition.value,
condition.required);
}
return s;
-}
+});
-Zotero.Search.prototype.addCondition = function(condition, operator, value, required) {
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+Zotero.Search.prototype.addCondition = Zotero.Promise.coroutine(function* (condition, operator, value, required) {
+ yield this.loadPrimaryData();
if (!Zotero.SearchConditions.hasOperator(condition, operator)){
throw ("Invalid operator '" + operator + "' for condition " + condition);
@@ -409,51 +333,51 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
var parts = Zotero.SearchConditions.parseSearchString(value);
for each(var part in parts) {
- this.addCondition('blockStart');
+ yield this.addCondition('blockStart');
// If search string is 8 characters, see if this is a item key
if (operator == 'contains' && part.text.length == 8) {
- this.addCondition('key', 'is', part.text, false);
+ yield this.addCondition('key', 'is', part.text, false);
}
if (condition == 'quicksearch-titleCreatorYear') {
- this.addCondition('title', operator, part.text, false);
- this.addCondition('publicationTitle', operator, part.text, false);
- this.addCondition('year', operator, part.text, false);
+ yield this.addCondition('title', operator, part.text, false);
+ yield this.addCondition('publicationTitle', operator, part.text, false);
+ yield this.addCondition('year', operator, part.text, false);
}
else {
- this.addCondition('field', operator, part.text, false);
- this.addCondition('tag', operator, part.text, false);
- this.addCondition('note', operator, part.text, false);
+ yield this.addCondition('field', operator, part.text, false);
+ yield this.addCondition('tag', operator, part.text, false);
+ yield this.addCondition('note', operator, part.text, false);
}
- this.addCondition('creator', operator, part.text, false);
+ yield this.addCondition('creator', operator, part.text, false);
if (condition == 'quicksearch-everything') {
- this.addCondition('annotation', operator, part.text, false);
+ yield this.addCondition('annotation', operator, part.text, false);
if (part.inQuotes) {
- this.addCondition('fulltextContent', operator, part.text, false);
+ yield this.addCondition('fulltextContent', operator, part.text, false);
}
else {
var splits = Zotero.Fulltext.semanticSplitter(part.text);
for each(var split in splits) {
- this.addCondition('fulltextWord', operator, split, false);
+ yield this.addCondition('fulltextWord', operator, split, false);
}
}
}
- this.addCondition('blockEnd');
+ yield this.addCondition('blockEnd');
}
if (condition == 'quicksearch-titleCreatorYear') {
- this.addCondition('noChildren', 'true');
+ yield this.addCondition('noChildren', 'true');
}
return false;
}
// Shortcut to add a collection
else if (condition == 'collectionID') {
- var c = Zotero.Collections.get(value);
+ var c = yield Zotero.Collections.getAsync(value);
if (!c) {
var msg = "Collection " + value + " not found";
Zotero.debug(msg, 2);
@@ -461,11 +385,12 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
return;
}
var lkh = Zotero.Collections.getLibraryKeyHash(c);
- return this.addCondition('collection', operator, lkh, required);
+ // TEMP: Bluebird return yield
+ return yield this.addCondition('collection', operator, lkh, required);
}
// Shortcut to add a saved search
else if (condition == 'savedSearchID') {
- var s = Zotero.Searches.get(value);
+ var s = yield Zotero.Searches.getAsync(value);
if (!s) {
var msg = "Saved search " + value + " not found";
Zotero.debug(msg, 2);
@@ -473,12 +398,14 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
return;
}
var lkh = Zotero.Searches.getLibraryKeyHash(s);
- return this.addCondition('savedSearch', operator, lkh, required);
+ // TEMP: Bluebird return yield
+ return yield this.addCondition('savedSearch', operator, lkh, required);
}
var searchConditionID = ++this._maxSearchConditionID;
- var [condition, mode] = Zotero.SearchConditions.parseCondition(condition);
+ let mode;
+ [condition, mode] = Zotero.SearchConditions.parseCondition(condition);
this._conditions[searchConditionID] = {
id: searchConditionID,
@@ -490,9 +417,9 @@ Zotero.Search.prototype.addCondition = function(condition, operator, value, requ
};
this._sql = null;
- this._sqlParams = null;
+ this._sqlParams = false;
return searchConditionID;
-}
+});
/*
@@ -504,17 +431,23 @@ Zotero.Search.prototype.setScope = function (searchObj, includeChildren) {
}
-Zotero.Search.prototype.updateCondition = function(searchConditionID, condition, operator, value, required){
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+/**
+ * @param {Number} searchConditionID
+ * @param {String} condition
+ * @param {String} operator
+ * @param {String} value
+ * @param {Boolean} [required]
+ * @return {Promise}
+ */
+Zotero.Search.prototype.updateCondition = Zotero.Promise.coroutine(function* (searchConditionID, condition, operator, value, required){
+ yield this.loadPrimaryData();
if (typeof this._conditions[searchConditionID] == 'undefined'){
- throw ('Invalid searchConditionID ' + searchConditionID + ' in updateCondition()');
+ throw new Error('Invalid searchConditionID ' + searchConditionID);
}
if (!Zotero.SearchConditions.hasOperator(condition, operator)){
- throw ("Invalid operator '" + operator + "' for condition " + condition);
+ throw new Error("Invalid operator '" + operator + "' for condition " + condition);
}
var [condition, mode] = Zotero.SearchConditions.parseCondition(condition);
@@ -529,21 +462,19 @@ Zotero.Search.prototype.updateCondition = function(searchConditionID, condition,
};
this._sql = null;
- this._sqlParams = null;
-}
+ this._sqlParams = false;
+});
-Zotero.Search.prototype.removeCondition = function(searchConditionID){
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+Zotero.Search.prototype.removeCondition = Zotero.Promise.coroutine(function* (searchConditionID){
+ yield this.loadPrimaryData();
if (typeof this._conditions[searchConditionID] == 'undefined'){
throw ('Invalid searchConditionID ' + searchConditionID + ' in removeCondition()');
}
delete this._conditions[searchConditionID];
-}
+});
/*
@@ -551,9 +482,7 @@ Zotero.Search.prototype.removeCondition = function(searchConditionID){
* for the given searchConditionID
*/
Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+ this._requireData('primaryData');
return this._conditions[searchConditionID];
}
@@ -563,9 +492,8 @@ Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
* used in the search, indexed by searchConditionID
*/
Zotero.Search.prototype.getSearchConditions = function(){
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+ this._requireData('primaryData');
+
var conditions = [];
for (var id in this._conditions) {
var condition = this._conditions[id];
@@ -583,9 +511,7 @@ Zotero.Search.prototype.getSearchConditions = function(){
Zotero.Search.prototype.hasPostSearchFilter = function() {
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+ this._requireData('primaryData');
for each(var i in this._conditions){
if (i.condition == 'fulltextContent'){
return true;
@@ -595,302 +521,307 @@ Zotero.Search.prototype.hasPostSearchFilter = function() {
}
-/*
+/**
* Run the search and return an array of item ids for results
+ *
+ * @param {Boolean} [asTempTable=false]
+ * @return {Promise}
*/
-Zotero.Search.prototype.search = function(asTempTable){
- if ((this.id || this.key) && !this._loaded) {
- this.load();
- }
+Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable) {
+ var tmpTable;
- if (!this._sql){
- this._buildQuery();
- }
-
- // Default to 'all' mode
- var joinMode = 'all';
-
- // Set some variables for conditions to avoid further lookups
- for each(var condition in this._conditions) {
- switch (condition.condition) {
- case 'joinMode':
- if (condition.operator == 'any') {
- joinMode = 'any';
- }
- break;
-
- case 'fulltextContent':
- var fulltextContent = true;
- break;
-
- case 'includeParentsAndChildren':
- if (condition.operator == 'true') {
- var includeParentsAndChildren = true;
+ try {
+ yield this.loadPrimaryData();
+
+ if (!this._sql){
+ yield this._buildQuery();
+ }
+
+ // Default to 'all' mode
+ var joinMode = 'all';
+
+ // Set some variables for conditions to avoid further lookups
+ for each(var condition in this._conditions) {
+ switch (condition.condition) {
+ case 'joinMode':
+ if (condition.operator == 'any') {
+ joinMode = 'any';
+ }
+ break;
+
+ case 'fulltextContent':
+ var fulltextContent = true;
+ break;
+
+ case 'includeParentsAndChildren':
+ if (condition.operator == 'true') {
+ var includeParentsAndChildren = true;
+ }
+ break;
+
+ case 'includeParents':
+ if (condition.operator == 'true') {
+ var includeParents = true;
+ }
+ break;
+
+ case 'includeChildren':
+ if (condition.operator == 'true') {
+ var includeChildren = true;
+ }
+ break;
+
+ case 'blockStart':
+ var hasQuicksearch = true;
+ break;
+ }
+ }
+
+ // Run a subsearch to define the superset of possible results
+ if (this._scope) {
+ // If subsearch has post-search filter, run and insert ids into temp table
+ if (this._scope.hasPostSearchFilter()) {
+ var ids = yield this._scope.search();
+ if (!ids) {
+ return [];
}
- break;
+
+ Zotero.debug('g');
+ Zotero.debug(ids);
+ tmpTable = yield Zotero.Search.idsToTempTable(ids);
+ }
+ // Otherwise, just copy to temp table directly
+ else {
+ tmpTable = "tmpSearchResults_" + Zotero.randomString(8);
+ var sql = "CREATE TEMPORARY TABLE " + tmpTable + " AS "
+ + (yield this._scope.getSQL());
+ yield Zotero.DB.queryAsync(sql, yield this._scope.getSQLParams());
+ var sql = "CREATE INDEX " + tmpTable + "_itemID ON " + tmpTable + "(itemID)";
+ yield Zotero.DB.queryAsync(sql);
+ }
- case 'includeParents':
- if (condition.operator == 'true') {
- var includeParents = true;
- }
- break;
+ // Search ids in temp table
+ var sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE itemID IN (" + this._sql + ") "
+ + "AND ("
+ + "itemID IN (SELECT itemID FROM " + tmpTable + ")";
- case 'includeChildren':
- if (condition.operator == 'true') {
- var includeChildren = true;
- }
- break;
+ if (this._scopeIncludeChildren) {
+ sql += " OR itemID IN (SELECT itemID FROM itemAttachments"
+ + " WHERE parentItemID IN (SELECT itemID FROM " + tmpTable + ")) OR "
+ + "itemID IN (SELECT itemID FROM itemNotes"
+ + " WHERE parentItemID IN (SELECT itemID FROM " + tmpTable + "))";
+ }
+ sql += ")";
- case 'blockStart':
- var hasQuicksearch = true;
- break;
- }
- }
-
- // Run a subsearch to define the superset of possible results
- if (this._scope) {
- Zotero.DB.beginTransaction();
-
- // If subsearch has post-search filter, run and insert ids into temp table
- if (this._scope.hasPostSearchFilter()) {
- var ids = this._scope.search();
+ var res = yield Zotero.DB.valueQueryAsync(sql, this._sqlParams);
+ var ids = res ? res.split(",") : [];
+ /*
+ // DEBUG: Should this be here?
+ //
if (!ids) {
+ Zotero.DB.query("DROP TABLE " + tmpTable);
Zotero.DB.commitTransaction();
return false;
}
-
- var tmpTable = Zotero.Search.idsToTempTable(ids);
+ */
}
- // Otherwise, just copy to temp table directly
+ // Or just run main search
else {
- var tmpTable = "tmpSearchResults_" + Zotero.randomString(8);
- var sql = "CREATE TEMPORARY TABLE " + tmpTable + " AS "
- + this._scope.getSQL();
- Zotero.DB.query(sql, this._scope.getSQLParams());
- var sql = "CREATE INDEX " + tmpTable + "_itemID ON " + tmpTable + "(itemID)";
- Zotero.DB.query(sql);
+ var ids = yield Zotero.DB.columnQueryAsync(this._sql, this._sqlParams);
}
- // Search ids in temp table
- var sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE itemID IN (" + this._sql + ") "
- + "AND ("
- + "itemID IN (SELECT itemID FROM " + tmpTable + ")";
+ //Zotero.debug('IDs from main search or subsearch: ');
+ //Zotero.debug(ids);
- if (this._scopeIncludeChildren) {
- sql += " OR itemID IN (SELECT itemID FROM itemAttachments"
- + " WHERE sourceItemID IN (SELECT itemID FROM " + tmpTable + ")) OR "
- + "itemID IN (SELECT itemID FROM itemNotes"
- + " WHERE sourceItemID IN (SELECT itemID FROM " + tmpTable + "))";
- }
- sql += ")";
+ //Zotero.debug('Join mode: ' + joinMode);
- var res = Zotero.DB.valueQuery(sql, this._sqlParams),
- ids = res ? res.split(",") : [];
- /*
- // DEBUG: Should this be here?
+ // Filter results with fulltext search
//
- if (!ids) {
- Zotero.DB.query("DROP TABLE " + tmpTable);
- Zotero.DB.commitTransaction();
- return false;
- }
- */
- }
- // Or just run main search
- else {
- var ids = Zotero.DB.columnQuery(this._sql, this._sqlParams);
- }
-
- //Zotero.debug('IDs from main search or subsearch: ');
- //Zotero.debug(ids);
-
- //Zotero.debug('Join mode: ' + joinMode);
-
- // Filter results with fulltext search
- //
- // If join mode ALL, return the (intersection of main and fulltext word search)
- // filtered by fulltext content
- //
- // If join mode ANY or there's a quicksearch (which we assume
- // fulltextContent is part of), return the union of the main search and
- // (a separate fulltext word search filtered by fulltext content)
- for each(var condition in this._conditions){
- if (condition['condition']=='fulltextContent'){
- var fulltextWordIntersectionFilter = function (val, index, array) !!hash[val]
- var fulltextWordIntersectionConditionFilter = function(val, index, array) {
- return hash[val] ?
- (condition.operator == 'contains') :
- (condition.operator == 'doesNotContain');
- };
-
- // Regexp mode -- don't use fulltext word index
- if (condition.mode && condition.mode.indexOf('regexp') == 0) {
- // In an ANY search, only bother scanning items that
- // haven't already been found by the main search
- if (joinMode == 'any') {
- if (!tmpTable) {
- Zotero.DB.beginTransaction();
- var tmpTable = Zotero.Search.idsToTempTable(ids);
+ // If join mode ALL, return the (intersection of main and fulltext word search)
+ // filtered by fulltext content
+ //
+ // If join mode ANY or there's a quicksearch (which we assume
+ // fulltextContent is part of), return the union of the main search and
+ // (a separate fulltext word search filtered by fulltext content)
+ for each(var condition in this._conditions){
+ if (condition['condition']=='fulltextContent'){
+ var fulltextWordIntersectionFilter = function (val, index, array) !!hash[val];
+ var fulltextWordIntersectionConditionFilter = function(val, index, array) {
+ return hash[val] ?
+ (condition.operator == 'contains') :
+ (condition.operator == 'doesNotContain');
+ };
+
+ // Regexp mode -- don't use fulltext word index
+ if (condition.mode && condition.mode.indexOf('regexp') == 0) {
+ // In an ANY search, only bother scanning items that
+ // haven't already been found by the main search
+ if (joinMode == 'any') {
+ if (!tmpTable) {
+ Zotero.debug('=');
+ Zotero.debug(ids);
+ tmpTable = yield Zotero.Search.idsToTempTable(ids);
+ }
+
+ var sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE "
+ + "itemID NOT IN (SELECT itemID FROM " + tmpTable + ")";
+ var res = yield Zotero.DB.valueQuery(sql);
+ var scopeIDs = res ? res.split(",") : [];
+ }
+ // If an ALL search, scan only items from the main search
+ else {
+ var scopeIDs = ids;
}
-
- var sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE "
- + "itemID NOT IN (SELECT itemID FROM " + tmpTable + ")";
- var res = Zotero.DB.valueQuery(sql);
- var scopeIDs = res ? res.split(",") : [];
}
- // If an ALL search, scan only items from the main search
+ // If not regexp mode, run a new search against the fulltext word
+ // index for words in this phrase
else {
- var scopeIDs = ids;
- }
- }
- // If not regexp mode, run a new search against the fulltext word
- // index for words in this phrase
- else {
- Zotero.debug('Running subsearch against fulltext word index');
- var s = new Zotero.Search();
-
- // Add any necessary conditions to the fulltext word search --
- // those that are required in an ANY search and any outside the
- // quicksearch in an ALL search
- for each(var c in this._conditions) {
- if (c.condition == 'blockStart') {
- var inQS = true;
- continue;
+ Zotero.debug('Running subsearch against fulltext word index');
+ var s = new Zotero.Search();
+
+ // Add any necessary conditions to the fulltext word search --
+ // those that are required in an ANY search and any outside the
+ // quicksearch in an ALL search
+ for each(var c in this._conditions) {
+ if (c.condition == 'blockStart') {
+ var inQS = true;
+ continue;
+ }
+ else if (c.condition == 'blockEnd') {
+ inQS = false;
+ continue;
+ }
+ else if (c.condition == 'fulltextContent' ||
+ c.condition == 'fulltextContent' ||
+ inQS) {
+ continue;
+ }
+ else if (joinMode == 'any' && !c.required) {
+ continue;
+ }
+ yield s.addCondition(c.condition, c.operator, c.value);
}
- else if (c.condition == 'blockEnd') {
- inQS = false;
- continue;
+
+ var splits = Zotero.Fulltext.semanticSplitter(condition.value);
+ for each(var split in splits){
+ yield s.addCondition('fulltextWord', condition.operator, split);
}
- else if (c.condition == 'fulltextContent' ||
- c.condition == 'fulltextContent' ||
- inQS) {
- continue;
+ var fulltextWordIDs = yield s.search();
+
+ //Zotero.debug("Fulltext word IDs");
+ //Zotero.debug(fulltextWordIDs);
+
+ // If ALL mode, set intersection of main search and fulltext word index
+ // as the scope for the fulltext content search
+ if (joinMode == 'all' && !hasQuicksearch) {
+ var hash = {};
+ for (let i=0; i<fulltextWordIDs.length; i++) {
+ hash[fulltextWordIDs[i].id] = true;
+ }
+
+ if (ids) {
+ var scopeIDs = ids.filter(fulltextWordIntersectionFilter);
+ }
+ else {
+ var scopeIDs = [];
+ }
}
- else if (joinMode == 'any' && !c.required) {
- continue;
+ // If ANY mode, just use fulltext word index hits for content search,
+ // since the main results will be added in below
+ else {
+ var scopeIDs = fulltextWordIDs;
}
- s.addCondition(c.condition, c.operator, c.value);
- }
-
- var splits = Zotero.Fulltext.semanticSplitter(condition.value);
- for each(var split in splits){
- s.addCondition('fulltextWord', condition.operator, split);
}
- var fulltextWordIDs = s.search();
-
- //Zotero.debug("Fulltext word IDs");
- //Zotero.debug(fulltextWordIDs);
- // If ALL mode, set intersection of main search and fulltext word index
- // as the scope for the fulltext content search
- if (joinMode == 'all' && !hasQuicksearch) {
+ if (scopeIDs && scopeIDs.length) {
+ var fulltextIDs = yield Zotero.Fulltext.findTextInItems(scopeIDs,
+ condition['value'], condition['mode']);
+
var hash = {};
- for each(var id in fulltextWordIDs){
- hash[id] = true;
+ for (let i=0; i<fulltextIDs.length; i++) {
+ hash[fulltextIDs[i].id] = true;
}
- if (ids) {
- var scopeIDs = ids.filter(fulltextWordIntersectionFilter);
- }
- else {
- var scopeIDs = [];
+ filteredIDs = scopeIDs.filter(fulltextWordIntersectionConditionFilter);
+ }
+ else {
+ var filteredIDs = [];
+ }
+
+ //Zotero.debug("Filtered IDs:")
+ //Zotero.debug(filteredIDs);
+
+ // If join mode ANY, add any new items from the fulltext content
+ // search to the main search results
+ //
+ // We only do this if there are primary conditions that alter the
+ // main search, since otherwise all items will match
+ if (this._hasPrimaryConditions &&
+ (joinMode == 'any' || hasQuicksearch) && ids) {
+ //Zotero.debug("Adding filtered IDs to main set");
+ for (let i=0; i<filteredIDs.length; i++) {
+ let id = filteredIDs[i];
+ if (ids.indexOf(id) == -1) {
+ ids.push(id);
+ }
}
}
- // If ANY mode, just use fulltext word index hits for content search,
- // since the main results will be added in below
else {
- var scopeIDs = fulltextWordIDs;
+ //Zotero.debug("Replacing main set with filtered IDs");
+ ids = filteredIDs;
}
}
+ }
+
+ if (this.hasPostSearchFilter() &&
+ (includeParentsAndChildren || includeParents || includeChildren)) {
+ Zotero.debug('b');
+ Zotero.debug(ids);
+ var tmpTable = yield Zotero.Search.idsToTempTable(ids);
- if (scopeIDs && scopeIDs.length) {
- var fulltextIDs = Zotero.Fulltext.findTextInItems(scopeIDs,
- condition['value'], condition['mode']);
-
- var hash = {};
- for each(var val in fulltextIDs){
- hash[val.id] = true;
- }
-
- filteredIDs = scopeIDs.filter(fulltextWordIntersectionConditionFilter);
+ if (includeParentsAndChildren || includeParents) {
+ //Zotero.debug("Adding parent items to result set");
+ var sql = "SELECT parentItemID FROM itemAttachments "
+ + "WHERE itemID IN (SELECT itemID FROM " + tmpTable + ") "
+ + " AND parentItemID IS NOT NULL "
+ + "UNION SELECT parentItemID FROM itemNotes "
+ + "WHERE itemID IN (SELECT itemID FROM " + tmpTable + ")"
+ + " AND parentItemID IS NOT NULL";
}
- else {
- var filteredIDs = [];
+
+ if (includeParentsAndChildren || includeChildren) {
+ //Zotero.debug("Adding child items to result set");
+ var childrenSQL = "SELECT itemID FROM itemAttachments WHERE "
+ + "parentItemID IN (SELECT itemID FROM " + tmpTable + ") UNION "
+ + "SELECT itemID FROM itemNotes WHERE parentItemID IN "
+ + "(SELECT itemID FROM " + tmpTable + ")";
+
+ if (includeParentsAndChildren || includeParents) {
+ sql += " UNION " + childrenSQL;
+ }
+ else {
+ sql = childrenSQL;
+ }
}
- //Zotero.debug("Filtered IDs:")
- //Zotero.debug(filteredIDs);
+ sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE itemID IN (" + sql + ")";
+ var res = yield Zotero.DB.valueQueryAsync(sql);
+ var parentChildIDs = res ? res.split(",") : [];
- // If join mode ANY, add any new items from the fulltext content
- // search to the main search results
- //
- // We only do this if there are primary conditions that alter the
- // main search, since otherwise all items will match
- if (this._hasPrimaryConditions &&
- (joinMode == 'any' || hasQuicksearch) && ids) {
- //Zotero.debug("Adding filtered IDs to main set");
- for each(var id in filteredIDs) {
+ // Add parents and children to main ids
+ if (parentChildIDs) {
+ for (var i=0; i<parentChildIDs.length; i++) {
+ var id = parentChildIDs[i];
if (ids.indexOf(id) == -1) {
ids.push(id);
}
}
}
- else {
- //Zotero.debug("Replacing main set with filtered IDs");
- ids = filteredIDs;
- }
}
}
-
- if (tmpTable) {
- Zotero.DB.query("DROP TABLE " + tmpTable);
- Zotero.DB.commitTransaction();
- }
-
- if (this.hasPostSearchFilter() &&
- (includeParentsAndChildren || includeParents || includeChildren)) {
- Zotero.DB.beginTransaction();
- var tmpTable = Zotero.Search.idsToTempTable(ids);
-
- if (includeParentsAndChildren || includeParents) {
- //Zotero.debug("Adding parent items to result set");
- var sql = "SELECT sourceItemID FROM itemAttachments "
- + "WHERE itemID IN (SELECT itemID FROM " + tmpTable + ") "
- + " AND sourceItemID IS NOT NULL "
- + "UNION SELECT sourceItemID FROM itemNotes "
- + "WHERE itemID IN (SELECT itemID FROM " + tmpTable + ")"
- + " AND sourceItemID IS NOT NULL";
- }
-
- if (includeParentsAndChildren || includeChildren) {
- //Zotero.debug("Adding child items to result set");
- var childrenSQL = "SELECT itemID FROM itemAttachments WHERE "
- + "sourceItemID IN (SELECT itemID FROM " + tmpTable + ") UNION "
- + "SELECT itemID FROM itemNotes WHERE sourceItemID IN "
- + "(SELECT itemID FROM " + tmpTable + ")";
-
- if (includeParentsAndChildren || includeParents) {
- sql += " UNION " + childrenSQL;
- }
- else {
- sql = childrenSQL;
- }
- }
-
- sql = "SELECT GROUP_CONCAT(itemID) FROM items WHERE itemID IN (" + sql + ")";
- var res = Zotero.DB.valueQuery(sql);
- var parentChildIDs = res ? res.split(",") : [];
- Zotero.DB.query("DROP TABLE " + tmpTable);
- Zotero.DB.commitTransaction();
-
- // Add parents and children to main ids
- if (parentChildIDs) {
- for (var i=0; i<parentChildIDs.length; i++) {
- var id = parentChildIDs[i];
- if (ids.indexOf(id) == -1) {
- ids.push(id);
- }
- }
+ finally {
+ if (tmpTable && !asTempTable) {
+ yield Zotero.DB.queryAsync("DROP TABLE " + tmpTable);
}
}
@@ -898,16 +829,16 @@ Zotero.Search.prototype.search = function(asTempTable){
//Zotero.debug(ids);
if (!ids || !ids.length) {
- return false;
+ return [];
}
if (asTempTable) {
- var table = Zotero.Search.idsToTempTable(ids);
- return table;
+ Zotero.debug('c');
+ Zotero.debug(ids);
+ return Zotero.Search.idsToTempTable(ids);
}
-
return ids;
-}
+});
Zotero.Search.prototype.serialize = function() {
@@ -915,9 +846,7 @@ Zotero.Search.prototype.serialize = function() {
primary: {
id: this.id,
libraryID: this.libraryID,
- key: this.key,
- dateAdded: this.dateAdded,
- dateModified: this.dateModified
+ key: this.key
},
fields: {
name: this.name,
@@ -931,20 +860,77 @@ Zotero.Search.prototype.serialize = function() {
/*
* Get the SQL string for the search
*/
-Zotero.Search.prototype.getSQL = function(){
- if (!this._sql){
- this._buildQuery();
+Zotero.Search.prototype.getSQL = Zotero.Promise.coroutine(function* () {
+ if (!this._sql) {
+ yield this._buildQuery();
}
return this._sql;
-}
+});
-Zotero.Search.prototype.getSQLParams = function(){
- if (!this._sql){
- this._buildQuery();
+Zotero.Search.prototype.getSQLParams = Zotero.Promise.coroutine(function* () {
+ if (!this._sql) {
+ yield this._buildQuery();
}
return this._sqlParams;
-}
+});
+
+
+Zotero.Search.prototype.loadConditions = Zotero.Promise.coroutine(function* (reload) {
+ Zotero.debug("Loading conditions for search " + this.libraryKey);
+
+ if (this._loaded.conditions && !reload) {
+ return;
+ }
+
+ if (!this.id) {
+ throw new Error('ID not set for object before attempting to load conditions');
+ }
+
+ var sql = "SELECT * FROM savedSearchConditions "
+ + "WHERE savedSearchID=? ORDER BY searchConditionID";
+ var conditions = yield Zotero.DB.queryAsync(sql, this.id);
+
+ if (conditions.length) {
+ this._maxSearchConditionID = conditions[conditions.length - 1].searchConditionID;
+ }
+
+ // Reindex conditions, in case they're not contiguous in the DB
+ var conditionID = 1;
+
+ for (let i=0; i<conditions.length; i++) {
+ // Parse "condition[/mode]"
+ var [condition, mode] =
+ Zotero.SearchConditions.parseCondition(conditions[i]['condition']);
+
+ var cond = Zotero.SearchConditions.get(condition);
+ if (!cond || cond.noLoad) {
+ Zotero.debug("Invalid saved search condition '" + condition + "' -- skipping", 2);
+ continue;
+ }
+
+ // Convert itemTypeID to itemType
+ //
+ // TEMP: This can be removed at some point
+ if (condition == 'itemTypeID') {
+ condition = 'itemType';
+ conditions[i].value = Zotero.ItemTypes.getName(conditions[i].value);
+ }
+
+ this._conditions[conditionID] = {
+ id: conditionID,
+ condition: condition,
+ mode: mode,
+ operator: conditions[i].operator,
+ value: conditions[i].value,
+ required: conditions[i].required
+ };
+
+ conditionID++;
+ }
+
+ this._loaded.conditions = true;
+});
Zotero.Search.prototype._prepFieldChange = function (field) {
@@ -969,56 +955,30 @@ Zotero.Search.idsToTempTable = function (ids) {
var tmpTable = "tmpSearchResults_" + Zotero.randomString(8);
- Zotero.DB.beginTransaction();
-
- var sql = "CREATE TEMPORARY TABLE " + tmpTable + " (itemID INT)";
- Zotero.DB.query(sql);
-
- var sql = "INSERT INTO " + tmpTable + " SELECT ? ";
- for (var i=0; i<(N_COMBINED_INSERTS-1); i++) {
- sql += "UNION SELECT ? ";
- }
-
- var insertStatement = Zotero.DB.getStatement(sql),
- n = ids.length;
- for (var i=0; i<n; i+=N_COMBINED_INSERTS) {
- for (var j=0; j<N_COMBINED_INSERTS; j++) {
- insertStatement.bindInt32Parameter(j, ids[i+j]);
- }
- try {
- insertStatement.execute();
- }
- catch (e) {
- throw (Zotero.DB.getLastErrorString());
+ return Zotero.DB.executeTransaction(function* () {
+ var sql = "CREATE TEMPORARY TABLE " + tmpTable + " (itemID INT)";
+ yield Zotero.DB.queryAsync(sql);
+
+ var ids2 = ids ? ids.concat() : [];
+ while (ids2.length) {
+ let chunk = ids2.splice(0, N_COMBINED_INSERTS);
+ let sql = 'INSERT INTO ' + tmpTable + ' '
+ + chunk.map(function () "SELECT ?").join(" UNION ");
+ yield Zotero.DB.queryAsync(sql, chunk, { debug: false });
}
- }
- insertStatement.finalize();
-
- var nRemainingInserts = (n % N_COMBINED_INSERTS);
- var remainingInsertStart = n-nRemainingInserts-1;
- var sql = "INSERT INTO " + tmpTable + " SELECT ? ";
- for (var i=remainingInsertStart; i<n; i++) {
- sql += "UNION SELECT ? ";
- }
-
- var insertStatement = Zotero.DB.getStatement(sql);
- for (var j=remainingInsertStart; j<n; j++) {
- insertStatement.bindInt32Parameter(j-remainingInsertStart, ids[remainingInsertStart]);
- }
-
- var sql = "CREATE INDEX " + tmpTable + "_itemID ON " + tmpTable + "(itemID)";
- Zotero.DB.query(sql);
-
- Zotero.DB.commitTransaction();
-
- return tmpTable;
+
+ var sql = "CREATE INDEX " + tmpTable + "_itemID ON " + tmpTable + "(itemID)";
+ yield Zotero.DB.queryAsync(sql);
+
+ return tmpTable;
+ });
}
/*
* Build the SQL query for the search
*/
-Zotero.Search.prototype._buildQuery = function(){
+Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
var sql = 'SELECT itemID FROM items';
var sqlParams = [];
// Separate ANY conditions for 'required' condition support
@@ -1108,27 +1068,27 @@ Zotero.Search.prototype._buildQuery = function(){
+ "SELECT itemID FROM deletedItems "
+ "UNION "
+ "SELECT itemID FROM itemNotes "
- + "WHERE sourceItemID IS NOT NULL AND "
- + "sourceItemID IN (SELECT itemID FROM deletedItems) "
+ + "WHERE parentItemID IS NOT NULL AND "
+ + "parentItemID IN (SELECT itemID FROM deletedItems) "
+ "UNION "
+ "SELECT itemID FROM itemAttachments "
- + "WHERE sourceItemID IS NOT NULL AND "
- + "sourceItemID IN (SELECT itemID FROM deletedItems) "
+ + "WHERE parentItemID IS NOT NULL AND "
+ + "parentItemID IN (SELECT itemID FROM deletedItems) "
+ ")";
if (noChildren){
sql += " AND (itemID NOT IN (SELECT itemID FROM itemNotes "
- + "WHERE sourceItemID IS NOT NULL) AND itemID NOT IN "
+ + "WHERE parentItemID IS NOT NULL) AND itemID NOT IN "
+ "(SELECT itemID FROM itemAttachments "
- + "WHERE sourceItemID IS NOT NULL))";
+ + "WHERE parentItemID IS NOT NULL))";
}
if (unfiled) {
sql += " AND (itemID NOT IN (SELECT itemID FROM collectionItems) "
// Exclude children
+ "AND itemID NOT IN "
- + "(SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL "
- + "UNION SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL)"
+ + "(SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL "
+ + "UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL)"
+ ")";
}
@@ -1224,7 +1184,7 @@ Zotero.Search.prototype._buildQuery = function(){
if (condition.value) {
var lkh = Zotero.Collections.parseLibraryKeyHash(condition.value);
if (lkh) {
- col = Zotero.Collections.getByLibraryAndKey(lkh.libraryID, lkh.key);
+ col = yield Zotero.Collections.getByLibraryAndKey(lkh.libraryID, lkh.key);
}
}
if (!col) {
@@ -1265,7 +1225,7 @@ Zotero.Search.prototype._buildQuery = function(){
if (condition.value) {
var lkh = Zotero.Searches.parseLibraryKeyHash(condition.value);
if (lkh) {
- search = Zotero.Searches.getByLibraryAndKey(lkh.libraryID, lkh.key);
+ search = yield Zotero.Searches.getByLibraryAndKey(lkh.libraryID, lkh.key);
}
}
if (!search) {
@@ -1287,14 +1247,14 @@ Zotero.Search.prototype._buildQuery = function(){
// or that this slows things down with large libraries
// -- should probably use a temporary table instead
if (hasFilter){
- var subids = search.search();
+ let subids = yield search.search();
condSQL += subids.join();
}
// Otherwise just put the SQL in a subquery
else {
- condSQL += search.getSQL();
- var subpar = search.getSQLParams();
- for (var k in subpar){
+ condSQL += yield search.getSQL();
+ let subpar = yield search.getSQLParams();
+ for (let k in subpar){
condSQLParams.push(subpar[k]);
}
}
@@ -1334,13 +1294,12 @@ Zotero.Search.prototype._buildQuery = function(){
case 'creator':
case 'lastName':
- condSQL += "creatorID IN (SELECT creatorID FROM creators "
- + "NATURAL JOIN creatorData WHERE ";
+ condSQL += "creatorID IN (SELECT creatorID FROM creators WHERE ";
openParens++;
break;
case 'childNote':
- condSQL += "itemID IN (SELECT sourceItemID FROM "
+ condSQL += "itemID IN (SELECT parentItemID FROM "
+ "itemNotes WHERE ";
openParens++;
break;
@@ -1589,18 +1548,18 @@ Zotero.Search.prototype._buildQuery = function(){
if (includeParentsAndChildren || includeParents) {
var parentSQL = "SELECT itemID FROM items WHERE "
- + "itemID IN (SELECT sourceItemID FROM itemAttachments "
+ + "itemID IN (SELECT parentItemID FROM itemAttachments "
+ "WHERE itemID IN (" + condSQL + ")) "
- + "OR itemID IN (SELECT sourceItemID FROM itemNotes "
+ + "OR itemID IN (SELECT parentItemID FROM itemNotes "
+ "WHERE itemID IN (" + condSQL + ")) ";
var parentSQLParams = condSQLParams.concat(condSQLParams);
}
if (includeParentsAndChildren || includeChildren) {
var childrenSQL = "SELECT itemID FROM itemAttachments WHERE "
- + "sourceItemID IN (" + condSQL + ") UNION "
+ + "parentItemID IN (" + condSQL + ") UNION "
+ "SELECT itemID FROM itemNotes "
- + "WHERE sourceItemID IN (" + condSQL + ")";
+ + "WHERE parentItemID IN (" + condSQL + ")";
var childSQLParams = condSQLParams.concat(condSQLParams);
}
@@ -1680,8 +1639,8 @@ Zotero.Search.prototype._buildQuery = function(){
}
this._sql = sql;
- this._sqlParams = sqlParams.length ? sqlParams : null;
-}
+ this._sqlParams = sqlParams.length ? sqlParams : false;
+});
Zotero.Search.prototype._generateKey = function () {
@@ -1694,38 +1653,16 @@ Zotero.Searches = new function(){
Zotero.DataObjects.apply(this, ['search', 'searches', 'savedSearch', 'savedSearches']);
this.constructor.prototype = new Zotero.DataObjects();
- this.get = get;
- this.erase = erase;
-
-
- /**
- * Retrieve a saved search
- *
- * @param int id savedSearchID
- * @return object|bool Zotero.Search object,
- * or false if it doesn't exist
- */
- function get(id) {
- var sql = "SELECT COUNT(*) FROM savedSearches WHERE savedSearchID=?";
- if (Zotero.DB.valueQuery(sql, id)) {
- var search = new Zotero.Search;
- search.id = id;
- return search;
- }
- return false;
- }
-
-
/**
* Returns an array of Zotero.Search objects, ordered by name
*
* @param {Integer} [libraryID=0]
*/
- this.getAll = function (libraryID) {
+ this.getAll = Zotero.Promise.coroutine(function* (libraryID) {
var sql = "SELECT savedSearchID AS id, savedSearchName AS name "
+ "FROM savedSearches WHERE libraryID=?";
sql += " ORDER BY name COLLATE NOCASE";
- var rows = Zotero.DB.query(sql, [libraryID ? libraryID : 0]);
+ var rows = yield Zotero.DB.queryAsync(sql, [libraryID ? libraryID : 0]);
if (!rows) {
return [];
}
@@ -1737,37 +1674,46 @@ Zotero.Searches = new function(){
});
var searches = [];
- for each(var row in rows) {
- var search = new Zotero.Search;
- search.id = row.id;
+ for (i=0; i<rows.length; i++) {
+ let search = new Zotero.Search;
+ search.id = rows[i].id;
+ yield search.loadPrimaryData();
searches.push(search);
}
return searches;
- }
+ });
/*
* Delete a given saved search from the DB
*/
- function erase(ids) {
+ this.erase = Zotero.Promise.coroutine(function* (ids) {
ids = Zotero.flattenArguments(ids);
var notifierData = {};
- Zotero.DB.beginTransaction();
- for each(var id in ids) {
- var search = new Zotero.Search;
- search.id = id;
- notifierData[id] = { old: search.serialize() };
-
- var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
- Zotero.DB.query(sql, id);
-
- var sql = "DELETE FROM savedSearches WHERE savedSearchID=?";
- Zotero.DB.query(sql, id);
- }
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ var search = new Zotero.Search;
+ search.id = id;
+ notifierData[id] = { old: search.serialize() };
+
+ var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
+ yield Zotero.DB.queryAsync(sql, id);
+
+ var sql = "DELETE FROM savedSearches WHERE savedSearchID=?";
+ yield Zotero.DB.queryAsync(sql, id);
+ }
+ });
Zotero.Notifier.trigger('delete', 'search', ids, notifierData);
+ });
+
+
+ this._getPrimaryDataSQL = function () {
+ // This should be the same as the query in Zotero.Search.loadPrimaryData(),
+ // just without a specific savedSearchID
+ return "SELECT O.* FROM savedSearches O WHERE 1";
}
}
diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js
@@ -97,7 +97,7 @@ Zotero.Server.Connector.GetTranslators.prototype = {
Zotero.Translators.getAll().then(function(translators) {
var responseData = me._serializeTranslators(translators);
sendResponseCallback(200, "application/json", JSON.stringify(responseData));
- }).fail(function(e) {
+ }).catch(function(e) {
sendResponseCallback(500);
throw e;
}).done();
@@ -440,7 +440,11 @@ Zotero.Server.Connector.SaveSnapshot.prototype = {
// save snapshot
if(filesEditable) {
- Zotero.Attachments.importFromDocument(doc, itemID);
+ // TODO: async
+ Zotero.Attachments.importFromDocument({
+ document: doc,
+ parentItemID: itemID
+ });
}
sendResponseCallback(201);
diff --git a/chrome/content/zotero/xpcom/storage.js b/chrome/content/zotero/xpcom/storage.js
@@ -103,7 +103,7 @@ Zotero.Sync.Storage = new function () {
var librarySyncTimes = {};
// Get personal library file sync mode
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
// TODO: Make sure modes are active
if (options.libraries && options.libraries.indexOf(0) == -1) {
@@ -149,11 +149,11 @@ Zotero.Sync.Storage = new function () {
else {
var promise = mode.cacheCredentials();
}
- promises.push(Q.allSettled([mode, promise]));
+ promises.push(Zotero.Promise.allSettled([mode, promise]));
}
}
- return Q.all(promises)
+ return Zotero.Promise.all(promises)
// Get library last-sync times
.then(function (cacheCredentialsPromises) {
var promises = [];
@@ -165,8 +165,8 @@ Zotero.Sync.Storage = new function () {
let mode = results[0].value;
if (mode == Zotero.Sync.Storage.WebDAV) {
if (results[1].state == "rejected") {
- promises.push(Q.allSettled(
- [0, Q.reject(results[1].reason)]
+ promises.push(Zotero.Promise.allSettled(
+ [0, Zotero.Promise.reject(results[1].reason)]
));
// Skip further syncing of user library
delete libraryModes[0];
@@ -179,16 +179,16 @@ Zotero.Sync.Storage = new function () {
// Get the last sync time for each library
if (self.downloadOnSync(libraryID)) {
- promises.push(Q.allSettled(
+ promises.push(Zotero.Promise.allSettled(
[libraryID, libraryModes[libraryID].getLastSyncTime(libraryID)]
));
}
// If download-as-needed, we don't need the last sync time
else {
- promises.push(Q.allSettled([libraryID, null]));
+ promises.push(Zotero.Promise.allSettled([libraryID, null]));
}
}
- return Q.all(promises);
+ return Zotero.Promise.all(promises);
});
})
.then(function (promises) {
@@ -245,7 +245,7 @@ Zotero.Sync.Storage = new function () {
}
promises.push(promise);
}
- return Q.all(promises)
+ return Zotero.Promise.all(promises)
.then(function () {
// Queue files to download and upload from each library
for (let libraryID in librarySyncTimes) {
@@ -308,13 +308,13 @@ Zotero.Sync.Storage = new function () {
// Start queues for each library
for (let libraryID in librarySyncTimes) {
libraryID = parseInt(libraryID);
- libraryQueues.push(Q.allSettled(
+ libraryQueues.push(Zotero.Promise.allSettled(
[libraryID, Zotero.Sync.Storage.QueueManager.start(libraryID)]
));
}
// The promise is done when all libraries are done
- return Q.all(libraryQueues);
+ return Zotero.Promise.all(libraryQueues);
});
})
.then(function (promises) {
@@ -329,14 +329,14 @@ Zotero.Sync.Storage = new function () {
if (results[1].state == "fulfilled") {
libraryQueues.forEach(function (queuePromise) {
- if (queuePromise.isFulfilled()) {
- let result = queuePromise.inspect().value;
+ if (queueZotero.Promise.isFulfilled()) {
+ let result = queueZotero.Promise.value();
Zotero.debug("File " + result.type + " sync finished "
+ "for library " + libraryID);
if (result.localChanges) {
changedLibraries.push(libraryID);
}
- finalPromises.push(Q.allSettled([
+ finalPromises.push(Zotero.Promise.allSettled([
libraryID,
libraryModes[libraryID].setLastSyncTime(
libraryID,
@@ -345,11 +345,11 @@ Zotero.Sync.Storage = new function () {
]));
}
else {
- let e = queuePromise.inspect().reason;
+ let e = queueZotero.Promise.reason();
Zotero.debug("File " + e.type + " sync failed "
+ "for library " + libraryID);
- finalPromises.push(Q.allSettled(
- [libraryID, Q.reject(e)]
+ finalPromises.push(Zotero.Promise.allSettled(
+ [libraryID, Zotero.Promise.reject(e)]
));
}
});
@@ -382,7 +382,7 @@ Zotero.Sync.Storage = new function () {
Zotero.debug("No local changes made during file sync");
}
- return Q.all(finalPromises)
+ return Zotero.Promise.all(finalPromises)
.then(function (promises) {
var results = {
changesMade: !!changedLibraries.length,
@@ -695,7 +695,7 @@ Zotero.Sync.Storage = new function () {
* FALSE otherwise
*/
this.checkForUpdatedFiles = function (libraryID, itemIDs, itemModTimes) {
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
libraryID = parseInt(libraryID);
if (isNaN(libraryID)) {
libraryID = false;
@@ -813,7 +813,7 @@ Zotero.Sync.Storage = new function () {
var updatedStates = {};
let checkItems = function () {
- if (!items.length) return Q();
+ if (!items.length) return Zotero.Promise.resolve();
//Zotero.debug("Memory usage: " + memmgr.resident);
@@ -829,7 +829,7 @@ Zotero.Sync.Storage = new function () {
return checkItems();
}
let file = null;
- return Q(OS.File.open(nsIFile.path))
+ return Zotero.Promise.resolve(OS.File.open(nsIFile.path))
.then(function (promisedFile) {
file = promisedFile;
return file.stat()
@@ -905,7 +905,7 @@ Zotero.Sync.Storage = new function () {
// We have to close the file before modifying it from the main
// thread (at least on Windows, where assigning lastModifiedTime
// throws an NS_ERROR_FILE_IS_LOCKED otherwise)
- return Q(file.close())
+ return Zotero.Promise.resolve(file.close())
.then(function () {
Zotero.debug("Mod time didn't match (" + fmtime + "!=" + mtime + ") "
+ "but hash did for " + nsIFile.leafName + " for item " + lk
@@ -1008,7 +1008,7 @@ Zotero.Sync.Storage = new function () {
}
// TODO: start sync icon in cacheCredentials
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
return mode.cacheCredentials();
})
.then(function () {
@@ -1045,9 +1045,10 @@ Zotero.Sync.Storage = new function () {
*
* This is called from Zotero.Sync.Server.StreamListener.onStopRequest()
*
- * @return {Object} data Properties 'request', 'item', 'compressed', 'syncModTime', 'syncHash'
+ * @return {Promise<Object>} data - Promise for object with properties 'request', 'item',
+ * 'compressed', 'syncModTime', 'syncHash'
*/
- this.processDownload = function (data) {
+ this.processDownload = Zotero.Promise.coroutine(function* (data) {
var funcName = "Zotero.Sync.Storage.processDownload()";
if (!data) {
@@ -1073,10 +1074,10 @@ Zotero.Sync.Storage = new function () {
// TODO: Test file hash
if (data.compressed) {
- var newFile = _processZipDownload(item);
+ var newFile = yield _processZipDownload(item);
}
else {
- var newFile = _processDownload(item);
+ var newFile = yield _processDownload(item);
}
// If |newFile| is set, the file was renamed, so set item filename to that
@@ -1153,7 +1154,7 @@ Zotero.Sync.Storage = new function () {
Zotero.DB.commitTransaction();
return true;
- }
+ });
this.checkServerPromise = function (mode) {
@@ -1220,9 +1221,9 @@ Zotero.Sync.Storage = new function () {
var item = Zotero.Items.getByLibraryAndKey(libraryID, key);
Zotero.Notifier.trigger('redraw', 'item', item.id, { column: "hasAttachment" });
- var parent = item.getSource();
+ var parent = item.parentItemKey;
if (parent) {
- var parentItem = Zotero.Items.get(parent);
+ var parentItem = Zotero.Items.getByLibraryAndKey(libraryID, parent);
var parentLibraryKey = libraryID + "/" + parentItem.key;
if (percentage !== false) {
_itemDownloadPercentages[parentLibraryKey] = percentage;
@@ -1320,7 +1321,7 @@ Zotero.Sync.Storage = new function () {
}
- function _processDownload(item) {
+ var _processDownload = Zotero.Promise.coroutine(function* (item) {
var funcName = "Zotero.Sync.Storage._processDownload()";
var tempFile = Zotero.getTempDirectory();
@@ -1331,9 +1332,9 @@ Zotero.Sync.Storage = new function () {
throw ("Downloaded file not found in " + funcName);
}
- var parentDir = Zotero.Attachments.getStorageDirectory(item.id);
+ var parentDir = Zotero.Attachments.getStorageDirectory(item);
if (!parentDir.exists()) {
- Zotero.Attachments.createDirectoryForItem(item.id);
+ yield Zotero.Attachments.createDirectoryForItem(item);
}
_deleteExistingAttachmentFiles(item);
@@ -1433,10 +1434,10 @@ Zotero.Sync.Storage = new function () {
}
return returnFile;
- }
+ });
- function _processZipDownload(item) {
+ var _processZipDownload = Zotero.Promise.coroutine(function* (item) {
var funcName = "Zotero.Sync.Storage._processDownloadedZip()";
var zipFile = Zotero.getTempDirectory();
@@ -1470,9 +1471,9 @@ Zotero.Sync.Storage = new function () {
return false;
}
- var parentDir = Zotero.Attachments.getStorageDirectory(item.id);
+ var parentDir = Zotero.Attachments.getStorageDirectory(item);
if (!parentDir.exists()) {
- Zotero.Attachments.createDirectoryForItem(item.id);
+ yield Zotero.Attachments.createDirectoryForItem(item);
}
try {
@@ -1717,13 +1718,13 @@ Zotero.Sync.Storage = new function () {
zipFile.remove(false);
return returnFile;
- }
+ });
function _deleteExistingAttachmentFiles(item) {
var funcName = "Zotero.Sync.Storage._deleteExistingAttachmentFiles()";
- var parentDir = Zotero.Attachments.getStorageDirectory(item.id);
+ var parentDir = Zotero.Attachments.getStorageDirectory(item);
// Delete existing files
var otherFiles = parentDir.directoryEntries;
@@ -1799,7 +1800,7 @@ Zotero.Sync.Storage = new function () {
));
}
- var dir = Zotero.Attachments.getStorageDirectoryByKey(item.key);
+ var dir = Zotero.Attachments.getStorageDirectory(item);
var tmpFile = Zotero.getTempDirectory();
tmpFile.append(item.key + '.zip');
diff --git a/chrome/content/zotero/xpcom/storage/queue.js b/chrome/content/zotero/xpcom/storage/queue.js
@@ -225,7 +225,7 @@ Zotero.Sync.Storage.Queue.prototype.addRequest = function (request, highPriority
Zotero.Sync.Storage.Queue.prototype.start = function () {
if (!this._deferred || this._deferred.promise.isFulfilled()) {
Zotero.debug("Creating deferred for queue " + this.name);
- this._deferred = Q.defer();
+ this._deferred = Zotero.Promise.defer();
}
// The queue manager needs to know what queues were running in the
// current session
@@ -284,7 +284,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
let requestName = name;
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
var promise = request.start();
self.advance();
return promise;
@@ -319,7 +319,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
let requestName = request.name;
- // This isn't in an fcall() because the request needs to get marked
+ // This isn't in a Zotero.Promise.try() because the request needs to get marked
// as running immediately so that it doesn't get run again by a
// subsequent advance() call.
try {
@@ -330,8 +330,7 @@ Zotero.Sync.Storage.Queue.prototype.advance = function () {
self.error(e);
}
- Q.when(promise)
- .then(function (result) {
+ promise.then(function (result) {
if (result.localChanges) {
self._localChanges = true;
}
diff --git a/chrome/content/zotero/xpcom/storage/queueManager.js b/chrome/content/zotero/xpcom/storage/queueManager.js
@@ -53,7 +53,7 @@ Zotero.Sync.Storage.QueueManager = new function () {
Zotero.debug("No files to sync" + suffix);
}
- return Q.allSettled(promises)
+ return Zotero.Promise.allSettled(promises)
.then(function (results) {
Zotero.debug("All storage queues are finished" + suffix);
diff --git a/chrome/content/zotero/xpcom/storage/request.js b/chrome/content/zotero/xpcom/storage/request.js
@@ -41,7 +41,7 @@ Zotero.Sync.Storage.Request = function (name, callbacks) {
this.progress = 0;
this.progressMax = 0;
- this._deferred = Q.defer();
+ this._deferred = Zotero.Promise.defer();
this._running = false;
this._stopping = false;
this._percentage = 0;
@@ -199,7 +199,7 @@ Zotero.Sync.Storage.Request.prototype.start = function () {
//
// The main sync logic is triggered here.
- Q.all([f(this) for each(f in this._onStart)])
+ Zotero.Promise.all([f(this) for each(f in this._onStart)])
.then(function (results) {
return {
localChanges: results.some(function (val) val && val.localChanges == true),
diff --git a/chrome/content/zotero/xpcom/storage/webdav.js b/chrome/content/zotero/xpcom/storage/webdav.js
@@ -265,7 +265,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
channel.setRequestHeader('Keep-Alive', '', false);
channel.setRequestHeader('Connection', '', false);
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@@ -276,7 +276,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
data.request.setChannel(false);
deferred.resolve(
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
return onUploadComplete(httpRequest, status, response, data);
})
);
@@ -479,13 +479,13 @@ Zotero.Sync.Storage.WebDAV = (function () {
};
if (files.length == 0) {
- return Q(results);
+ return Zotero.Promise.resolve(results);
}
let deleteURI = _rootURI.clone();
// This should never happen, but let's be safe
if (!deleteURI.spec.match(/\/$/)) {
- return Q.reject("Root URI does not end in slash in "
+ return Zotero.Promise.reject("Root URI does not end in slash in "
+ "Zotero.Sync.Storage.WebDAV.deleteStorageFiles()");
}
@@ -559,12 +559,8 @@ Zotero.Sync.Storage.WebDAV = (function () {
Components.utils.import("resource://zotero/concurrent-caller.js");
var caller = new ConcurrentCaller(4);
caller.stopOnError = true;
- caller.setLogger(function (msg) {
- Zotero.debug("[ConcurrentCaller] " + msg);
- });
- caller.setErrorLogger(function (msg) {
- Components.utils.reportError(msg);
- });
+ caller.setLogger(function (msg) Zotero.debug(msg));
+ caller.onError(function (e) Components.utils.reportError(e));
return caller.fcall(funcs)
.then(function () {
return results;
@@ -883,7 +879,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
destFile.remove(false);
}
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@@ -977,7 +973,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
obj._uploadFile = function (request) {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var created = Zotero.Sync.Storage.createUploadFile(
request,
function (data) {
@@ -986,14 +982,14 @@ Zotero.Sync.Storage.WebDAV = (function () {
return;
}
deferred.resolve(
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
return processUploadFile(data);
})
);
}
);
if (!created) {
- return Q(false);
+ return Zotero.Promise.resolve(false);
}
return deferred.promise;
};
@@ -1005,7 +1001,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
// Cache the credentials at the root URI
var self = this;
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
return self._cacheCredentials();
})
.then(function () {
@@ -1066,7 +1062,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
+ "for GET request");
}
- return Q.reject(e);
+ return Zotero.Promise.reject(e);
}
// TODO: handle browser offline exception
else {
@@ -1128,7 +1124,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
Zotero.debug("Credentials are cached");
_cachedCredentials = true;
})
- .fail(function (e) {
+ .catch(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
var msg = "HTTP " + e.status + " error from WebDAV server "
+ "for OPTIONS request";
@@ -1142,7 +1138,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
obj._checkServer = function () {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
try {
// Clear URIs
@@ -1564,7 +1560,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
* @param {Function} callback Passed number of files deleted
*/
obj._purgeDeletedStorageFiles = function () {
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
if (!this.includeUserFiles) {
return false;
}
@@ -1614,7 +1610,7 @@ Zotero.Sync.Storage.WebDAV = (function () {
* Delete orphaned storage files older than a day before last sync time
*/
obj._purgeOrphanedStorageFiles = function () {
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
const daysBeforeSyncTime = 1;
if (!this.includeUserFiles) {
@@ -1639,10 +1635,10 @@ Zotero.Sync.Storage.WebDAV = (function () {
var lastSyncDate = new Date(Zotero.Sync.Server.lastLocalSyncTime * 1000);
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
Zotero.HTTP.WebDAV.doProp("PROPFIND", uri, xmlstr, function (xmlhttp) {
- Q.fcall(function () {
+ Zotero.Promise.try(function () {
Zotero.debug(xmlhttp.responseText);
var funcName = "Zotero.Sync.Storage.purgeOrphanedStorageFiles()";
diff --git a/chrome/content/zotero/xpcom/storage/zfs.js b/chrome/content/zotero/xpcom/storage/zfs.js
@@ -278,7 +278,7 @@ Zotero.Sync.Storage.ZFS = (function () {
}
return uploadCallback(item, url, uploadKey, params);
})
- .fail(function (e) {
+ .catch(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 413) {
var retry = e.xmlhttp.getResponseHeader('Retry-After');
@@ -458,7 +458,7 @@ Zotero.Sync.Storage.ZFS = (function () {
request.setChannel(channel);
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@@ -571,7 +571,7 @@ Zotero.Sync.Storage.ZFS = (function () {
remoteChanges: true
};
})
- .fail(function (e) {
+ .catch(function (e) {
var msg = "Unexpected file registration status " + e.status
+ " (" + Zotero.Items.getLibraryKeyHash(item) + ")";
Zotero.debug(msg, 1);
@@ -835,7 +835,7 @@ Zotero.Sync.Storage.ZFS = (function () {
Zotero.File.checkFileAccessError(e, destFile, 'create');
}
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var listener = new Zotero.Sync.Storage.StreamListener(
{
@@ -960,7 +960,7 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._uploadFile = function (request) {
var item = Zotero.Sync.Storage.getItemFromRequestName(request.name);
if (Zotero.Attachments.getNumFiles(item) > 1) {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
var created = Zotero.Sync.Storage.createUploadFile(
request,
function (data) {
@@ -972,7 +972,7 @@ Zotero.Sync.Storage.ZFS = (function () {
}
);
if (!created) {
- return Q(false);
+ return Zotero.Promise.resolve(false);
}
return deferred.promise;
}
@@ -989,7 +989,7 @@ Zotero.Sync.Storage.ZFS = (function () {
var lastSyncURI = this._getLastSyncURI(libraryID);
var self = this;
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
// Cache the credentials at the root
return self._cacheCredentials();
})
@@ -1010,14 +1010,14 @@ Zotero.Sync.Storage.ZFS = (function () {
+ libraryID + " was " + date);
return ts;
})
- .fail(function (e) {
+ .catch(function (e) {
if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
if (e.status == 401 || e.status == 403) {
Zotero.debug("Clearing ZFS authentication credentials", 2);
_cachedCredentials = false;
}
- return Q.reject(e);
+ return Zotero.Promise.reject(e);
}
// TODO: handle browser offline exception
else {
@@ -1054,7 +1054,7 @@ Zotero.Sync.Storage.ZFS = (function () {
sql, ['storage_zfs_' + libraryID, { int: ts }]
);
})
- .fail(function (e) {
+ .catch(function (e) {
var msg = "Unexpected status code " + e.xmlhttp.status
+ " setting last file sync time";
Zotero.debug(msg, 1);
@@ -1089,7 +1089,7 @@ Zotero.Sync.Storage.ZFS = (function () {
obj._cacheCredentials = function () {
if (_cachedCredentials) {
Zotero.debug("ZFS credentials are already cached");
- return Q();
+ return Zotero.Promise.resolve();
}
var uri = this.rootURI;
@@ -1126,7 +1126,7 @@ Zotero.Sync.Storage.ZFS = (function () {
* Remove all synced files from the server
*/
obj._purgeDeletedStorageFiles = function () {
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
// Cache the credentials at the root
return this._cacheCredentials();
}.bind(this))
diff --git a/chrome/content/zotero/xpcom/style.js b/chrome/content/zotero/xpcom/style.js
@@ -34,7 +34,7 @@ Zotero.Styles = new function() {
var _renamedStyles = null;
- Components.utils.import("resource://zotero/q.js");
+ //Components.utils.import("resource://zotero/bluebird.js");
Components.utils.import("resource://gre/modules/Services.jsm");
this.xsltProcessor = null;
@@ -179,7 +179,7 @@ Zotero.Styles = new function() {
* with the validation error if validation fails, or resolved if it is not.
*/
this.validate = function(style) {
- var deferred = Q.defer(),
+ var deferred = Zotero.Promise.defer(),
worker = new Worker("resource://zotero/csl-validator.js");
worker.onmessage = function(event) {
if(event.data) {
@@ -212,7 +212,7 @@ Zotero.Styles = new function() {
styleInstalled = _install(style, origin);
}
- styleInstalled.fail(function(error) {
+ styleInstalled.catch(function(error) {
// Unless user cancelled, show an alert with the error
if(typeof error === "object" && error instanceof Zotero.Exception.UserCancelled) return;
if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
@@ -238,7 +238,7 @@ Zotero.Styles = new function() {
if(!_initialized || !_cacheTranslatorData) Zotero.Styles.init();
var existingFile, destFile, source, styleID
- return Q.fcall(function() {
+ return Zotero.Promise.try(function() {
// First, parse style and make sure it's valid XML
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser),
@@ -334,7 +334,7 @@ Zotero.Styles = new function() {
}
}
- return Zotero.Styles.validate(style).fail(function(validationErrors) {
+ return Zotero.Styles.validate(style).catch(function(validationErrors) {
Zotero.logError("Style from "+origin+" failed to validate:\n\n"+validationErrors);
// If validation fails on the parent of a dependent style, ignore it (for now)
@@ -362,7 +362,7 @@ Zotero.Styles = new function() {
if(source.substr(0, 7) === "http://" || source.substr(0, 8) === "https://") {
return Zotero.HTTP.promise("GET", source).then(function(xmlhttp) {
return _install(xmlhttp.responseText, origin, true);
- }).fail(function(error) {
+ }).catch(function(error) {
if(typeof error === "object" && error instanceof Zotero.Exception.Alert) {
throw new Zotero.Exception.Alert("styles.installSourceError", [origin, source],
"styles.install.title", error);
diff --git a/chrome/content/zotero/xpcom/syncedSettings.js b/chrome/content/zotero/xpcom/syncedSettings.js
@@ -31,26 +31,20 @@ Zotero.SyncedSettings = (function () {
// Public methods
//
var module = {
- get: function (libraryID, setting) {
- return Q.fcall(function () {
- var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
- return JSON.parse(Zotero.DB.valueQuery(sql, [setting, libraryID]));
- });
- },
-
-
- set: function (libraryID, setting, value, version, synced) {
- var self = this;
- return Q.fcall(function () {
- return self.setSynchronous(libraryID, setting, value, version, synced);
- });
- },
+ get: Zotero.Promise.coroutine(function* (libraryID, setting) {
+ var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
+ var json = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
+ if (!json) {
+ return false;
+ }
+ return JSON.parse(json);
+ }),
- setSynchronous: function (libraryID, setting, value, version, synced) {
+ set: Zotero.Promise.coroutine(function* (libraryID, setting, value, version, synced) {
// TODO: get rid of this once we have proper affected rows handling
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
- var currentValue = Zotero.DB.valueQuery(sql, [setting, libraryID]);
+ var currentValue = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
// Make sure we can tell the difference between a
// missing setting (FALSE as returned by valueQuery())
@@ -79,7 +73,7 @@ Zotero.SyncedSettings = (function () {
// Clear
if (typeof value == 'undefined') {
var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?";
- Zotero.DB.query(sql, [setting, libraryID]);
+ yield Zotero.DB.queryAsync(sql, [setting, libraryID]);
Zotero.Notifier.trigger('delete', 'setting', [id], extraData);
return true;
@@ -99,16 +93,16 @@ Zotero.SyncedSettings = (function () {
if (hasCurrentValue) {
var sql = "UPDATE syncedSettings SET value=?, synced=? WHERE setting=? AND libraryID=?";
- Zotero.DB.query(sql, [JSON.stringify(value), synced, setting, libraryID]);
+ yield Zotero.DB.queryAsync(sql, [JSON.stringify(value), synced, setting, libraryID]);
}
else {
var sql = "INSERT INTO syncedSettings "
+ "(setting, libraryID, value, synced) VALUES (?, ?, ?, ?)";
- Zotero.DB.query(sql, [setting, libraryID, JSON.stringify(value), synced]);
+ yield Zotero.DB.queryAsync(sql, [setting, libraryID, JSON.stringify(value), synced]);
}
Zotero.Notifier.trigger(event, 'setting', [id], extraData);
return true;
- }
+ })
};
return module;
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
@@ -313,7 +313,7 @@ Zotero.Translate.Sandbox = {
}
var translator = translation.translator[0];
- (typeof translator === "object" ? Q(translator) : Zotero.Translators.get(translator)).
+ (typeof translator === "object" ? Zotero.Promise.resolve(translator) : Zotero.Translators.get(translator)).
then(function(translator) {
return translation._loadTranslator(translator);
}).then(function() {
@@ -354,7 +354,7 @@ Zotero.Translate.Sandbox = {
callback(sandbox);
translate.decrementAsyncProcesses("safeTranslator#getTranslatorObject()");
- }).fail(function(e) {
+ }).catch(function(e) {
translate.complete(false, e);
return;
});
@@ -977,7 +977,7 @@ Zotero.Translate.Base.prototype = {
if(checkSetTranslator) {
// setTranslator must be called beforehand if checkSetTranslator is set
if( !this.translator || !this.translator[0] ) {
- return Q.reject(new Error("getTranslators: translator must be set via setTranslator before calling" +
+ return Zotero.Promise.reject(new Error("getTranslators: translator must be set via setTranslator before calling" +
" getTranslators with the checkSetTranslator flag"));
}
var promises = new Array();
@@ -992,8 +992,8 @@ Zotero.Translate.Base.prototype = {
/**TODO: check that the translator is of appropriate type?*/
if(t) promises.push(t);
}
- if(!promises.length) return Q.reject(new Error("getTranslators: no valid translators were set"));
- potentialTranslators = Q.all(promises);
+ if(!promises.length) return Zotero.Promise.reject(new Error("getTranslators: no valid translators were set"));
+ potentialTranslators = Zotero.Promise.all(promises);
} else {
potentialTranslators = this._getTranslatorsGetPotentialTranslators();
}
@@ -1022,7 +1022,7 @@ Zotero.Translate.Base.prototype = {
// Attach handler for translators, so that we can return a
// promise that provides them.
// TODO make me._detect() return a promise
- var deferred = Q.defer(),
+ var deferred = Zotero.Promise.defer(),
translatorsHandler = function(obj, translators) {
me.removeHandler("translators", translatorsHandler);
deferred.resolve(translators);
@@ -1054,7 +1054,7 @@ Zotero.Translate.Base.prototype = {
}
return deferred.promise;
- }).fail(function(e) {
+ }).catch(function(e) {
Zotero.logError(e);
me.complete(false, e);
});
@@ -1441,7 +1441,7 @@ Zotero.Translate.Base.prototype = {
"ZOTERO_TRANSLATOR_INFO"],
(translator.file ? translator.file.path : translator.label));
me._translatorInfo = me._sandboxManager.sandbox.ZOTERO_TRANSLATOR_INFO;
- }).fail(function(e) {
+ }).catch(function(e) {
me.complete(false, e);
});
},
@@ -2060,7 +2060,7 @@ Zotero.Translate.Export.prototype.complete = function(returnValue, error) {
*/
Zotero.Translate.Export.prototype.getTranslators = function() {
if(this._currentState === "detect") {
- return Q.reject(new Error("getTranslators: detection is already running"));
+ return Zotero.Promise.reject(new Error("getTranslators: detection is already running"));
}
var me = this;
return Zotero.Translators.getAllForType(this.type).then(function(translators) {
diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js
@@ -216,7 +216,7 @@ Zotero.Translate.ItemSaver.prototype = {
return topLevelCollection;
},
- "_saveAttachmentFile":function(attachment, parentID, attachmentCallback) {
+ "_saveAttachmentFile": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
const urlRe = /(([a-z][-+\.a-z0-9]*):\/\/[^\s]*)/i; //according to RFC3986
Zotero.debug("Translate: Adding attachment", 4);
@@ -241,37 +241,46 @@ Zotero.Translate.ItemSaver.prototype = {
if(!attachment.path) {
// create from URL
attachment.linkMode = "linked_file";
- try {
- var myID = Zotero.Attachments.linkFromURL(attachment.url, parentID,
- (attachment.mimeType ? attachment.mimeType : undefined),
- (attachment.title ? attachment.title : undefined));
- } catch(e) {
+ var newItem = yield Zotero.Attachments.linkFromURL({
+ url: attachment.url,
+ parentItemID: parentID,
+ contentType: attachment.mimeType ? attachment.mimeType : undefined,
+ title: attachment.title ? attachment.title : undefined
+ })
+ .catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
+ });
+ if (!newItem) {
return false;
}
- Zotero.debug("Translate: Created attachment; id is "+myID, 4);
+ Zotero.debug("Translate: Created attachment; id is " + newItem.id, 4);
attachmentCallback(attachment, 100);
- var newItem = Zotero.Items.get(myID);
} else {
var file = this._parsePath(attachment.path);
if(!file) return;
if (attachment.url) {
attachment.linkMode = "imported_url";
- var myID = Zotero.Attachments.importSnapshotFromFile(file,
- attachment.url, attachment.title, attachment.mimeType, attachment.charset,
- parentID);
+ var newItem = yield Zotero.Attachments.importSnapshotFromFile({
+ file: file,
+ url: attachment.url,
+ title: attachment.title,
+ contentType: attachment.mimeType,
+ charset: attachment.charset,
+ parentItemID: parentID
+ });
}
else {
attachment.linkMode = "imported_file";
- var myID = Zotero.Attachments.importFromFile(file, parentID);
+ var newItem = yield Zotero.Attachments.importFromFile({
+ file: file,
+ parentItemID: parentID
+ });
}
attachmentCallback(attachment, 100);
}
- var newItem = Zotero.Items.get(myID);
-
// save fields
attachment.itemType = "attachment";
this._saveFields(attachment, newItem);
@@ -281,10 +290,10 @@ Zotero.Translate.ItemSaver.prototype = {
newItem.setNote(attachment.note);
}
- newItem.save();
+ yield newItem.save();
return newItem;
- },
+ }),
"_parsePathURI":function(path) {
try {
@@ -381,7 +390,7 @@ Zotero.Translate.ItemSaver.prototype = {
return false;
},
- "_saveAttachmentDownload":function(attachment, parentID, attachmentCallback) {
+ "_saveAttachmentDownload": Zotero.Promise.coroutine(function* (attachment, parentID, attachmentCallback) {
Zotero.debug("Translate: Adding attachment", 4);
if(!attachment.url && !attachment.document) {
@@ -413,80 +422,99 @@ Zotero.Translate.ItemSaver.prototype = {
// if snapshot is explicitly set to false, attach as link
attachment.linkMode = "linked_url";
if(attachment.document) {
- try {
- Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
- (attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
- title);
- attachmentCallback(attachment, 100);
- } catch(e) {
+ yield Zotero.Attachments.linkFromURL({
+ url: attachment.document.location.href,
+ parentItemID: parentID,
+ contentType: attachment.mimeType ? attachment.mimeType : attachment.document.contentType,
+ title: title
+ })
+ .catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
- }
+ });
+
+ attachmentCallback(attachment, 100);
+
return true;
} else {
if(!attachment.mimeType || !title) {
Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
}
- try {
- Zotero.Attachments.linkFromURL(attachment.url, parentID,
- (attachment.mimeType ? attachment.mimeType : undefined),
- title);
- attachmentCallback(attachment, 100);
- } catch(e) {
+ yield Zotero.Attachments.linkFromURL({
+ url: attachment.url,
+ parentItemID: parentID,
+ contentType: attachment.mimeType ? attachment.mimeType : undefined,
+ title: title
+ })
+ .catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
- }
+ });
+
+ attachmentCallback(attachment, 100);
+
return true;
}
} else {
// if snapshot is not explicitly set to false, retrieve snapshot
if(attachment.document) {
- try {
- attachment.linkMode = "imported_url";
- Zotero.Attachments.importFromDocument(attachment.document,
- parentID, title, null, function(status, err) {
- if(status) {
- attachmentCallback(attachment, 100);
- } else {
- attachmentCallback(attachment, false, err);
- }
- }, this._libraryID);
- attachmentCallback(attachment, 0);
- } catch(e) {
+ attachment.linkMode = "imported_url";
+
+ attachmentCallback(attachment, 0);
+
+ yield Zotero.Attachments.importFromDocument({
+ libraryID: this._libraryID,
+ document: attachment.document,
+ parentItemID: parentID,
+ title: title
+ })
+ .then(function (attachmentItem) {
+ attachmentCallback(attachment, 100);
+ })
+ .catch(function (e) {
Zotero.debug("Translate: Error attaching document", 2);
attachmentCallback(attachment, false, e);
- }
+ });
+
return true;
// Save attachment if snapshot pref enabled or not HTML
// (in which case downloadAssociatedFiles applies)
} else {
var mimeType = (attachment.mimeType ? attachment.mimeType : null);
- var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID);
- try {
- Zotero.debug('Importing attachment from URL');
- attachment.linkMode = "imported_url";
- Zotero.Attachments.importFromURL(attachment.url, parentID, title,
- fileBaseName, null, mimeType, this._libraryID, function(status, err) {
- // TODO: actually indicate progress during download
- if(status) {
- attachmentCallback(attachment, 100);
- } else {
- attachmentCallback(attachment, false, err);
- }
- }, this._cookieSandbox);
- attachmentCallback(attachment, 0);
- } catch(e) {
+ let parentItem = yield Zotero.Items.getAsync(parentID);
+ var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
+
+ Zotero.debug('Importing attachment from URL');
+ attachment.linkMode = "imported_url";
+
+ attachmentCallback(attachment, 0);
+
+ yield Zotero.Attachments.importFromURL({
+ libraryID: this._libraryID,
+ url: attachment.url,
+ parentItemID: parentID,
+ title: title,
+ fileBaseName: fileBaseName,
+ contentType: mimeType,
+ cookieSandbox: this._cookieSandbox
+ })
+ .then(function (attachmentItem) {
+ // TODO: actually indicate progress during download
+ attachmentCallback(attachment, 100);
+ })
+ .catch(function (e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
attachmentCallback(attachment, false, e);
- }
+ });
+
return true;
}
}
}
return false;
- },
+ }),
"_saveFields":function(item, newItem) {
// fields that should be handled differently
@@ -587,7 +615,7 @@ Zotero.Translate.ItemSaver.prototype = {
myNote.libraryID = this._libraryID;
myNote.setNote(typeof note == "object" ? note.note : note);
if(parentID) {
- myNote.setSource(parentID);
+ myNote.parentID = parentID;
}
var noteID = myNote.save();
@@ -706,7 +734,7 @@ Zotero.Translate.ItemGetter.prototype = {
},
"setAll":function(getChildCollections) {
- this._itemsLeft = Zotero.Items.getAll(true);
+ this._itemsLeft = Zotero.Items.getAll(0, true);
if(getChildCollections) {
this._collectionsLeft = Zotero.getCollections();
diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js
@@ -131,7 +131,7 @@ Zotero.Translator.prototype.init = function(info) {
* Load code for a translator
*/
Zotero.Translator.prototype.getCode = function() {
- if(this.code) return Q(this.code);
+ if(this.code) return Zotero.Promise.resolve(this.code);
var me = this;
if(Zotero.isConnector) {
diff --git a/chrome/content/zotero/xpcom/translation/translators.js b/chrome/content/zotero/xpcom/translation/translators.js
@@ -37,7 +37,7 @@ Zotero.Translators = new function() {
/**
* Initializes translator cache, loading all relevant translators into memory
*/
- this.reinit = Q.async(function() {
+ this.reinit = Zotero.Promise.coroutine(function* () {
var start = (new Date()).getTime();
var transactionStarted = false;
@@ -156,7 +156,7 @@ Zotero.Translators = new function() {
.then(function(source) {
return Zotero.Translators.load(file, infoRe.exec(source)[0], source);
})
- .fail(function() {
+ .catch(function() {
throw "Invalid or missing translator metadata JSON object in " + file.leafName;
});
}
@@ -388,7 +388,7 @@ Zotero.Translators = new function() {
var sameFile = translator && destFile.equals(translator.file);
if (sameFile) return;
- return Q(OS.File.exists(destFile.path))
+ return Zotero.Promise.resolve(OS.File.exists(destFile.path))
.then(function (exists) {
if (exists) {
var msg = "Overwriting translator with same filename '"
@@ -402,7 +402,7 @@ Zotero.Translators = new function() {
.then(function () {
if (!translator) return;
- return Q(OS.File.exists(translator.file.path))
+ return Zotero.Promise.resolve(OS.File.exists(translator.file.path))
.then(function (exists) {
translator.file.remove(false);
});
diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js
@@ -561,10 +561,10 @@ Zotero.Utilities = {
*/
"arrayDiff":function(array1, array2, useIndex) {
if (!Array.isArray(array1)) {
- throw ("array1 is not an array (" + array1 + ")");
+ throw new Error("array1 is not an array (" + array1 + ")");
}
if (!Array.isArray(array2)) {
- throw ("array2 is not an array (" + array2 + ")");
+ throw new Error("array2 is not an array (" + array2 + ")");
}
var val, pos, vals = [];
@@ -578,6 +578,39 @@ Zotero.Utilities = {
return vals;
},
+
+ /**
+ * Determine whether two arrays are identical
+ *
+ * Modified from http://stackoverflow.com/a/14853974
+ *
+ * @return {Boolean}
+ */
+ "arrayEquals": function (array1, array2) {
+ // If either array is a falsy value, return
+ if (!array1 || !array2)
+ return false;
+
+ // Compare lengths - can save a lot of time
+ if (array1.length != array2.length)
+ return false;
+
+ for (var i = 0, l=array1.length; i < l; i++) {
+ // Check if we have nested arrays
+ if (array1[i] instanceof Array && array2[i] instanceof Array) {
+ // Recurse into the nested arrays
+ if (!array1[i].compare(array2[i]))
+ return false;
+ }
+ else if (array1[i] != array2[i]) {
+ // Warning - two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+ return true;
+ },
+
+
/**
* Return new array with values shuffled
*
@@ -648,6 +681,7 @@ Zotero.Utilities = {
return retValues;
},
+
/**
* Generate a random integer between min and max inclusive
*
@@ -1178,7 +1212,7 @@ Zotero.Utilities = {
}
if (maxLevel === undefined) {
- maxLevel = 4;
+ maxLevel = 5;
}
// The padding given at the beginning of the line.
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -30,6 +30,31 @@
* @class Utility functions not made available to translators
*/
Zotero.Utilities.Internal = {
+ /**
+ * Run a function on chunks of a given size of an array's elements.
+ *
+ * @param {Array} arr
+ * @param {Integer} chunkSize
+ * @param {Function} func
+ * @return {Array} The return values from the successive runs
+ */
+ "forEachChunkAsync": Zotero.Promise.coroutine(function* (arr, chunkSize, func) {
+ var retValues = [];
+ var tmpArray = arr.concat();
+ var num = arr.length;
+ var done = 0;
+
+ do {
+ var chunk = tmpArray.splice(0, chunkSize);
+ done += chunk.length;
+ retValues.push(yield func(chunk));
+ }
+ while (done < num);
+
+ return retValues;
+ }),
+
+
/*
* Adapted from http://developer.mozilla.org/en/docs/nsICryptoHash
*
@@ -98,7 +123,7 @@ Zotero.Utilities.Internal = {
"md5Async": function (file, base64) {
const CHUNK_SIZE = 16384;
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
@@ -256,14 +281,14 @@ Zotero.Utilities.Internal = {
*/
"exec":function(cmd, args) {
if(!cmd.isExecutable()) {
- return Q.reject(cmd.path+" is not an executable");
+ return Zotero.Promise.reject(cmd.path+" is not an executable");
}
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(cmd);
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
proc.runwAsync(args, args.length, {"observe":function(subject, topic) {
if(topic !== "process-finished") {
deferred.reject(new Error(cmd.path+" failed"));
@@ -313,6 +338,30 @@ Zotero.Utilities.Internal = {
childWindow = childWindow.parent;
if(childWindow === parentWindow) return true;
}
+ },
+
+
+ /**
+ * A generator that yields promises that delay for the given intervals
+ *
+ * @param {Array<Integer>} intervals An array of intervals in milliseconds
+ * @param {Boolean} [finite=FALSE] If TRUE, repeat the last interval forever
+ */
+ "delayGenerator": function (intervals, finite) {
+ var lastInterval = intervals[intervals.length - 1];
+ while (true) {
+ let interval = intervals.shift();
+ if (interval) {
+ lastInterval = interval;
+ yield Zotero.Promise.delay(interval);
+ }
+ else if (finite) {
+ yield Zotero.Promise.delay(lastInterval);
+ }
+ else {
+ break;
+ }
+ }
}
}
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
@@ -41,7 +41,6 @@ const ZOTERO_CONFIG = {
};
// Commonly used imports accessible anywhere
-Components.utils.import("resource://zotero/q.js");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
@@ -85,6 +84,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
this.isWin;
this.initialURL; // used by Schema to show the changelog on upgrades
+ Components.utils.import("resource://zotero/bluebird.js", this);
this.__defineGetter__('userID', function () {
if (_userID !== undefined) return _userID;
@@ -162,7 +162,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
_locked = lock;
if (!wasLocked && lock) {
- this.unlockDeferred = Q.defer();
+ this.unlockDeferred = Zotero.Promise.defer();
this.unlockPromise = this.unlockDeferred.promise;
}
else if (wasLocked && !lock) {
@@ -237,7 +237,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
return false;
}
- this.initializationDeferred = Q.defer();
+ this.initializationDeferred = Zotero.Promise.defer();
this.initializationPromise = this.initializationDeferred.promise;
this.locked = true;
@@ -257,11 +257,11 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
this.platformMajorVersion = parseInt(appInfo.platformVersion.match(/^[0-9]+/)[0]);
this.isFx = true;
this.isStandalone = Services.appinfo.ID == ZOTERO_CONFIG['GUID'];
- return Q.fcall(function () {
+ return Zotero.Promise.try(function () {
if(Zotero.isStandalone) {
return Services.appinfo.version;
} else {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID(
ZOTERO_CONFIG.GUID,
@@ -494,7 +494,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
}
} else {
Zotero.debug("Loading in full mode");
- return Q.fcall(_initFull)
+ return Zotero.Promise.try(_initFull)
.then(function (success) {
if(!success) return false;
@@ -537,7 +537,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
*
* @return {Promise:Boolean}
*/
- function _initFull() {
+ var _initFull = Zotero.Promise.coroutine(function* () {
Zotero.VersionHeader.init();
// Check for DB restore
@@ -573,17 +573,14 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.HTTP.triggerProxyAuth();
// Add notifier queue callbacks to the DB layer
- Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
- Zotero.DB.addCallback('commit', Zotero.Notifier.commit);
- Zotero.DB.addCallback('rollback', Zotero.Notifier.reset);
+ Zotero.DB.addCallback('begin', function () { return Zotero.Notifier.begin(); });
+ Zotero.DB.addCallback('commit', function () { return Zotero.Notifier.commit(); });
+ Zotero.DB.addCallback('rollback', function () { return Zotero.Notifier.reset(); });
- return Q.fcall(function () {
+ try {
// Require >=2.1b3 database to ensure proper locking
- if (!Zotero.isStandalone) {
- return;
- }
- return Zotero.Schema.getDBVersion('system')
- .then(function (dbSystemVersion) {
+ if (Zotero.isStandalone) {
+ let dbSystemVersion = yield Zotero.Schema.getDBVersion('system');
if (dbSystemVersion > 0 && dbSystemVersion < 31) {
var dir = Zotero.getProfileDirectory();
dir.append('zotero');
@@ -636,11 +633,11 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
throw true;
}
- });
- })
- .then(function () {
- return Zotero.Schema.updateSchema()
- .then(function (updated) {
+ }
+
+ try {
+ var updated = yield Zotero.Schema.updateSchema();
+
Zotero.locked = false;
// Initialize various services
@@ -650,7 +647,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.Server.init();
}
- Zotero.Fulltext.init();
+ yield Zotero.Fulltext.init();
Zotero.Notifier.registerObserver(Zotero.Tags, 'setting');
@@ -666,18 +663,22 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
// Initialize Locate Manager
Zotero.LocateManager.init();
+ Zotero.Collections.init();
+ Zotero.Items.init();
+ Zotero.Searches.init();
+ Zotero.Groups.init();
+
Zotero.Items.startEmptyTrashTimer();
- })
- .catch(function (e) {
+ }
+ catch (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e); // DEBUG: doesn't always work
- if (typeof e == 'string' && e.match('newer than SQL file')) {
- var kbURL = "http://zotero.org/support/kb/newer_db_version";
- var msg = Zotero.localeJoin([
- Zotero.getString('startupError.zoteroVersionIsOlder'),
- Zotero.getString('startupError.zoteroVersionIsOlder.upgrade')
- ]) + "\n\n"
+ if (typeof e == 'string' && (e.indexOf('newer than SQL file') != -1
+ || e.indexOf('Database is incompatible') != -1)) {
+ var kbURL = "https://www.zotero.org/support/kb/newer_db_version";
+ var msg = Zotero.getString('startupError.zoteroVersionIsOlder')
+ + " " + Zotero.getString('startupError.zoteroVersionIsOlder.upgrade') + "\n\n"
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
+ Zotero.getString('general.seeForMoreInformation', kbURL);
Zotero.startupError = msg;
@@ -747,16 +748,15 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
Zotero.startupError = Zotero.getString('startupError.databaseUpgradeError') + "\n\n" + e;
throw true;
- });
- })
- .then(function () {
+ };
+
return true;
- })
- .catch(function (e) {
+ }
+ catch (e) {
Zotero.skipLoading = true;
return false;
- });
- }
+ }
+ });
/**
* Initializes the DB connection
@@ -907,10 +907,10 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
});
}
- return Q();
+ return Zotero.Promise.resolve();
} catch(e) {
Zotero.debug(e);
- return Q.reject(e);
+ return Zotero.Promise.reject(e);
}
}
@@ -1320,7 +1320,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
}
/**
- * Log a JS error to the Mozilla JS error console.
+ * Log a JS error to the Mozilla JS error console and the text console
* @param {Exception} err
*/
function logError(err) {
@@ -1688,6 +1688,20 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
};
};
+
+ this.serial = function (fn) {
+ Components.utils.import("resource://zotero/concurrent-caller.js");
+ var caller = new ConcurrentCaller(1);
+ caller.setLogger(Zotero.debug);
+ return function () {
+ var args = arguments;
+ return caller.fcall(function () {
+ return fn.apply(this, args);
+ }.bind(this));
+ };
+ }
+
+
/**
* Pumps a generator until it yields false. See itemTreeView.js for an example.
*
@@ -1747,13 +1761,22 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
* Pumps a generator until it yields false. Unlike the above, this returns a promise.
*/
this.promiseGenerator = function(generator, ms) {
- var deferred = Q.defer();
+ var deferred = Zotero.Promise.defer();
this.pumpGenerator(generator, ms,
function(e) { deferred.reject(e); },
function(data) { deferred.resolve(data) });
return deferred.promise;
};
+
+ this.spawn = function (generator, thisObject) {
+ if (thisObject) {
+ return Zotero.Promise.coroutine(generator.bind(thisObject))();
+ }
+ return Zotero.Promise.coroutine(generator)();
+ }
+
+
/**
* Emulates the behavior of window.setTimeout, but ensures that callbacks do not get called
* during Zotero.wait()
@@ -2027,22 +2050,24 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
/*
* Clear entries that no longer exist from various tables
*/
- this.purgeDataObjects = function (skipStoragePurge) {
- Zotero.Creators.purge();
- Zotero.Tags.purge();
+ this.purgeDataObjects = Zotero.Promise.coroutine(function* (skipStoragePurge) {
+ yield Zotero.Creators.purge();
+ yield Zotero.Tags.purge();
// TEMP: Disabled until we have async DB (and maybe SQLite FTS)
//Zotero.Fulltext.purgeUnusedWords();
- Zotero.Items.purge();
+ yield Zotero.Items.purge();
// DEBUG: this might not need to be permanent
Zotero.Relations.purge();
- }
+ });
this.reloadDataObjects = function () {
- Zotero.Tags.reloadAll();
- Zotero.Collections.reloadAll();
- Zotero.Creators.reloadAll();
- Zotero.Items.reloadAll();
+ return Zotero.Promise.all([
+ Zotero.Tags.reloadAll(),
+ Zotero.Collections.reloadAll(),
+ Zotero.Creators.reloadAll(),
+ Zotero.Items.reloadAll()
+ ]);
}
@@ -2577,8 +2602,7 @@ Zotero.VersionHeader = {
}
Zotero.DragDrop = {
- currentDragEvent: null,
- currentTarget: null,
+ currentEvent: null,
currentOrientation: 0,
getDataFromDataTransfer: function (dataTransfer, firstOnly) {
@@ -2630,23 +2654,22 @@ Zotero.DragDrop = {
},
- getDragSource: function () {
- var dt = this.currentDragEvent.dataTransfer;
- if (!dt) {
+ getDragSource: function (dataTransfer) {
+ if (!dataTransfer) {
Zotero.debug("Drag data not available", 2);
return false;
}
- // For items, the drag source is the ItemGroup of the parent window
+ // For items, the drag source is the CollectionTreeRow of the parent window
// of the source tree
- if (dt.types.contains("zotero/item")) {
- var sourceNode = dt.mozSourceNode;
+ if (dataTransfer.types.contains("zotero/item")) {
+ var sourceNode = dataTransfer.mozSourceNode;
if (!sourceNode || sourceNode.tagName != 'treechildren'
|| sourceNode.parentElement.id != 'zotero-items-tree') {
return false;
}
var win = sourceNode.ownerDocument.defaultView;
- return win.ZoteroPane.collectionsView.itemGroup;
+ return win.ZoteroPane.collectionsView.selectedTreeRow;
}
else {
return false;
@@ -2654,8 +2677,7 @@ Zotero.DragDrop = {
},
- getDragTarget: function () {
- var event = this.currentDragEvent;
+ getDragTarget: function (event) {
var target = event.target;
if (target.tagName == 'treechildren') {
var tree = target.parentNode;
@@ -2663,7 +2685,7 @@ Zotero.DragDrop = {
let row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
let win = tree.ownerDocument.defaultView;
- return win.ZoteroPane.collectionsView.getItemGroupAtRow(row.value);
+ return win.ZoteroPane.collectionsView.getRow(row.value);
}
}
return false;
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -37,50 +37,29 @@ var ZoteroPane = new function()
//Privileged methods
this.init = init;
this.destroy = destroy;
- this.makeVisible = makeVisible;
this.isShowing = isShowing;
this.isFullScreen = isFullScreen;
this.handleKeyDown = handleKeyDown;
this.handleKeyUp = handleKeyUp;
this.setHighlightedRowsCallback = setHighlightedRowsCallback;
this.handleKeyPress = handleKeyPress;
- this.newItem = newItem;
- this.newCollection = newCollection;
- this.newSearch = newSearch;
- this.openAdvancedSearchWindow = openAdvancedSearchWindow;
- this.toggleTagSelector = toggleTagSelector;
- this.updateTagSelectorSize = updateTagSelectorSize;
- this.getTagSelection = getTagSelection;
- this.clearTagSelection = clearTagSelection;
- this.updateTagFilter = updateTagFilter;
- this.onCollectionSelected = onCollectionSelected;
- this.reindexItem = reindexItem;
- this.duplicateSelectedItem = duplicateSelectedItem;
this.editSelectedCollection = editSelectedCollection;
- this.copySelectedItemsToClipboard = copySelectedItemsToClipboard;
- this.clearQuicksearch = clearQuicksearch;
this.handleSearchKeypress = handleSearchKeypress;
this.handleSearchInput = handleSearchInput;
- this.search = search;
- this.selectItem = selectItem;
this.getSelectedCollection = getSelectedCollection;
this.getSelectedSavedSearch = getSelectedSavedSearch;
this.getSelectedItems = getSelectedItems;
this.getSortedItems = getSortedItems;
this.getSortField = getSortField;
this.getSortDirection = getSortDirection;
- this.buildItemContextMenu = buildItemContextMenu;
this.loadURI = loadURI;
this.setItemsPaneMessage = setItemsPaneMessage;
this.clearItemsPaneMessage = clearItemsPaneMessage;
this.contextPopupShowing = contextPopupShowing;
this.openNoteWindow = openNoteWindow;
- this.addTextToNote = addTextToNote;
this.addAttachmentFromDialog = addAttachmentFromDialog;
- this.viewAttachment = viewAttachment;
this.viewSelectedAttachment = viewSelectedAttachment;
this.showAttachmentNotFoundDialog = showAttachmentNotFoundDialog;
- this.relinkAttachment = relinkAttachment;
this.reportErrors = reportErrors;
this.displayErrorMessage = displayErrorMessage;
@@ -151,6 +130,10 @@ var ZoteroPane = new function()
//Initialize collections view
ZoteroPane_Local.collectionsView = new Zotero.CollectionTreeView();
+ // Handle an error in setTree()/refresh()
+ ZoteroPane_Local.collectionsView.onError = function (e) {
+ ZoteroPane_Local.displayErrorMessage();
+ };
var collectionsTree = document.getElementById('zotero-collections-tree');
collectionsTree.view = ZoteroPane_Local.collectionsView;
collectionsTree.controllers.appendController(new Zotero.CollectionTreeCommandController(collectionsTree));
@@ -165,6 +148,11 @@ var ZoteroPane = new function()
var menu = document.getElementById("contentAreaContextMenu");
menu.addEventListener("popupshowing", ZoteroPane_Local.contextPopupShowing, false);
+ var tagSelector = document.getElementById('zotero-tag-selector');
+ tagSelector.onchange = function () {
+ return ZoteroPane_Local.updateTagFilter();
+ };
+
Zotero.Keys.windowInit(document);
if (Zotero.restoreFromServer) {
@@ -282,7 +270,7 @@ var ZoteroPane = new function()
menuitem.setAttribute("label", itemTypes[i].localized);
menuitem.setAttribute("tooltiptext", "");
let type = itemTypes[i].id;
- menuitem.addEventListener("command", function() { ZoteroPane_Local.newItem(type, {}, null, true); }, false);
+ menuitem.addEventListener("command", function() { ZoteroPane_Local.newItem(type, {}, null, true).done(); }, false);
moreMenu.appendChild(menuitem);
}
}
@@ -320,7 +308,7 @@ var ZoteroPane = new function()
menuitem.setAttribute("label", itemTypes[i].localized);
menuitem.setAttribute("tooltiptext", "");
let type = itemTypes[i].id;
- menuitem.addEventListener("command", function() { ZoteroPane_Local.newItem(type, {}, null, true); }, false);
+ menuitem.addEventListener("command", function() { ZoteroPane_Local.newItem(type, {}, null, true).done(); }, false);
menuitem.className = "zotero-tb-add";
addMenu.insertBefore(menuitem, separator);
}
@@ -355,103 +343,98 @@ var ZoteroPane = new function()
* @return {Boolean} True if Zotero pane should be loaded, false otherwise (if an error
* occurred)
*/
- function makeVisible()
- {
+ this.makeVisible = Zotero.Promise.coroutine(function* () {
if (Zotero.locked) {
Zotero.showZoteroPaneProgressMeter();
}
- Zotero.unlockPromise
- .then(function () {
- Zotero.hideZoteroPaneOverlays();
-
- // If pane not loaded, load it or display an error message
- if (!ZoteroPane_Local.loaded) {
- ZoteroPane_Local.init();
- }
-
- // If Zotero could not be initialized, display an error message and return
- if (!Zotero || Zotero.skipLoading) {
- this.displayStartupError();
- return false;
- }
-
- if(!_madeVisible) {
- this.buildItemTypeSubMenu();
- }
- _madeVisible = true;
-
- this.unserializePersist();
- this.updateToolbarPosition();
- this.updateTagSelectorSize();
-
- // restore saved row selection (for tab switching)
- var containerWindow = (window.ZoteroTab ? window.ZoteroTab.containerWindow : window);
- if(containerWindow.zoteroSavedCollectionSelection) {
- this.collectionsView.rememberSelection(containerWindow.zoteroSavedCollectionSelection);
- delete containerWindow.zoteroSavedCollectionSelection;
- }
+
+ yield Zotero.unlockPromise;
+
+ Zotero.hideZoteroPaneOverlays();
+
+ // If pane not loaded, load it or display an error message
+ if (!ZoteroPane_Local.loaded) {
+ ZoteroPane_Local.init();
+ }
+
+ // If Zotero could not be initialized, display an error message and return
+ if (!Zotero || Zotero.skipLoading) {
+ this.displayStartupError();
+ return false;
+ }
+
+ if(!_madeVisible) {
+ this.buildItemTypeSubMenu();
+ }
+ _madeVisible = true;
+
+ yield this.unserializePersist();
+ this.updateToolbarPosition();
+ this.updateTagSelectorSize();
+
+ // restore saved row selection (for tab switching)
+ var containerWindow = (window.ZoteroTab ? window.ZoteroTab.containerWindow : window);
+ if(containerWindow.zoteroSavedCollectionSelection) {
+ yield this.collectionsView.rememberSelection(containerWindow.zoteroSavedCollectionSelection);
+ delete containerWindow.zoteroSavedCollectionSelection;
+ }
+
+ // restore saved item selection (for tab switching)
+ if(containerWindow.zoteroSavedItemSelection) {
+ let self = this;
+ // hack to restore saved selection after itemTreeView finishes loading
+ window.setTimeout(function() {
+ if(containerWindow.zoteroSavedItemSelection) {
+ yield self.itemsView.rememberSelection(containerWindow.zoteroSavedItemSelection);
+ delete containerWindow.zoteroSavedItemSelection;
+ }
+ }, 51);
+ }
+
+ // Focus the quicksearch on pane open
+ var searchBar = document.getElementById('zotero-tb-search');
+ setTimeout(function () {
+ searchBar.inputField.select();
+ }, 1);
+
+ var d = new Date();
+ yield Zotero.purgeDataObjects();
+ var d2 = new Date();
+ Zotero.debug("Purged data tables in " + (d2 - d) + " ms");
+
+ // Auto-sync on pane open
+ if (Zotero.Prefs.get('sync.autoSync')) {
+ yield Zotero.proxyAuthComplete.delay(1000);
- // restore saved item selection (for tab switching)
- if(containerWindow.zoteroSavedItemSelection) {
- var me = this;
- // hack to restore saved selection after itemTreeView finishes loading
- window.setTimeout(function() {
- if(containerWindow.zoteroSavedItemSelection) {
- me.itemsView.rememberSelection(containerWindow.zoteroSavedItemSelection);
- delete containerWindow.zoteroSavedItemSelection;
- }
- }, 51);
+ if (!Zotero.Sync.Server.enabled) {
+ Zotero.debug('Sync not enabled -- skipping auto-sync', 4);
+ return;
}
- // Focus the quicksearch on pane open
- var searchBar = document.getElementById('zotero-tb-search');
- setTimeout(function () {
- searchBar.inputField.select();
- }, 1);
-
- var d = new Date();
- Zotero.purgeDataObjects();
- var d2 = new Date();
- Zotero.debug("Purged data tables in " + (d2 - d) + " ms");
-
- // Auto-sync on pane open
- if (Zotero.Prefs.get('sync.autoSync')) {
- Zotero.proxyAuthComplete
- .delay(1000)
- .then(function () {
- if (!Zotero.Sync.Server.enabled) {
- Zotero.debug('Sync not enabled -- skipping auto-sync', 4);
- return;
- }
-
- if (Zotero.Sync.Server.syncInProgress || Zotero.Sync.Storage.syncInProgress) {
- Zotero.debug('Sync already running -- skipping auto-sync', 4);
- return;
- }
-
- if (Zotero.Sync.Server.manualSyncRequired) {
- Zotero.debug('Manual sync required -- skipping auto-sync', 4);
- return;
- }
-
- Zotero.Sync.Runner.sync({
- background: true
- });
- })
- .done();
+ if (Zotero.Sync.Server.syncInProgress || Zotero.Sync.Storage.syncInProgress) {
+ Zotero.debug('Sync already running -- skipping auto-sync', 4);
+ return;
}
- // Set sync icon to spinning or not
- //
- // We don't bother setting an error state at open
- if (Zotero.Sync.Server.syncInProgress || Zotero.Sync.Storage.syncInProgress) {
- Zotero.Sync.Runner.setSyncIcon('animate');
+ if (Zotero.Sync.Server.manualSyncRequired) {
+ Zotero.debug('Manual sync required -- skipping auto-sync', 4);
+ return;
}
- return true;
- }.bind(this))
- .done();
- }
+ Zotero.Sync.Runner.sync({
+ background: true
+ });
+ }
+
+ // Set sync icon to spinning or not
+ //
+ // We don't bother setting an error state at open
+ if (Zotero.Sync.Server.syncInProgress || Zotero.Sync.Storage.syncInProgress) {
+ Zotero.Sync.Runner.setSyncIcon('animate');
+ }
+
+ return true;
+ });
/**
* Function to be called before ZoteroPane_Local is hidden. Does not actually hide the Zotero pane.
@@ -662,49 +645,51 @@ var ZoteroPane = new function()
document.getElementById('zotero-tb-search').select();
break;
case 'newItem':
- // Default to most recent item type from here or the
- // New Type menu
- var mru = Zotero.Prefs.get('newItemTypeMRU');
- // Or fall back to 'book'
- var typeID = mru ? mru.split(',')[0] : 2;
- ZoteroPane_Local.newItem(typeID);
- let itemBox = document.getElementById('zotero-editpane-item-box');
- var menu = itemBox.itemTypeMenu;
- var self = this;
- var handleTypeChange = function () {
- self.addItemTypeToNewItemTypeMRU(this.itemTypeMenu.value);
- itemBox.removeHandler('itemtypechange', handleTypeChange);
- };
- // Only update the MRU when the menu is opened for the
- // keyboard shortcut, not on subsequent opens
- var removeTypeChangeHandler = function () {
- itemBox.removeHandler('itemtypechange', handleTypeChange);
- itemBox.itemTypeMenu.firstChild.removeEventListener('popuphiding', removeTypeChangeHandler);
- // Focus the title field after menu closes
- itemBox.focusFirstField();
- };
- itemBox.addHandler('itemtypechange', handleTypeChange);
- itemBox.itemTypeMenu.firstChild.addEventListener('popuphiding', removeTypeChangeHandler);
-
- menu.focus();
- document.getElementById('zotero-editpane-item-box').itemTypeMenu.menupopup.openPopup(menu, "before_start", 0, 0);
+ Zotero.Promise.coroutine(function* () {
+ // Default to most recent item type from here or the
+ // New Type menu
+ var mru = Zotero.Prefs.get('newItemTypeMRU');
+ // Or fall back to 'book'
+ var typeID = mru ? mru.split(',')[0] : 2;
+ yield ZoteroPane_Local.newItem(typeID);
+ let itemBox = document.getElementById('zotero-editpane-item-box');
+ var menu = itemBox.itemTypeMenu;
+ var self = this;
+ var handleTypeChange = function () {
+ self.addItemTypeToNewItemTypeMRU(this.itemTypeMenu.value);
+ itemBox.removeHandler('itemtypechange', handleTypeChange);
+ };
+ // Only update the MRU when the menu is opened for the
+ // keyboard shortcut, not on subsequent opens
+ var removeTypeChangeHandler = function () {
+ itemBox.removeHandler('itemtypechange', handleTypeChange);
+ itemBox.itemTypeMenu.firstChild.removeEventListener('popuphiding', removeTypeChangeHandler);
+ // Focus the title field after menu closes
+ itemBox.focusFirstField();
+ };
+ itemBox.addHandler('itemtypechange', handleTypeChange);
+ itemBox.itemTypeMenu.firstChild.addEventListener('popuphiding', removeTypeChangeHandler);
+
+ menu.focus();
+ document.getElementById('zotero-editpane-item-box').itemTypeMenu.menupopup.openPopup(menu, "before_start", 0, 0);
+ })();
break;
case 'newNote':
// If a regular item is selected, use that as the parent.
// If a child item is selected, use its parent as the parent.
// Otherwise create a standalone note.
- var parent = false;
+ var parentKey = false;
var items = ZoteroPane_Local.getSelectedItems();
if (items.length == 1) {
if (items[0].isRegularItem()) {
- parent = items[0].id;
+ parentKey = items[0].key;
}
else {
- parent = items[0].getSource();
+ parentKey = items[0].parentItemKey;
}
}
// Use key that's not the modifier as the popup toggle
- ZoteroPane_Local.newNote(event.altKey, parent);
+ ZoteroPane_Local.newNote(event.altKey, parentKey);
break;
case 'toggleTagSelector':
ZoteroPane_Local.toggleTagSelector();
@@ -721,6 +706,9 @@ var ZoteroPane = new function()
case 'importFromClipboard':
Zotero_File_Interface.importFromClipboard();
break;
+ case 'sync':
+ Zotero.Sync.Runner.sync();
+ break;
default:
throw ('Command "' + command + '" not found in ZoteroPane_Local.handleKeyDown()');
}
@@ -739,7 +727,7 @@ var ZoteroPane = new function()
*
* _data_ is an optional object with field:value for itemData
*/
- function newItem(typeID, data, row, manual)
+ this.newItem = Zotero.Promise.coroutine(function* (typeID, data, row, manual)
{
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
@@ -757,46 +745,42 @@ var ZoteroPane = new function()
}
if (row !== undefined && row !== null) {
- var itemGroup = this.collectionsView._getItemAtRow(row);
- var libraryID = itemGroup.ref.libraryID;
+ var collectionTreeRow = this.collectionsView.getRow(row);
+ var libraryID = collectionTreeRow.ref.libraryID;
}
else {
var libraryID = 0;
- var itemGroup = null;
- }
-
- Zotero.DB.beginTransaction();
-
- var item = new Zotero.Item(typeID);
- item.libraryID = libraryID;
- for (var i in data) {
- item.setField(i, data[i]);
+ var collectionTreeRow = null;
}
- var itemID = item.save();
- if (itemGroup && itemGroup.isCollection()) {
- itemGroup.ref.addItem(itemID);
- }
-
- Zotero.DB.commitTransaction();
+ let itemID;
+ yield Zotero.DB.executeTransaction(function () {
+ var item = new Zotero.Item(typeID);
+ item.libraryID = libraryID;
+ for (var i in data) {
+ item.setField(i, data[i]);
+ }
+ itemID = yield item.save();
+
+ if (collectionTreeRow && collectionTreeRow.isCollection()) {
+ collectionTreeRow.ref.addItem(itemID);
+ }
+ });
//set to Info tab
document.getElementById('zotero-view-item').selectedIndex = 0;
if (manual) {
- // Focus the title field
- if (this.selectItem(itemID)) {
- setTimeout(function () {
- document.getElementById('zotero-editpane-item-box').focusFirstField();
- }, 0);
- }
-
// Update most-recently-used list for New Item menu
this.addItemTypeToNewItemTypeMRU(typeID);
+
+ yield this.selectItem(itemID);
+ // Focus the title field
+ document.getElementById('zotero-editpane-item-box').focusFirstField();
}
return Zotero.Items.get(itemID);
- }
+ });
this.addItemTypeToNewItemTypeMRU = function (itemTypeID) {
@@ -816,8 +800,7 @@ var ZoteroPane = new function()
}
- function newCollection(parent)
- {
+ this.newCollection = Zotero.Promise.method(function (parentKey) {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return false;
@@ -828,11 +811,16 @@ var ZoteroPane = new function()
return;
}
+ var libraryID = this.getSelectedLibraryID();
+
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
-
- var untitled = Zotero.DB.getNextName('collections', 'collectionName',
- Zotero.getString('pane.collections.untitled'));
+ var untitled = Zotero.DB.getNextName(
+ libraryID,
+ 'collections',
+ 'collectionName',
+ Zotero.getString('pane.collections.untitled')
+ );
var newName = { value: untitled };
var result = promptService.prompt(window,
@@ -850,11 +838,11 @@ var ZoteroPane = new function()
}
var collection = new Zotero.Collection;
- collection.libraryID = this.getSelectedLibraryID();
+ collection.libraryID = libraryID;
collection.name = newName.value;
- collection.parent = parent;
- collection.save();
- }
+ collection.parentKey = parentKey;
+ return collection.save();
+ });
this.newGroup = function () {
@@ -862,8 +850,7 @@ var ZoteroPane = new function()
}
- function newSearch()
- {
+ this.newSearch = Zotero.Promise.coroutine(function* () {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return false;
@@ -871,14 +858,14 @@ var ZoteroPane = new function()
var s = new Zotero.Search();
s.libraryID = this.getSelectedLibraryID();
- s.addCondition('title', 'contains', '');
+ yield s.addCondition('title', 'contains', '');
var untitled = Zotero.getString('pane.collections.untitled');
untitled = Zotero.DB.getNextName('savedSearches', 'savedSearchName',
Zotero.getString('pane.collections.untitled'));
var io = {dataIn: {search: s, name: untitled}, dataOut: null};
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
- }
+ });
this.setVirtual = function (libraryID, mode, show) {
@@ -967,7 +954,7 @@ var ZoteroPane = new function()
}
- function openAdvancedSearchWindow() {
+ this.openAdvancedSearchWindow = Zotero.Promise.coroutine(function* () {
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var enumerator = wm.getEnumerator('zotero:search');
@@ -982,13 +969,14 @@ var ZoteroPane = new function()
var s = new Zotero.Search();
s.libraryID = this.getSelectedLibraryID();
- s.addCondition('title', 'contains', '');
+ yield s.addCondition('title', 'contains', '');
+
var io = {dataIn: {search: s}, dataOut: null};
window.openDialog('chrome://zotero/content/advancedSearch.xul', '', 'chrome,dialog=no,centerscreen', io);
- }
+ });
- function toggleTagSelector(){
+ this.toggleTagSelector = Zotero.Promise.coroutine(function* () {
var tagSelector = document.getElementById('zotero-tag-selector');
var showing = tagSelector.getAttribute('collapsed') == 'true';
@@ -998,17 +986,17 @@ var ZoteroPane = new function()
// If showing, set scope to items in current view
// and focus filter textbox
if (showing) {
- _setTagScope();
+ yield this.setTagScope();
tagSelector.focusTextbox();
}
// If hiding, clear selection
else {
tagSelector.uninit();
}
- }
+ });
- function updateTagSelectorSize() {
+ this.updateTagSelectorSize = function () {
//Zotero.debug('Updating tag selector size');
var zoteroPane = document.getElementById('zotero-pane-stack');
var splitter = document.getElementById('zotero-tags-splitter');
@@ -1070,28 +1058,30 @@ var ZoteroPane = new function()
}
- function getTagSelection(){
+ function getTagSelection () {
var tagSelector = document.getElementById('zotero-tag-selector');
return tagSelector.selection ? tagSelector.selection : {};
}
- function clearTagSelection() {
- if (!Zotero.Utilities.isEmpty(this.getTagSelection())) {
- var tagSelector = document.getElementById('zotero-tag-selector');
- tagSelector.clearAll();
+ this.clearTagSelection = Zotero.Promise.coroutine(function* () {
+ if (Zotero.Utilities.isEmpty(getTagSelection())) {
+ return false;
}
- }
+ var tagSelector = document.getElementById('zotero-tag-selector');
+ yield tagSelector.clearAll();
+ return true;
+ });
/*
* Sets the tag filter on the items view
*/
- function updateTagFilter(){
+ this.updateTagFilter = Zotero.Promise.coroutine(function* () {
if (this.itemsView) {
- this.itemsView.setFilter('tags', getTagSelection());
+ yield this.itemsView.setFilter('tags', getTagSelection());
}
- }
+ });
/*
@@ -1099,53 +1089,55 @@ var ZoteroPane = new function()
*
* Passed to the items tree to trigger on changes
*/
- function _setTagScope() {
- var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex);
+ this.setTagScope = Zotero.Promise.coroutine(function* () {
+ var collectionTreeRow = self.getCollectionTreeRow();
var tagSelector = document.getElementById('zotero-tag-selector');
if (!tagSelector.getAttribute('collapsed') ||
tagSelector.getAttribute('collapsed') == 'false') {
Zotero.debug('Updating tag selector with current tags');
- if (itemGroup.editable) {
+ if (collectionTreeRow.editable) {
tagSelector.mode = 'edit';
}
else {
tagSelector.mode = 'view';
}
- tagSelector.libraryID = itemGroup.ref.libraryID;
- tagSelector.scope = itemGroup.getChildTags();
+ tagSelector.collectionTreeRow = collectionTreeRow;
+ tagSelector.updateScope = self.setTagScope;
+ tagSelector.libraryID = collectionTreeRow.ref.libraryID;
+ tagSelector.scope = yield collectionTreeRow.getChildTags();
}
- }
+ });
- function onCollectionSelected()
- {
- if (this.itemsView)
- {
+ this.onCollectionSelected = Zotero.Promise.coroutine(function* () {
+ var collectionTreeRow = this.getCollectionTreeRow();
+
+ if (this.itemsView.collectionTreeRow == collectionTreeRow) {
+ Zotero.debug("Collection selection hasn't changed");
+ return;
+ }
+
+ if (this.itemsView) {
this.itemsView.unregister();
document.getElementById('zotero-items-tree').view = this.itemsView = null;
}
- // Clear quick search and tag selector when switching views
- document.getElementById('zotero-tb-search').value = "";
- document.getElementById('zotero-tag-selector').clearAll();
-
if (this.collectionsView.selection.count != 1) {
- document.getElementById('zotero-items-tree').view = this.itemsView = null;
return;
}
- // this.collectionsView.selection.currentIndex != -1
-
- var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ // Clear quick search and tag selector when switching views
+ document.getElementById('zotero-tb-search').value = "";
+ yield document.getElementById('zotero-tag-selector').clearAll();
// Not necessary with seltype="cell", which calls nsITreeView::isSelectable()
- /*if (itemgroup.isSeparator()) {
+ /*if (collectionTreeRow.isSeparator()) {
document.getElementById('zotero-items-tree').view = this.itemsView = null;
return;
}*/
- itemgroup.setSearch('');
- itemgroup.setTags(getTagSelection());
+ collectionTreeRow.setSearch('');
+ collectionTreeRow.setTags(getTagSelection());
// Enable or disable toolbar icons and menu options as necessary
const disableIfNoEdit = [
@@ -1163,11 +1155,11 @@ var ZoteroPane = new function()
// If a trash is selected, new collection depends on the
// editability of the library
- if (itemgroup.isTrash() &&
+ if (collectionTreeRow.isTrash() &&
disableIfNoEdit[i] == 'cmd_zotero_newCollection') {
- if (itemgroup.ref.libraryID) {
+ if (collectionTreeRow.ref.libraryID) {
var overrideEditable =
- Zotero.Libraries.isEditable(itemgroup.ref.libraryID);
+ Zotero.Libraries.isEditable(collectionTreeRow.ref.libraryID);
}
else {
var overrideEditable = true;
@@ -1177,224 +1169,235 @@ var ZoteroPane = new function()
var overrideEditable = false;
}
- if (itemgroup.editable || overrideEditable) {
+ if (collectionTreeRow.editable || overrideEditable) {
if(el.hasAttribute("disabled")) el.removeAttribute("disabled");
} else {
el.setAttribute("disabled", "true");
}
}
+ this.itemsView = new Zotero.ItemTreeView(collectionTreeRow);
+ this.itemsView.onError = function () {
+ ZoteroPane_Local.displayErrorMessage();
+ };
+ this.itemsView.addCallback(this.setTagScope);
+ document.getElementById('zotero-items-tree').view = this.itemsView;
+ this.itemsView.selection.clearSelection();
+
+ // Add events to treecolpicker to update menu before showing/hiding
try {
- Zotero.UnresponsiveScriptIndicator.disable();
- this.itemsView = new Zotero.ItemTreeView(itemgroup);
- this.itemsView.addCallback(_setTagScope);
- document.getElementById('zotero-items-tree').view = this.itemsView;
- this.itemsView.selection.clearSelection();
-
- // Add events to treecolpicker to update menu before showing/hiding
- try {
- let treecols = document.getElementById('zotero-items-columns-header');
- let treecolpicker = treecols.boxObject.firstChild.nextSibling;
- let menupopup = treecolpicker.boxObject.firstChild.nextSibling;
- let attr = menupopup.getAttribute('onpopupshowing');
- if (attr.indexOf('Zotero') == -1) {
- menupopup.setAttribute('onpopupshowing', 'ZoteroPane.itemsView.onColumnPickerShowing(event);')
- // Keep whatever else is there
- + ' ' + attr;
- menupopup.setAttribute('onpopuphidden', 'ZoteroPane.itemsView.onColumnPickerHidden(event);')
- // Keep whatever else is there
- + ' ' + menupopup.getAttribute('onpopuphidden');
- }
- }
- catch (e) {
- Zotero.debug(e);
+ let treecols = document.getElementById('zotero-items-columns-header');
+ let treecolpicker = treecols.boxObject.firstChild.nextSibling;
+ let menupopup = treecolpicker.boxObject.firstChild.nextSibling;
+ let attr = menupopup.getAttribute('onpopupshowing');
+ if (attr.indexOf('Zotero') == -1) {
+ menupopup.setAttribute('onpopupshowing', 'ZoteroPane.itemsView.onColumnPickerShowing(event);')
+ // Keep whatever else is there
+ + ' ' + attr;
+ menupopup.setAttribute('onpopuphidden', 'ZoteroPane.itemsView.onColumnPickerHidden(event);')
+ // Keep whatever else is there
+ + ' ' + menupopup.getAttribute('onpopuphidden');
}
}
- finally {
- Zotero.UnresponsiveScriptIndicator.enable();
+ catch (e) {
+ Zotero.debug(e);
}
- Zotero.Prefs.set('lastViewedFolder', itemgroup.id);
- }
+ Zotero.Prefs.set('lastViewedFolder', collectionTreeRow.id);
+ });
- this.getItemGroup = function () {
- if (!this.collectionsView.selection) {
+ this.getCollectionTreeRow = function () {
+ if (!this.collectionsView.selection.count) {
return false;
}
- return this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ return this.collectionsView.getRow(this.collectionsView.selection.currentIndex);
}
+ /**
+ * @return {Promise}
+ */
this.itemSelected = function (event) {
- if (!Zotero.stateCheck()) {
- this.displayErrorMessage();
- return;
- }
-
- // DEBUG: Is this actually possible?
- if (!this.itemsView) {
- Components.utils.reportError("this.itemsView is not defined in ZoteroPane.itemSelected()");
- }
-
- // Display restore button if items selected in Trash
- if (this.itemsView.selection.count) {
- document.getElementById('zotero-item-restore-button').hidden
- = !this.itemsView._itemGroup.isTrash()
- || _nonDeletedItemsSelected(this.itemsView);
- }
- else {
- document.getElementById('zotero-item-restore-button').hidden = true;
- }
-
- var tabs = document.getElementById('zotero-view-tabbox');
-
- // save note when switching from a note
- if(document.getElementById('zotero-item-pane-content').selectedIndex == 2) {
- document.getElementById('zotero-note-editor').save();
- }
-
- var itemGroup = this.getItemGroup();
-
- // Single item selected
- if (this.itemsView.selection.count == 1 && this.itemsView.selection.currentIndex != -1)
- {
- var item = this.itemsView.getSelectedItems()[0];
+ return Zotero.spawn(function* () {
+ if (!Zotero.stateCheck()) {
+ this.displayErrorMessage();
+ return;
+ }
- if (item.isNote()) {
- var noteEditor = document.getElementById('zotero-note-editor');
- noteEditor.mode = this.collectionsView.editable ? 'edit' : 'view';
-
- var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
-
- noteEditor.parent = null;
- noteEditor.item = item;
-
- // If loading new or different note, disable undo while we repopulate the text field
- // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
- // undo content from another note into the current one.
- if (clearUndo) {
- noteEditor.clearUndo();
- }
-
- var viewButton = document.getElementById('zotero-view-note-button');
- if (this.collectionsView.editable) {
- viewButton.hidden = false;
- viewButton.setAttribute('noteID', item.id);
- if (item.getSource()) {
- viewButton.setAttribute('sourceID', item.getSource());
- }
- else {
- viewButton.removeAttribute('sourceID');
- }
- }
- else {
- viewButton.hidden = true;
- }
-
- document.getElementById('zotero-item-pane-content').selectedIndex = 2;
+ // Display restore button if items selected in Trash
+ if (this.itemsView.selection.count) {
+ document.getElementById('zotero-item-restore-button').hidden
+ = !this.getCollectionTreeRow().isTrash()
+ || _nonDeletedItemsSelected(this.itemsView);
+ }
+ else {
+ document.getElementById('zotero-item-restore-button').hidden = true;
}
- else if (item.isAttachment()) {
- var attachmentBox = document.getElementById('zotero-attachment-box');
- attachmentBox.mode = this.collectionsView.editable ? 'edit' : 'view';
- attachmentBox.item = item;
-
- document.getElementById('zotero-item-pane-content').selectedIndex = 3;
+ var tabs = document.getElementById('zotero-view-tabbox');
+
+ // save note when switching from a note
+ if(document.getElementById('zotero-item-pane-content').selectedIndex == 2) {
+ // TODO: only try to save when selected item is different
+ yield document.getElementById('zotero-note-editor').save();
}
- // Regular item
- else {
- var isCommons = itemGroup.isBucket();
-
- document.getElementById('zotero-item-pane-content').selectedIndex = 1;
- var tabBox = document.getElementById('zotero-view-tabbox');
- var pane = tabBox.selectedIndex;
- tabBox.firstChild.hidden = isCommons;
+ var collectionTreeRow = this.getCollectionTreeRow();
+
+ // Single item selected
+ if (this.itemsView.selection.count == 1 && this.itemsView.selection.currentIndex != -1)
+ {
+ var item = this.itemsView.getSelectedItems()[0];
- var button = document.getElementById('zotero-item-show-original');
- if (isCommons) {
- button.hidden = false;
- button.disabled = !this.getOriginalItem();
- }
- else {
- button.hidden = true;
+ if (item.isNote()) {
+ var noteEditor = document.getElementById('zotero-note-editor');
+ noteEditor.mode = this.collectionsView.editable ? 'edit' : 'view';
+
+ var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
+
+ noteEditor.parent = null;
+ noteEditor.item = item;
+
+ // If loading new or different note, disable undo while we repopulate the text field
+ // so Undo doesn't end up clearing the field. This also ensures that Undo doesn't
+ // undo content from another note into the current one.
+ if (clearUndo) {
+ noteEditor.clearUndo();
+ }
+
+ var viewButton = document.getElementById('zotero-view-note-button');
+ if (this.collectionsView.editable) {
+ viewButton.hidden = false;
+ viewButton.setAttribute('noteID', item.id);
+ if (!item.isTopLevelItem()) {
+ viewButton.setAttribute('parentItemID', item.parentItemID);
+ }
+ else {
+ viewButton.removeAttribute('parentItemID');
+ }
+ }
+ else {
+ viewButton.hidden = true;
+ }
+
+ document.getElementById('zotero-item-pane-content').selectedIndex = 2;
}
- if (this.collectionsView.editable) {
- ZoteroItemPane.viewItem(item, null, pane);
- tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex;
+ else if (item.isAttachment()) {
+ var attachmentBox = document.getElementById('zotero-attachment-box');
+ attachmentBox.mode = this.collectionsView.editable ? 'edit' : 'view';
+ attachmentBox.item = item;
+
+ document.getElementById('zotero-item-pane-content').selectedIndex = 3;
}
+
+ // Regular item
else {
- ZoteroItemPane.viewItem(item, 'view', pane);
- tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex;
- }
- }
- }
- // Zero or multiple items selected
- else {
- var count = this.itemsView.selection.count;
-
- // Display duplicates merge interface in item pane
- if (itemGroup.isDuplicates()) {
- if (!itemGroup.editable) {
- if (count) {
- var msg = Zotero.getString('pane.item.duplicates.writeAccessRequired');
+ var isCommons = collectionTreeRow.isBucket();
+
+ document.getElementById('zotero-item-pane-content').selectedIndex = 1;
+ var tabBox = document.getElementById('zotero-view-tabbox');
+ var pane = tabBox.selectedIndex;
+ tabBox.firstChild.hidden = isCommons;
+
+ var button = document.getElementById('zotero-item-show-original');
+ if (isCommons) {
+ button.hidden = false;
+ button.disabled = !this.getOriginalItem();
}
else {
- var msg = Zotero.getString('pane.item.selected.zero');
+ button.hidden = true;
}
- this.setItemPaneMessage(msg);
- }
- else if (count) {
- document.getElementById('zotero-item-pane-content').selectedIndex = 4;
- // Load duplicates UI code
- if (typeof Zotero_Duplicates_Pane == 'undefined') {
- Zotero.debug("Loading duplicatesMerge.js");
- Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Components.interfaces.mozIJSSubScriptLoader)
- .loadSubScript("chrome://zotero/content/duplicatesMerge.js");
+ if (this.collectionsView.editable) {
+ yield ZoteroItemPane.viewItem(item, null, pane);
+ tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex;
+ }
+ else {
+ yield ZoteroItemPane.viewItem(item, 'view', pane);
+ tabs.selectedIndex = document.getElementById('zotero-view-item').selectedIndex;
}
-
- // On a Select All of more than a few items, display a row
- // count instead of the usual item type mismatch error
- var displayNumItemsOnTypeError = count > 5 && count == this.itemsView.rowCount;
-
- // Initialize the merge pane with the selected items
- Zotero_Duplicates_Pane.setItems(this.getSelectedItems(), displayNumItemsOnTypeError);
- }
- else {
- var msg = Zotero.getString('pane.item.duplicates.selectToMerge');
- this.setItemPaneMessage(msg);
}
}
- // Display label in the middle of the item pane
+ // Zero or multiple items selected
else {
- if (count) {
- var msg = Zotero.getString('pane.item.selected.multiple', count);
+ var count = this.itemsView.selection.count;
+
+ // Display duplicates merge interface in item pane
+ if (collectionTreeRow.isDuplicates()) {
+ if (!collectionTreeRow.editable) {
+ if (count) {
+ var msg = Zotero.getString('pane.item.duplicates.writeAccessRequired');
+ }
+ else {
+ var msg = Zotero.getString('pane.item.selected.zero');
+ }
+ this.setItemPaneMessage(msg);
+ }
+ else if (count) {
+ document.getElementById('zotero-item-pane-content').selectedIndex = 4;
+
+ // Load duplicates UI code
+ if (typeof Zotero_Duplicates_Pane == 'undefined') {
+ Zotero.debug("Loading duplicatesMerge.js");
+ Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://zotero/content/duplicatesMerge.js");
+ }
+
+ // On a Select All of more than a few items, display a row
+ // count instead of the usual item type mismatch error
+ var displayNumItemsOnTypeError = count > 5 && count == this.itemsView.rowCount;
+
+ // Initialize the merge pane with the selected items
+ Zotero_Duplicates_Pane.setItems(this.getSelectedItems(), displayNumItemsOnTypeError);
+ }
+ else {
+ var msg = Zotero.getString('pane.item.duplicates.selectToMerge');
+ this.setItemPaneMessage(msg);
+ }
}
+ // Display label in the middle of the item pane
else {
- var rowCount = this.itemsView.rowCount;
- var str = 'pane.item.unselected.';
- switch (rowCount){
- case 0:
- str += 'zero';
- break;
- case 1:
- str += 'singular';
- break;
- default:
- str += 'plural';
- break;
+ if (count) {
+ var msg = Zotero.getString('pane.item.selected.multiple', count);
+ }
+ else {
+ var rowCount = this.itemsView.rowCount;
+ var str = 'pane.item.unselected.';
+ switch (rowCount){
+ case 0:
+ str += 'zero';
+ break;
+ case 1:
+ str += 'singular';
+ break;
+ default:
+ str += 'plural';
+ break;
+ }
+ var msg = Zotero.getString(str, [rowCount]);
}
- var msg = Zotero.getString(str, [rowCount]);
+
+ this.setItemPaneMessage(msg);
}
-
- this.setItemPaneMessage(msg);
}
- }
- }
+ }, this)
+ .then(function () {
+ // See note in itemTreeView.js::selectItem()
+ if (this.itemsView._itemSelectedPromiseResolver) {
+ this.itemsView._itemSelectedPromiseResolver.resolve();
+ }
+ }.bind(this))
+ .catch(function (e) {
+ Zotero.debug(e, 1);
+ if (this.itemsView._itemSelectedPromiseResolver) {
+ this.itemsView._itemSelectedPromiseResolver.reject(e);
+ }
+ throw e;
+ }.bind(this));;
+ };
/**
@@ -1409,7 +1412,7 @@ var ZoteroPane = new function()
for (var i=0, len=itemsView.selection.getRangeCount(); i<len; i++) {
itemsView.selection.getRangeAt(i, start, end);
for (var j=start.value; j<=end.value; j++) {
- if (!itemsView._getItemAtRow(j).ref.deleted) {
+ if (!itemsView.getRow(j).ref.deleted) {
return true;
}
}
@@ -1418,11 +1421,14 @@ var ZoteroPane = new function()
}
+ /**
+ * @return {Promise}
+ */
this.updateNoteButtonMenu = function () {
var items = ZoteroPane_Local.getSelectedItems();
- var button = document.getElementById('zotero-tb-add-child-note');
- button.disabled = !this.canEdit() ||
- !(items.length == 1 && (items[0].isRegularItem() || !items[0].isTopLevelItem()));
+ var cmd = document.getElementById('cmd_zotero_newChildNote');
+ cmd.setAttribute("disabled", !this.canEdit() ||
+ !(items.length == 1 && (items[0].isRegularItem() || !items[0].isTopLevelItem())));
}
@@ -1438,7 +1444,7 @@ var ZoteroPane = new function()
return;
}
- var itemgroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ var collectionTreeRow = this.collectionsView.selectedTreeRow;
var canEditFiles = this.canEditFiles();
var prefix = "menuitem-iconic zotero-menuitem-attachments-";
@@ -1449,7 +1455,7 @@ var ZoteroPane = new function()
switch (className) {
case prefix + 'link':
- node.disabled = itemgroup.isWithinGroup();
+ node.disabled = collectionTreeRow.isWithinGroup();
break;
case prefix + 'snapshot':
@@ -1492,7 +1498,10 @@ var ZoteroPane = new function()
}
- function reindexItem() {
+ /**
+ * @return {Promise}
+ */
+ this.reindexItem = Zotero.Promise.coroutine(function* () {
var items = this.getSelectedItems();
if (!items) {
return;
@@ -1512,44 +1521,47 @@ var ZoteroPane = new function()
if (checkPDF) {
var installed = this.checkPDFConverter();
if (!installed) {
- document.getElementById('zotero-attachment-box').updateItemIndexedState();
+ yield document.getElementById('zotero-attachment-box').updateItemIndexedState();
return;
}
}
- Zotero.Fulltext.indexItems(itemIDs, true);
- document.getElementById('zotero-attachment-box').updateItemIndexedState();
- }
+ yield Zotero.Fulltext.indexItems(itemIDs, true);
+ yield document.getElementById('zotero-attachment-box').updateItemIndexedState();
+ });
- function duplicateSelectedItem() {
- if (!this.canEdit()) {
- this.displayCannotEditLibraryMessage();
+ /**
+ * @return {Promise}
+ */
+ this.duplicateSelectedItem = Zotero.Promise.coroutine(function* () {
+ var self = this;
+ if (!self.canEdit()) {
+ self.displayCannotEditLibraryMessage();
return;
}
- var item = this.getSelectedItems()[0];
-
- Zotero.DB.beginTransaction();
-
- // Create new unsaved clone item in target library
- var newItem = new Zotero.Item(item.itemTypeID);
- newItem.libraryID = item.libraryID;
- // DEBUG: save here because clone() doesn't currently work on unsaved tagged items
- var id = newItem.save();
-
- var newItem = Zotero.Items.get(id);
- item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
- newItem.save();
+ var item = self.getSelectedItems()[0];
+ var newItem;
- if (this.itemsView._itemGroup.isCollection() && !newItem.getSource()) {
- this.itemsView._itemGroup.ref.addItem(newItem.id);
- }
-
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function () {
+ // Create new unsaved clone item in target library
+ newItem = new Zotero.Item(item.itemTypeID);
+ newItem.libraryID = item.libraryID;
+ // DEBUG: save here because clone() doesn't currently work on unsaved tagged items
+ var id = yield newItem.save();
+
+ var newItem = yield Zotero.Items.getAsync(id);
+ item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
+ yield newItem.save();
+
+ if (self.collectionsView.selectedTreeRow.isCollection() && newItem.isTopLevelItem()) {
+ self.collectionsView.selectedTreeRow.ref.addItem(newItem.id);
+ }
+ });
- this.selectItem(newItem.id);
- }
+ yield self.selectItem(newItem.id);
+ });
this.deleteSelectedItem = function () {
@@ -1568,9 +1580,9 @@ var ZoteroPane = new function()
if (!this.itemsView || !this.itemsView.selection.count) {
return;
}
- var itemGroup = this.itemsView._itemGroup;
+ var collectionTreeRow = this.collectionsView.selectedTreeRow;
- if (!itemGroup.isTrash() && !itemGroup.isBucket() && !this.canEdit()) {
+ if (!collectionTreeRow.isTrash() && !collectionTreeRow.isBucket() && !this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
@@ -1588,39 +1600,39 @@ var ZoteroPane = new function()
)
};
- if (itemGroup.isLibrary(true)) {
+ if (collectionTreeRow.isLibrary(true)) {
// In library, don't prompt if meta key was pressed
var prompt = (force && !fromMenu) ? false : toTrash;
}
- else if (itemGroup.isCollection()) {
+ else if (collectionTreeRow.isCollection()) {
// In collection, only prompt if trashing
var prompt = force ? toTrash : false;
}
- else if (itemGroup.isSearch() || itemGroup.isUnfiled() || itemGroup.isDuplicates()) {
+ else if (collectionTreeRow.isSearch() || collectionTreeRow.isUnfiled() || collectionTreeRow.isDuplicates()) {
if (!force) {
return;
}
var prompt = toTrash;
}
// Do nothing in trash view if any non-deleted items are selected
- else if (itemGroup.isTrash()) {
+ else if (collectionTreeRow.isTrash()) {
var start = {};
var end = {};
for (var i=0, len=this.itemsView.selection.getRangeCount(); i<len; i++) {
this.itemsView.selection.getRangeAt(i, start, end);
for (var j=start.value; j<=end.value; j++) {
- if (!this.itemsView._getItemAtRow(j).ref.deleted) {
+ if (!this.itemsView.getRow(j).ref.deleted) {
return;
}
}
}
var prompt = toDelete;
}
- else if (itemGroup.isBucket()) {
+ else if (collectionTreeRow.isBucket()) {
var prompt = toDelete;
}
// Do nothing in share views
- else if (itemGroup.isShare()) {
+ else if (collectionTreeRow.isShare()) {
return;
}
@@ -1653,16 +1665,16 @@ var ZoteroPane = new function()
this.deleteSelectedCollection = function (deleteItems) {
- var itemGroup = this.getItemGroup();
+ var collectionTreeRow = this.getCollectionTreeRow();
// Remove virtual duplicates collection
- if (itemGroup.isDuplicates()) {
- this.setVirtual(itemGroup.ref.libraryID, 'duplicates', false);
+ if (collectionTreeRow.isDuplicates()) {
+ this.setVirtual(collectionTreeRow.ref.libraryID, 'duplicates', false);
return;
}
// Remove virtual unfiled collection
- else if (itemGroup.isUnfiled()) {
- this.setVirtual(itemGroup.ref.libraryID, 'unfiled', false);
+ else if (collectionTreeRow.isUnfiled()) {
+ this.setVirtual(collectionTreeRow.ref.libraryID, 'unfiled', false);
return;
}
@@ -1677,7 +1689,7 @@ var ZoteroPane = new function()
buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
if (this.collectionsView.selection.count == 1) {
- if (itemGroup.isCollection())
+ if (collectionTreeRow.isCollection())
{
if (deleteItems) {
var index = ps.confirmEx(
@@ -1705,7 +1717,7 @@ var ZoteroPane = new function()
this.collectionsView.deleteSelection(deleteItems);
}
}
- else if (itemGroup.isSearch())
+ else if (collectionTreeRow.isSearch())
{
var index = ps.confirmEx(
@@ -1727,9 +1739,9 @@ var ZoteroPane = new function()
// Currently used only for Commons to find original linked item
this.getOriginalItem = function () {
var item = this.getSelectedItems()[0];
- var itemGroup = this.getItemGroup();
+ var collectionTreeRow = this.getCollectionTreeRow();
// TEMP: Commons buckets only
- return itemGroup.ref.getLocalItem(item);
+ return collectionTreeRow.ref.getLocalItem(item);
}
@@ -1739,26 +1751,34 @@ var ZoteroPane = new function()
Zotero.debug("Original item not found");
return;
}
- this.selectItem(item.id);
+ this.selectItem(item.id).done();
}
- this.restoreSelectedItems = function () {
+ /**
+ * @return {Promise}
+ */
+ this.restoreSelectedItems = Zotero.Promise.coroutine(function* () {
var items = this.getSelectedItems();
if (!items) {
return;
}
- Zotero.DB.beginTransaction();
- for (var i=0; i<items.length; i++) {
- items[i].deleted = false;
- items[i].save();
- }
- Zotero.DB.commitTransaction();
- }
+ yield Zotero.DB.executeTransaction(function* () {
+ for (let i=0; i<items.length; i++) {
+ items[i].deleted = false;
+ items[i].save({
+ skipDateModifiedUpdate: true
+ });
+ }
+ }.bind(this));
+ });
- this.emptyTrash = function () {
+ /**
+ * @return {Promise}
+ */
+ this.emptyTrash = Zotero.Promise.coroutine(function* () {
var libraryID = this.getSelectedLibraryID();
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
@@ -1771,10 +1791,10 @@ var ZoteroPane = new function()
+ Zotero.getString('general.actionCannotBeUndone')
);
if (result) {
- Zotero.Items.emptyTrash(libraryID);
- Zotero.purgeDataObjects(true);
+ let deleted = yield Zotero.Items.emptyTrash(libraryID);
+ yield Zotero.purgeDataObjects(true);
}
- }
+ });
function editSelectedCollection()
@@ -1785,7 +1805,7 @@ var ZoteroPane = new function()
}
if (this.collectionsView.selection.count > 0) {
- var row = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ var row = this.collectionsView.getRow(this.collectionsView.selection.currentIndex);
if (row.isCollection()) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
@@ -1813,7 +1833,7 @@ var ZoteroPane = new function()
}
- function copySelectedItemsToClipboard(asCitations) {
+ this.copySelectedItemsToClipboard = function (asCitations) {
var items = this.getSelectedItems();
if (!items.length) {
return;
@@ -1842,7 +1862,7 @@ var ZoteroPane = new function()
}
var url = (window.content && window.content.location ? window.content.location.href : null);
- var [mode, format] = Zotero.QuickCopy.getFormatFromURL(url).split('=');
+ var [mode, format] = (yield Zotero.QuickCopy.getFormatFromURL(url)).split('=');
var [mode, contentType] = mode.split('/');
if (mode == 'bibliography') {
@@ -1865,13 +1885,15 @@ var ZoteroPane = new function()
}
- function clearQuicksearch() {
+ this.clearQuicksearch = Zotero.Promise.coroutine(function* () {
var search = document.getElementById('zotero-tb-search');
- if (search.value != '') {
+ if (search.value !== '') {
search.value = '';
- ZoteroPane_Local.search();
+ yield ZoteroPane_Local.search();
+ return true;
}
- }
+ return false;
+ });
function handleSearchKeypress(textbox, event) {
@@ -1910,17 +1932,20 @@ var ZoteroPane = new function()
}
- function search(runAdvanced)
- {
- if (this.itemsView) {
- var search = document.getElementById('zotero-tb-search');
- if (!runAdvanced && search.value.indexOf('"') != -1) {
- return;
- }
- var searchVal = search.value;
- this.itemsView.setFilter('search', searchVal);
+ /**
+ * @return {Promise}
+ */
+ this.search = Zotero.Promise.coroutine(function* (runAdvanced) {
+ if (!this.itemsView) {
+ return;
}
- }
+ var search = document.getElementById('zotero-tb-search');
+ if (!runAdvanced && search.value.indexOf('"') != -1) {
+ return;
+ }
+ var searchVal = search.value;
+ return this.itemsView.setFilter('search', searchVal);
+ });
/*
@@ -1929,13 +1954,13 @@ var ZoteroPane = new function()
* If _inLibrary_, force switch to Library
* If _expand_, open item if it's a container
*/
- function selectItem(itemID, inLibrary, expand)
+ this.selectItem = Zotero.Promise.coroutine(function* (itemID, inLibrary, expand)
{
if (!itemID) {
return false;
}
- var item = Zotero.Items.get(itemID);
+ var item = yield Zotero.Items.getAsync(itemID);
if (!item) {
return false;
}
@@ -1949,23 +1974,23 @@ var ZoteroPane = new function()
// If in a different library
if (item.libraryID != currentLibraryID) {
Zotero.debug("Library ID differs; switching library");
- this.collectionsView.selectLibrary(item.libraryID);
+ yield this.collectionsView.selectLibrary(item.libraryID);
}
// Force switch to library view
- else if (!this.itemsView._itemGroup.isLibrary() && inLibrary) {
+ else if (!this.collectionsView.selectedTreeRow.isLibrary() && inLibrary) {
Zotero.debug("Told to select in library; switching to library");
- this.collectionsView.selectLibrary(item.libraryID);
+ yield this.collectionsView.selectLibrary(item.libraryID);
}
- var selected = this.itemsView.selectItem(itemID, expand);
+ var selected = yield this.itemsView.selectItem(itemID, expand);
if (!selected) {
Zotero.debug("Item was not selected; switching to library");
- this.collectionsView.selectLibrary(item.libraryID);
- this.itemsView.selectItem(itemID, expand);
+ yield this.collectionsView.selectLibrary(item.libraryID);
+ yield this.itemsView.selectItem(itemID, expand);
}
return true;
- }
+ });
this.getSelectedLibraryID = function () {
@@ -1981,7 +2006,7 @@ var ZoteroPane = new function()
function getSelectedSavedSearch(asID)
{
if (this.collectionsView.selection.count > 0 && this.collectionsView.selection.currentIndex != -1) {
- var collection = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ var collection = this.collectionsView.getRow(this.collectionsView.selection.currentIndex);
if (collection && collection.isSearch()) {
return asID ? collection.ref.id : collection.ref;
}
@@ -2010,9 +2035,9 @@ var ZoteroPane = new function()
&& this.collectionsView.selection.count > 0
&& this.collectionsView.selection.currentIndex != -1) {
- var itemGroup = this.getItemGroup();
- if (itemGroup && itemGroup.isGroup()) {
- return asID ? itemGroup.ref.id : itemGroup.ref;
+ var collectionTreeRow = this.getCollectionTreeRow();
+ if (collectionTreeRow && collectionTreeRow.isGroup()) {
+ return asID ? collectionTreeRow.ref.id : collectionTreeRow.ref;
}
}
return false;
@@ -2051,8 +2076,7 @@ var ZoteroPane = new function()
}
- this.buildCollectionContextMenu = function buildCollectionContextMenu()
- {
+ this.buildCollectionContextMenu = function () {
var options = [
"newCollection",
"newSavedSearch",
@@ -2081,13 +2105,13 @@ var ZoteroPane = new function()
var menu = document.getElementById('zotero-collectionmenu');
- var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
+ var collectionTreeRow = this.collectionsView.selectedTreeRow;
// By default things are hidden and visible, so we only need to record
// when things are visible and when they're visible but disabled
var show = [], disable = [];
- if (itemGroup.isCollection()) {
+ if (collectionTreeRow.isCollection()) {
show = [
m.newSubcollection,
m.sep1,
@@ -2117,7 +2141,7 @@ var ZoteroPane = new function()
menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.collection'));
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.collection'));
}
- else if (itemGroup.isSearch()) {
+ else if (collectionTreeRow.isSearch()) {
show = [
m.editSelectedCollection,
m.deleteCollection,
@@ -2140,25 +2164,25 @@ var ZoteroPane = new function()
menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.savedSearch'));
menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.savedSearch'));
}
- else if (itemGroup.isTrash()) {
+ else if (collectionTreeRow.isTrash()) {
show = [m.emptyTrash];
}
- else if (itemGroup.isGroup()) {
+ else if (collectionTreeRow.isGroup()) {
show = [m.newCollection, m.newSavedSearch, m.sep1, m.showDuplicates, m.showUnfiled];
}
- else if (itemGroup.isDuplicates() || itemGroup.isUnfiled()) {
+ else if (collectionTreeRow.isDuplicates() || collectionTreeRow.isUnfiled()) {
show = [
m.deleteCollection
];
menu.childNodes[m.deleteCollection].setAttribute('label', Zotero.getString('general.hide'));
}
- else if (itemGroup.isHeader()) {
- if (itemGroup.ref.id == 'commons-header') {
+ else if (collectionTreeRow.isHeader()) {
+ if (collectionTreeRow.ref.id == 'commons-header') {
show = [m.createCommonsBucket];
}
}
- else if (itemGroup.isBucket()) {
+ else if (collectionTreeRow.isBucket()) {
show = [m.refreshCommonsBucket];
}
// Library
@@ -2171,13 +2195,13 @@ var ZoteroPane = new function()
//
// Some actions are disabled via their commands in onCollectionSelected()
var s = [m.newSubcollection, m.editSelectedCollection, m.deleteCollection, m.deleteCollectionAndItems];
- if (itemGroup.isWithinGroup() && !itemGroup.editable && !itemGroup.isDuplicates() && !itemGroup.isUnfiled()) {
+ if (collectionTreeRow.isWithinGroup() && !collectionTreeRow.editable && !collectionTreeRow.isDuplicates() && !collectionTreeRow.isUnfiled()) {
disable = disable.concat(s);
}
// If within non-editable group or trash it empty, disable Empty Trash
- if (itemGroup.isTrash()) {
- if ((itemGroup.isWithinGroup() && !itemGroup.isWithinEditableGroup()) || !this.itemsView.rowCount) {
+ if (collectionTreeRow.isTrash()) {
+ if ((collectionTreeRow.isWithinGroup() && !collectionTreeRow.isWithinEditableGroup()) || !this.itemsView.rowCount) {
disable.push(m.emptyTrash);
}
}
@@ -2199,8 +2223,7 @@ var ZoteroPane = new function()
}
}
- function buildItemContextMenu()
- {
+ this.buildItemContextMenu = Zotero.Promise.coroutine(function* () {
var options = [
'showInLibrary',
'sep1',
@@ -2242,9 +2265,9 @@ var ZoteroPane = new function()
return;
}
- var itemGroup = this.getItemGroup();
+ var collectionTreeRow = this.getCollectionTreeRow();
- if(itemGroup.isTrash()) {
+ if(collectionTreeRow.isTrash()) {
show.push(m.restoreToLibrary);
} else {
show.push(m.deleteFromLibrary);
@@ -2265,11 +2288,11 @@ var ZoteroPane = new function()
}
for each(var item in items) {
- if (canMerge && !item.isRegularItem() || itemGroup.isDuplicates()) {
+ if (canMerge && !item.isRegularItem() || collectionTreeRow.isDuplicates()) {
canMerge = false;
}
- if (canIndex && !Zotero.Fulltext.canReindex(item.id)) {
+ if (canIndex && !(yield Zotero.Fulltext.canReindex(item))) {
canIndex = false;
}
@@ -2278,7 +2301,7 @@ var ZoteroPane = new function()
}
// Show rename option only if all items are child attachments
- if (canRename && (!item.isAttachment() || !item.getSource() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
+ if (canRename && (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL)) {
canRename = false;
}
}
@@ -2317,7 +2340,7 @@ var ZoteroPane = new function()
// Block certain actions on files if no access and at least one item
// is an imported attachment
- if (!itemGroup.filesEditable) {
+ if (!collectionTreeRow.filesEditable) {
var hasImportedAttachment = false;
for (var i=0; i<items.length; i++) {
var item = items[i];
@@ -2335,17 +2358,17 @@ var ZoteroPane = new function()
// Single item selected
else
{
- var item = this.getSelectedItems()[0];
- var itemID = item.id;
- menu.setAttribute('itemID', itemID);
+ let item = this.getSelectedItems()[0];
+ menu.setAttribute('itemID', item.id);
+ menu.setAttribute('itemKey', item.key);
// Show in Library
- if (!itemGroup.isLibrary() && !itemGroup.isWithinGroup()) {
+ if (!collectionTreeRow.isLibrary() && !collectionTreeRow.isWithinGroup()) {
show.push(m.showInLibrary, m.sep1);
}
// Disable actions in the trash
- if (itemGroup.isTrash()) {
+ if (collectionTreeRow.isTrash()) {
disable.push(m.deleteItem);
}
@@ -2368,14 +2391,14 @@ var ZoteroPane = new function()
}
// Attachment rename option
- if (item.getSource() && item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ if (!item.isTopLevelItem() && item.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
show.push(m.renameAttachments);
showSep4 = true;
}
// If not linked URL, show reindex line
if (Zotero.Fulltext.pdfConverterIsRegistered()
- && Zotero.Fulltext.canReindex(item.id)) {
+ && (yield Zotero.Fulltext.canReindex(item))) {
show.push(m.reindexItem);
showSep4 = true;
}
@@ -2393,7 +2416,7 @@ var ZoteroPane = new function()
this.updateAttachmentButtonMenu(popup);
// Block certain actions on files if no access
- if (item.isImportedAttachment() && !itemGroup.filesEditable) {
+ if (item.isImportedAttachment() && !collectionTreeRow.filesEditable) {
var d = [m.deleteFromLibrary, m.createParent, m.renameAttachments];
for each(var val in d) {
disable.push(val);
@@ -2405,7 +2428,7 @@ var ZoteroPane = new function()
else
{
// Show in Library
- if (!itemGroup.isLibrary()) {
+ if (!collectionTreeRow.isLibrary()) {
show.push(m.showInLibrary, m.sep1);
}
@@ -2414,11 +2437,11 @@ var ZoteroPane = new function()
}
// TODO: implement menu for remote items
- if (!itemGroup.editable) {
+ if (!collectionTreeRow.editable) {
for (var i in m) {
// Still show export/bib/report for non-editable views
// (other than Commons buckets, which aren't real items)
- if (!itemGroup.isBucket()) {
+ if (!collectionTreeRow.isBucket()) {
switch (i) {
case 'exportItems':
case 'createBib':
@@ -2432,7 +2455,7 @@ var ZoteroPane = new function()
}
// Remove from collection
- if (itemGroup.isCollection() && !(item && item.getSource()))
+ if (collectionTreeRow.isCollection() && !(item && !item.isTopLevelItem()))
{
menu.childNodes[m.deleteItem].setAttribute('label', Zotero.getString('pane.items.menu.remove' + multiple));
show.push(m.deleteItem);
@@ -2466,7 +2489,7 @@ var ZoteroPane = new function()
// add locate menu options
Zotero_LocateMenu.buildContextMenu(menu, true);
- }
+ });
this.onTreeMouseDown = function (event) {
@@ -2485,13 +2508,13 @@ var ZoteroPane = new function()
}
if (tree.id == 'zotero-collections-tree') {
- let itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
+ let collectionTreeRow = ZoteroPane_Local.collectionsView.getRow(row.value);
// Prevent the tree's select event from being called for a click
// on a library sync error icon
- if (itemGroup.isLibrary(true)) {
+ if (collectionTreeRow.isLibrary(true)) {
if (col.value.id == 'zotero-collections-sync-status-column') {
- var errors = Zotero.Sync.Runner.getErrors(itemGroup.ref.libraryID);
+ var errors = Zotero.Sync.Runner.getErrors(collectionTreeRow.ref.libraryID);
if (errors) {
event.stopPropagation();
return;
@@ -2503,9 +2526,9 @@ var ZoteroPane = new function()
// Automatically select all equivalent items when clicking on an item
// in duplicates view
else if (tree.id == 'zotero-items-tree') {
- let itemGroup = ZoteroPane_Local.getItemGroup();
+ let collectionTreeRow = ZoteroPane_Local.getCollectionTreeRow();
- if (itemGroup.isDuplicates()) {
+ if (collectionTreeRow.isDuplicates()) {
// Trigger only on primary-button single clicks without modifiers
// (so that items can still be selected and deselected manually)
if (!event || event.detail != 1 || event.button != 0 || event.metaKey
@@ -2530,8 +2553,8 @@ var ZoteroPane = new function()
}
// Duplicated in itemTreeView.js::notify()
- var itemID = ZoteroPane_Local.itemsView._getItemAtRow(row.value).ref.id;
- var setItemIDs = itemGroup.ref.getSetItemsByItemID(itemID);
+ var itemID = ZoteroPane_Local.itemsView.getRow(row.value).ref.id;
+ var setItemIDs = collectionTreeRow.ref.getSetItemsByItemID(itemID);
ZoteroPane_Local.itemsView.selectItems(setItemIDs);
// Prevent the tree's select event from being called here,
@@ -2562,13 +2585,13 @@ var ZoteroPane = new function()
}
if (tree.id == 'zotero-collections-tree') {
- let itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row.value);
+ let collectionTreeRow = ZoteroPane_Local.collectionsView.getRow(row.value);
// Show the error panel when clicking a library-specific
// sync error icon
- if (itemGroup.isLibrary(true)) {
+ if (collectionTreeRow.isLibrary(true)) {
if (col.value.id == 'zotero-collections-sync-status-column') {
- var errors = Zotero.Sync.Runner.getErrors(itemGroup.ref.libraryID);
+ var errors = Zotero.Sync.Runner.getErrors(collectionTreeRow.ref.libraryID);
if (!errors) {
return;
}
@@ -2595,8 +2618,8 @@ var ZoteroPane = new function()
// selected during mousedown(), as is the case in duplicates mode),
// it fires select() again. We prevent that here.
else if (tree.id == 'zotero-items-tree') {
- let itemGroup = ZoteroPane_Local.getItemGroup();
- if (itemGroup.isDuplicates()) {
+ let collectionTreeRow = ZoteroPane_Local.getCollectionTreeRow();
+ if (collectionTreeRow.isDuplicates()) {
if (event.button != 0 || event.metaKey || event.shiftKey
|| event.altKey || event.ctrlKey) {
return;
@@ -2614,10 +2637,10 @@ var ZoteroPane = new function()
return;
}
- var itemGroup = ZoteroPane_Local.getItemGroup();
+ var collectionTreeRow = ZoteroPane_Local.getCollectionTreeRow();
// Ignore double-clicks in duplicates view on everything except attachments
- if (itemGroup.isDuplicates()) {
+ if (collectionTreeRow.isDuplicates()) {
var items = ZoteroPane_Local.getSelectedItems();
if (items.length != 1 || !items[0].isAttachment()) {
event.stopPropagation();
@@ -2637,7 +2660,7 @@ var ZoteroPane = new function()
return;
}
- if (itemGroup.isLibrary()) {
+ if (collectionTreeRow.isLibrary()) {
var uri = Zotero.URI.getCurrentUserLibraryURI();
if (uri) {
ZoteroPane_Local.loadURI(uri);
@@ -2646,25 +2669,25 @@ var ZoteroPane = new function()
return;
}
- if (itemGroup.isSearch()) {
+ if (collectionTreeRow.isSearch()) {
ZoteroPane_Local.editSelectedCollection();
return;
}
- if (itemGroup.isGroup()) {
- var uri = Zotero.URI.getGroupURI(itemGroup.ref, true);
+ if (collectionTreeRow.isGroup()) {
+ var uri = Zotero.URI.getGroupURI(collectionTreeRow.ref, true);
ZoteroPane_Local.loadURI(uri);
event.stopPropagation();
return;
}
// Ignore double-clicks on Unfiled Items source row
- if (itemGroup.isUnfiled()) {
+ if (collectionTreeRow.isUnfiled()) {
return;
}
- if (itemGroup.isHeader()) {
- if (itemGroup.ref.id == 'group-libraries-header') {
+ if (collectionTreeRow.isHeader()) {
+ if (collectionTreeRow.ref.id == 'group-libraries-header') {
var uri = Zotero.URI.getGroupsURL();
ZoteroPane_Local.loadURI(uri);
event.stopPropagation();
@@ -2672,8 +2695,8 @@ var ZoteroPane = new function()
return;
}
- if (itemGroup.isBucket()) {
- ZoteroPane_Local.loadURI(itemGroup.ref.uri);
+ if (collectionTreeRow.isBucket()) {
+ ZoteroPane_Local.loadURI(collectionTreeRow.ref.uri);
event.stopPropagation();
}
}
@@ -2898,16 +2921,18 @@ var ZoteroPane = new function()
var menu = document.getElementById('zotero-content-area-context-menu');
var disabled = Zotero.locked;
if (!disabled && self.collectionsView.selection && self.collectionsView.selection.count) {
- var itemGroup = self.collectionsView._getItemAtRow(self.collectionsView.selection.currentIndex);
- disabled = !itemGroup.editable;
+ var collectionTreeRow = self.collectionsView.selectedTreeRow;
+ disabled = !collectionTreeRow.editable;
}
for each(var menuitem in menu.firstChild.childNodes) {
menuitem.disabled = disabled;
}
}
-
- this.newNote = function (popup, parent, text, citeURI) {
+ /**
+ * @return {Promise}
+ */
+ this.newNote = Zotero.Promise.coroutine(function* (popup, parentKey, text, citeURI) {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return;
@@ -2922,7 +2947,7 @@ var ZoteroPane = new function()
if (!text) {
text = '';
}
- text = Zotero.Utilities.trim(text);
+ text = text.trim();
if (text) {
text = '<blockquote'
@@ -2933,16 +2958,17 @@ var ZoteroPane = new function()
var item = new Zotero.Item('note');
item.libraryID = this.getSelectedLibraryID();
item.setNote(text);
- if (parent) {
- item.setSource(parent);
+ Zotero.debug(parentKey);
+ if (parentKey) {
+ item.parentKey = parentKey;
}
- var itemID = item.save();
+ var itemID = yield item.save();
- if (!parent && this.itemsView && this.itemsView._itemGroup.isCollection()) {
- this.itemsView._itemGroup.ref.addItem(itemID);
+ if (!parentKey && this.itemsView && this.collectionsView.selectedTreeRow.isCollection()) {
+ yield this.collectionsView.selectedTreeRow.ref.addItem(itemID);
}
- this.selectItem(itemID);
+ yield this.selectItem(itemID);
document.getElementById('zotero-note-editor').focus();
}
@@ -2951,42 +2977,57 @@ var ZoteroPane = new function()
// TODO: _text_
var c = this.getSelectedCollection();
if (c) {
- this.openNoteWindow(null, c.id, parent);
+ this.openNoteWindow(null, c.id, parentKey);
}
else {
- this.openNoteWindow(null, null, parent);
+ this.openNoteWindow(null, null, parentKey);
}
}
+ });
+
+
+ /**
+ * Creates a child note for the selected item or the selected item's parent
+ *
+ * @return {Promise}
+ */
+ this.newChildNote = function (popup) {
+ var selected = this.getSelectedItems()[0];
+ var parentKey = selected.parentItemKey;
+ parentKey = parentKey ? parentKey : selected.key;
+ this.newNote(popup, parentKey);
}
- function addTextToNote(text, citeURI)
- {
+ this.addSelectedTextToCurrentNote = Zotero.Promise.coroutine(function* () {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
+ var text = event.currentTarget.ownerDocument.popupNode.ownerDocument.defaultView.getSelection().toString();
+ var uri = event.currentTarget.ownerDocument.popupNode.ownerDocument.location.href;
+
if (!text) {
return false;
}
- text = Zotero.Utilities.trim(text);
+ text = text.trim();
if (!text.length) {
return false;
}
- text = '<blockquote'
- + (citeURI ? ' cite="' + citeURI + '"' : '')
- + '>' + Zotero.Utilities.text2html(text) + "</blockquote>";
+ text = '<blockquote' + (uri ? ' cite="' + uri + '"' : '') + '>'
+ + Zotero.Utilities.text2html(text) + "</blockquote>";
var items = this.getSelectedItems();
+
if (this.itemsView.selection.count == 1 && items[0] && items[0].isNote()) {
var note = items[0].getNote()
items[0].setNote(note + text);
- items[0].save();
+ yield items[0].save();
var noteElem = document.getElementById('zotero-note-editor')
noteElem.focus();
@@ -2994,9 +3035,20 @@ var ZoteroPane = new function()
}
return false;
- }
+ });
- function openNoteWindow(itemID, col, parentItemID)
+
+ this.createItemAndNoteFromSelectedText = function (event) {
+ var str = event.currentTarget.ownerDocument.popupNode.ownerDocument.defaultView.getSelection().toString();
+ var uri = event.currentTarget.ownerDocument.popupNode.ownerDocument.location.href;
+ var itemID = ZoteroPane.addItemFromPage();
+ var [libraryID, key] = Zotero.Items.getLibraryAndKeyFromID(itemID);
+ ZoteroPane.newNote(false, key, str, uri)
+ };
+
+
+
+ function openNoteWindow(itemID, col, parentKey)
{
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
@@ -3024,12 +3076,12 @@ var ZoteroPane = new function()
}
}
- var io = { itemID: itemID, collectionID: col, parentItemID: parentItemID };
+ var io = { itemID: itemID, collectionID: col, parentItemKey: parentKey };
window.openDialog('chrome://zotero/content/note.xul', name, 'chrome,resizable,centerscreen,dialog=false', io);
}
- this.addAttachmentFromURI = function (link, itemID) {
+ this.addAttachmentFromURI = Zotero.Promise.method(function (link, itemID) {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
@@ -3046,8 +3098,11 @@ var ZoteroPane = new function()
if (!result || !input.value) return false;
// Create a new attachment
- Zotero.Attachments.linkFromURL(input.value, itemID);
- }
+ return Zotero.Attachments.linkFromURL({
+ url: input.value,
+ parentItemID: itemID
+ });
+ });
function addAttachmentFromDialog(link, id)
@@ -3057,8 +3112,8 @@ var ZoteroPane = new function()
return;
}
- var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
- if (link && itemGroup.isWithinGroup()) {
+ var collectionTreeRow = this.getCollectionTreeRow();
+ if (link && collectionTreeRow.isWithinGroup()) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
ps.alert(null, "", "Linked files cannot be added to group libraries.");
@@ -3071,7 +3126,7 @@ var ZoteroPane = new function()
return;
}
- var libraryID = itemGroup.ref.libraryID;
+ var libraryID = collectionTreeRow.ref.libraryID;
var nsIFilePicker = Components.interfaces.nsIFilePicker;
var fp = Components.classes["@mozilla.org/filepicker;1"]
@@ -3103,23 +3158,25 @@ var ZoteroPane = new function()
this.addItemFromPage = function (itemType, saveSnapshot, row) {
- if(Zotero.isConnector) {
- // In connector, save page via Zotero Standalone
- var doc = window.content.document;
- Zotero.Connector.callMethod("saveSnapshot", {"url":doc.location.toString(),
- "cookie":doc.cookie, "html":doc.documentElement.innerHTML},
- function(returnValue, status) {
- _showPageSaveStatus(doc.title);
- });
- return;
- }
-
- if ((row || (this.collectionsView && this.collectionsView.selection)) && !this.canEdit(row)) {
- this.displayCannotEditLibraryMessage();
- return;
- }
-
- return this.addItemFromDocument(window.content.document, itemType, saveSnapshot, row);
+ return Zotero.Promise.try(function () {
+ if(Zotero.isConnector) {
+ // In connector, save page via Zotero Standalone
+ var doc = window.content.document;
+ Zotero.Connector.callMethod("saveSnapshot", {"url":doc.location.toString(),
+ "cookie":doc.cookie, "html":doc.documentElement.innerHTML},
+ function(returnValue, status) {
+ _showPageSaveStatus(doc.title);
+ });
+ return;
+ }
+
+ if ((row || (this.collectionsView && this.collectionsView.selection)) && !this.canEdit(row)) {
+ this.displayCannotEditLibraryMessage();
+ return;
+ }
+
+ return this.addItemFromDocument(window.content.document, itemType, saveSnapshot, row);
+ }.bind(this));
}
/**
@@ -3140,7 +3197,7 @@ var ZoteroPane = new function()
* @param {Boolean} [saveSnapshot] Force saving or non-saving of a snapshot,
* regardless of automaticSnapshots pref
*/
- this.addItemFromDocument = function (doc, itemType, saveSnapshot, row) {
+ this.addItemFromDocument = Zotero.Promise.coroutine(function* (doc, itemType, saveSnapshot, row) {
_showPageSaveStatus(doc.title);
// Save snapshot if explicitly enabled or automatically pref is set and not explicitly disabled
@@ -3189,12 +3246,12 @@ var ZoteroPane = new function()
}
if (row !== undefined) {
- var itemGroup = this.collectionsView._getItemAtRow(row);
- var libraryID = itemGroup.ref.libraryID;
+ var collectionTreeRow = this.collectionsView.getRow(row);
+ var libraryID = collectionTreeRow.ref.libraryID;
}
else {
var libraryID = 0;
- var itemGroup = null;
+ var collectionTreeRow = null;
}
//
//
@@ -3205,23 +3262,20 @@ var ZoteroPane = new function()
return;
}
- if (itemGroup && itemGroup.isCollection()) {
- var collectionID = itemGroup.ref.id;
+ if (collectionTreeRow && collectionTreeRow.isCollection()) {
+ var collectionID = collectionTreeRow.ref.id;
}
else {
var collectionID = false;
}
- var itemID = Zotero.Attachments.importFromDocument(doc, false, false, collectionID, null, libraryID);
-
- // importFromDocument() doesn't trigger the notifier for a second
- //
- // The one-second delay is weird but better than nothing
- var self = this;
- setTimeout(function () {
- self.selectItem(itemID);
- }, 1001);
+ let item = yield Zotero.Attachments.importFromDocument({
+ libraryID: libraryID,
+ document: doc,
+ parentCollectionIDs: [collectionID]
+ });
+ yield this.selectItem(item.id);
return;
}
}
@@ -3236,7 +3290,7 @@ var ZoteroPane = new function()
accessDate: "CURRENT_TIMESTAMP"
}
itemType = Zotero.ItemTypes.getID(itemType);
- var item = this.newItem(itemType, data, row);
+ var item = yield this.newItem(itemType, data, row);
if (item.libraryID) {
var group = Zotero.Groups.getByLibraryID(item.libraryID);
@@ -3250,40 +3304,53 @@ var ZoteroPane = new function()
var link = false;
if (link) {
- Zotero.Attachments.linkFromDocument(doc, item.id);
+ yield Zotero.Attachments.linkFromDocument({
+ document: doc,
+ parentItemID: item.id
+ });
}
else if (filesEditable) {
- Zotero.Attachments.importFromDocument(doc, item.id);
+ yield Zotero.Attachments.importFromDocument({
+ document: doc,
+ parentItemID: item.id
+ });
}
}
return item.id;
- }
+ });
- this.addItemFromURL = function (url, itemType, saveSnapshot, row) {
+ this.addItemFromURL = Zotero.Promise.coroutine(function* (url, itemType, saveSnapshot, row) {
if (window.content && url == window.content.document.location.href) {
return this.addItemFromPage(itemType, saveSnapshot, row);
}
- var self = this;
-
url = Zotero.Utilities.resolveIntermediateURL(url);
- Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) {
+ return Zotero.MIME.getMIMETypeFromURL(url)
+ .spread(function (mimeType, hasNativeHandler) {
+ var self = this;
+
// If native type, save using a hidden browser
if (hasNativeHandler) {
+ var deferred = Zotero.Promise.defer();
+
var processor = function (doc) {
- ZoteroPane_Local.addItemFromDocument(doc, itemType, saveSnapshot, row);
+ ZoteroPane_Local.addItemFromDocument(doc, itemType, saveSnapshot, row)
+ .then(function () {
+ deferred.resolve()
+ })
};
-
+ // TODO: processDocuments should wait for the processor promise to be resolved
var done = function () {}
-
var exception = function (e) {
- Zotero.debug(e);
+ Zotero.debug(e, 1);
+ deferred.reject(e);
}
-
Zotero.HTTP.processDocuments([url], processor, done, exception);
+
+ return deferred.promise;
}
// Otherwise create placeholder item, attach attachment, and update from that
else {
@@ -3311,12 +3378,12 @@ var ZoteroPane = new function()
}
if (row !== undefined) {
- var itemGroup = ZoteroPane_Local.collectionsView._getItemAtRow(row);
- var libraryID = itemGroup.ref.libraryID;
+ var collectionTreeRow = ZoteroPane_Local.collectionsView.getRow(row);
+ var libraryID = collectionTreeRow.ref.libraryID;
}
else {
var libraryID = 0;
- var itemGroup = null;
+ var collectionTreeRow = null;
}
//
//
@@ -3327,19 +3394,20 @@ var ZoteroPane = new function()
return;
}
- if (itemGroup && itemGroup.isCollection()) {
- var collectionID = itemGroup.ref.id;
+ if (collectionTreeRow && collectionTreeRow.isCollection()) {
+ var collectionID = collectionTreeRow.ref.id;
}
else {
var collectionID = false;
}
+ // TODO: Update for async DB
var attachmentItem = Zotero.Attachments.importFromURL(url, false,
false, false, collectionID, mimeType, libraryID,
function(attachmentItem) {
self.selectItem(attachmentItem.id);
});
-
+
return;
}
}
@@ -3348,39 +3416,39 @@ var ZoteroPane = new function()
itemType = 'webpage';
}
- var item = ZoteroPane_Local.newItem(itemType, {}, row);
-
- if (item.libraryID) {
- var group = Zotero.Groups.getByLibraryID(item.libraryID);
- filesEditable = group.filesEditable;
- }
- else {
- filesEditable = true;
- }
-
- // Save snapshot if explicitly enabled or automatically pref is set and not explicitly disabled
- if (saveSnapshot || (saveSnapshot !== false && Zotero.Prefs.get('automaticSnapshots'))) {
- var link = false;
-
- if (link) {
- //Zotero.Attachments.linkFromURL(doc, item.id);
+ return Zotero.Promise.coroutine(function* () {
+ var item = yield ZoteroPane_Local.newItem(itemType, {}, row)
+ if (item.libraryID) {
+ var group = Zotero.Groups.getByLibraryID(item.libraryID);
+ filesEditable = group.filesEditable;
+ }
+ else {
+ filesEditable = true;
}
- else if (filesEditable) {
- var attachmentItem = Zotero.Attachments.importFromURL(url, item.id, false, false, false, mimeType);
- if (attachmentItem) {
- item.setField('title', attachmentItem.getField('title'));
- item.setField('url', attachmentItem.getField('url'));
- item.setField('accessDate', attachmentItem.getField('accessDate'));
- item.save();
+
+ // Save snapshot if explicitly enabled or automatically pref is set and not explicitly disabled
+ if (saveSnapshot || (saveSnapshot !== false && Zotero.Prefs.get('automaticSnapshots'))) {
+ var link = false;
+
+ if (link) {
+ //Zotero.Attachments.linkFromURL(doc, item.id);
+ }
+ else if (filesEditable) {
+ var attachmentItem = Zotero.Attachments.importFromURL(url, item.id, false, false, false, mimeType);
+ if (attachmentItem) {
+ item.setField('title', attachmentItem.getField('title'));
+ item.setField('url', attachmentItem.getField('url'));
+ item.setField('accessDate', attachmentItem.getField('accessDate'));
+ yield item.save();
+ }
}
}
- }
-
- return item.id;
-
+
+ return item.id;
+ })();
}
});
- }
+ });
/*
@@ -3389,15 +3457,14 @@ var ZoteroPane = new function()
* |itemID| -- itemID of parent item
* |link| -- create web link instead of snapshot
*/
- this.addAttachmentFromPage = function (link, itemID)
- {
+ this.addAttachmentFromPage = Zotero.Promise.method(function (link, itemID) {
if (!Zotero.stateCheck()) {
this.displayErrorMessage(true);
return;
}
if (typeof itemID != 'number') {
- throw ("itemID must be an integer in ZoteroPane_Local.addAttachmentFromPage()");
+ throw new Error("itemID must be an integer");
}
var progressWin = new Zotero.ProgressWindow();
@@ -3409,15 +3476,19 @@ var ZoteroPane = new function()
progressWin.startCloseTimer();
if (link) {
- Zotero.Attachments.linkFromDocument(window.content.document, itemID);
- }
- else {
- Zotero.Attachments.importFromDocument(window.content.document, itemID);
+ return Zotero.Attachments.linkFromDocument({
+ document: window.content.document,
+ parentItemID: itemID
+ });
}
- }
+ return Zotero.Attachments.importFromDocument({
+ document: window.content.document,
+ parentItemID: itemID
+ });
+ });
- this.viewItems = function (items, event) {
+ this.viewItems = Zotero.Promise.coroutine(function* (items, event) {
if (items.length > 1) {
if (!event || (!event.metaKey && !event.shiftKey)) {
event = { metaKey: true, shiftKey: true };
@@ -3429,9 +3500,9 @@ var ZoteroPane = new function()
// Prefer local file attachments
var uri = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURI);
- var snapID = item.getBestAttachment();
- if (snapID) {
- ZoteroPane_Local.viewAttachment(snapID, event);
+ let attachment = yield item.getBestAttachment();
+ if (attachment) {
+ yield this.viewAttachment(attachment.id, event);
continue;
}
@@ -3452,31 +3523,29 @@ var ZoteroPane = new function()
if (!uri) {
var link = item.getAttachments()[0];
if (link) {
- link = Zotero.Items.get(link);
+ link = yield Zotero.Items.getAsync(link);
if (link) uri = link.getField('url');
}
}
if (uri) {
- ZoteroPane_Local.loadURI(uri, event);
+ this.loadURI(uri, event);
}
}
else if (item.isNote()) {
- if (!ZoteroPane_Local.collectionsView.editable) {
+ if (!this.collectionsView.editable) {
continue;
}
document.getElementById('zotero-view-note-button').doCommand();
}
else if (item.isAttachment()) {
- ZoteroPane_Local.viewAttachment(item.id, event);
+ yield this.viewAttachment(item.id, event);
}
}
- }
+ });
- function viewAttachment(itemIDs, event, noLocateOnMissing, forceExternalViewer) {
- Components.utils.import("resource://zotero/q.js");
-
+ this.viewAttachment = Zotero.Promise.coroutine(function* (itemIDs, event, noLocateOnMissing, forceExternalViewer) {
// If view isn't editable, don't show Locate button, since the updated
// path couldn't be sent back up
if (!this.collectionsView.editable) {
@@ -3493,9 +3562,9 @@ var ZoteroPane = new function()
}
for each(var itemID in itemIDs) {
- var item = Zotero.Items.get(itemID);
+ var item = yield Zotero.Items.getAsync(itemID);
if (!item.isAttachment()) {
- throw ("Item " + itemID + " is not an attachment in ZoteroPane_Local.viewAttachment()");
+ throw new Error("Item " + itemID + " is not an attachment");
}
if (item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
@@ -3503,14 +3572,16 @@ var ZoteroPane = new function()
continue;
}
- var file = item.getFile();
- if (file) {
- Zotero.debug("Opening " + file.path);
+ var path = yield item.getFilePath();
+ if (path) {
+ let file = Zotero.File.pathToFile(path);
+
+ Zotero.debug("Opening " + path);
if(forceExternalViewer !== undefined) {
var externalViewer = forceExternalViewer;
} else {
- var mimeType = Zotero.MIME.getMIMETypeFromFile(file);
+ var mimeType = yield Zotero.MIME.getMIMETypeFromFile(file);
//var mimeType = attachment.attachmentMIMEType;
// TODO: update DB with new info if changed?
@@ -3536,13 +3607,12 @@ var ZoteroPane = new function()
}
let downloadedItem = item;
- Q.fcall(function () {
- return Zotero.Sync.Storage.downloadFile(
- downloadedItem,
- {
- onProgress: function (progress, progressMax) {}
- });
- })
+ yield Zotero.Sync.Storage.downloadFile(
+ downloadedItem,
+ {
+ onProgress: function (progress, progressMax) {}
+ }
+ )
.then(function () {
if (!downloadedItem.getFile()) {
ZoteroPane_Local.showAttachmentNotFoundDialog(downloadedItem.id, noLocateOnMissing);
@@ -3554,18 +3624,18 @@ var ZoteroPane = new function()
Zotero.Notifier.trigger('redraw', 'item', []);
-
- ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
+ Zotero.debug('downloaded');
+ Zotero.debug(downloadedItem.id);
+ return ZoteroPane_Local.viewAttachment(downloadedItem.id, event, false, forceExternalViewer);
})
- .fail(function (e) {
+ .catch(function (e) {
// TODO: show error somewhere else
Zotero.debug(e, 1);
ZoteroPane_Local.syncAlert(e);
- })
- .done();
+ });
}
}
- }
+ });
/**
@@ -3633,8 +3703,8 @@ var ZoteroPane = new function()
}
- this.showAttachmentInFilesystem = function (itemID, noLocateOnMissing) {
- var attachment = Zotero.Items.get(itemID)
+ this.showAttachmentInFilesystem = Zotero.Promise.coroutine(function* (itemID, noLocateOnMissing) {
+ var attachment = yield Zotero.Items.getAsync(itemID)
if (attachment.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
var file = attachment.getFile();
if (file) {
@@ -3653,7 +3723,7 @@ var ZoteroPane = new function()
this.showAttachmentNotFoundDialog(attachment.id, noLocateOnMissing)
}
}
- }
+ });
/**
@@ -3669,8 +3739,8 @@ var ZoteroPane = new function()
row = this.collectionsView.selection.currentIndex;
}
- var itemGroup = this.collectionsView._getItemAtRow(row);
- return itemGroup.editable;
+ var collectionTreeRow = this.collectionsView.getRow(row);
+ return collectionTreeRow.editable;
}
@@ -3686,10 +3756,10 @@ var ZoteroPane = new function()
row = this.collectionsView.selection.currentIndex;
}
- var itemGroup = this.collectionsView._getItemAtRow(row);
+ var collectionTreeRow = this.collectionsView.getRow(row);
// TODO: isEditable for user library should just return true
- if (itemGroup.ref.libraryID) {
- return Zotero.Libraries.isEditable(itemGroup.ref.libraryID);
+ if (collectionTreeRow.ref.libraryID) {
+ return Zotero.Libraries.isEditable(collectionTreeRow.ref.libraryID);
}
return true;
}
@@ -3708,8 +3778,8 @@ var ZoteroPane = new function()
row = this.collectionsView.selection.currentIndex;
}
- var itemGroup = this.collectionsView._getItemAtRow(row);
- return itemGroup.filesEditable;
+ var collectionTreeRow = this.collectionsView.getRow(row);
+ return collectionTreeRow.filesEditable;
}
@@ -3832,13 +3902,12 @@ var ZoteroPane = new function()
};
- this.createParentItemsFromSelected = function () {
+ this.createParentItemsFromSelected = Zotero.Promise.coroutine(function* () {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
-
var items = this.getSelectedItems();
for (var i=0; i<items.length; i++) {
var item = items[i];
@@ -3846,29 +3915,31 @@ var ZoteroPane = new function()
throw('Item ' + itemID + ' is not a top-level attachment or note in ZoteroPane_Local.createParentItemsFromSelected()');
}
- Zotero.DB.beginTransaction();
- // TODO: remove once there are no top-level web attachments
- if (item.isWebAttachment()) {
- var parent = new Zotero.Item('webpage');
- }
- else {
- var parent = new Zotero.Item('document');
- }
- parent.libraryID = item.libraryID;
- parent.setField('title', item.getField('title'));
- if (item.isWebAttachment()) {
- parent.setField('accessDate', item.getField('accessDate'));
- parent.setField('url', item.getField('url'));
- }
- var itemID = parent.save();
- item.setSource(itemID);
- item.save();
- Zotero.DB.commitTransaction();
+ yield Zotero.DB.executeTransaction(function* () {
+ // TODO: remove once there are no top-level web attachments
+ if (item.isWebAttachment()) {
+ var parent = new Zotero.Item('webpage');
+ }
+ else {
+ var parent = new Zotero.Item('document');
+ }
+ parent.libraryID = item.libraryID;
+ parent.setField('title', item.getField('title'));
+ if (item.isWebAttachment()) {
+ parent.setField('accessDate', item.getField('accessDate'));
+ parent.setField('url', item.getField('url'));
+ }
+ var itemID = yield parent.save();
+ item.parentID = itemID;
+ yield item.save();
+ });
}
- }
+ });
- this.renameSelectedAttachmentsFromParents = function () {
+ this.renameSelectedAttachmentsFromParents = Zotero.Promise.coroutine(function* () {
+ // TEMP: fix
+
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
@@ -3879,7 +3950,7 @@ var ZoteroPane = new function()
for (var i=0; i<items.length; i++) {
var item = items[i];
- if (!item.isAttachment() || !item.getSource() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ if (!item.isAttachment() || item.isTopLevelItem() || item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
throw('Item ' + itemID + ' is not a child file attachment in ZoteroPane_Local.renameAttachmentFromParent()');
}
@@ -3888,8 +3959,9 @@ var ZoteroPane = new function()
continue;
}
- var parentItemID = item.getSource();
- var newName = Zotero.Attachments.getFileBaseNameFromItem(parentItemID);
+ let parentItemID = item.parentItemID;
+ let parentItem = yield Zotero.Items.getAsync(parentItemID);
+ var newName = Zotero.Attachments.getFileBaseNameFromItem(parentItem);
var ext = file.leafName.match(/\.[^\.]+$/);
if (ext) {
@@ -3903,20 +3975,20 @@ var ZoteroPane = new function()
}
item.setField('title', newName);
- item.save();
+ yield item.save();
}
return true;
- }
+ });
- function relinkAttachment(itemID) {
+ this.relinkAttachment = Zotero.Promise.coroutine(function* (itemID) {
if (!this.canEdit()) {
this.displayCannotEditLibraryMessage();
return;
}
- var item = Zotero.Items.get(itemID);
+ var item = yield Zotero.Items.getAsync(itemID);
if (!item) {
throw('Item ' + itemID + ' not found in ZoteroPane_Local.relinkAttachment()');
}
@@ -3941,7 +4013,7 @@ var ZoteroPane = new function()
file.QueryInterface(Components.interfaces.nsILocalFile);
item.relinkAttachmentFile(file);
}
- }
+ });
function reportErrors() {
@@ -4054,7 +4126,7 @@ var ZoteroPane = new function()
/**
* Unserializes zotero-persist elements from preferences
*/
- this.unserializePersist = function() {
+ this.unserializePersist = Zotero.Promise.coroutine(function* () {
_unserialized = true;
var serializedValues = Zotero.Prefs.get("pane.persist");
if(!serializedValues) return;
@@ -4071,10 +4143,10 @@ var ZoteroPane = new function()
if(this.itemsView) {
// may not yet be initialized
try {
- this.itemsView.sort();
+ yield this.itemsView.sort();
} catch(e) {};
}
- }
+ });
/**
* Serializes zotero-persist elements to preferences
diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul
@@ -60,6 +60,7 @@
<command id="cmd_zotero_newCollection" oncommand="ZoteroPane_Local.newCollection()"/>
<command id="cmd_zotero_newSavedSearch" oncommand="ZoteroPane_Local.newSearch()"/>
<command id="cmd_zotero_newStandaloneNote" oncommand="ZoteroPane_Local.newNote(event.shiftKey);"/>
+ <command id="cmd_zotero_newChildNote" oncommand="ZoteroPane_Local.newChildNote(event.shiftKey);"/>
<command id="cmd_zotero_newItemFromCurrentPage" oncommand="ZoteroPane.addItemFromPage('temporaryPDFHack', event.shiftKey ? !Zotero.Prefs.get('automaticSnapshots') : null)"/>
</commandset>
@@ -71,10 +72,10 @@
command="cmd_zotero_newItemFromCurrentPage"/>
<menuitem id="zotero-context-add-to-current-note" class="menu-iconic"
label="&zotero.contextMenu.addTextToCurrentNote;" hidden="true"
- oncommand="var str = event.currentTarget.ownerDocument.popupNode.ownerDocument.defaultView.getSelection().toString(); var uri = event.currentTarget.ownerDocument.popupNode.ownerDocument.location.href; ZoteroPane.addTextToNote(str, uri)"/>
+ oncommand="ZoteroPane.addSelectedTextToCurrentNote()"/>
<menuitem id="zotero-context-add-to-new-note" class="menu-iconic"
label="&zotero.contextMenu.addTextToNewNote;" hidden="true"
- oncommand="var str = event.currentTarget.ownerDocument.popupNode.ownerDocument.defaultView.getSelection().toString(); var uri = event.currentTarget.ownerDocument.popupNode.ownerDocument.location.href; var itemID = ZoteroPane.addItemFromPage(); ZoteroPane.newNote(false, itemID, str, uri)"/>
+ oncommand="ZoteroPane.createItemAndNoteFromSelectedText()"/>
<menuitem id="zotero-context-save-link-as-item" class="menu-iconic"
label="&zotero.contextMenu.saveLinkAsItem;" hidden="true"
oncommand="ZoteroPane.addItemFromURL(window.gContextMenu.linkURL, 'temporaryPDFHack')"/>
@@ -165,7 +166,7 @@
<toolbarbutton id="zotero-tb-note-add" class="zotero-tb-button" tooltiptext="&zotero.toolbar.newNote;" type="menu">
<menupopup onpopupshowing="ZoteroPane_Local.updateNoteButtonMenu()">
<menuitem label="&zotero.toolbar.note.standalone;" command="cmd_zotero_newStandaloneNote"/>
- <menuitem id="zotero-tb-add-child-note" label="&zotero.toolbar.note.child;" oncommand="var selected = ZoteroPane_Local.getSelectedItems()[0]; var parent = selected.getSource(); parent = parent ? parent : selected.id; ZoteroPane_Local.newNote(event.shiftKey, parent);"/>
+ <menuitem label="&zotero.toolbar.note.child;" command="cmd_zotero_newChildNote"/>
</menupopup>
</toolbarbutton>
<toolbarbutton id="zotero-tb-attachment-add" class="zotero-tb-button" tooltiptext="&zotero.items.menu.attach;" type="menu">
@@ -240,7 +241,7 @@
<!-- Keep order in sync with buildCollectionContextMenu -->
<menuitem class="menuitem-iconic zotero-menuitem-new-collection" label="&zotero.toolbar.newCollection.label;" command="cmd_zotero_newCollection"/>
<menuitem class="menuitem-iconic zotero-menuitem-new-saved-search" label="&zotero.toolbar.newSavedSearch.label;" command="cmd_zotero_newSavedSearch"/>
- <menuitem class="menuitem-iconic zotero-menuitem-new-collection" label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane_Local.newCollection(ZoteroPane_Local.getSelectedCollection().id)"/>
+ <menuitem class="menuitem-iconic zotero-menuitem-new-collection" label="&zotero.toolbar.newSubcollection.label;" oncommand="ZoteroPane_Local.newCollection(ZoteroPane_Local.getSelectedCollection().key)"/>
<menuseparator/>
<menuitem class="menuitem-iconic zotero-menuitem-show-duplicates" label="&zotero.toolbar.duplicate.label;" oncommand="ZoteroPane_Local.setVirtual(ZoteroPane_Local.getSelectedLibraryID(), 'duplicates', true)"/>
<menuitem class="menuitem-iconic zotero-menuitem-show-unfiled" label="&zotero.collections.showUnfiledItems;" oncommand="ZoteroPane_Local.setVirtual(ZoteroPane_Local.getSelectedLibraryID(), 'unfiled', true)"/>
@@ -261,7 +262,7 @@
<menuitem class="menuitem-iconic zotero-menuitem-show-in-library" label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane_Local.selectItem(this.parentNode.getAttribute('itemID'), true)"/>
<menuseparator/>
<!-- with icon: <menuitem class="menuitem-iconic" id="zotero-menuitem-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemID'))"/>-->
- <menuitem class="menuitem-iconic zotero-menuitem-attach-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemID'))"/>
+ <menuitem class="menuitem-iconic zotero-menuitem-attach-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemKey'))"/>
<menu class="menu-iconic zotero-menuitem-attach" label="&zotero.items.menu.attach;">
<menupopup id="zotero-add-attachment-popup">
<menuitem class="menuitem-iconic zotero-menuitem-attachments-snapshot standalone-no-display" label="&zotero.items.menu.attach.snapshot;" oncommand="var itemID = parseInt(this.parentNode.parentNode.parentNode.getAttribute('itemID')); ZoteroPane_Local.addAttachmentFromPage(false, itemID)"/>
@@ -272,7 +273,7 @@
</menupopup>
</menu>
<menuseparator/>
- <menuitem class="menuitem-iconic zotero-menuitem-duplicate-item" label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane_Local.duplicateSelectedItem();"/>
+ <menuitem class="menuitem-iconic zotero-menuitem-duplicate-item" label="&zotero.items.menu.duplicateItem;" oncommand="ZoteroPane_Local.duplicateSelectedItem().done();"/>
<menuitem class="menuitem-iconic zotero-menuitem-delete-collection" oncommand="ZoteroPane_Local.deleteSelectedItems();"/>
<menuitem class="menuitem-iconic zotero-menuitem-move-to-trash" oncommand="ZoteroPane_Local.deleteSelectedItems(true, true);"/>
<menuitem class="menuitem-iconic zotero-menuitem-restore-to-library" label="&zotero.items.menu.restoreToLibrary;" oncommand="ZoteroPane_Local.restoreSelectedItems();"/>
@@ -324,8 +325,7 @@
TODO: deal with this some other way?
-->
- <zoterotagselector id="zotero-tag-selector" zotero-persist="height,collapsed,showAutomatic,filterToScope"
- oncommand="ZoteroPane_Local.updateTagFilter()"/>
+ <zoterotagselector id="zotero-tag-selector" zotero-persist="height,collapsed,showAutomatic,filterToScope"/>
</vbox>
<splitter id="zotero-collections-splitter" resizebefore="closest" resizeafter="closest" collapse="before"
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
@@ -135,6 +135,7 @@
<!ENTITY zotero.toolbar.attachment.snapshot "Take Snapshot of Current Page">
<!ENTITY zotero.tagSelector.noTagsToDisplay "No tags to display">
+<!ENTITY zotero.tagSelector.loadingTags "Loading tags…">
<!ENTITY zotero.tagSelector.filter "Filter:">
<!ENTITY zotero.tagSelector.showAutomatic "Show Automatic">
<!ENTITY zotero.tagSelector.displayAllInLibrary "Display All Tags in This Library">
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
@@ -77,6 +77,8 @@ upgrade.integrityCheckFailed = Your Zotero database must be repaired before the
upgrade.loadDBRepairTool = Load Database Repair Tool
upgrade.couldNotMigrate = Zotero could not migrate all necessary files.\nPlease close any open attachment files and restart %S to try the upgrade again.
upgrade.couldNotMigrate.restart = If you continue to receive this message, restart your computer.
+upgrade.nonupgradeableDB1 = Zotero found an old database that cannot be upgraded to work with this version of Zotero.
+upgrade.nonupgradeableDB2 = To continue, upgrade your database using Zotero %S first or delete your Zotero data directory to start with a new database.
errorReport.reportError = Report Error…
errorReport.reportErrors = Report Errors…
diff --git a/components/zotero-autocomplete.js b/components/zotero-autocomplete.js
@@ -106,12 +106,16 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParams,
if (searchParams.fieldMode == 2) {
var sql = "SELECT DISTINCT CASE fieldMode WHEN 1 THEN lastName "
+ "WHEN 0 THEN firstName || ' ' || lastName END AS val, NULL AS comment "
- + "FROM creators NATURAL JOIN creatorData WHERE CASE fieldMode "
+ + "FROM creators ";
+ if (searchParams.libraryID !== undefined) {
+ sql += "JOIN itemCreators USING (creatorID) JOIN items USING (itemID) ";
+ }
+ sql += "WHERE CASE fieldMode "
+ "WHEN 1 THEN lastName "
+ "WHEN 0 THEN firstName || ' ' || lastName END "
+ "LIKE ? ";
var sqlParams = [searchString + '%'];
- if (typeof searchParams.libraryID != 'undefined') {
+ if (searchParams.libraryID !== undefined) {
sql += " AND libraryID=?";
sqlParams.push(searchParams.libraryID);
}
@@ -139,8 +143,11 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParams,
+ "ELSE 2 END AS comment";
}
- var fromSQL = " FROM creators NATURAL JOIN creatorData "
- + "WHERE " + subField + " LIKE ? " + "AND fieldMode=?";
+ var fromSQL = " FROM creators "
+ if (searchParams.libraryID !== undefined) {
+ fromSQL += "JOIN itemCreators USING (creatorID) JOIN items USING (itemID) ";
+ }
+ fromSQL += "WHERE " + subField + " LIKE ? " + "AND fieldMode=?";
var sqlParams = [
searchString + '%',
searchParams.fieldMode ? searchParams.fieldMode : 0
@@ -155,7 +162,7 @@ ZoteroAutoComplete.prototype.startSearch = function(searchString, searchParams,
}
fromSQL += ")";
}
- if (typeof searchParams.libraryID != 'undefined') {
+ if (searchParams.libraryID !== undefined) {
fromSQL += " AND libraryID=?";
sqlParams.push(searchParams.libraryID);
}
diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js
@@ -216,9 +216,9 @@ function ChromeExtensionHandler() {
for (var i=0; i<results.length; i++) {
// Don't add child items directly
// (instead mark their parents for inclusion below)
- var sourceItemID = results[i].getSource();
- if (sourceItemID) {
- searchParentIDs[sourceItemID] = true;
+ var parentItemID = results[i].getSource();
+ if (parentItemID) {
+ searchParentIDs[parentItemID] = true;
searchChildIDs[results[i].getID()] = true;
// Don't include all child items if any child
diff --git a/components/zotero-service.js b/components/zotero-service.js
@@ -60,23 +60,23 @@ const xpcomFilesLocal = [
'cite',
'cookieSandbox',
'data_access',
+ 'data/dataObject',
'data/dataObjects',
+ 'data/dataObjectLoader',
'data/dataObjectUtilities',
'data/cachedTypes',
+ 'data/notes',
'data/item',
'data/items',
'data/collection',
'data/collections',
- 'data/creator',
'data/creators',
'data/group',
'data/groups',
'data/itemFields',
- 'data/notes',
'data/libraries',
'data/relation',
'data/relations',
- 'data/tag',
'data/tags',
'db',
'duplicates',
@@ -95,6 +95,9 @@ const xpcomFilesLocal = [
'server',
'style',
'sync',
+ 'sync/syncEngine',
+ 'sync/syncAPI',
+ 'sync/syncLocal',
'storage',
'storage/streamListener',
'storage/queueManager',
@@ -295,9 +298,7 @@ function ZoteroService() {
if(isFirstLoadThisSession) {
makeZoteroContext(false);
- Q.fcall(function () {
- return zContext.Zotero.init(zInitOptions);
- })
+ zContext.Zotero.init(zInitOptions)
.catch(function (e) {
dump(e + "\n\n");
Components.utils.reportError(e);
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
@@ -79,6 +79,7 @@ pref("extensions.zotero.keys.quicksearch", 'K');
pref("extensions.zotero.keys.copySelectedItemCitationsToClipboard", 'A');
pref("extensions.zotero.keys.copySelectedItemsToClipboard", 'C');
pref("extensions.zotero.keys.toggleTagSelector", 'T');
+pref("extensions.zotero.keys.sync", 'Y');
// Fulltext indexing
pref("extensions.zotero.fulltext.textMaxLength", 500000);
diff --git a/resource/bluebird.js b/resource/bluebird.js
@@ -0,0 +1,5261 @@
+/**
+ * bluebird build version 2.2.2
+ * Features enabled: core, race, call_get, generators, map, nodeify, promisify, props, reduce, settle, some, progress, cancel, using, filter, any, each, timers
+*/
+/**
+ * @preserve Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+!function(e){
+ //
+ // Added by Zotero
+ //
+ EXPORTED_SYMBOLS = ["Promise"];
+
+ // Set BackstagePass (which contains .Error, etc.) as global object
+ global = this;
+
+ // Provide an implementation of setTimeout
+ global.setTimeout = new function() {
+ // We need to maintain references to running nsITimers. Otherwise, they can
+ // get garbage collected before they fire.
+ var _runningTimers = [];
+
+ return function setTimeout(func, ms) {
+ var useMethodjit = Components.utils.methodjit;
+ var timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback({"notify":function() {
+ // Remove timer from array so it can be garbage collected
+ _runningTimers.splice(_runningTimers.indexOf(timer), 1);
+
+ // Execute callback function
+ try {
+ func();
+ } catch(err) {
+ // Rethrow errors that occur so that they appear in the error
+ // console with the appropriate name and line numbers. While the
+ // the errors appear without this, the line numbers get eaten.
+ var scriptError = Components.classes["@mozilla.org/scripterror;1"]
+ .createInstance(Components.interfaces.nsIScriptError);
+ scriptError.init(
+ err.message || err.toString(),
+ err.fileName || err.filename || null,
+ null,
+ err.lineNumber || null,
+ null,
+ scriptError.errorFlag,
+ 'component javascript'
+ );
+ Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .logMessage(scriptError);
+ global.debug(err, 1);
+ global.debug(err.stack, 1);
+ }
+ }}, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ _runningTimers.push(timer);
+ }
+ };
+
+ global.debug = function (msg) {
+ dump(msg + "\n\n");
+ };
+
+ Promise = e();
+ // TEMP: Only turn on if debug logging enabled?
+ Promise.longStackTraces();
+ Promise.onPossiblyUnhandledRejection(function(error) {
+ global.debug('===========');
+ global.debug(error);
+ global.debug(error.stack);
+ throw error;
+ });
+ return;
+
+
+ "object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Promise=e():"undefined"!=typeof global?global.Promise=e():"undefined"!=typeof self&&(self.Promise=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise) {
+var SomePromiseArray = Promise._SomePromiseArray;
+function Promise$_Any(promises) {
+ var ret = new SomePromiseArray(promises);
+ var promise = ret.promise();
+ if (promise.isRejected()) {
+ return promise;
+ }
+ ret.setHowMany(1);
+ ret.setUnwrap();
+ ret.init();
+ return promise;
+}
+
+Promise.any = function Promise$Any(promises) {
+ return Promise$_Any(promises);
+};
+
+Promise.prototype.any = function Promise$any() {
+ return Promise$_Any(this);
+};
+
+};
+
+},{}],2:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var schedule = require("./schedule.js");
+var Queue = require("./queue.js");
+var errorObj = require("./util.js").errorObj;
+var tryCatch1 = require("./util.js").tryCatch1;
+var _process = typeof process !== "undefined" ? process : void 0;
+
+function Async() {
+ this._isTickUsed = false;
+ this._schedule = schedule;
+ this._length = 0;
+ this._lateBuffer = new Queue(16);
+ this._functionBuffer = new Queue(65536);
+ var self = this;
+ this.consumeFunctionBuffer = function Async$consumeFunctionBuffer() {
+ self._consumeFunctionBuffer();
+ };
+}
+
+Async.prototype.haveItemsQueued = function Async$haveItemsQueued() {
+ return this._length > 0;
+};
+
+Async.prototype.invokeLater = function Async$invokeLater(fn, receiver, arg) {
+ if (_process !== void 0 &&
+ _process.domain != null &&
+ !fn.domain) {
+ fn = _process.domain.bind(fn);
+ }
+ this._lateBuffer.push(fn, receiver, arg);
+ this._queueTick();
+};
+
+Async.prototype.invoke = function Async$invoke(fn, receiver, arg) {
+ if (_process !== void 0 &&
+ _process.domain != null &&
+ !fn.domain) {
+ fn = _process.domain.bind(fn);
+ }
+ var functionBuffer = this._functionBuffer;
+ functionBuffer.push(fn, receiver, arg);
+ this._length = functionBuffer.length();
+ this._queueTick();
+};
+
+Async.prototype._consumeFunctionBuffer =
+function Async$_consumeFunctionBuffer() {
+ var functionBuffer = this._functionBuffer;
+ while (functionBuffer.length() > 0) {
+ var fn = functionBuffer.shift();
+ var receiver = functionBuffer.shift();
+ var arg = functionBuffer.shift();
+ fn.call(receiver, arg);
+ }
+ this._reset();
+ this._consumeLateBuffer();
+};
+
+Async.prototype._consumeLateBuffer = function Async$_consumeLateBuffer() {
+ var buffer = this._lateBuffer;
+ while(buffer.length() > 0) {
+ var fn = buffer.shift();
+ var receiver = buffer.shift();
+ var arg = buffer.shift();
+ var res = tryCatch1(fn, receiver, arg);
+ if (res === errorObj) {
+ this._queueTick();
+ if (fn.domain != null) {
+ fn.domain.emit("error", res.e);
+ } else {
+ throw res.e;
+ }
+ }
+ }
+};
+
+Async.prototype._queueTick = function Async$_queue() {
+ if (!this._isTickUsed) {
+ this._schedule(this.consumeFunctionBuffer);
+ this._isTickUsed = true;
+ }
+};
+
+Async.prototype._reset = function Async$_reset() {
+ this._isTickUsed = false;
+ this._length = 0;
+};
+
+module.exports = new Async();
+
+},{"./queue.js":25,"./schedule.js":28,"./util.js":35}],3:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var Promise = require("./promise.js")();
+module.exports = Promise;
+},{"./promise.js":20}],4:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var cr = Object.create;
+if (cr) {
+ var callerCache = cr(null);
+ var getterCache = cr(null);
+ callerCache[" size"] = getterCache[" size"] = 0;
+}
+
+module.exports = function(Promise) {
+var util = require("./util.js");
+var canEvaluate = util.canEvaluate;
+var isIdentifier = util.isIdentifier;
+
+function makeMethodCaller (methodName) {
+ return new Function("obj", " \n\
+ 'use strict' \n\
+ var len = this.length; \n\
+ switch(len) { \n\
+ case 1: return obj.methodName(this[0]); \n\
+ case 2: return obj.methodName(this[0], this[1]); \n\
+ case 3: return obj.methodName(this[0], this[1], this[2]); \n\
+ case 0: return obj.methodName(); \n\
+ default: return obj.methodName.apply(obj, this); \n\
+ } \n\
+ ".replace(/methodName/g, methodName));
+}
+
+function makeGetter (propertyName) {
+ return new Function("obj", " \n\
+ 'use strict'; \n\
+ return obj.propertyName; \n\
+ ".replace("propertyName", propertyName));
+}
+
+function getCompiled(name, compiler, cache) {
+ var ret = cache[name];
+ if (typeof ret !== "function") {
+ if (!isIdentifier(name)) {
+ return null;
+ }
+ ret = compiler(name);
+ cache[name] = ret;
+ cache[" size"]++;
+ if (cache[" size"] > 512) {
+ var keys = Object.keys(cache);
+ for (var i = 0; i < 256; ++i) delete cache[keys[i]];
+ cache[" size"] = keys.length - 256;
+ }
+ }
+ return ret;
+}
+
+function getMethodCaller(name) {
+ return getCompiled(name, makeMethodCaller, callerCache);
+}
+
+function getGetter(name) {
+ return getCompiled(name, makeGetter, getterCache);
+}
+
+function caller(obj) {
+ return obj[this.pop()].apply(obj, this);
+}
+Promise.prototype.call = function Promise$call(methodName) {
+ var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+ if (canEvaluate) {
+ var maybeCaller = getMethodCaller(methodName);
+ if (maybeCaller !== null) {
+ return this._then(maybeCaller, void 0, void 0, args, void 0);
+ }
+ }
+ args.push(methodName);
+ return this._then(caller, void 0, void 0, args, void 0);
+};
+
+function namedGetter(obj) {
+ return obj[this];
+}
+function indexedGetter(obj) {
+ return obj[this];
+}
+Promise.prototype.get = function Promise$get(propertyName) {
+ var isIndex = (typeof propertyName === "number");
+ var getter;
+ if (!isIndex) {
+ if (canEvaluate) {
+ var maybeGetter = getGetter(propertyName);
+ getter = maybeGetter !== null ? maybeGetter : namedGetter;
+ } else {
+ getter = namedGetter;
+ }
+ } else {
+ getter = indexedGetter;
+ }
+ return this._then(getter, void 0, void 0, propertyName, void 0);
+};
+};
+
+},{"./util.js":35}],5:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var errors = require("./errors.js");
+var canAttach = errors.canAttach;
+var async = require("./async.js");
+var CancellationError = errors.CancellationError;
+
+Promise.prototype._cancel = function Promise$_cancel(reason) {
+ if (!this.isCancellable()) return this;
+ var parent;
+ var promiseToReject = this;
+ while ((parent = promiseToReject._cancellationParent) !== void 0 &&
+ parent.isCancellable()) {
+ promiseToReject = parent;
+ }
+ promiseToReject._attachExtraTrace(reason);
+ promiseToReject._rejectUnchecked(reason);
+};
+
+Promise.prototype.cancel = function Promise$cancel(reason) {
+ if (!this.isCancellable()) return this;
+ reason = reason !== void 0
+ ? (canAttach(reason) ? reason : new Error(reason + ""))
+ : new CancellationError();
+ async.invokeLater(this._cancel, this, reason);
+ return this;
+};
+
+Promise.prototype.cancellable = function Promise$cancellable() {
+ if (this._cancellable()) return this;
+ this._setCancellable();
+ this._cancellationParent = void 0;
+ return this;
+};
+
+Promise.prototype.uncancellable = function Promise$uncancellable() {
+ var ret = new Promise(INTERNAL);
+ ret._propagateFrom(this, 2 | 4);
+ ret._follow(this);
+ ret._unsetCancellable();
+ return ret;
+};
+
+Promise.prototype.fork =
+function Promise$fork(didFulfill, didReject, didProgress) {
+ var ret = this._then(didFulfill, didReject, didProgress,
+ void 0, void 0);
+
+ ret._setCancellable();
+ ret._cancellationParent = void 0;
+ return ret;
+};
+};
+
+},{"./async.js":2,"./errors.js":10}],6:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function() {
+var inherits = require("./util.js").inherits;
+var defineProperty = require("./es5.js").defineProperty;
+
+var rignore = new RegExp(
+ "\\b(?:[a-zA-Z0-9.]+\\$_\\w+|" +
+ "tryCatch(?:1|2|3|4|Apply)|new \\w*PromiseArray|" +
+ "\\w*PromiseArray\\.\\w*PromiseArray|" +
+ "setTimeout|CatchFilter\\$_\\w+|makeNodePromisified|processImmediate|" +
+ "process._tickCallback|nextTick|Async\\$\\w+)\\b"
+);
+
+var rtraceline = null;
+var formatStack = null;
+
+function formatNonError(obj) {
+ var str;
+ if (typeof obj === "function") {
+ str = "[function " +
+ (obj.name || "anonymous") +
+ "]";
+ } else {
+ str = obj.toString();
+ var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/;
+ if (ruselessToString.test(str)) {
+ try {
+ var newStr = JSON.stringify(obj);
+ str = newStr;
+ }
+ catch(e) {
+
+ }
+ }
+ if (str.length === 0) {
+ str = "(empty array)";
+ }
+ }
+ return ("(<" + snip(str) + ">, no stack trace)");
+}
+
+function snip(str) {
+ var maxChars = 41;
+ if (str.length < maxChars) {
+ return str;
+ }
+ return str.substr(0, maxChars - 3) + "...";
+}
+
+function CapturedTrace(ignoreUntil, isTopLevel) {
+ this.captureStackTrace(CapturedTrace, isTopLevel);
+
+}
+inherits(CapturedTrace, Error);
+
+CapturedTrace.prototype.captureStackTrace =
+function CapturedTrace$captureStackTrace(ignoreUntil, isTopLevel) {
+ captureStackTrace(this, ignoreUntil, isTopLevel);
+};
+
+CapturedTrace.possiblyUnhandledRejection =
+function CapturedTrace$PossiblyUnhandledRejection(reason) {
+ if (typeof console === "object") {
+ var message;
+ if (typeof reason === "object" || typeof reason === "function") {
+ var stack = reason.stack;
+ message = "Possibly unhandled " + formatStack(stack, reason);
+ } else {
+ message = "Possibly unhandled " + String(reason);
+ }
+ if (typeof console.error === "function" ||
+ typeof console.error === "object") {
+ console.error(message);
+ } else if (typeof console.log === "function" ||
+ typeof console.log === "object") {
+ console.log(message);
+ }
+ }
+};
+
+CapturedTrace.combine = function CapturedTrace$Combine(current, prev) {
+ var curLast = current.length - 1;
+ for (var i = prev.length - 1; i >= 0; --i) {
+ var line = prev[i];
+ if (current[curLast] === line) {
+ current.pop();
+ curLast--;
+ } else {
+ break;
+ }
+ }
+
+ current.push("From previous event:");
+ var lines = current.concat(prev);
+
+ var ret = [];
+
+ for (var i = 0, len = lines.length; i < len; ++i) {
+
+ if ((rignore.test(lines[i]) ||
+ (i > 0 && !rtraceline.test(lines[i])) &&
+ lines[i] !== "From previous event:")
+ ) {
+ continue;
+ }
+ ret.push(lines[i]);
+ }
+ return ret;
+};
+
+CapturedTrace.protectErrorMessageNewlines = function(stack) {
+ for (var i = 0; i < stack.length; ++i) {
+ if (rtraceline.test(stack[i])) {
+ break;
+ }
+ }
+
+ if (i <= 1) return;
+
+ var errorMessageLines = [];
+ for (var j = 0; j < i; ++j) {
+ errorMessageLines.push(stack.shift());
+ }
+ stack.unshift(errorMessageLines.join("\u0002\u0000\u0001"));
+};
+
+CapturedTrace.isSupported = function CapturedTrace$IsSupported() {
+ return typeof captureStackTrace === "function";
+};
+
+var captureStackTrace = (function stackDetection() {
+ if (typeof Error.stackTraceLimit === "number" &&
+ typeof Error.captureStackTrace === "function") {
+ rtraceline = /^\s*at\s*/;
+ formatStack = function(stack, error) {
+ if (typeof stack === "string") return stack;
+
+ if (error.name !== void 0 &&
+ error.message !== void 0) {
+ return error.name + ". " + error.message;
+ }
+ return formatNonError(error);
+
+
+ };
+ var captureStackTrace = Error.captureStackTrace;
+ return function CapturedTrace$_captureStackTrace(
+ receiver, ignoreUntil) {
+ captureStackTrace(receiver, ignoreUntil);
+ };
+ }
+ var err = new Error();
+
+ if (typeof err.stack === "string" &&
+ typeof "".startsWith === "function" &&
+ (err.stack.startsWith("stackDetection@")) &&
+ stackDetection.name === "stackDetection") {
+
+ defineProperty(Error, "stackTraceLimit", {
+ writable: true,
+ enumerable: false,
+ configurable: false,
+ value: 25
+ });
+ rtraceline = /@/;
+ var rline = /[@\n]/;
+
+ formatStack = function(stack, error) {
+ if (typeof stack === "string") {
+ return (error.name + ". " + error.message + "\n" + stack);
+ }
+
+ if (error.name !== void 0 &&
+ error.message !== void 0) {
+ return error.name + ". " + error.message;
+ }
+ return formatNonError(error);
+ };
+
+ return function captureStackTrace(o) {
+ var stack = new Error().stack;
+ var split = stack.split(rline);
+ var len = split.length;
+ var ret = "";
+ for (var i = 0; i < len; i += 2) {
+ ret += split[i];
+ ret += "@";
+ ret += split[i + 1];
+ ret += "\n";
+ }
+ o.stack = ret;
+ };
+ } else {
+ formatStack = function(stack, error) {
+ if (typeof stack === "string") return stack;
+
+ if ((typeof error === "object" ||
+ typeof error === "function") &&
+ error.name !== void 0 &&
+ error.message !== void 0) {
+ return error.name + ". " + error.message;
+ }
+ return formatNonError(error);
+ };
+
+ return null;
+ }
+})();
+
+return CapturedTrace;
+};
+
+},{"./es5.js":12,"./util.js":35}],7:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(NEXT_FILTER) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+var keys = require("./es5.js").keys;
+var TypeError = errors.TypeError;
+
+function CatchFilter(instances, callback, promise) {
+ this._instances = instances;
+ this._callback = callback;
+ this._promise = promise;
+}
+
+function CatchFilter$_safePredicate(predicate, e) {
+ var safeObject = {};
+ var retfilter = tryCatch1(predicate, safeObject, e);
+
+ if (retfilter === errorObj) return retfilter;
+
+ var safeKeys = keys(safeObject);
+ if (safeKeys.length) {
+ errorObj.e = new TypeError(
+ "Catch filter must inherit from Error "
+ + "or be a simple predicate function");
+ return errorObj;
+ }
+ return retfilter;
+}
+
+CatchFilter.prototype.doFilter = function CatchFilter$_doFilter(e) {
+ var cb = this._callback;
+ var promise = this._promise;
+ var boundTo = promise._boundTo;
+ for (var i = 0, len = this._instances.length; i < len; ++i) {
+ var item = this._instances[i];
+ var itemIsErrorType = item === Error ||
+ (item != null && item.prototype instanceof Error);
+
+ if (itemIsErrorType && e instanceof item) {
+ var ret = tryCatch1(cb, boundTo, e);
+ if (ret === errorObj) {
+ NEXT_FILTER.e = ret.e;
+ return NEXT_FILTER;
+ }
+ return ret;
+ } else if (typeof item === "function" && !itemIsErrorType) {
+ var shouldHandle = CatchFilter$_safePredicate(item, e);
+ if (shouldHandle === errorObj) {
+ var trace = errors.canAttach(errorObj.e)
+ ? errorObj.e
+ : new Error(errorObj.e + "");
+ this._promise._attachExtraTrace(trace);
+ e = errorObj.e;
+ break;
+ } else if (shouldHandle) {
+ var ret = tryCatch1(cb, boundTo, e);
+ if (ret === errorObj) {
+ NEXT_FILTER.e = ret.e;
+ return NEXT_FILTER;
+ }
+ return ret;
+ }
+ }
+ }
+ NEXT_FILTER.e = e;
+ return NEXT_FILTER;
+};
+
+return CatchFilter;
+};
+
+},{"./errors.js":10,"./es5.js":12,"./util.js":35}],8:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var util = require("./util.js");
+var isPrimitive = util.isPrimitive;
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+
+module.exports = function(Promise) {
+var returner = function Promise$_returner() {
+ return this;
+};
+var thrower = function Promise$_thrower() {
+ throw this;
+};
+
+var wrapper = function Promise$_wrapper(value, action) {
+ if (action === 1) {
+ return function Promise$_thrower() {
+ throw value;
+ };
+ } else if (action === 2) {
+ return function Promise$_returner() {
+ return value;
+ };
+ }
+};
+
+
+Promise.prototype["return"] =
+Promise.prototype.thenReturn =
+function Promise$thenReturn(value) {
+ if (wrapsPrimitiveReceiver && isPrimitive(value)) {
+ return this._then(
+ wrapper(value, 2),
+ void 0,
+ void 0,
+ void 0,
+ void 0
+ );
+ }
+ return this._then(returner, void 0, void 0, value, void 0);
+};
+
+Promise.prototype["throw"] =
+Promise.prototype.thenThrow =
+function Promise$thenThrow(reason) {
+ if (wrapsPrimitiveReceiver && isPrimitive(reason)) {
+ return this._then(
+ wrapper(reason, 1),
+ void 0,
+ void 0,
+ void 0,
+ void 0
+ );
+ }
+ return this._then(thrower, void 0, void 0, reason, void 0);
+};
+};
+
+},{"./util.js":35}],9:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var PromiseReduce = Promise.reduce;
+
+Promise.prototype.each = function Promise$each(fn) {
+ return PromiseReduce(this, fn, null, INTERNAL);
+};
+
+Promise.each = function Promise$Each(promises, fn) {
+ return PromiseReduce(promises, fn, null, INTERNAL);
+};
+};
+
+},{}],10:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var Objectfreeze = require("./es5.js").freeze;
+var util = require("./util.js");
+var inherits = util.inherits;
+var notEnumerableProp = util.notEnumerableProp;
+
+function markAsOriginatingFromRejection(e) {
+ try {
+ notEnumerableProp(e, "isOperational", true);
+ }
+ catch(ignore) {}
+}
+
+function originatesFromRejection(e) {
+ if (e == null) return false;
+ return ((e instanceof OperationalError) ||
+ e["isOperational"] === true);
+}
+
+function isError(obj) {
+ // Added by Zotero
+ return obj.message && obj.stack;
+ return obj instanceof Error;
+}
+
+function canAttach(obj) {
+ return isError(obj);
+}
+
+function subError(nameProperty, defaultMessage) {
+ function SubError(message) {
+ if (!(this instanceof SubError)) return new SubError(message);
+ this.message = typeof message === "string" ? message : defaultMessage;
+ this.name = nameProperty;
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
+ }
+ inherits(SubError, Error);
+ return SubError;
+}
+
+var _TypeError, _RangeError;
+var CancellationError = subError("CancellationError", "cancellation error");
+var TimeoutError = subError("TimeoutError", "timeout error");
+var AggregateError = subError("AggregateError", "aggregate error");
+try {
+ _TypeError = TypeError;
+ _RangeError = RangeError;
+} catch(e) {
+ _TypeError = subError("TypeError", "type error");
+ _RangeError = subError("RangeError", "range error");
+}
+
+var methods = ("join pop push shift unshift slice filter forEach some " +
+ "every map indexOf lastIndexOf reduce reduceRight sort reverse").split(" ");
+
+for (var i = 0; i < methods.length; ++i) {
+ if (typeof Array.prototype[methods[i]] === "function") {
+ AggregateError.prototype[methods[i]] = Array.prototype[methods[i]];
+ }
+}
+
+AggregateError.prototype.length = 0;
+AggregateError.prototype["isOperational"] = true;
+var level = 0;
+AggregateError.prototype.toString = function() {
+ var indent = Array(level * 4 + 1).join(" ");
+ var ret = "\n" + indent + "AggregateError of:" + "\n";
+ level++;
+ indent = Array(level * 4 + 1).join(" ");
+ for (var i = 0; i < this.length; ++i) {
+ var str = this[i] === this ? "[Circular AggregateError]" : this[i] + "";
+ var lines = str.split("\n");
+ for (var j = 0; j < lines.length; ++j) {
+ lines[j] = indent + lines[j];
+ }
+ str = lines.join("\n");
+ ret += str + "\n";
+ }
+ level--;
+ return ret;
+};
+
+function OperationalError(message) {
+ this.name = "OperationalError";
+ this.message = message;
+ this.cause = message;
+ this["isOperational"] = true;
+
+ if (message instanceof Error) {
+ this.message = message.message;
+ this.stack = message.stack;
+ } else if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
+
+}
+inherits(OperationalError, Error);
+
+var key = "__BluebirdErrorTypes__";
+var errorTypes = Error[key];
+if (!errorTypes) {
+ errorTypes = Objectfreeze({
+ CancellationError: CancellationError,
+ TimeoutError: TimeoutError,
+ OperationalError: OperationalError,
+ RejectionError: OperationalError,
+ AggregateError: AggregateError
+ });
+ notEnumerableProp(Error, key, errorTypes);
+}
+
+module.exports = {
+ Error: Error,
+ TypeError: _TypeError,
+ RangeError: _RangeError,
+ CancellationError: errorTypes.CancellationError,
+ OperationalError: errorTypes.OperationalError,
+ TimeoutError: errorTypes.TimeoutError,
+ AggregateError: errorTypes.AggregateError,
+ originatesFromRejection: originatesFromRejection,
+ markAsOriginatingFromRejection: markAsOriginatingFromRejection,
+ canAttach: canAttach
+};
+
+},{"./es5.js":12,"./util.js":35}],11:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise) {
+var TypeError = require('./errors.js').TypeError;
+
+function apiRejection(msg) {
+ var error = new TypeError(msg);
+ var ret = Promise.rejected(error);
+ var parent = ret._peekContext();
+ if (parent != null) {
+ parent._attachExtraTrace(error);
+ }
+ return ret;
+}
+
+return apiRejection;
+};
+
+},{"./errors.js":10}],12:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+var isES5 = (function(){
+ "use strict";
+ return this === void 0;
+})();
+
+if (isES5) {
+ module.exports = {
+ freeze: Object.freeze,
+ defineProperty: Object.defineProperty,
+ keys: Object.keys,
+ getPrototypeOf: Object.getPrototypeOf,
+ isArray: Array.isArray,
+ isES5: isES5
+ };
+} else {
+ var has = {}.hasOwnProperty;
+ var str = {}.toString;
+ var proto = {}.constructor.prototype;
+
+ var ObjectKeys = function ObjectKeys(o) {
+ var ret = [];
+ for (var key in o) {
+ if (has.call(o, key)) {
+ ret.push(key);
+ }
+ }
+ return ret;
+ }
+
+ var ObjectDefineProperty = function ObjectDefineProperty(o, key, desc) {
+ o[key] = desc.value;
+ return o;
+ }
+
+ var ObjectFreeze = function ObjectFreeze(obj) {
+ return obj;
+ }
+
+ var ObjectGetPrototypeOf = function ObjectGetPrototypeOf(obj) {
+ try {
+ return Object(obj).constructor.prototype;
+ }
+ catch (e) {
+ return proto;
+ }
+ }
+
+ var ArrayIsArray = function ArrayIsArray(obj) {
+ try {
+ return str.call(obj) === "[object Array]";
+ }
+ catch(e) {
+ return false;
+ }
+ }
+
+ module.exports = {
+ isArray: ArrayIsArray,
+ keys: ObjectKeys,
+ defineProperty: ObjectDefineProperty,
+ freeze: ObjectFreeze,
+ getPrototypeOf: ObjectGetPrototypeOf,
+ isES5: isES5
+ };
+}
+
+},{}],13:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var PromiseMap = Promise.map;
+
+Promise.prototype.filter = function Promise$filter(fn, options) {
+ return PromiseMap(this, fn, options, INTERNAL);
+};
+
+Promise.filter = function Promise$Filter(promises, fn, options) {
+ return PromiseMap(promises, fn, options, INTERNAL);
+};
+};
+
+},{}],14:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, NEXT_FILTER, cast) {
+var util = require("./util.js");
+var wrapsPrimitiveReceiver = util.wrapsPrimitiveReceiver;
+var isPrimitive = util.isPrimitive;
+var thrower = util.thrower;
+
+function returnThis() {
+ return this;
+}
+function throwThis() {
+ throw this;
+}
+function return$(r) {
+ return function Promise$_returner() {
+ return r;
+ };
+}
+function throw$(r) {
+ return function Promise$_thrower() {
+ throw r;
+ };
+}
+function promisedFinally(ret, reasonOrValue, isFulfilled) {
+ var then;
+ if (wrapsPrimitiveReceiver && isPrimitive(reasonOrValue)) {
+ then = isFulfilled ? return$(reasonOrValue) : throw$(reasonOrValue);
+ } else {
+ then = isFulfilled ? returnThis : throwThis;
+ }
+ return ret._then(then, thrower, void 0, reasonOrValue, void 0);
+}
+
+function finallyHandler(reasonOrValue) {
+ var promise = this.promise;
+ var handler = this.handler;
+
+ var ret = promise._isBound()
+ ? handler.call(promise._boundTo)
+ : handler();
+
+ if (ret !== void 0) {
+ var maybePromise = cast(ret, void 0);
+ if (maybePromise instanceof Promise) {
+ return promisedFinally(maybePromise, reasonOrValue,
+ promise.isFulfilled());
+ }
+ }
+
+ if (promise.isRejected()) {
+ NEXT_FILTER.e = reasonOrValue;
+ return NEXT_FILTER;
+ } else {
+ return reasonOrValue;
+ }
+}
+
+function tapHandler(value) {
+ var promise = this.promise;
+ var handler = this.handler;
+
+ var ret = promise._isBound()
+ ? handler.call(promise._boundTo, value)
+ : handler(value);
+
+ if (ret !== void 0) {
+ var maybePromise = cast(ret, void 0);
+ if (maybePromise instanceof Promise) {
+ return promisedFinally(maybePromise, value, true);
+ }
+ }
+ return value;
+}
+
+Promise.prototype._passThroughHandler =
+function Promise$_passThroughHandler(handler, isFinally) {
+ if (typeof handler !== "function") return this.then();
+
+ var promiseAndHandler = {
+ promise: this,
+ handler: handler
+ };
+
+ return this._then(
+ isFinally ? finallyHandler : tapHandler,
+ isFinally ? finallyHandler : void 0, void 0,
+ promiseAndHandler, void 0);
+};
+
+Promise.prototype.lastly =
+Promise.prototype["finally"] = function Promise$finally(handler) {
+ return this._passThroughHandler(handler, true);
+};
+
+Promise.prototype.tap = function Promise$tap(handler) {
+ return this._passThroughHandler(handler, false);
+};
+};
+
+},{"./util.js":35}],15:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, apiRejection, INTERNAL, cast) {
+var errors = require("./errors.js");
+var TypeError = errors.TypeError;
+var deprecated = require("./util.js").deprecated;
+var util = require("./util.js");
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var yieldHandlers = [];
+
+function promiseFromYieldHandler(value, yieldHandlers) {
+ var _errorObj = errorObj;
+ var _Promise = Promise;
+ var len = yieldHandlers.length;
+ for (var i = 0; i < len; ++i) {
+ var result = tryCatch1(yieldHandlers[i], void 0, value);
+ if (result === _errorObj) {
+ return _Promise.reject(_errorObj.e);
+ }
+ var maybePromise = cast(result, promiseFromYieldHandler);
+ if (maybePromise instanceof _Promise) return maybePromise;
+ }
+ return null;
+}
+
+function PromiseSpawn(generatorFunction, receiver, yieldHandler) {
+ var promise = this._promise = new Promise(INTERNAL);
+ promise._setTrace(void 0);
+ this._generatorFunction = generatorFunction;
+ this._receiver = receiver;
+ this._generator = void 0;
+ this._yieldHandlers = typeof yieldHandler === "function"
+ ? [yieldHandler].concat(yieldHandlers)
+ : yieldHandlers;
+}
+
+PromiseSpawn.prototype.promise = function PromiseSpawn$promise() {
+ return this._promise;
+};
+
+PromiseSpawn.prototype._run = function PromiseSpawn$_run() {
+ this._generator = this._generatorFunction.call(this._receiver);
+ this._receiver =
+ this._generatorFunction = void 0;
+ this._next(void 0);
+};
+
+PromiseSpawn.prototype._continue = function PromiseSpawn$_continue(result) {
+ if (result === errorObj) {
+ this._generator = void 0;
+ var trace = errors.canAttach(result.e)
+ ? result.e : new Error(result.e + "");
+ this._promise._attachExtraTrace(trace);
+ this._promise._reject(result.e, trace);
+ return;
+ }
+
+ var value = result.value;
+ if (result.done === true) {
+ this._generator = void 0;
+ if (!this._promise._tryFollow(value)) {
+ this._promise._fulfill(value);
+ }
+ } else {
+ var maybePromise = cast(value, void 0);
+ if (!(maybePromise instanceof Promise)) {
+ maybePromise =
+ promiseFromYieldHandler(maybePromise, this._yieldHandlers);
+ if (maybePromise === null) {
+ // Added by Zotero
+ let e = new TypeError(
+ "A value was yielded that could not be treated as a promise: " + value
+ );
+ global.debug(e);
+ global.debug(e.stack);
+
+ this._throw(new TypeError("A value was yielded that could not be treated as a promise"));
+ return;
+ }
+ }
+ maybePromise._then(
+ this._next,
+ this._throw,
+ void 0,
+ this,
+ null
+ );
+ }
+};
+
+PromiseSpawn.prototype._throw = function PromiseSpawn$_throw(reason) {
+ if (errors.canAttach(reason))
+ this._promise._attachExtraTrace(reason);
+ this._continue(
+ tryCatch1(this._generator["throw"], this._generator, reason)
+ );
+};
+
+PromiseSpawn.prototype._next = function PromiseSpawn$_next(value) {
+ this._continue(
+ tryCatch1(this._generator.next, this._generator, value)
+ );
+};
+
+Promise.coroutine =
+function Promise$Coroutine(generatorFunction, options) {
+ if (typeof generatorFunction !== "function") {
+ throw new TypeError("generatorFunction must be a function");
+ }
+ var yieldHandler = Object(options).yieldHandler;
+ var PromiseSpawn$ = PromiseSpawn;
+ return function () {
+ var generator = generatorFunction.apply(this, arguments);
+ var spawn = new PromiseSpawn$(void 0, void 0, yieldHandler);
+ spawn._generator = generator;
+ spawn._next(void 0);
+ return spawn.promise();
+ };
+};
+
+Promise.coroutine.addYieldHandler = function(fn) {
+ if (typeof fn !== "function") throw new TypeError("fn must be a function");
+ yieldHandlers.push(fn);
+};
+
+// Added by Zotero to allow yielding arrays of promises in coroutines
+Promise.coroutine.addYieldHandler(function(yieldedValue) {
+ if (Array.isArray(yieldedValue)) return Promise.all(yieldedValue);
+});
+
+Promise.spawn = function Promise$Spawn(generatorFunction) {
+ deprecated("Promise.spawn is deprecated. Use Promise.coroutine instead.");
+ if (typeof generatorFunction !== "function") {
+ return apiRejection("generatorFunction must be a function");
+ }
+ var spawn = new PromiseSpawn(generatorFunction, this);
+ var ret = spawn.promise();
+ spawn._run(Promise.spawn);
+ return ret;
+};
+};
+
+},{"./errors.js":10,"./util.js":35}],16:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports =
+function(Promise, PromiseArray, cast, INTERNAL) {
+var util = require("./util.js");
+var canEvaluate = util.canEvaluate;
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+
+
+if (canEvaluate) {
+ var thenCallback = function(i) {
+ return new Function("value", "holder", " \n\
+ 'use strict'; \n\
+ holder.pIndex = value; \n\
+ holder.checkFulfillment(this); \n\
+ ".replace(/Index/g, i));
+ };
+
+ var caller = function(count) {
+ var values = [];
+ for (var i = 1; i <= count; ++i) values.push("holder.p" + i);
+ return new Function("holder", " \n\
+ 'use strict'; \n\
+ var callback = holder.fn; \n\
+ return callback(values); \n\
+ ".replace(/values/g, values.join(", ")));
+ };
+ var thenCallbacks = [];
+ var callers = [void 0];
+ for (var i = 1; i <= 5; ++i) {
+ thenCallbacks.push(thenCallback(i));
+ callers.push(caller(i));
+ }
+
+ var Holder = function(total, fn) {
+ this.p1 = this.p2 = this.p3 = this.p4 = this.p5 = null;
+ this.fn = fn;
+ this.total = total;
+ this.now = 0;
+ };
+
+ Holder.prototype.callers = callers;
+ Holder.prototype.checkFulfillment = function(promise) {
+ var now = this.now;
+ now++;
+ var total = this.total;
+ if (now >= total) {
+ var handler = this.callers[total];
+ var ret = tryCatch1(handler, void 0, this);
+ if (ret === errorObj) {
+ promise._rejectUnchecked(ret.e);
+ } else if (!promise._tryFollow(ret)) {
+ promise._fulfillUnchecked(ret);
+ }
+ } else {
+ this.now = now;
+ }
+ };
+}
+
+
+
+
+Promise.join = function Promise$Join() {
+ var last = arguments.length - 1;
+ var fn;
+ if (last > 0 && typeof arguments[last] === "function") {
+ fn = arguments[last];
+ if (last < 6 && canEvaluate) {
+ var ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ var holder = new Holder(last, fn);
+ var reject = ret._reject;
+ var callbacks = thenCallbacks;
+ for (var i = 0; i < last; ++i) {
+ var maybePromise = cast(arguments[i], void 0);
+ if (maybePromise instanceof Promise) {
+ if (maybePromise.isPending()) {
+ maybePromise._then(callbacks[i], reject,
+ void 0, ret, holder);
+ } else if (maybePromise.isFulfilled()) {
+ callbacks[i].call(ret,
+ maybePromise._settledValue, holder);
+ } else {
+ ret._reject(maybePromise._settledValue);
+ maybePromise._unsetRejectionIsUnhandled();
+ }
+ } else {
+ callbacks[i].call(ret, maybePromise, holder);
+ }
+ }
+ return ret;
+ }
+ }
+ var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+ var ret = new PromiseArray(args).promise();
+ return fn !== void 0 ? ret.spread(fn) : ret;
+};
+
+};
+
+},{"./util.js":35}],17:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray, apiRejection, cast, INTERNAL) {
+var util = require("./util.js");
+var tryCatch3 = util.tryCatch3;
+var errorObj = util.errorObj;
+var PENDING = {};
+var EMPTY_ARRAY = [];
+
+function MappingPromiseArray(promises, fn, limit, _filter) {
+ this.constructor$(promises);
+ this._callback = fn;
+ this._preservedValues = _filter === INTERNAL
+ ? new Array(this.length())
+ : null;
+ this._limit = limit;
+ this._inFlight = 0;
+ this._queue = limit >= 1 ? [] : EMPTY_ARRAY;
+ this._init$(void 0, -2);
+}
+util.inherits(MappingPromiseArray, PromiseArray);
+
+MappingPromiseArray.prototype._init = function MappingPromiseArray$_init() {};
+
+MappingPromiseArray.prototype._promiseFulfilled =
+function MappingPromiseArray$_promiseFulfilled(value, index) {
+ var values = this._values;
+ if (values === null) return;
+
+ var length = this.length();
+ var preservedValues = this._preservedValues;
+ var limit = this._limit;
+ if (values[index] === PENDING) {
+ values[index] = value;
+ if (limit >= 1) {
+ this._inFlight--;
+ this._drainQueue();
+ if (this._isResolved()) return;
+ }
+ } else {
+ if (limit >= 1 && this._inFlight >= limit) {
+ values[index] = value;
+ this._queue.push(index);
+ return;
+ }
+ if (preservedValues !== null) preservedValues[index] = value;
+
+ var callback = this._callback;
+ var receiver = this._promise._boundTo;
+ var ret = tryCatch3(callback, receiver, value, index, length);
+ if (ret === errorObj) return this._reject(ret.e);
+
+ var maybePromise = cast(ret, void 0);
+ if (maybePromise instanceof Promise) {
+ if (maybePromise.isPending()) {
+ if (limit >= 1) this._inFlight++;
+ values[index] = PENDING;
+ return maybePromise._proxyPromiseArray(this, index);
+ } else if (maybePromise.isFulfilled()) {
+ ret = maybePromise.value();
+ } else {
+ maybePromise._unsetRejectionIsUnhandled();
+ return this._reject(maybePromise.reason());
+ }
+ }
+ values[index] = ret;
+ }
+ var totalResolved = ++this._totalResolved;
+ if (totalResolved >= length) {
+ if (preservedValues !== null) {
+ this._filter(values, preservedValues);
+ } else {
+ this._resolve(values);
+ }
+
+ }
+};
+
+MappingPromiseArray.prototype._drainQueue =
+function MappingPromiseArray$_drainQueue() {
+ var queue = this._queue;
+ var limit = this._limit;
+ var values = this._values;
+ while (queue.length > 0 && this._inFlight < limit) {
+ var index = queue.pop();
+ this._promiseFulfilled(values[index], index);
+ }
+};
+
+MappingPromiseArray.prototype._filter =
+function MappingPromiseArray$_filter(booleans, values) {
+ var len = values.length;
+ var ret = new Array(len);
+ var j = 0;
+ for (var i = 0; i < len; ++i) {
+ if (booleans[i]) ret[j++] = values[i];
+ }
+ ret.length = j;
+ this._resolve(ret);
+};
+
+MappingPromiseArray.prototype.preservedValues =
+function MappingPromiseArray$preserveValues() {
+ return this._preservedValues;
+};
+
+function map(promises, fn, options, _filter) {
+ var limit = typeof options === "object" && options !== null
+ ? options.concurrency
+ : 0;
+ limit = typeof limit === "number" &&
+ isFinite(limit) && limit >= 1 ? limit : 0;
+ return new MappingPromiseArray(promises, fn, limit, _filter);
+}
+
+Promise.prototype.map = function Promise$map(fn, options) {
+ if (typeof fn !== "function") return apiRejection("fn must be a function");
+
+ return map(this, fn, options, null).promise();
+};
+
+Promise.map = function Promise$Map(promises, fn, options, _filter) {
+ if (typeof fn !== "function") return apiRejection("fn must be a function");
+ return map(promises, fn, options, _filter).promise();
+};
+
+
+};
+
+},{"./util.js":35}],18:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise) {
+var util = require("./util.js");
+var async = require("./async.js");
+var tryCatch2 = util.tryCatch2;
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+
+function thrower(r) {
+ throw r;
+}
+
+function Promise$_spreadAdapter(val, receiver) {
+ if (!util.isArray(val)) return Promise$_successAdapter(val, receiver);
+ var ret = util.tryCatchApply(this, [null].concat(val), receiver);
+ if (ret === errorObj) {
+ async.invokeLater(thrower, void 0, ret.e);
+ }
+}
+
+function Promise$_successAdapter(val, receiver) {
+ var nodeback = this;
+ var ret = val === void 0
+ ? tryCatch1(nodeback, receiver, null)
+ : tryCatch2(nodeback, receiver, null, val);
+ if (ret === errorObj) {
+ async.invokeLater(thrower, void 0, ret.e);
+ }
+}
+function Promise$_errorAdapter(reason, receiver) {
+ var nodeback = this;
+ var ret = tryCatch1(nodeback, receiver, reason);
+ if (ret === errorObj) {
+ async.invokeLater(thrower, void 0, ret.e);
+ }
+}
+
+Promise.prototype.nodeify = function Promise$nodeify(nodeback, options) {
+ if (typeof nodeback == "function") {
+ var adapter = Promise$_successAdapter;
+ if (options !== void 0 && Object(options).spread) {
+ adapter = Promise$_spreadAdapter;
+ }
+ this._then(
+ adapter,
+ Promise$_errorAdapter,
+ void 0,
+ nodeback,
+ this._boundTo
+ );
+ }
+ return this;
+};
+};
+
+},{"./async.js":2,"./util.js":35}],19:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray) {
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+var tryCatch1 = util.tryCatch1;
+var errorObj = util.errorObj;
+
+Promise.prototype.progressed = function Promise$progressed(handler) {
+ return this._then(void 0, void 0, handler, void 0, void 0);
+};
+
+Promise.prototype._progress = function Promise$_progress(progressValue) {
+ if (this._isFollowingOrFulfilledOrRejected()) return;
+ this._progressUnchecked(progressValue);
+
+};
+
+Promise.prototype._progressHandlerAt =
+function Promise$_progressHandlerAt(index) {
+ return index === 0
+ ? this._progressHandler0
+ : this[(index << 2) + index - 5 + 2];
+};
+
+Promise.prototype._doProgressWith =
+function Promise$_doProgressWith(progression) {
+ var progressValue = progression.value;
+ var handler = progression.handler;
+ var promise = progression.promise;
+ var receiver = progression.receiver;
+
+ var ret = tryCatch1(handler, receiver, progressValue);
+ if (ret === errorObj) {
+ if (ret.e != null &&
+ ret.e.name !== "StopProgressPropagation") {
+ var trace = errors.canAttach(ret.e)
+ ? ret.e : new Error(ret.e + "");
+ promise._attachExtraTrace(trace);
+ promise._progress(ret.e);
+ }
+ } else if (ret instanceof Promise) {
+ ret._then(promise._progress, null, null, promise, void 0);
+ } else {
+ promise._progress(ret);
+ }
+};
+
+
+Promise.prototype._progressUnchecked =
+function Promise$_progressUnchecked(progressValue) {
+ if (!this.isPending()) return;
+ var len = this._length();
+ var progress = this._progress;
+ for (var i = 0; i < len; i++) {
+ var handler = this._progressHandlerAt(i);
+ var promise = this._promiseAt(i);
+ if (!(promise instanceof Promise)) {
+ var receiver = this._receiverAt(i);
+ if (typeof handler === "function") {
+ handler.call(receiver, progressValue, promise);
+ } else if (receiver instanceof Promise && receiver._isProxied()) {
+ receiver._progressUnchecked(progressValue);
+ } else if (receiver instanceof PromiseArray) {
+ receiver._promiseProgressed(progressValue, promise);
+ }
+ continue;
+ }
+
+ if (typeof handler === "function") {
+ async.invoke(this._doProgressWith, this, {
+ handler: handler,
+ promise: promise,
+ receiver: this._receiverAt(i),
+ value: progressValue
+ });
+ } else {
+ async.invoke(progress, promise, progressValue);
+ }
+ }
+};
+};
+
+},{"./async.js":2,"./errors.js":10,"./util.js":35}],20:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var old;
+if (typeof Promise !== "undefined") old = Promise;
+function noConflict(bluebird) {
+ try { if (Promise === bluebird) Promise = old; }
+ catch (e) {}
+ return bluebird;
+}
+module.exports = function() {
+var util = require("./util.js");
+var async = require("./async.js");
+var errors = require("./errors.js");
+
+var INTERNAL = function(){};
+var APPLY = {};
+var NEXT_FILTER = {e: null};
+
+var cast = require("./thenables.js")(Promise, INTERNAL);
+var PromiseArray = require("./promise_array.js")(Promise, INTERNAL, cast);
+var CapturedTrace = require("./captured_trace.js")();
+var CatchFilter = require("./catch_filter.js")(NEXT_FILTER);
+var PromiseResolver = require("./promise_resolver.js");
+
+var isArray = util.isArray;
+
+var errorObj = util.errorObj;
+var tryCatch1 = util.tryCatch1;
+var tryCatch2 = util.tryCatch2;
+var tryCatchApply = util.tryCatchApply;
+var RangeError = errors.RangeError;
+var TypeError = errors.TypeError;
+var CancellationError = errors.CancellationError;
+var TimeoutError = errors.TimeoutError;
+var OperationalError = errors.OperationalError;
+var originatesFromRejection = errors.originatesFromRejection;
+var markAsOriginatingFromRejection = errors.markAsOriginatingFromRejection;
+var canAttach = errors.canAttach;
+var thrower = util.thrower;
+var apiRejection = require("./errors_api_rejection")(Promise);
+
+
+var makeSelfResolutionError = function Promise$_makeSelfResolutionError() {
+ return new TypeError("circular promise resolution chain");
+};
+
+function Promise(resolver) {
+ if (typeof resolver !== "function") {
+ throw new TypeError("the promise constructor requires a resolver function");
+ }
+ if (this.constructor !== Promise) {
+ throw new TypeError("the promise constructor cannot be invoked directly");
+ }
+ this._bitField = 0;
+ this._fulfillmentHandler0 = void 0;
+ this._rejectionHandler0 = void 0;
+ this._promise0 = void 0;
+ this._receiver0 = void 0;
+ this._settledValue = void 0;
+ this._boundTo = void 0;
+ if (resolver !== INTERNAL) this._resolveFromResolver(resolver);
+}
+
+Promise.prototype.bind = function Promise$bind(thisArg) {
+ var ret = new Promise(INTERNAL);
+ ret._follow(this);
+ ret._propagateFrom(this, 2 | 1);
+ ret._setBoundTo(thisArg);
+ return ret;
+};
+
+Promise.prototype.toString = function Promise$toString() {
+ return "[object Promise]";
+};
+
+Promise.prototype.caught = Promise.prototype["catch"] =
+function Promise$catch(fn) {
+ var len = arguments.length;
+ if (len > 1) {
+ var catchInstances = new Array(len - 1),
+ j = 0, i;
+ for (i = 0; i < len - 1; ++i) {
+ var item = arguments[i];
+ if (typeof item === "function") {
+ catchInstances[j++] = item;
+ } else {
+ var catchFilterTypeError =
+ new TypeError(
+ "A catch filter must be an error constructor "
+ + "or a filter function");
+
+ this._attachExtraTrace(catchFilterTypeError);
+ async.invoke(this._reject, this, catchFilterTypeError);
+ return;
+ }
+ }
+ catchInstances.length = j;
+ fn = arguments[i];
+
+ this._resetTrace();
+ var catchFilter = new CatchFilter(catchInstances, fn, this);
+ return this._then(void 0, catchFilter.doFilter, void 0,
+ catchFilter, void 0);
+ }
+ return this._then(void 0, fn, void 0, void 0, void 0);
+};
+
+Promise.prototype.then =
+function Promise$then(didFulfill, didReject, didProgress) {
+ return this._then(didFulfill, didReject, didProgress,
+ void 0, void 0);
+};
+
+
+Promise.prototype.done =
+function Promise$done(didFulfill, didReject, didProgress) {
+ var promise = this._then(didFulfill, didReject, didProgress,
+ void 0, void 0);
+ promise._setIsFinal();
+};
+
+Promise.prototype.spread = function Promise$spread(didFulfill, didReject) {
+ return this._then(didFulfill, didReject, void 0,
+ APPLY, void 0);
+};
+
+Promise.prototype.isCancellable = function Promise$isCancellable() {
+ return !this.isResolved() &&
+ this._cancellable();
+};
+
+Promise.prototype.toJSON = function Promise$toJSON() {
+ var ret = {
+ isFulfilled: false,
+ isRejected: false,
+ fulfillmentValue: void 0,
+ rejectionReason: void 0
+ };
+ if (this.isFulfilled()) {
+ ret.fulfillmentValue = this._settledValue;
+ ret.isFulfilled = true;
+ } else if (this.isRejected()) {
+ ret.rejectionReason = this._settledValue;
+ ret.isRejected = true;
+ }
+ return ret;
+};
+
+Promise.prototype.all = function Promise$all() {
+ return new PromiseArray(this).promise();
+};
+
+
+Promise.is = function Promise$Is(val) {
+ return val instanceof Promise;
+};
+
+Promise.all = function Promise$All(promises) {
+ return new PromiseArray(promises).promise();
+};
+
+Promise.prototype.error = function Promise$_error(fn) {
+ return this.caught(originatesFromRejection, fn);
+};
+
+Promise.prototype._resolveFromSyncValue =
+function Promise$_resolveFromSyncValue(value) {
+ if (value === errorObj) {
+ this._cleanValues();
+ this._setRejected();
+ this._settledValue = value.e;
+ this._ensurePossibleRejectionHandled();
+ } else {
+ var maybePromise = cast(value, void 0);
+ if (maybePromise instanceof Promise) {
+ this._follow(maybePromise);
+ } else {
+ this._cleanValues();
+ this._setFulfilled();
+ this._settledValue = value;
+ }
+ }
+};
+
+Promise.method = function Promise$_Method(fn) {
+ if (typeof fn !== "function") {
+ throw new TypeError("fn must be a function");
+ }
+ return function Promise$_method() {
+ var value;
+ switch(arguments.length) {
+ case 0: value = tryCatch1(fn, this, void 0); break;
+ case 1: value = tryCatch1(fn, this, arguments[0]); break;
+ case 2: value = tryCatch2(fn, this, arguments[0], arguments[1]); break;
+ default:
+ var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}
+ value = tryCatchApply(fn, args, this); break;
+ }
+ var ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ ret._resolveFromSyncValue(value);
+ return ret;
+ };
+};
+
+Promise.attempt = Promise["try"] = function Promise$_Try(fn, args, ctx) {
+ if (typeof fn !== "function") {
+ return apiRejection("fn must be a function");
+ }
+ var value = isArray(args)
+ ? tryCatchApply(fn, args, ctx)
+ : tryCatch1(fn, ctx, args);
+
+ var ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ ret._resolveFromSyncValue(value);
+ return ret;
+};
+
+Promise.defer = Promise.pending = function Promise$Defer() {
+ var promise = new Promise(INTERNAL);
+ promise._setTrace(void 0);
+ return new PromiseResolver(promise);
+};
+
+Promise.bind = function Promise$Bind(thisArg) {
+ var ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ ret._setFulfilled();
+ ret._setBoundTo(thisArg);
+ return ret;
+};
+
+Promise.cast = function Promise$_Cast(obj) {
+ var ret = cast(obj, void 0);
+ if (!(ret instanceof Promise)) {
+ var val = ret;
+ ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ ret._setFulfilled();
+ ret._cleanValues();
+ ret._settledValue = val;
+ }
+ return ret;
+};
+
+Promise.resolve = Promise.fulfilled = Promise.cast;
+
+Promise.reject = Promise.rejected = function Promise$Reject(reason) {
+ var ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ markAsOriginatingFromRejection(reason);
+ ret._cleanValues();
+ ret._setRejected();
+ ret._settledValue = reason;
+ if (!canAttach(reason)) {
+ var trace = new Error(reason + "");
+ ret._setCarriedStackTrace(trace);
+ }
+ ret._ensurePossibleRejectionHandled();
+ return ret;
+};
+
+Promise.onPossiblyUnhandledRejection =
+function Promise$OnPossiblyUnhandledRejection(fn) {
+ CapturedTrace.possiblyUnhandledRejection = typeof fn === "function"
+ ? fn : void 0;
+};
+
+var unhandledRejectionHandled;
+Promise.onUnhandledRejectionHandled =
+function Promise$onUnhandledRejectionHandled(fn) {
+ unhandledRejectionHandled = typeof fn === "function" ? fn : void 0;
+};
+
+var debugging = false || !!(
+ typeof process !== "undefined" &&
+ typeof process.execPath === "string" &&
+ typeof process.env === "object" &&
+ (process.env["BLUEBIRD_DEBUG"] ||
+ process.env["NODE_ENV"] === "development")
+);
+
+
+Promise.longStackTraces = function Promise$LongStackTraces() {
+ if (async.haveItemsQueued() &&
+ debugging === false
+ ) {
+ throw new Error("cannot enable long stack traces after promises have been created");
+ }
+ debugging = CapturedTrace.isSupported();
+};
+
+Promise.hasLongStackTraces = function Promise$HasLongStackTraces() {
+ return debugging && CapturedTrace.isSupported();
+};
+
+Promise.prototype._then =
+function Promise$_then(
+ didFulfill,
+ didReject,
+ didProgress,
+ receiver,
+ internalData
+) {
+ var haveInternalData = internalData !== void 0;
+ var ret = haveInternalData ? internalData : new Promise(INTERNAL);
+
+ if (!haveInternalData) {
+ if (debugging) {
+ var haveSameContext = this._peekContext() === this._traceParent;
+ ret._traceParent = haveSameContext ? this._traceParent : this;
+ }
+ ret._propagateFrom(this, 7);
+ }
+
+ var callbackIndex =
+ this._addCallbacks(didFulfill, didReject, didProgress, ret, receiver);
+
+ if (this.isResolved()) {
+ async.invoke(this._queueSettleAt, this, callbackIndex);
+ }
+
+ return ret;
+};
+
+Promise.prototype._length = function Promise$_length() {
+ return this._bitField & 262143;
+};
+
+Promise.prototype._isFollowingOrFulfilledOrRejected =
+function Promise$_isFollowingOrFulfilledOrRejected() {
+ return (this._bitField & 939524096) > 0;
+};
+
+Promise.prototype._isFollowing = function Promise$_isFollowing() {
+ return (this._bitField & 536870912) === 536870912;
+};
+
+Promise.prototype._setLength = function Promise$_setLength(len) {
+ this._bitField = (this._bitField & -262144) |
+ (len & 262143);
+};
+
+Promise.prototype._setFulfilled = function Promise$_setFulfilled() {
+ this._bitField = this._bitField | 268435456;
+};
+
+Promise.prototype._setRejected = function Promise$_setRejected() {
+ this._bitField = this._bitField | 134217728;
+};
+
+Promise.prototype._setFollowing = function Promise$_setFollowing() {
+ this._bitField = this._bitField | 536870912;
+};
+
+Promise.prototype._setIsFinal = function Promise$_setIsFinal() {
+ this._bitField = this._bitField | 33554432;
+};
+
+Promise.prototype._isFinal = function Promise$_isFinal() {
+ return (this._bitField & 33554432) > 0;
+};
+
+Promise.prototype._cancellable = function Promise$_cancellable() {
+ return (this._bitField & 67108864) > 0;
+};
+
+Promise.prototype._setCancellable = function Promise$_setCancellable() {
+ this._bitField = this._bitField | 67108864;
+};
+
+Promise.prototype._unsetCancellable = function Promise$_unsetCancellable() {
+ this._bitField = this._bitField & (~67108864);
+};
+
+Promise.prototype._setRejectionIsUnhandled =
+function Promise$_setRejectionIsUnhandled() {
+ this._bitField = this._bitField | 2097152;
+};
+
+Promise.prototype._unsetRejectionIsUnhandled =
+function Promise$_unsetRejectionIsUnhandled() {
+ this._bitField = this._bitField & (~2097152);
+ if (this._isUnhandledRejectionNotified()) {
+ this._unsetUnhandledRejectionIsNotified();
+ this._notifyUnhandledRejectionIsHandled();
+ }
+};
+
+Promise.prototype._isRejectionUnhandled =
+function Promise$_isRejectionUnhandled() {
+ return (this._bitField & 2097152) > 0;
+};
+
+Promise.prototype._setUnhandledRejectionIsNotified =
+function Promise$_setUnhandledRejectionIsNotified() {
+ this._bitField = this._bitField | 524288;
+};
+
+Promise.prototype._unsetUnhandledRejectionIsNotified =
+function Promise$_unsetUnhandledRejectionIsNotified() {
+ this._bitField = this._bitField & (~524288);
+};
+
+Promise.prototype._isUnhandledRejectionNotified =
+function Promise$_isUnhandledRejectionNotified() {
+ return (this._bitField & 524288) > 0;
+};
+
+Promise.prototype._setCarriedStackTrace =
+function Promise$_setCarriedStackTrace(capturedTrace) {
+ this._bitField = this._bitField | 1048576;
+ this._fulfillmentHandler0 = capturedTrace;
+};
+
+Promise.prototype._unsetCarriedStackTrace =
+function Promise$_unsetCarriedStackTrace() {
+ this._bitField = this._bitField & (~1048576);
+ this._fulfillmentHandler0 = void 0;
+};
+
+Promise.prototype._isCarryingStackTrace =
+function Promise$_isCarryingStackTrace() {
+ return (this._bitField & 1048576) > 0;
+};
+
+Promise.prototype._getCarriedStackTrace =
+function Promise$_getCarriedStackTrace() {
+ return this._isCarryingStackTrace()
+ ? this._fulfillmentHandler0
+ : void 0;
+};
+
+Promise.prototype._receiverAt = function Promise$_receiverAt(index) {
+ var ret = index === 0
+ ? this._receiver0
+ : this[(index << 2) + index - 5 + 4];
+ if (this._isBound() && ret === void 0) {
+ return this._boundTo;
+ }
+ return ret;
+};
+
+Promise.prototype._promiseAt = function Promise$_promiseAt(index) {
+ return index === 0
+ ? this._promise0
+ : this[(index << 2) + index - 5 + 3];
+};
+
+Promise.prototype._fulfillmentHandlerAt =
+function Promise$_fulfillmentHandlerAt(index) {
+ return index === 0
+ ? this._fulfillmentHandler0
+ : this[(index << 2) + index - 5 + 0];
+};
+
+Promise.prototype._rejectionHandlerAt =
+function Promise$_rejectionHandlerAt(index) {
+ return index === 0
+ ? this._rejectionHandler0
+ : this[(index << 2) + index - 5 + 1];
+};
+
+Promise.prototype._addCallbacks = function Promise$_addCallbacks(
+ fulfill,
+ reject,
+ progress,
+ promise,
+ receiver
+) {
+ var index = this._length();
+
+ if (index >= 262143 - 5) {
+ index = 0;
+ this._setLength(0);
+ }
+
+ if (index === 0) {
+ this._promise0 = promise;
+ if (receiver !== void 0) this._receiver0 = receiver;
+ if (typeof fulfill === "function" && !this._isCarryingStackTrace())
+ this._fulfillmentHandler0 = fulfill;
+ if (typeof reject === "function") this._rejectionHandler0 = reject;
+ if (typeof progress === "function") this._progressHandler0 = progress;
+ } else {
+ var base = (index << 2) + index - 5;
+ this[base + 3] = promise;
+ this[base + 4] = receiver;
+ this[base + 0] = typeof fulfill === "function"
+ ? fulfill : void 0;
+ this[base + 1] = typeof reject === "function"
+ ? reject : void 0;
+ this[base + 2] = typeof progress === "function"
+ ? progress : void 0;
+ }
+ this._setLength(index + 1);
+ return index;
+};
+
+Promise.prototype._setProxyHandlers =
+function Promise$_setProxyHandlers(receiver, promiseSlotValue) {
+ var index = this._length();
+
+ if (index >= 262143 - 5) {
+ index = 0;
+ this._setLength(0);
+ }
+ if (index === 0) {
+ this._promise0 = promiseSlotValue;
+ this._receiver0 = receiver;
+ } else {
+ var base = (index << 2) + index - 5;
+ this[base + 3] = promiseSlotValue;
+ this[base + 4] = receiver;
+ this[base + 0] =
+ this[base + 1] =
+ this[base + 2] = void 0;
+ }
+ this._setLength(index + 1);
+};
+
+Promise.prototype._proxyPromiseArray =
+function Promise$_proxyPromiseArray(promiseArray, index) {
+ this._setProxyHandlers(promiseArray, index);
+};
+
+Promise.prototype._proxyPromise = function Promise$_proxyPromise(promise) {
+ promise._setProxied();
+ this._setProxyHandlers(promise, -1);
+};
+
+Promise.prototype._setBoundTo = function Promise$_setBoundTo(obj) {
+ if (obj !== void 0) {
+ this._bitField = this._bitField | 8388608;
+ this._boundTo = obj;
+ } else {
+ this._bitField = this._bitField & (~8388608);
+ }
+};
+
+Promise.prototype._isBound = function Promise$_isBound() {
+ return (this._bitField & 8388608) === 8388608;
+};
+
+Promise.prototype._resolveFromResolver =
+function Promise$_resolveFromResolver(resolver) {
+ var promise = this;
+ this._setTrace(void 0);
+ this._pushContext();
+
+ function Promise$_resolver(val) {
+ if (promise._tryFollow(val)) {
+ return;
+ }
+ promise._fulfill(val);
+ }
+ function Promise$_rejecter(val) {
+ var trace = canAttach(val) ? val : new Error(val + "");
+ promise._attachExtraTrace(trace);
+ markAsOriginatingFromRejection(val);
+ promise._reject(val, trace === val ? void 0 : trace);
+ }
+ var r = tryCatch2(resolver, void 0, Promise$_resolver, Promise$_rejecter);
+ this._popContext();
+
+ if (r !== void 0 && r === errorObj) {
+ var e = r.e;
+ var trace = canAttach(e) ? e : new Error(e + "");
+ promise._reject(e, trace);
+ }
+};
+
+Promise.prototype._spreadSlowCase =
+function Promise$_spreadSlowCase(targetFn, promise, values, boundTo) {
+ var promiseForAll = new PromiseArray(values).promise();
+ var promise2 = promiseForAll._then(function() {
+ return targetFn.apply(boundTo, arguments);
+ }, void 0, void 0, APPLY, void 0);
+ promise._follow(promise2);
+};
+
+Promise.prototype._callSpread =
+function Promise$_callSpread(handler, promise, value) {
+ var boundTo = this._boundTo;
+ if (isArray(value)) {
+ for (var i = 0, len = value.length; i < len; ++i) {
+ if (cast(value[i], void 0) instanceof Promise) {
+ this._spreadSlowCase(handler, promise, value, boundTo);
+ return;
+ }
+ }
+ }
+ promise._pushContext();
+ return tryCatchApply(handler, value, boundTo);
+};
+
+Promise.prototype._callHandler =
+function Promise$_callHandler(
+ handler, receiver, promise, value) {
+ var x;
+ if (receiver === APPLY && !this.isRejected()) {
+ x = this._callSpread(handler, promise, value);
+ } else {
+ promise._pushContext();
+ x = tryCatch1(handler, receiver, value);
+ }
+ promise._popContext();
+ return x;
+};
+
+Promise.prototype._settlePromiseFromHandler =
+function Promise$_settlePromiseFromHandler(
+ handler, receiver, value, promise
+) {
+ if (!(promise instanceof Promise)) {
+ handler.call(receiver, value, promise);
+ return;
+ }
+ var x = this._callHandler(handler, receiver, promise, value);
+ if (promise._isFollowing()) return;
+
+ if (x === errorObj || x === promise || x === NEXT_FILTER) {
+ var err = x === promise
+ ? makeSelfResolutionError()
+ : x.e;
+ var trace = canAttach(err) ? err : new Error(err + "");
+ if (x !== NEXT_FILTER) promise._attachExtraTrace(trace);
+ promise._rejectUnchecked(err, trace);
+ } else {
+ var castValue = cast(x, promise);
+ if (castValue instanceof Promise) {
+ if (castValue.isRejected() &&
+ !castValue._isCarryingStackTrace() &&
+ !canAttach(castValue._settledValue)) {
+ var trace = new Error(castValue._settledValue + "");
+ promise._attachExtraTrace(trace);
+ castValue._setCarriedStackTrace(trace);
+ }
+ promise._follow(castValue);
+ promise._propagateFrom(castValue, 1);
+ } else {
+ promise._fulfillUnchecked(x);
+ }
+ }
+};
+
+Promise.prototype._follow =
+function Promise$_follow(promise) {
+ this._setFollowing();
+
+ if (promise.isPending()) {
+ this._propagateFrom(promise, 1);
+ promise._proxyPromise(this);
+ } else if (promise.isFulfilled()) {
+ this._fulfillUnchecked(promise._settledValue);
+ } else {
+ this._rejectUnchecked(promise._settledValue,
+ promise._getCarriedStackTrace());
+ }
+
+ if (promise._isRejectionUnhandled()) promise._unsetRejectionIsUnhandled();
+
+ if (debugging &&
+ promise._traceParent == null) {
+ promise._traceParent = this;
+ }
+};
+
+Promise.prototype._tryFollow =
+function Promise$_tryFollow(value) {
+ if (this._isFollowingOrFulfilledOrRejected() ||
+ value === this) {
+ return false;
+ }
+ var maybePromise = cast(value, void 0);
+ if (!(maybePromise instanceof Promise)) {
+ return false;
+ }
+ this._follow(maybePromise);
+ return true;
+};
+
+Promise.prototype._resetTrace = function Promise$_resetTrace() {
+ if (debugging) {
+ this._trace = new CapturedTrace(this._peekContext() === void 0);
+ }
+};
+
+Promise.prototype._setTrace = function Promise$_setTrace(parent) {
+ if (debugging) {
+ var context = this._peekContext();
+ this._traceParent = context;
+ var isTopLevel = context === void 0;
+ if (parent !== void 0 &&
+ parent._traceParent === context) {
+ this._trace = parent._trace;
+ } else {
+ this._trace = new CapturedTrace(isTopLevel);
+ }
+ }
+ return this;
+};
+
+Promise.prototype._attachExtraTrace =
+function Promise$_attachExtraTrace(error) {
+ if (debugging) {
+ var promise = this;
+ var stack = error.stack;
+ stack = typeof stack === "string" ? stack.split("\n") : [];
+ CapturedTrace.protectErrorMessageNewlines(stack);
+ var headerLineCount = 1;
+ var combinedTraces = 1;
+ while(promise != null &&
+ promise._trace != null) {
+ stack = CapturedTrace.combine(
+ stack,
+ promise._trace.stack.split("\n")
+ );
+ promise = promise._traceParent;
+ combinedTraces++;
+ }
+
+ var stackTraceLimit = Error.stackTraceLimit || 10;
+ var max = (stackTraceLimit + headerLineCount) * combinedTraces;
+ var len = stack.length;
+ if (len > max) {
+ stack.length = max;
+ }
+
+ if (len > 0)
+ stack[0] = stack[0].split("\u0002\u0000\u0001").join("\n");
+
+ if (stack.length <= headerLineCount) {
+ error.stack = "(No stack trace)";
+ } else {
+ error.stack = stack.join("\n");
+ }
+ }
+};
+
+Promise.prototype._cleanValues = function Promise$_cleanValues() {
+ if (this._cancellable()) {
+ this._cancellationParent = void 0;
+ }
+};
+
+Promise.prototype._propagateFrom =
+function Promise$_propagateFrom(parent, flags) {
+ if ((flags & 1) > 0 && parent._cancellable()) {
+ this._setCancellable();
+ this._cancellationParent = parent;
+ }
+ if ((flags & 4) > 0) {
+ this._setBoundTo(parent._boundTo);
+ }
+ if ((flags & 2) > 0) {
+ this._setTrace(parent);
+ }
+};
+
+Promise.prototype._fulfill = function Promise$_fulfill(value) {
+ if (this._isFollowingOrFulfilledOrRejected()) return;
+ this._fulfillUnchecked(value);
+};
+
+Promise.prototype._reject =
+function Promise$_reject(reason, carriedStackTrace) {
+ if (this._isFollowingOrFulfilledOrRejected()) return;
+ this._rejectUnchecked(reason, carriedStackTrace);
+};
+
+Promise.prototype._settlePromiseAt = function Promise$_settlePromiseAt(index) {
+ var handler = this.isFulfilled()
+ ? this._fulfillmentHandlerAt(index)
+ : this._rejectionHandlerAt(index);
+
+ var value = this._settledValue;
+ var receiver = this._receiverAt(index);
+ var promise = this._promiseAt(index);
+
+ if (typeof handler === "function") {
+ this._settlePromiseFromHandler(handler, receiver, value, promise);
+ } else {
+ var done = false;
+ var isFulfilled = this.isFulfilled();
+ if (receiver !== void 0) {
+ if (receiver instanceof Promise &&
+ receiver._isProxied()) {
+ receiver._unsetProxied();
+
+ if (isFulfilled) receiver._fulfillUnchecked(value);
+ else receiver._rejectUnchecked(value,
+ this._getCarriedStackTrace());
+ done = true;
+ } else if (receiver instanceof PromiseArray) {
+ if (isFulfilled) receiver._promiseFulfilled(value, promise);
+ else receiver._promiseRejected(value, promise);
+ done = true;
+ }
+ }
+
+ if (!done) {
+ if (isFulfilled) promise._fulfill(value);
+ else promise._reject(value, this._getCarriedStackTrace());
+ }
+ }
+
+ if (index >= 256) {
+ this._queueGC();
+ }
+};
+
+Promise.prototype._isProxied = function Promise$_isProxied() {
+ return (this._bitField & 4194304) === 4194304;
+};
+
+Promise.prototype._setProxied = function Promise$_setProxied() {
+ this._bitField = this._bitField | 4194304;
+};
+
+Promise.prototype._unsetProxied = function Promise$_unsetProxied() {
+ this._bitField = this._bitField & (~4194304);
+};
+
+Promise.prototype._isGcQueued = function Promise$_isGcQueued() {
+ return (this._bitField & -1073741824) === -1073741824;
+};
+
+Promise.prototype._setGcQueued = function Promise$_setGcQueued() {
+ this._bitField = this._bitField | -1073741824;
+};
+
+Promise.prototype._unsetGcQueued = function Promise$_unsetGcQueued() {
+ this._bitField = this._bitField & (~-1073741824);
+};
+
+Promise.prototype._queueGC = function Promise$_queueGC() {
+ if (this._isGcQueued()) return;
+ this._setGcQueued();
+ async.invokeLater(this._gc, this, void 0);
+};
+
+Promise.prototype._gc = function Promise$gc() {
+ var len = this._length() * 5;
+ for (var i = 0; i < len; i++) {
+ delete this[i];
+ }
+ this._setLength(0);
+ this._unsetGcQueued();
+};
+
+Promise.prototype._queueSettleAt = function Promise$_queueSettleAt(index) {
+ if (this._isRejectionUnhandled()) this._unsetRejectionIsUnhandled();
+ async.invoke(this._settlePromiseAt, this, index);
+};
+
+Promise.prototype._fulfillUnchecked =
+function Promise$_fulfillUnchecked(value) {
+ if (!this.isPending()) return;
+ if (value === this) {
+ var err = makeSelfResolutionError();
+ this._attachExtraTrace(err);
+ return this._rejectUnchecked(err, void 0);
+ }
+ this._cleanValues();
+ this._setFulfilled();
+ this._settledValue = value;
+ var len = this._length();
+
+ if (len > 0) {
+ async.invoke(this._settlePromises, this, len);
+ }
+};
+
+Promise.prototype._rejectUncheckedCheckError =
+function Promise$_rejectUncheckedCheckError(reason) {
+ var trace = canAttach(reason) ? reason : new Error(reason + "");
+ this._rejectUnchecked(reason, trace === reason ? void 0 : trace);
+};
+
+Promise.prototype._rejectUnchecked =
+function Promise$_rejectUnchecked(reason, trace) {
+ if (!this.isPending()) return;
+ if (reason === this) {
+ var err = makeSelfResolutionError();
+ this._attachExtraTrace(err);
+ return this._rejectUnchecked(err);
+ }
+ this._cleanValues();
+ this._setRejected();
+ this._settledValue = reason;
+
+ if (this._isFinal()) {
+ async.invokeLater(thrower, void 0, trace === void 0 ? reason : trace);
+ return;
+ }
+ var len = this._length();
+
+ if (trace !== void 0) this._setCarriedStackTrace(trace);
+
+ if (len > 0) {
+ async.invoke(this._rejectPromises, this, null);
+ } else {
+ this._ensurePossibleRejectionHandled();
+ }
+};
+
+Promise.prototype._rejectPromises = function Promise$_rejectPromises() {
+ this._settlePromises();
+ this._unsetCarriedStackTrace();
+};
+
+Promise.prototype._settlePromises = function Promise$_settlePromises() {
+ var len = this._length();
+ for (var i = 0; i < len; i++) {
+ this._settlePromiseAt(i);
+ }
+};
+
+Promise.prototype._ensurePossibleRejectionHandled =
+function Promise$_ensurePossibleRejectionHandled() {
+ this._setRejectionIsUnhandled();
+ if (CapturedTrace.possiblyUnhandledRejection !== void 0) {
+ async.invokeLater(this._notifyUnhandledRejection, this, void 0);
+ }
+};
+
+Promise.prototype._notifyUnhandledRejectionIsHandled =
+function Promise$_notifyUnhandledRejectionIsHandled() {
+ if (typeof unhandledRejectionHandled === "function") {
+ async.invokeLater(unhandledRejectionHandled, void 0, this);
+ }
+};
+
+Promise.prototype._notifyUnhandledRejection =
+function Promise$_notifyUnhandledRejection() {
+ if (this._isRejectionUnhandled()) {
+ var reason = this._settledValue;
+ var trace = this._getCarriedStackTrace();
+
+ this._setUnhandledRejectionIsNotified();
+
+ if (trace !== void 0) {
+ this._unsetCarriedStackTrace();
+ reason = trace;
+ }
+ if (typeof CapturedTrace.possiblyUnhandledRejection === "function") {
+ CapturedTrace.possiblyUnhandledRejection(reason, this);
+ }
+ }
+};
+
+var contextStack = [];
+Promise.prototype._peekContext = function Promise$_peekContext() {
+ var lastIndex = contextStack.length - 1;
+ if (lastIndex >= 0) {
+ return contextStack[lastIndex];
+ }
+ return void 0;
+
+};
+
+Promise.prototype._pushContext = function Promise$_pushContext() {
+ if (!debugging) return;
+ contextStack.push(this);
+};
+
+Promise.prototype._popContext = function Promise$_popContext() {
+ if (!debugging) return;
+ contextStack.pop();
+};
+
+Promise.noConflict = function Promise$NoConflict() {
+ return noConflict(Promise);
+};
+
+Promise.setScheduler = function(fn) {
+ if (typeof fn !== "function") throw new TypeError("fn must be a function");
+ async._schedule = fn;
+};
+
+if (!CapturedTrace.isSupported()) {
+ Promise.longStackTraces = function(){};
+ debugging = false;
+}
+
+Promise._makeSelfResolutionError = makeSelfResolutionError;
+require("./finally.js")(Promise, NEXT_FILTER, cast);
+require("./direct_resolve.js")(Promise);
+require("./synchronous_inspection.js")(Promise);
+require("./join.js")(Promise, PromiseArray, cast, INTERNAL);
+Promise.RangeError = RangeError;
+Promise.CancellationError = CancellationError;
+Promise.TimeoutError = TimeoutError;
+Promise.TypeError = TypeError;
+Promise.OperationalError = OperationalError;
+Promise.RejectionError = OperationalError;
+Promise.AggregateError = errors.AggregateError;
+
+util.toFastProperties(Promise);
+util.toFastProperties(Promise.prototype);
+Promise.Promise = Promise;
+require('./timers.js')(Promise,INTERNAL,cast);
+require('./race.js')(Promise,INTERNAL,cast);
+require('./call_get.js')(Promise);
+require('./generators.js')(Promise,apiRejection,INTERNAL,cast);
+require('./map.js')(Promise,PromiseArray,apiRejection,cast,INTERNAL);
+require('./nodeify.js')(Promise);
+require('./promisify.js')(Promise,INTERNAL);
+require('./props.js')(Promise,PromiseArray,cast);
+require('./reduce.js')(Promise,PromiseArray,apiRejection,cast,INTERNAL);
+require('./settle.js')(Promise,PromiseArray);
+require('./some.js')(Promise,PromiseArray,apiRejection);
+require('./progress.js')(Promise,PromiseArray);
+require('./cancel.js')(Promise,INTERNAL);
+require('./filter.js')(Promise,INTERNAL);
+require('./any.js')(Promise,PromiseArray);
+require('./each.js')(Promise,INTERNAL);
+require('./using.js')(Promise,apiRejection,cast);
+
+Promise.prototype = Promise.prototype;
+return Promise;
+
+};
+
+},{"./any.js":1,"./async.js":2,"./call_get.js":4,"./cancel.js":5,"./captured_trace.js":6,"./catch_filter.js":7,"./direct_resolve.js":8,"./each.js":9,"./errors.js":10,"./errors_api_rejection":11,"./filter.js":13,"./finally.js":14,"./generators.js":15,"./join.js":16,"./map.js":17,"./nodeify.js":18,"./progress.js":19,"./promise_array.js":21,"./promise_resolver.js":22,"./promisify.js":23,"./props.js":24,"./race.js":26,"./reduce.js":27,"./settle.js":29,"./some.js":30,"./synchronous_inspection.js":31,"./thenables.js":32,"./timers.js":33,"./using.js":34,"./util.js":35}],21:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL, cast) {
+var canAttach = require("./errors.js").canAttach;
+var util = require("./util.js");
+var isArray = util.isArray;
+
+function toResolutionValue(val) {
+ switch(val) {
+ case -1: return void 0;
+ case -2: return [];
+ case -3: return {};
+ }
+}
+
+function PromiseArray(values) {
+ var promise = this._promise = new Promise(INTERNAL);
+ var parent = void 0;
+ if (values instanceof Promise) {
+ parent = values;
+ promise._propagateFrom(parent, 1 | 4);
+ }
+ promise._setTrace(parent);
+ this._values = values;
+ this._length = 0;
+ this._totalResolved = 0;
+ this._init(void 0, -2);
+}
+PromiseArray.prototype.length = function PromiseArray$length() {
+ return this._length;
+};
+
+PromiseArray.prototype.promise = function PromiseArray$promise() {
+ return this._promise;
+};
+
+PromiseArray.prototype._init =
+function PromiseArray$_init(_, resolveValueIfEmpty) {
+ var values = cast(this._values, void 0);
+ if (values instanceof Promise) {
+ this._values = values;
+ values._setBoundTo(this._promise._boundTo);
+ if (values.isFulfilled()) {
+ values = values._settledValue;
+ if (!isArray(values)) {
+ var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+ this.__hardReject__(err);
+ return;
+ }
+ } else if (values.isPending()) {
+ values._then(
+ PromiseArray$_init,
+ this._reject,
+ void 0,
+ this,
+ resolveValueIfEmpty
+ );
+ return;
+ } else {
+ values._unsetRejectionIsUnhandled();
+ this._reject(values._settledValue);
+ return;
+ }
+ } else if (!isArray(values)) {
+ var err = new Promise.TypeError("expecting an array, a promise or a thenable");
+ this.__hardReject__(err);
+ return;
+ }
+
+ if (values.length === 0) {
+ if (resolveValueIfEmpty === -5) {
+ this._resolveEmptyArray();
+ }
+ else {
+ this._resolve(toResolutionValue(resolveValueIfEmpty));
+ }
+ return;
+ }
+ var len = this.getActualLength(values.length);
+ var newLen = len;
+ var newValues = this.shouldCopyValues() ? new Array(len) : this._values;
+ var isDirectScanNeeded = false;
+ for (var i = 0; i < len; ++i) {
+ var maybePromise = cast(values[i], void 0);
+ if (maybePromise instanceof Promise) {
+ if (maybePromise.isPending()) {
+ maybePromise._proxyPromiseArray(this, i);
+ } else {
+ maybePromise._unsetRejectionIsUnhandled();
+ isDirectScanNeeded = true;
+ }
+ } else {
+ isDirectScanNeeded = true;
+ }
+ newValues[i] = maybePromise;
+ }
+ this._values = newValues;
+ this._length = newLen;
+ if (isDirectScanNeeded) {
+ this._scanDirectValues(len);
+ }
+};
+
+PromiseArray.prototype._settlePromiseAt =
+function PromiseArray$_settlePromiseAt(index) {
+ var value = this._values[index];
+ if (!(value instanceof Promise)) {
+ this._promiseFulfilled(value, index);
+ } else if (value.isFulfilled()) {
+ this._promiseFulfilled(value._settledValue, index);
+ } else if (value.isRejected()) {
+ this._promiseRejected(value._settledValue, index);
+ }
+};
+
+PromiseArray.prototype._scanDirectValues =
+function PromiseArray$_scanDirectValues(len) {
+ for (var i = 0; i < len; ++i) {
+ if (this._isResolved()) {
+ break;
+ }
+ this._settlePromiseAt(i);
+ }
+};
+
+PromiseArray.prototype._isResolved = function PromiseArray$_isResolved() {
+ return this._values === null;
+};
+
+PromiseArray.prototype._resolve = function PromiseArray$_resolve(value) {
+ this._values = null;
+ this._promise._fulfill(value);
+};
+
+PromiseArray.prototype.__hardReject__ =
+PromiseArray.prototype._reject = function PromiseArray$_reject(reason) {
+ this._values = null;
+ var trace = canAttach(reason) ? reason : new Error(reason + "");
+ this._promise._attachExtraTrace(trace);
+ this._promise._reject(reason, trace);
+};
+
+PromiseArray.prototype._promiseProgressed =
+function PromiseArray$_promiseProgressed(progressValue, index) {
+ if (this._isResolved()) return;
+ this._promise._progress({
+ index: index,
+ value: progressValue
+ });
+};
+
+
+PromiseArray.prototype._promiseFulfilled =
+function PromiseArray$_promiseFulfilled(value, index) {
+ if (this._isResolved()) return;
+ this._values[index] = value;
+ var totalResolved = ++this._totalResolved;
+ if (totalResolved >= this._length) {
+ this._resolve(this._values);
+ }
+};
+
+PromiseArray.prototype._promiseRejected =
+function PromiseArray$_promiseRejected(reason, index) {
+ if (this._isResolved()) return;
+ this._totalResolved++;
+ this._reject(reason);
+};
+
+PromiseArray.prototype.shouldCopyValues =
+function PromiseArray$_shouldCopyValues() {
+ return true;
+};
+
+PromiseArray.prototype.getActualLength =
+function PromiseArray$getActualLength(len) {
+ return len;
+};
+
+return PromiseArray;
+};
+
+},{"./errors.js":10,"./util.js":35}],22:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var util = require("./util.js");
+var maybeWrapAsError = util.maybeWrapAsError;
+var errors = require("./errors.js");
+var TimeoutError = errors.TimeoutError;
+var OperationalError = errors.OperationalError;
+var async = require("./async.js");
+var haveGetters = util.haveGetters;
+var es5 = require("./es5.js");
+
+function isUntypedError(obj) {
+ return obj instanceof Error &&
+ es5.getPrototypeOf(obj) === Error.prototype;
+}
+
+function wrapAsOperationalError(obj) {
+ var ret;
+ if (isUntypedError(obj)) {
+ ret = new OperationalError(obj);
+ } else {
+ ret = obj;
+ }
+ errors.markAsOriginatingFromRejection(ret);
+ return ret;
+}
+
+function nodebackForPromise(promise) {
+ function PromiseResolver$_callback(err, value) {
+ if (promise === null) return;
+
+ if (err) {
+ var wrapped = wrapAsOperationalError(maybeWrapAsError(err));
+ promise._attachExtraTrace(wrapped);
+ promise._reject(wrapped);
+ } else if (arguments.length > 2) {
+ var $_len = arguments.length;var args = new Array($_len - 1); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}
+ promise._fulfill(args);
+ } else {
+ promise._fulfill(value);
+ }
+
+ promise = null;
+ }
+ return PromiseResolver$_callback;
+}
+
+
+var PromiseResolver;
+if (!haveGetters) {
+ PromiseResolver = function PromiseResolver(promise) {
+ this.promise = promise;
+ this.asCallback = nodebackForPromise(promise);
+ this.callback = this.asCallback;
+ };
+}
+else {
+ PromiseResolver = function PromiseResolver(promise) {
+ this.promise = promise;
+ };
+}
+if (haveGetters) {
+ var prop = {
+ get: function() {
+ return nodebackForPromise(this.promise);
+ }
+ };
+ es5.defineProperty(PromiseResolver.prototype, "asCallback", prop);
+ es5.defineProperty(PromiseResolver.prototype, "callback", prop);
+}
+
+PromiseResolver._nodebackForPromise = nodebackForPromise;
+
+PromiseResolver.prototype.toString = function PromiseResolver$toString() {
+ return "[object PromiseResolver]";
+};
+
+PromiseResolver.prototype.resolve =
+PromiseResolver.prototype.fulfill = function PromiseResolver$resolve(value) {
+ if (!(this instanceof PromiseResolver)) {
+ throw new TypeError("Illegal invocation, resolver resolve/reject must be called within a resolver context. Consider using the promise constructor instead.");
+ }
+
+ var promise = this.promise;
+ if (promise._tryFollow(value)) {
+ return;
+ }
+ async.invoke(promise._fulfill, promise, value);
+};
+
+PromiseResolver.prototype.reject = function PromiseResolver$reject(reason) {
+ if (!(this instanceof PromiseResolver)) {
+ throw new TypeError("Illegal invocation, resolver resolve/reject must be called within a resolver context. Consider using the promise constructor instead.");
+ }
+
+ var promise = this.promise;
+ errors.markAsOriginatingFromRejection(reason);
+ var trace = errors.canAttach(reason) ? reason : new Error(reason + "");
+ promise._attachExtraTrace(trace);
+ async.invoke(promise._reject, promise, reason);
+ if (trace !== reason) {
+ async.invoke(this._setCarriedStackTrace, this, trace);
+ }
+};
+
+PromiseResolver.prototype.progress =
+function PromiseResolver$progress(value) {
+ if (!(this instanceof PromiseResolver)) {
+ throw new TypeError("Illegal invocation, resolver resolve/reject must be called within a resolver context. Consider using the promise constructor instead.");
+ }
+ async.invoke(this.promise._progress, this.promise, value);
+};
+
+PromiseResolver.prototype.cancel = function PromiseResolver$cancel() {
+ async.invoke(this.promise.cancel, this.promise, void 0);
+};
+
+PromiseResolver.prototype.timeout = function PromiseResolver$timeout() {
+ this.reject(new TimeoutError("timeout"));
+};
+
+PromiseResolver.prototype.isResolved = function PromiseResolver$isResolved() {
+ return this.promise.isResolved();
+};
+
+PromiseResolver.prototype.toJSON = function PromiseResolver$toJSON() {
+ return this.promise.toJSON();
+};
+
+PromiseResolver.prototype._setCarriedStackTrace =
+function PromiseResolver$_setCarriedStackTrace(trace) {
+ if (this.promise.isRejected()) {
+ this.promise._setCarriedStackTrace(trace);
+ }
+};
+
+module.exports = PromiseResolver;
+
+},{"./async.js":2,"./errors.js":10,"./es5.js":12,"./util.js":35}],23:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var THIS = {};
+var util = require("./util.js");
+var nodebackForPromise = require("./promise_resolver.js")
+ ._nodebackForPromise;
+var withAppended = util.withAppended;
+var maybeWrapAsError = util.maybeWrapAsError;
+var canEvaluate = util.canEvaluate;
+var TypeError = require("./errors").TypeError;
+var defaultSuffix = "Async";
+var defaultFilter = function(name, func) {
+ return util.isIdentifier(name) &&
+ name.charAt(0) !== "_" &&
+ !util.isClass(func);
+};
+var defaultPromisified = {__isPromisified__: true};
+
+
+function escapeIdentRegex(str) {
+ return str.replace(/([$])/, "\\$");
+}
+
+function isPromisified(fn) {
+ try {
+ return fn.__isPromisified__ === true;
+ }
+ catch (e) {
+ return false;
+ }
+}
+
+function hasPromisified(obj, key, suffix) {
+ var val = util.getDataPropertyOrDefault(obj, key + suffix,
+ defaultPromisified);
+ return val ? isPromisified(val) : false;
+}
+function checkValid(ret, suffix, suffixRegexp) {
+ for (var i = 0; i < ret.length; i += 2) {
+ var key = ret[i];
+ if (suffixRegexp.test(key)) {
+ var keyWithoutAsyncSuffix = key.replace(suffixRegexp, "");
+ for (var j = 0; j < ret.length; j += 2) {
+ if (ret[j] === keyWithoutAsyncSuffix) {
+ throw new TypeError("Cannot promisify an API " +
+ "that has normal methods with '"+suffix+"'-suffix");
+ }
+ }
+ }
+ }
+}
+
+function promisifiableMethods(obj, suffix, suffixRegexp, filter) {
+ var keys = util.inheritedDataKeys(obj);
+ var ret = [];
+ for (var i = 0; i < keys.length; ++i) {
+ var key = keys[i];
+ var value = obj[key];
+ if (typeof value === "function" &&
+ !isPromisified(value) &&
+ !hasPromisified(obj, key, suffix) &&
+ filter(key, value, obj)) {
+ ret.push(key, value);
+ }
+ }
+ checkValid(ret, suffix, suffixRegexp);
+ return ret;
+}
+
+function switchCaseArgumentOrder(likelyArgumentCount) {
+ var ret = [likelyArgumentCount];
+ var min = Math.max(0, likelyArgumentCount - 1 - 5);
+ for(var i = likelyArgumentCount - 1; i >= min; --i) {
+ if (i === likelyArgumentCount) continue;
+ ret.push(i);
+ }
+ for(var i = likelyArgumentCount + 1; i <= 5; ++i) {
+ ret.push(i);
+ }
+ return ret;
+}
+
+function argumentSequence(argumentCount) {
+ return util.filledRange(argumentCount, "arguments[", "]");
+}
+
+function parameterDeclaration(parameterCount) {
+ return util.filledRange(parameterCount, "_arg", "");
+}
+
+function parameterCount(fn) {
+ if (typeof fn.length === "number") {
+ return Math.max(Math.min(fn.length, 1023 + 1), 0);
+ }
+ return 0;
+}
+
+function generatePropertyAccess(key) {
+ if (util.isIdentifier(key)) {
+ return "." + key;
+ }
+ else return "['" + key.replace(/(['\\])/g, "\\$1") + "']";
+}
+
+function makeNodePromisifiedEval(callback, receiver, originalName, fn, suffix) {
+ var newParameterCount = Math.max(0, parameterCount(fn) - 1);
+ var argumentOrder = switchCaseArgumentOrder(newParameterCount);
+ var callbackName =
+ (typeof originalName === "string" && util.isIdentifier(originalName)
+ ? originalName + suffix
+ : "promisified");
+
+ function generateCallForArgumentCount(count) {
+ var args = argumentSequence(count).join(", ");
+ var comma = count > 0 ? ", " : "";
+ var ret;
+ if (typeof callback === "string") {
+ ret = " \n\
+ this.method(args, fn); \n\
+ break; \n\
+ ".replace(".method", generatePropertyAccess(callback));
+ } else if (receiver === THIS) {
+ ret = " \n\
+ callback.call(this, args, fn); \n\
+ break; \n\
+ ";
+ } else if (receiver !== void 0) {
+ ret = " \n\
+ callback.call(receiver, args, fn); \n\
+ break; \n\
+ ";
+ } else {
+ ret = " \n\
+ callback(args, fn); \n\
+ break; \n\
+ ";
+ }
+ return ret.replace("args", args).replace(", ", comma);
+ }
+
+ function generateArgumentSwitchCase() {
+ var ret = "";
+ for(var i = 0; i < argumentOrder.length; ++i) {
+ ret += "case " + argumentOrder[i] +":" +
+ generateCallForArgumentCount(argumentOrder[i]);
+ }
+ var codeForCall;
+ if (typeof callback === "string") {
+ codeForCall = " \n\
+ this.property.apply(this, args); \n\
+ "
+ .replace(".property", generatePropertyAccess(callback));
+ } else if (receiver === THIS) {
+ codeForCall = " \n\
+ callback.apply(this, args); \n\
+ ";
+ } else {
+ codeForCall = " \n\
+ callback.apply(receiver, args); \n\
+ ";
+ }
+
+ ret += " \n\
+ default: \n\
+ var args = new Array(len + 1); \n\
+ var i = 0; \n\
+ for (var i = 0; i < len; ++i) { \n\
+ args[i] = arguments[i]; \n\
+ } \n\
+ args[i] = fn; \n\
+ [CodeForCall] \n\
+ break; \n\
+ ".replace("[CodeForCall]", codeForCall);
+ return ret;
+ }
+
+ return new Function("Promise",
+ "callback",
+ "receiver",
+ "withAppended",
+ "maybeWrapAsError",
+ "nodebackForPromise",
+ "INTERNAL"," \n\
+ var ret = function FunctionName(Parameters) { \n\
+ 'use strict'; \n\
+ var len = arguments.length; \n\
+ var promise = new Promise(INTERNAL); \n\
+ promise._setTrace(void 0); \n\
+ var fn = nodebackForPromise(promise); \n\
+ try { \n\
+ switch(len) { \n\
+ [CodeForSwitchCase] \n\
+ } \n\
+ } catch (e) { \n\
+ var wrapped = maybeWrapAsError(e); \n\
+ promise._attachExtraTrace(wrapped); \n\
+ promise._reject(wrapped); \n\
+ } \n\
+ return promise; \n\
+ }; \n\
+ ret.__isPromisified__ = true; \n\
+ return ret; \n\
+ "
+ .replace("FunctionName", callbackName)
+ .replace("Parameters", parameterDeclaration(newParameterCount))
+ .replace("[CodeForSwitchCase]", generateArgumentSwitchCase()))(
+ Promise,
+ callback,
+ receiver,
+ withAppended,
+ maybeWrapAsError,
+ nodebackForPromise,
+ INTERNAL
+ );
+}
+
+function makeNodePromisifiedClosure(callback, receiver) {
+ function promisified() {
+ var _receiver = receiver;
+ if (receiver === THIS) _receiver = this;
+ if (typeof callback === "string") {
+ callback = _receiver[callback];
+ }
+ var promise = new Promise(INTERNAL);
+ promise._setTrace(void 0);
+ var fn = nodebackForPromise(promise);
+ try {
+ callback.apply(_receiver, withAppended(arguments, fn));
+ } catch(e) {
+ var wrapped = maybeWrapAsError(e);
+ promise._attachExtraTrace(wrapped);
+ promise._reject(wrapped);
+ }
+ return promise;
+ }
+ promisified.__isPromisified__ = true;
+ return promisified;
+}
+
+var makeNodePromisified = canEvaluate
+ ? makeNodePromisifiedEval
+ : makeNodePromisifiedClosure;
+
+function promisifyAll(obj, suffix, filter, promisifier) {
+ var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$");
+ var methods =
+ promisifiableMethods(obj, suffix, suffixRegexp, filter);
+
+ for (var i = 0, len = methods.length; i < len; i+= 2) {
+ var key = methods[i];
+ var fn = methods[i+1];
+ var promisifiedKey = key + suffix;
+ obj[promisifiedKey] = promisifier === makeNodePromisified
+ ? makeNodePromisified(key, THIS, key, fn, suffix)
+ : promisifier(fn);
+ }
+ util.toFastProperties(obj);
+ return obj;
+}
+
+function promisify(callback, receiver) {
+ return makeNodePromisified(callback, receiver, void 0, callback);
+}
+
+Promise.promisify = function Promise$Promisify(fn, receiver) {
+ if (typeof fn !== "function") {
+ throw new TypeError("fn must be a function");
+ }
+ if (isPromisified(fn)) {
+ return fn;
+ }
+ return promisify(fn, arguments.length < 2 ? THIS : receiver);
+};
+
+Promise.promisifyAll = function Promise$PromisifyAll(target, options) {
+ if (typeof target !== "function" && typeof target !== "object") {
+ throw new TypeError("the target of promisifyAll must be an object or a function");
+ }
+ options = Object(options);
+ var suffix = options.suffix;
+ if (typeof suffix !== "string") suffix = defaultSuffix;
+ var filter = options.filter;
+ if (typeof filter !== "function") filter = defaultFilter;
+ var promisifier = options.promisifier;
+ if (typeof promisifier !== "function") promisifier = makeNodePromisified;
+
+ if (!util.isIdentifier(suffix)) {
+ throw new RangeError("suffix must be a valid identifier");
+ }
+
+ var keys = util.inheritedDataKeys(target, {includeHidden: true});
+ for (var i = 0; i < keys.length; ++i) {
+ var value = target[keys[i]];
+ if (keys[i] !== "constructor" &&
+ util.isClass(value)) {
+ promisifyAll(value.prototype, suffix, filter, promisifier);
+ promisifyAll(value, suffix, filter, promisifier);
+ }
+ }
+
+ return promisifyAll(target, suffix, filter, promisifier);
+};
+};
+
+
+},{"./errors":10,"./promise_resolver.js":22,"./util.js":35}],24:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray, cast) {
+var util = require("./util.js");
+var apiRejection = require("./errors_api_rejection")(Promise);
+var isObject = util.isObject;
+var es5 = require("./es5.js");
+
+function PropertiesPromiseArray(obj) {
+ var keys = es5.keys(obj);
+ var len = keys.length;
+ var values = new Array(len * 2);
+ for (var i = 0; i < len; ++i) {
+ var key = keys[i];
+ values[i] = obj[key];
+ values[i + len] = key;
+ }
+ this.constructor$(values);
+}
+util.inherits(PropertiesPromiseArray, PromiseArray);
+
+PropertiesPromiseArray.prototype._init =
+function PropertiesPromiseArray$_init() {
+ this._init$(void 0, -3) ;
+};
+
+PropertiesPromiseArray.prototype._promiseFulfilled =
+function PropertiesPromiseArray$_promiseFulfilled(value, index) {
+ if (this._isResolved()) return;
+ this._values[index] = value;
+ var totalResolved = ++this._totalResolved;
+ if (totalResolved >= this._length) {
+ var val = {};
+ var keyOffset = this.length();
+ for (var i = 0, len = this.length(); i < len; ++i) {
+ val[this._values[i + keyOffset]] = this._values[i];
+ }
+ this._resolve(val);
+ }
+};
+
+PropertiesPromiseArray.prototype._promiseProgressed =
+function PropertiesPromiseArray$_promiseProgressed(value, index) {
+ if (this._isResolved()) return;
+
+ this._promise._progress({
+ key: this._values[index + this.length()],
+ value: value
+ });
+};
+
+PropertiesPromiseArray.prototype.shouldCopyValues =
+function PropertiesPromiseArray$_shouldCopyValues() {
+ return false;
+};
+
+PropertiesPromiseArray.prototype.getActualLength =
+function PropertiesPromiseArray$getActualLength(len) {
+ return len >> 1;
+};
+
+function Promise$_Props(promises) {
+ var ret;
+ var castValue = cast(promises, void 0);
+
+ if (!isObject(castValue)) {
+ return apiRejection("cannot await properties of a non-object");
+ } else if (castValue instanceof Promise) {
+ ret = castValue._then(Promise.props, void 0, void 0, void 0, void 0);
+ } else {
+ ret = new PropertiesPromiseArray(castValue).promise();
+ }
+
+ if (castValue instanceof Promise) {
+ ret._propagateFrom(castValue, 4);
+ }
+ return ret;
+}
+
+Promise.prototype.props = function Promise$props() {
+ return Promise$_Props(this);
+};
+
+Promise.props = function Promise$Props(promises) {
+ return Promise$_Props(promises);
+};
+};
+
+},{"./errors_api_rejection":11,"./es5.js":12,"./util.js":35}],25:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+function arrayCopy(src, srcIndex, dst, dstIndex, len) {
+ for (var j = 0; j < len; ++j) {
+ dst[j + dstIndex] = src[j + srcIndex];
+ }
+}
+
+function Queue(capacity) {
+ this._capacity = capacity;
+ this._length = 0;
+ this._front = 0;
+ this._makeCapacity();
+}
+
+Queue.prototype._willBeOverCapacity =
+function Queue$_willBeOverCapacity(size) {
+ return this._capacity < size;
+};
+
+Queue.prototype._pushOne = function Queue$_pushOne(arg) {
+ var length = this.length();
+ this._checkCapacity(length + 1);
+ var i = (this._front + length) & (this._capacity - 1);
+ this[i] = arg;
+ this._length = length + 1;
+};
+
+Queue.prototype.push = function Queue$push(fn, receiver, arg) {
+ var length = this.length() + 3;
+ if (this._willBeOverCapacity(length)) {
+ this._pushOne(fn);
+ this._pushOne(receiver);
+ this._pushOne(arg);
+ return;
+ }
+ var j = this._front + length - 3;
+ this._checkCapacity(length);
+ var wrapMask = this._capacity - 1;
+ this[(j + 0) & wrapMask] = fn;
+ this[(j + 1) & wrapMask] = receiver;
+ this[(j + 2) & wrapMask] = arg;
+ this._length = length;
+};
+
+Queue.prototype.shift = function Queue$shift() {
+ var front = this._front,
+ ret = this[front];
+
+ this[front] = void 0;
+ this._front = (front + 1) & (this._capacity - 1);
+ this._length--;
+ return ret;
+};
+
+Queue.prototype.length = function Queue$length() {
+ return this._length;
+};
+
+Queue.prototype._makeCapacity = function Queue$_makeCapacity() {
+ var len = this._capacity;
+ for (var i = 0; i < len; ++i) {
+ this[i] = void 0;
+ }
+};
+
+Queue.prototype._checkCapacity = function Queue$_checkCapacity(size) {
+ if (this._capacity < size) {
+ this._resizeTo(this._capacity << 3);
+ }
+};
+
+Queue.prototype._resizeTo = function Queue$_resizeTo(capacity) {
+ var oldFront = this._front;
+ var oldCapacity = this._capacity;
+ var oldQueue = new Array(oldCapacity);
+ var length = this.length();
+
+ arrayCopy(this, 0, oldQueue, 0, oldCapacity);
+ this._capacity = capacity;
+ this._makeCapacity();
+ this._front = 0;
+ if (oldFront + length <= oldCapacity) {
+ arrayCopy(oldQueue, oldFront, this, 0, length);
+ } else { var lengthBeforeWrapping =
+ length - ((oldFront + length) & (oldCapacity - 1));
+
+ arrayCopy(oldQueue, oldFront, this, 0, lengthBeforeWrapping);
+ arrayCopy(oldQueue, 0, this, lengthBeforeWrapping,
+ length - lengthBeforeWrapping);
+ }
+};
+
+module.exports = Queue;
+
+},{}],26:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL, cast) {
+var apiRejection = require("./errors_api_rejection.js")(Promise);
+var isArray = require("./util.js").isArray;
+
+var raceLater = function Promise$_raceLater(promise) {
+ return promise.then(function(array) {
+ return Promise$_Race(array, promise);
+ });
+};
+
+var hasOwn = {}.hasOwnProperty;
+function Promise$_Race(promises, parent) {
+ var maybePromise = cast(promises, void 0);
+
+ if (maybePromise instanceof Promise) {
+ return raceLater(maybePromise);
+ } else if (!isArray(promises)) {
+ return apiRejection("expecting an array, a promise or a thenable");
+ }
+
+ var ret = new Promise(INTERNAL);
+ if (parent !== void 0) {
+ ret._propagateFrom(parent, 7);
+ } else {
+ ret._setTrace(void 0);
+ }
+ var fulfill = ret._fulfill;
+ var reject = ret._reject;
+ for (var i = 0, len = promises.length; i < len; ++i) {
+ var val = promises[i];
+
+ if (val === void 0 && !(hasOwn.call(promises, i))) {
+ continue;
+ }
+
+ Promise.cast(val)._then(fulfill, reject, void 0, ret, null);
+ }
+ return ret;
+}
+
+Promise.race = function Promise$Race(promises) {
+ return Promise$_Race(promises, void 0);
+};
+
+Promise.prototype.race = function Promise$race() {
+ return Promise$_Race(this, void 0);
+};
+
+};
+
+},{"./errors_api_rejection.js":11,"./util.js":35}],27:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, PromiseArray, apiRejection, cast, INTERNAL) {
+var util = require("./util.js");
+var tryCatch4 = util.tryCatch4;
+var tryCatch3 = util.tryCatch3;
+var errorObj = util.errorObj;
+function ReductionPromiseArray(promises, fn, accum, _each) {
+ this.constructor$(promises);
+ this._preservedValues = _each === INTERNAL ? [] : null;
+ this._zerothIsAccum = (accum === void 0);
+ this._gotAccum = false;
+ this._reducingIndex = (this._zerothIsAccum ? 1 : 0);
+ this._valuesPhase = undefined;
+
+ var maybePromise = cast(accum, void 0);
+ var rejected = false;
+ var isPromise = maybePromise instanceof Promise;
+ if (isPromise) {
+ if (maybePromise.isPending()) {
+ maybePromise._proxyPromiseArray(this, -1);
+ } else if (maybePromise.isFulfilled()) {
+ accum = maybePromise.value();
+ this._gotAccum = true;
+ } else {
+ maybePromise._unsetRejectionIsUnhandled();
+ this._reject(maybePromise.reason());
+ rejected = true;
+ }
+ }
+ if (!(isPromise || this._zerothIsAccum)) this._gotAccum = true;
+ this._callback = fn;
+ this._accum = accum;
+ if (!rejected) this._init$(void 0, -5);
+}
+util.inherits(ReductionPromiseArray, PromiseArray);
+
+ReductionPromiseArray.prototype._init =
+function ReductionPromiseArray$_init() {};
+
+ReductionPromiseArray.prototype._resolveEmptyArray =
+function ReductionPromiseArray$_resolveEmptyArray() {
+ if (this._gotAccum || this._zerothIsAccum) {
+ this._resolve(this._preservedValues !== null
+ ? [] : this._accum);
+ }
+};
+
+ReductionPromiseArray.prototype._promiseFulfilled =
+function ReductionPromiseArray$_promiseFulfilled(value, index) {
+ var values = this._values;
+ if (values === null) return;
+ var length = this.length();
+ var preservedValues = this._preservedValues;
+ var isEach = preservedValues !== null;
+ var gotAccum = this._gotAccum;
+ var valuesPhase = this._valuesPhase;
+ var valuesPhaseIndex;
+ if (!valuesPhase) {
+ valuesPhase = this._valuesPhase = Array(length);
+ for (valuesPhaseIndex=0; valuesPhaseIndex<length; ++valuesPhaseIndex) {
+ valuesPhase[valuesPhaseIndex] = 0;
+ }
+ }
+ valuesPhaseIndex = valuesPhase[index];
+
+ if (index === 0 && this._zerothIsAccum) {
+ if (!gotAccum) {
+ this._accum = value;
+ this._gotAccum = gotAccum = true;
+ }
+ valuesPhase[index] = ((valuesPhaseIndex === 0)
+ ? 1 : 2);
+ } else if (index === -1) {
+ if (!gotAccum) {
+ this._accum = value;
+ this._gotAccum = gotAccum = true;
+ }
+ } else {
+ if (valuesPhaseIndex === 0) {
+ valuesPhase[index] = 1;
+ }
+ else {
+ valuesPhase[index] = 2;
+ if (gotAccum) {
+ this._accum = value;
+ }
+ }
+ }
+ if (!gotAccum) return;
+
+ var callback = this._callback;
+ var receiver = this._promise._boundTo;
+ var ret;
+
+ for (var i = this._reducingIndex; i < length; ++i) {
+ valuesPhaseIndex = valuesPhase[i];
+ if (valuesPhaseIndex === 2) {
+ this._reducingIndex = i + 1;
+ continue;
+ }
+ if (valuesPhaseIndex !== 1) return;
+
+ value = values[i];
+ if (value instanceof Promise) {
+ if (value.isFulfilled()) {
+ value = value._settledValue;
+ } else if (value.isPending()) {
+ return;
+ } else {
+ value._unsetRejectionIsUnhandled();
+ return this._reject(value.reason());
+ }
+ }
+
+ if (isEach) {
+ preservedValues.push(value);
+ ret = tryCatch3(callback, receiver, value, i, length);
+ }
+ else {
+ ret = tryCatch4(callback, receiver, this._accum, value, i, length);
+ }
+
+ if (ret === errorObj) return this._reject(ret.e);
+
+ var maybePromise = cast(ret, void 0);
+ if (maybePromise instanceof Promise) {
+ if (maybePromise.isPending()) {
+ valuesPhase[i] = 4;
+ return maybePromise._proxyPromiseArray(this, i);
+ } else if (maybePromise.isFulfilled()) {
+ ret = maybePromise.value();
+ } else {
+ maybePromise._unsetRejectionIsUnhandled();
+ return this._reject(maybePromise.reason());
+ }
+ }
+
+ this._reducingIndex = i + 1;
+ this._accum = ret;
+ }
+
+ if (this._reducingIndex < length) return;
+ this._resolve(isEach ? preservedValues : this._accum);
+};
+
+function reduce(promises, fn, initialValue, _each) {
+ if (typeof fn !== "function") return apiRejection("fn must be a function");
+ var array = new ReductionPromiseArray(promises, fn, initialValue, _each);
+ return array.promise();
+}
+
+Promise.prototype.reduce = function Promise$reduce(fn, initialValue) {
+ return reduce(this, fn, initialValue, null);
+};
+
+Promise.reduce = function Promise$Reduce(promises, fn, initialValue, _each) {
+ return reduce(promises, fn, initialValue, _each);
+};
+};
+
+},{"./util.js":35}],28:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var schedule;
+var _MutationObserver;
+if (typeof process === "object" && typeof process.version === "string") {
+ schedule = function Promise$_Scheduler(fn) {
+ process.nextTick(fn);
+ };
+}
+else if ((typeof MutationObserver !== "undefined" &&
+ (_MutationObserver = MutationObserver)) ||
+ (typeof WebKitMutationObserver !== "undefined" &&
+ (_MutationObserver = WebKitMutationObserver))) {
+ schedule = (function() {
+ var div = document.createElement("div");
+ var queuedFn = void 0;
+ var observer = new _MutationObserver(
+ function Promise$_Scheduler() {
+ var fn = queuedFn;
+ queuedFn = void 0;
+ fn();
+ }
+ );
+ observer.observe(div, {
+ attributes: true
+ });
+ return function Promise$_Scheduler(fn) {
+ queuedFn = fn;
+ div.setAttribute("class", "foo");
+ };
+
+ })();
+}
+else if (typeof setTimeout !== "undefined") {
+ schedule = function Promise$_Scheduler(fn) {
+ setTimeout(fn, 0);
+ };
+}
+else throw new Error("no async scheduler available");
+module.exports = schedule;
+
+},{}],29:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports =
+ function(Promise, PromiseArray) {
+var PromiseInspection = Promise.PromiseInspection;
+var util = require("./util.js");
+
+function SettledPromiseArray(values) {
+ this.constructor$(values);
+}
+util.inherits(SettledPromiseArray, PromiseArray);
+
+SettledPromiseArray.prototype._promiseResolved =
+function SettledPromiseArray$_promiseResolved(index, inspection) {
+ this._values[index] = inspection;
+ var totalResolved = ++this._totalResolved;
+ if (totalResolved >= this._length) {
+ this._resolve(this._values);
+ }
+};
+
+SettledPromiseArray.prototype._promiseFulfilled =
+function SettledPromiseArray$_promiseFulfilled(value, index) {
+ if (this._isResolved()) return;
+ var ret = new PromiseInspection();
+ ret._bitField = 268435456;
+ ret._settledValue = value;
+ this._promiseResolved(index, ret);
+};
+SettledPromiseArray.prototype._promiseRejected =
+function SettledPromiseArray$_promiseRejected(reason, index) {
+ if (this._isResolved()) return;
+ var ret = new PromiseInspection();
+ ret._bitField = 134217728;
+ ret._settledValue = reason;
+ this._promiseResolved(index, ret);
+};
+
+Promise.settle = function Promise$Settle(promises) {
+ return new SettledPromiseArray(promises).promise();
+};
+
+Promise.prototype.settle = function Promise$settle() {
+ return new SettledPromiseArray(this).promise();
+};
+};
+
+},{"./util.js":35}],30:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports =
+function(Promise, PromiseArray, apiRejection) {
+var util = require("./util.js");
+var RangeError = require("./errors.js").RangeError;
+var AggregateError = require("./errors.js").AggregateError;
+var isArray = util.isArray;
+
+
+function SomePromiseArray(values) {
+ this.constructor$(values);
+ this._howMany = 0;
+ this._unwrap = false;
+ this._initialized = false;
+}
+util.inherits(SomePromiseArray, PromiseArray);
+
+SomePromiseArray.prototype._init = function SomePromiseArray$_init() {
+ if (!this._initialized) {
+ return;
+ }
+ if (this._howMany === 0) {
+ this._resolve([]);
+ return;
+ }
+ this._init$(void 0, -5);
+ var isArrayResolved = isArray(this._values);
+ if (!this._isResolved() &&
+ isArrayResolved &&
+ this._howMany > this._canPossiblyFulfill()) {
+ this._reject(this._getRangeError(this.length()));
+ }
+};
+
+SomePromiseArray.prototype.init = function SomePromiseArray$init() {
+ this._initialized = true;
+ this._init();
+};
+
+SomePromiseArray.prototype.setUnwrap = function SomePromiseArray$setUnwrap() {
+ this._unwrap = true;
+};
+
+SomePromiseArray.prototype.howMany = function SomePromiseArray$howMany() {
+ return this._howMany;
+};
+
+SomePromiseArray.prototype.setHowMany =
+function SomePromiseArray$setHowMany(count) {
+ if (this._isResolved()) return;
+ this._howMany = count;
+};
+
+SomePromiseArray.prototype._promiseFulfilled =
+function SomePromiseArray$_promiseFulfilled(value) {
+ if (this._isResolved()) return;
+ this._addFulfilled(value);
+ if (this._fulfilled() === this.howMany()) {
+ this._values.length = this.howMany();
+ if (this.howMany() === 1 && this._unwrap) {
+ this._resolve(this._values[0]);
+ } else {
+ this._resolve(this._values);
+ }
+ }
+
+};
+SomePromiseArray.prototype._promiseRejected =
+function SomePromiseArray$_promiseRejected(reason) {
+ if (this._isResolved()) return;
+ this._addRejected(reason);
+ if (this.howMany() > this._canPossiblyFulfill()) {
+ var e = new AggregateError();
+ for (var i = this.length(); i < this._values.length; ++i) {
+ e.push(this._values[i]);
+ }
+ this._reject(e);
+ }
+};
+
+SomePromiseArray.prototype._fulfilled = function SomePromiseArray$_fulfilled() {
+ return this._totalResolved;
+};
+
+SomePromiseArray.prototype._rejected = function SomePromiseArray$_rejected() {
+ return this._values.length - this.length();
+};
+
+SomePromiseArray.prototype._addRejected =
+function SomePromiseArray$_addRejected(reason) {
+ this._values.push(reason);
+};
+
+SomePromiseArray.prototype._addFulfilled =
+function SomePromiseArray$_addFulfilled(value) {
+ this._values[this._totalResolved++] = value;
+};
+
+SomePromiseArray.prototype._canPossiblyFulfill =
+function SomePromiseArray$_canPossiblyFulfill() {
+ return this.length() - this._rejected();
+};
+
+SomePromiseArray.prototype._getRangeError =
+function SomePromiseArray$_getRangeError(count) {
+ var message = "Input array must contain at least " +
+ this._howMany + " items but contains only " + count + " items";
+ return new RangeError(message);
+};
+
+SomePromiseArray.prototype._resolveEmptyArray =
+function SomePromiseArray$_resolveEmptyArray() {
+ this._reject(this._getRangeError(0));
+};
+
+function Promise$_Some(promises, howMany) {
+ if ((howMany | 0) !== howMany || howMany < 0) {
+ return apiRejection("expecting a positive integer");
+ }
+ var ret = new SomePromiseArray(promises);
+ var promise = ret.promise();
+ if (promise.isRejected()) {
+ return promise;
+ }
+ ret.setHowMany(howMany);
+ ret.init();
+ return promise;
+}
+
+Promise.some = function Promise$Some(promises, howMany) {
+ return Promise$_Some(promises, howMany);
+};
+
+Promise.prototype.some = function Promise$some(howMany) {
+ return Promise$_Some(this, howMany);
+};
+
+Promise._SomePromiseArray = SomePromiseArray;
+};
+
+},{"./errors.js":10,"./util.js":35}],31:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise) {
+function PromiseInspection(promise) {
+ if (promise !== void 0) {
+ this._bitField = promise._bitField;
+ this._settledValue = promise.isResolved()
+ ? promise._settledValue
+ : void 0;
+ }
+ else {
+ this._bitField = 0;
+ this._settledValue = void 0;
+ }
+}
+
+PromiseInspection.prototype.isFulfilled =
+Promise.prototype.isFulfilled = function Promise$isFulfilled() {
+ return (this._bitField & 268435456) > 0;
+};
+
+PromiseInspection.prototype.isRejected =
+Promise.prototype.isRejected = function Promise$isRejected() {
+ return (this._bitField & 134217728) > 0;
+};
+
+PromiseInspection.prototype.isPending =
+Promise.prototype.isPending = function Promise$isPending() {
+ return (this._bitField & 402653184) === 0;
+};
+
+PromiseInspection.prototype.value =
+Promise.prototype.value = function Promise$value() {
+ if (!this.isFulfilled()) {
+ throw new TypeError("cannot get fulfillment value of a non-fulfilled promise");
+ }
+ return this._settledValue;
+};
+
+PromiseInspection.prototype.error =
+PromiseInspection.prototype.reason =
+Promise.prototype.reason = function Promise$reason() {
+ if (!this.isRejected()) {
+ throw new TypeError("cannot get rejection reason of a non-rejected promise");
+ }
+ return this._settledValue;
+};
+
+PromiseInspection.prototype.isResolved =
+Promise.prototype.isResolved = function Promise$isResolved() {
+ return (this._bitField & 402653184) > 0;
+};
+
+Promise.PromiseInspection = PromiseInspection;
+};
+
+},{}],32:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function(Promise, INTERNAL) {
+var util = require("./util.js");
+var canAttach = require("./errors.js").canAttach;
+var errorObj = util.errorObj;
+var isObject = util.isObject;
+
+function getThen(obj) {
+ try {
+ return obj.then;
+ }
+ catch(e) {
+ errorObj.e = e;
+ return errorObj;
+ }
+}
+
+function Promise$_Cast(obj, originalPromise) {
+ if (isObject(obj)) {
+ if (obj instanceof Promise) {
+ return obj;
+ }
+ else if (isAnyBluebirdPromise(obj)) {
+ var ret = new Promise(INTERNAL);
+ ret._setTrace(void 0);
+ obj._then(
+ ret._fulfillUnchecked,
+ ret._rejectUncheckedCheckError,
+ ret._progressUnchecked,
+ ret,
+ null
+ );
+ ret._setFollowing();
+ return ret;
+ }
+ var then = getThen(obj);
+ if (then === errorObj) {
+ if (originalPromise !== void 0 && canAttach(then.e)) {
+ originalPromise._attachExtraTrace(then.e);
+ }
+ return Promise.reject(then.e);
+ } else if (typeof then === "function") {
+ return Promise$_doThenable(obj, then, originalPromise);
+ }
+ }
+ return obj;
+}
+
+var hasProp = {}.hasOwnProperty;
+function isAnyBluebirdPromise(obj) {
+ return hasProp.call(obj, "_promise0");
+}
+
+function Promise$_doThenable(x, then, originalPromise) {
+ var resolver = Promise.defer();
+ var called = false;
+ try {
+ then.call(
+ x,
+ Promise$_resolveFromThenable,
+ Promise$_rejectFromThenable,
+ Promise$_progressFromThenable
+ );
+ } catch(e) {
+ if (!called) {
+ called = true;
+ var trace = canAttach(e) ? e : new Error(e + "");
+ if (originalPromise !== void 0) {
+ originalPromise._attachExtraTrace(trace);
+ }
+ resolver.promise._reject(e, trace);
+ }
+ }
+ return resolver.promise;
+
+ function Promise$_resolveFromThenable(y) {
+ if (called) return;
+ called = true;
+
+ if (x === y) {
+ var e = Promise._makeSelfResolutionError();
+ if (originalPromise !== void 0) {
+ originalPromise._attachExtraTrace(e);
+ }
+ resolver.promise._reject(e, void 0);
+ return;
+ }
+ resolver.resolve(y);
+ }
+
+ function Promise$_rejectFromThenable(r) {
+ if (called) return;
+ called = true;
+ var trace = canAttach(r) ? r : new Error(r + "");
+ if (originalPromise !== void 0) {
+ originalPromise._attachExtraTrace(trace);
+ }
+ resolver.promise._reject(r, trace);
+ }
+
+ function Promise$_progressFromThenable(v) {
+ if (called) return;
+ var promise = resolver.promise;
+ if (typeof promise._progress === "function") {
+ promise._progress(v);
+ }
+ }
+}
+
+return Promise$_Cast;
+};
+
+},{"./errors.js":10,"./util.js":35}],33:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var _setTimeout = function(fn, ms) {
+ var len = arguments.length;
+ var arg0 = arguments[2];
+ var arg1 = arguments[3];
+ var arg2 = len >= 5 ? arguments[4] : void 0;
+ setTimeout(function() {
+ fn(arg0, arg1, arg2);
+ }, ms);
+};
+
+module.exports = function(Promise, INTERNAL, cast) {
+var util = require("./util.js");
+var errors = require("./errors.js");
+var apiRejection = require("./errors_api_rejection")(Promise);
+var TimeoutError = Promise.TimeoutError;
+
+var afterTimeout = function Promise$_afterTimeout(promise, message, ms) {
+ if (!promise.isPending()) return;
+ if (typeof message !== "string") {
+ message = "operation timed out after" + " " + ms + " ms"
+ }
+ var err = new TimeoutError(message);
+ errors.markAsOriginatingFromRejection(err);
+ promise._attachExtraTrace(err);
+ promise._cancel(err);
+};
+
+var afterDelay = function Promise$_afterDelay(value, promise) {
+ promise._fulfill(value);
+};
+
+var delay = Promise.delay = function Promise$Delay(value, ms) {
+ if (ms === void 0) {
+ ms = value;
+ value = void 0;
+ }
+ ms = +ms;
+ var maybePromise = cast(value, void 0);
+ var promise = new Promise(INTERNAL);
+
+ if (maybePromise instanceof Promise) {
+ promise._propagateFrom(maybePromise, 7);
+ promise._follow(maybePromise);
+ return promise.then(function(value) {
+ return Promise.delay(value, ms);
+ });
+ } else {
+ promise._setTrace(void 0);
+ _setTimeout(afterDelay, ms, value, promise);
+ }
+ return promise;
+};
+
+Promise.prototype.delay = function Promise$delay(ms) {
+ return delay(this, ms);
+};
+
+Promise.prototype.timeout = function Promise$timeout(ms, message) {
+ ms = +ms;
+
+ var ret = new Promise(INTERNAL);
+ ret._propagateFrom(this, 7);
+ ret._follow(this);
+ _setTimeout(afterTimeout, ms, ret, message, ms);
+ return ret.cancellable();
+};
+
+};
+
+},{"./errors.js":10,"./errors_api_rejection":11,"./util.js":35}],34:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+module.exports = function (Promise, apiRejection, cast) {
+ var TypeError = require("./errors.js").TypeError;
+ var inherits = require("./util.js").inherits;
+ var PromiseInspection = Promise.PromiseInspection;
+
+ function inspectionMapper(inspections) {
+ var len = inspections.length;
+ for (var i = 0; i < len; ++i) {
+ var inspection = inspections[i];
+ if (inspection.isRejected()) {
+ return Promise.reject(inspection.error());
+ }
+ inspections[i] = inspection.value();
+ }
+ return inspections;
+ }
+
+ function thrower(e) {
+ setTimeout(function(){throw e;}, 0);
+ }
+
+ function dispose(resources, inspection) {
+ var i = 0;
+ var len = resources.length;
+ var ret = Promise.defer();
+ function iterator() {
+ if (i >= len) return ret.resolve();
+ var maybePromise = cast(resources[i++], void 0);
+ if (maybePromise instanceof Promise &&
+ maybePromise._isDisposable()) {
+ try {
+ maybePromise = cast(maybePromise._getDisposer()
+ .tryDispose(inspection), void 0);
+ } catch (e) {
+ return thrower(e);
+ }
+ if (maybePromise instanceof Promise) {
+ return maybePromise._then(iterator, thrower,
+ null, null, null);
+ }
+ }
+ iterator();
+ }
+ iterator();
+ return ret.promise;
+ }
+
+ function disposerSuccess(value) {
+ var inspection = new PromiseInspection();
+ inspection._settledValue = value;
+ inspection._bitField = 268435456;
+ return dispose(this, inspection).thenReturn(value);
+ }
+
+ function disposerFail(reason) {
+ var inspection = new PromiseInspection();
+ inspection._settledValue = reason;
+ inspection._bitField = 134217728;
+ return dispose(this, inspection).thenThrow(reason);
+ }
+
+ function Disposer(data, promise) {
+ this._data = data;
+ this._promise = promise;
+ }
+
+ Disposer.prototype.data = function Disposer$data() {
+ return this._data;
+ };
+
+ Disposer.prototype.promise = function Disposer$promise() {
+ return this._promise;
+ };
+
+ Disposer.prototype.resource = function Disposer$resource() {
+ if (this.promise().isFulfilled()) {
+ return this.promise().value();
+ }
+ return null;
+ };
+
+ Disposer.prototype.tryDispose = function(inspection) {
+ var resource = this.resource();
+ var ret = resource !== null
+ ? this.doDispose(resource, inspection) : null;
+ this._promise._unsetDisposable();
+ this._data = this._promise = null;
+ return ret;
+ };
+
+ function FunctionDisposer(fn, promise) {
+ this.constructor$(fn, promise);
+ }
+ inherits(FunctionDisposer, Disposer);
+
+ FunctionDisposer.prototype.doDispose = function (resource, inspection) {
+ var fn = this.data();
+ return fn.call(resource, resource, inspection);
+ };
+
+ Promise.using = function Promise$using() {
+ var len = arguments.length;
+ if (len < 2) return apiRejection(
+ "you must pass at least 2 arguments to Promise.using");
+ var fn = arguments[len - 1];
+ if (typeof fn !== "function") return apiRejection("fn must be a function");
+ len--;
+ var resources = new Array(len);
+ for (var i = 0; i < len; ++i) {
+ var resource = arguments[i];
+ if (resource instanceof Disposer) {
+ var disposer = resource;
+ resource = resource.promise();
+ resource._setDisposable(disposer);
+ }
+ resources[i] = resource;
+ }
+
+ return Promise.settle(resources)
+ .then(inspectionMapper)
+ .spread(fn)
+ ._then(disposerSuccess, disposerFail, void 0, resources, void 0);
+ };
+
+ Promise.prototype._setDisposable =
+ function Promise$_setDisposable(disposer) {
+ this._bitField = this._bitField | 262144;
+ this._disposer = disposer;
+ };
+
+ Promise.prototype._isDisposable = function Promise$_isDisposable() {
+ return (this._bitField & 262144) > 0;
+ };
+
+ Promise.prototype._getDisposer = function Promise$_getDisposer() {
+ return this._disposer;
+ };
+
+ Promise.prototype._unsetDisposable = function Promise$_unsetDisposable() {
+ this._bitField = this._bitField & (~262144);
+ this._disposer = void 0;
+ };
+
+ Promise.prototype.disposer = function Promise$disposer(fn) {
+ if (typeof fn === "function") {
+ return new FunctionDisposer(fn, this);
+ }
+ throw new TypeError();
+ };
+
+};
+
+},{"./errors.js":10,"./util.js":35}],35:[function(require,module,exports){
+/**
+ * Copyright (c) 2014 Petka Antonov
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:</p>
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+"use strict";
+var es5 = require("./es5.js");
+var haveGetters = (function(){
+ try {
+ var o = {};
+ es5.defineProperty(o, "f", {
+ get: function () {
+ return 3;
+ }
+ });
+ return o.f === 3;
+ }
+ catch (e) {
+ return false;
+ }
+
+})();
+var canEvaluate = typeof navigator == "undefined";
+var errorObj = {e: {}};
+function tryCatch1(fn, receiver, arg) {
+ try { return fn.call(receiver, arg); }
+ catch (e) {
+ errorObj.e = e;
+ return errorObj;
+ }
+}
+
+function tryCatch2(fn, receiver, arg, arg2) {
+ try { return fn.call(receiver, arg, arg2); }
+ catch (e) {
+ errorObj.e = e;
+ return errorObj;
+ }
+}
+
+function tryCatch3(fn, receiver, arg, arg2, arg3) {
+ try { return fn.call(receiver, arg, arg2, arg3); }
+ catch (e) {
+ errorObj.e = e;
+ return errorObj;
+ }
+}
+
+function tryCatch4(fn, receiver, arg, arg2, arg3, arg4) {
+ try { return fn.call(receiver, arg, arg2, arg3, arg4); }
+ catch (e) {
+ errorObj.e = e;
+ return errorObj;
+ }
+}
+
+function tryCatchApply(fn, args, receiver) {
+ try { return fn.apply(receiver, args); }
+ catch (e) {
+ errorObj.e = e;
+ return errorObj;
+ }
+}
+
+var inherits = function(Child, Parent) {
+ var hasProp = {}.hasOwnProperty;
+
+ function T() {
+ this.constructor = Child;
+ this.constructor$ = Parent;
+ for (var propertyName in Parent.prototype) {
+ if (hasProp.call(Parent.prototype, propertyName) &&
+ propertyName.charAt(propertyName.length-1) !== "$"
+ ) {
+ this[propertyName + "$"] = Parent.prototype[propertyName];
+ }
+ }
+ }
+ T.prototype = Parent.prototype;
+ Child.prototype = new T();
+ return Child.prototype;
+};
+
+function asString(val) {
+ return typeof val === "string" ? val : ("" + val);
+}
+
+function isPrimitive(val) {
+ return val == null || val === true || val === false ||
+ typeof val === "string" || typeof val === "number";
+
+}
+
+function isObject(value) {
+ return !isPrimitive(value);
+}
+
+function maybeWrapAsError(maybeError) {
+ if (!isPrimitive(maybeError)) return maybeError;
+
+ return new Error(asString(maybeError));
+}
+
+function withAppended(target, appendee) {
+ var len = target.length;
+ var ret = new Array(len + 1);
+ var i;
+ for (i = 0; i < len; ++i) {
+ ret[i] = target[i];
+ }
+ ret[i] = appendee;
+ return ret;
+}
+
+function getDataPropertyOrDefault(obj, key, defaultValue) {
+ if (es5.isES5) {
+ var desc = Object.getOwnPropertyDescriptor(obj, key);
+ if (desc != null) {
+ return desc.get == null && desc.set == null
+ ? desc.value
+ : defaultValue;
+ }
+ } else {
+ return {}.hasOwnProperty.call(obj, key) ? obj[key] : void 0;
+ }
+}
+
+function notEnumerableProp(obj, name, value) {
+ if (isPrimitive(obj)) return obj;
+ var descriptor = {
+ value: value,
+ configurable: true,
+ enumerable: false,
+ writable: true
+ };
+ es5.defineProperty(obj, name, descriptor);
+ return obj;
+}
+
+
+var wrapsPrimitiveReceiver = (function() {
+ return this !== "string";
+}).call("string");
+
+function thrower(r) {
+ throw r;
+}
+
+var inheritedDataKeys = (function() {
+ if (es5.isES5) {
+ return function(obj, opts) {
+ var ret = [];
+ var visitedKeys = Object.create(null);
+ var getKeys = Object(opts).includeHidden
+ ? Object.getOwnPropertyNames
+ : Object.keys;
+ while (obj != null) {
+ var keys;
+ try {
+ keys = getKeys(obj);
+ } catch (e) {
+ return ret;
+ }
+ for (var i = 0; i < keys.length; ++i) {
+ var key = keys[i];
+ if (visitedKeys[key]) continue;
+ visitedKeys[key] = true;
+ var desc = Object.getOwnPropertyDescriptor(obj, key);
+ if (desc != null && desc.get == null && desc.set == null) {
+ ret.push(key);
+ }
+ }
+ obj = es5.getPrototypeOf(obj);
+ }
+ return ret;
+ };
+ } else {
+ return function(obj) {
+ var ret = [];
+ /*jshint forin:false */
+ for (var key in obj) {
+ ret.push(key);
+ }
+ return ret;
+ };
+ }
+
+})();
+
+function isClass(fn) {
+ try {
+ if (typeof fn === "function") {
+ var keys = es5.keys(fn.prototype);
+ return keys.length > 0 &&
+ !(keys.length === 1 && keys[0] === "constructor");
+ }
+ return false;
+ } catch (e) {
+ return false;
+ }
+}
+
+function toFastProperties(obj) {
+ /*jshint -W027*/
+ function f() {}
+ f.prototype = obj;
+ return f;
+ eval(obj);
+}
+
+var rident = /^[a-z$_][a-z$_0-9]*$/i;
+function isIdentifier(str) {
+ return rident.test(str);
+}
+
+function filledRange(count, prefix, suffix) {
+ var ret = new Array(count);
+ for(var i = 0; i < count; ++i) {
+ ret[i] = prefix + i + suffix;
+ }
+ return ret;
+}
+
+var ret = {
+ isClass: isClass,
+ isIdentifier: isIdentifier,
+ inheritedDataKeys: inheritedDataKeys,
+ getDataPropertyOrDefault: getDataPropertyOrDefault,
+ thrower: thrower,
+ isArray: es5.isArray,
+ haveGetters: haveGetters,
+ notEnumerableProp: notEnumerableProp,
+ isPrimitive: isPrimitive,
+ isObject: isObject,
+ canEvaluate: canEvaluate,
+ errorObj: errorObj,
+ tryCatch1: tryCatch1,
+ tryCatch2: tryCatch2,
+ tryCatch3: tryCatch3,
+ tryCatch4: tryCatch4,
+ tryCatchApply: tryCatchApply,
+ inherits: inherits,
+ withAppended: withAppended,
+ asString: asString,
+ maybeWrapAsError: maybeWrapAsError,
+ wrapsPrimitiveReceiver: wrapsPrimitiveReceiver,
+ toFastProperties: toFastProperties,
+ filledRange: filledRange
+};
+
+module.exports = ret;
+
+},{"./es5.js":12}]},{},[3])
+(3)
+});
+;
+\ No newline at end of file
diff --git a/resource/concurrent-caller.js b/resource/concurrent-caller.js
@@ -24,19 +24,19 @@
*/
EXPORTED_SYMBOLS = ["ConcurrentCaller"];
-Components.utils.import("resource://zotero/q.js");
+Components.utils.import("resource://zotero/bluebird.js");
/**
* Call a fixed number of functions at once, queueing the rest until slots
* open and returning a promise for the final completion. The functions do
* not need to return promises, but they should if they have asynchronous
- * work to perform..
+ * work to perform.
*
* Example:
*
* var caller = new ConcurrentCaller(2);
* caller.stopOnError = true;
- * caller.fcall([foo, bar, baz, qux).done();
+ * caller.fcall([foo, bar, baz, qux);
*
* In this example, foo and bar would run immediately, and baz and qux would
* be queued for later. When foo or bar finished, baz would be run, followed
@@ -93,16 +93,16 @@ ConcurrentCaller.prototype.fcall = function (func) {
//this._log("Running fcall on function");
promises.push(this.fcall(func[i]));
}
- return Q.allSettled(promises);
+ return Promise.settle(promises);
}
// If we're at the maximum number of concurrent functions,
// queue this function for later
if (this._numRunning == this._numConcurrent) {
this._log("Already at " + this._numConcurrent + " -- queueing for later");
- var deferred = Q.defer();
+ var deferred = Promise.defer();
this._queue.push({
- func: Q.fbind(func),
+ func: Promise.method(func),
deferred: deferred
});
return deferred.promise;
@@ -112,7 +112,7 @@ ConcurrentCaller.prototype.fcall = function (func) {
// Otherwise run it now
this._numRunning++;
- return this._onFunctionDone(Q.fcall(func));
+ return this._onFunctionDone(Promise.try(func));
}
@@ -124,58 +124,55 @@ ConcurrentCaller.prototype.stop = function () {
ConcurrentCaller.prototype._onFunctionDone = function (promise) {
var self = this;
- return Q.when(
- promise,
- function (promise) {
- self._numRunning--;
-
- self._log("Done with function ("
- + self._numRunning + "/" + self._numConcurrent + " running, "
- + self._queue.length + " queued)");
-
- // If there's a function to call and we're under the concurrent limit,
- // run it now
- let f = self._queue.shift();
- if (f && self._numRunning < self._numConcurrent) {
- // Wait until the specified interval has elapsed or the current
- // pause (if there is one) is over, whichever is longer
- let interval = self._interval;
- let now = Date.now();
- if (self._pauseUntil > now && (self._pauseUntil - now > interval)) {
- interval = self._pauseUntil - now;
- }
- Q.delay(interval)
- .then(function () {
- self._log("Running new function ("
- + self._numRunning + "/" + self._numConcurrent + " running, "
- + self._queue.length + " queued)");
-
- self._numRunning++;
- var p = self._onFunctionDone(f.func());
- f.deferred.resolve(p);
- });
+ return promise.then(function (promise) {
+ self._numRunning--;
+
+ self._log("Done with function ("
+ + self._numRunning + "/" + self._numConcurrent + " running, "
+ + self._queue.length + " queued)");
+
+ // If there's a function to call and we're under the concurrent limit,
+ // run it now
+ let f = self._queue.shift();
+ if (f && self._numRunning < self._numConcurrent) {
+ // Wait until the specified interval has elapsed or the current
+ // pause (if there is one) is over, whichever is longer
+ let interval = self._interval;
+ let now = Date.now();
+ if (self._pauseUntil > now && (self._pauseUntil - now > interval)) {
+ interval = self._pauseUntil - now;
}
-
- return promise;
- },
- function (e) {
- self._numRunning--;
-
- self._log("Done with function (" + self._numRunning + "/" + self._numConcurrent + ", "
- + self._queue.length + " in queue)");
-
- if (self.onError) {
- self.onError(e);
- }
-
- if (self.stopOnError && self._queue.length) {
- self._log("Stopping on error: " + e);
- self._queue = [];
- }
-
- throw e;
+ Promise.delay(interval)
+ .then(function () {
+ self._log("Running new function ("
+ + self._numRunning + "/" + self._numConcurrent + " running, "
+ + self._queue.length + " queued)");
+
+ self._numRunning++;
+ var p = self._onFunctionDone(f.func());
+ f.deferred.resolve(p);
+ });
+ }
+
+ return promise;
+ })
+ .catch(function (e) {
+ self._numRunning--;
+
+ self._log("Done with function (" + self._numRunning + "/" + self._numConcurrent + ", "
+ + self._queue.length + " in queue)");
+
+ if (self.onError) {
+ self.onError(e);
+ }
+
+ if (self.stopOnError && self._queue.length) {
+ self._log("Stopping on error: " + e);
+ self._queue = [];
}
- );
+
+ throw e;
+ });
};
diff --git a/resource/q.js b/resource/q.js
@@ -1,1980 +0,0 @@
-// vim:ts=4:sts=4:sw=4:
-/*!
- *
- * Copyright 2009-2012 Kris Kowal under the terms of the MIT
- * license found at http://github.com/kriskowal/q/raw/master/LICENSE
- *
- * With parts by Tyler Close
- * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found
- * at http://www.opensource.org/licenses/mit-license.html
- * Forked at ref_send.js version: 2009-05-11
- *
- * With parts by Mark Miller
- * Copyright (C) 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-(function (definition) {
- // Turn off strict mode for this function so we can assign to global.Q
- /* jshint strict: false */
-
- // This file will function properly as a <script> tag, or a module
- // using CommonJS and NodeJS or RequireJS module formats. In
- // Common/Node/RequireJS, the module exports the Q API and when
- // executed as a simple <script>, it creates a Q global instead.
-
- // Montage Require
- if (typeof bootstrap === "function") {
- bootstrap("promise", definition);
-
- // CommonJS
- } else if (typeof exports === "object") {
- module.exports = definition();
-
- // RequireJS
- } else if (typeof define === "function" && define.amd) {
- define(definition);
-
- // SES (Secure EcmaScript)
- } else if (typeof ses !== "undefined") {
- if (!ses.ok()) {
- return;
- } else {
- ses.makeQ = definition;
- }
-
- // Mozilla JSM (added by Zotero)
- } else if (~String(this).indexOf('BackstagePass')) {
- EXPORTED_SYMBOLS = ["Q"];
-
- // Q expects an implementation of setTimeout
- setTimeout = new function() {
- // We need to maintain references to running nsITimers. Otherwise, they can
- // get garbage collected before they fire.
- var _runningTimers = [];
-
- return function setTimeout(func, ms) {
- var useMethodjit = Components.utils.methodjit,
- timer = Components.classes["@mozilla.org/timer;1"].
- createInstance(Components.interfaces.nsITimer);
- timer.initWithCallback({"notify":function() {
- // Remove timer from array so it can be garbage collected
- _runningTimers.splice(_runningTimers.indexOf(timer), 1);
-
- // Execute callback function
- try {
- func();
- } catch(err) {
- // Rethrow errors that occur so that they appear in the error
- // console with the appropriate name and line numbers. While the
- // the errors appear without this, the line numbers get eaten.
- var scriptError = Components.classes["@mozilla.org/scripterror;1"]
- .createInstance(Components.interfaces.nsIScriptError);
- scriptError.init(
- err.message || err.toString(),
- err.fileName || err.filename || null,
- null,
- err.lineNumber || null,
- null,
- scriptError.errorFlag,
- 'component javascript'
- );
- Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService)
- .logMessage(scriptError);
- }
- }}, ms, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
- _runningTimers.push(timer);
- }
- };
- Q = definition();
-
- // <script>
- } else {
- Q = definition();
- }
-
-})(function () {
-"use strict";
-
-var hasStacks = false;
-try {
- throw new Error();
-} catch (e) {
- hasStacks = !!e.stack;
-}
-
-// All code after this point will be filtered from stack traces reported
-// by Q.
-var qStartingLine = captureLine();
-var qFileName;
-
-// shims
-
-// used for fallback in "allResolved"
-var noop = function () {};
-
-// Use the fastest possible means to execute a task in a future turn
-// of the event loop.
-var nextTick =(function () {
- // linked list of tasks (single, with head node)
- var head = {task: void 0, next: null};
- var tail = head;
- var flushing = false;
- var requestTick = void 0;
- var isNodeJS = false;
-
- function flush() {
- /* jshint loopfunc: true */
-
- while (head.next) {
- head = head.next;
- var task = head.task;
- head.task = void 0;
- var domain = head.domain;
-
- if (domain) {
- head.domain = void 0;
- domain.enter();
- }
-
- try {
- task();
-
- } catch (e) {
- if (isNodeJS) {
- // In node, uncaught exceptions are considered fatal errors.
- // Re-throw them synchronously to interrupt flushing!
-
- // Ensure continuation if the uncaught exception is suppressed
- // listening "uncaughtException" events (as domains does).
- // Continue in next event to avoid tick recursion.
- if (domain) {
- domain.exit();
- }
- setTimeout(flush, 0);
- if (domain) {
- domain.enter();
- }
-
- throw e;
-
- } else {
- // In browsers, uncaught exceptions are not fatal.
- // Re-throw them asynchronously to avoid slow-downs.
- setTimeout(function() {
- throw e;
- }, 0);
- }
- }
-
- if (domain) {
- domain.exit();
- }
- }
-
- flushing = false;
- }
-
- nextTick = function (task) {
- tail = tail.next = {
- task: task,
- domain: isNodeJS && process.domain,
- next: null
- };
-
- if (!flushing) {
- flushing = true;
- requestTick();
- }
- };
-
- if (typeof process !== "undefined" && process.nextTick) {
- // Node.js before 0.9. Note that some fake-Node environments, like the
- // Mocha test runner, introduce a `process` global without a `nextTick`.
- isNodeJS = true;
-
- requestTick = function () {
- process.nextTick(flush);
- };
-
- } else if (typeof setImmediate === "function") {
- // In IE10, Node.js 0.9+, or https://github.com/NobleJS/setImmediate
- if (typeof window !== "undefined") {
- requestTick = setImmediate.bind(window, flush);
- } else {
- requestTick = function () {
- setImmediate(flush);
- };
- }
-
- } else if (typeof MessageChannel !== "undefined") {
- // modern browsers
- // http://www.nonblocking.io/2011/06/windownexttick.html
- var channel = new MessageChannel();
- channel.port1.onmessage = flush;
- requestTick = function () {
- channel.port2.postMessage(0);
- };
-
- } else {
- // old browsers
- requestTick = function () {
- setTimeout(flush, 0);
- };
- }
-
- return nextTick;
-})();
-
-// Attempt to make generics safe in the face of downstream
-// modifications.
-// There is no situation where this is necessary.
-// If you need a security guarantee, these primordials need to be
-// deeply frozen anyway, and if you don’t need a security guarantee,
-// this is just plain paranoid.
-// However, this does have the nice side-effect of reducing the size
-// of the code by reducing x.call() to merely x(), eliminating many
-// hard-to-minify characters.
-// See Mark Miller’s explanation of what this does.
-// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
-var call = Function.call;
-function uncurryThis(f) {
- return function () {
- return call.apply(f, arguments);
- };
-}
-// This is equivalent, but slower:
-// uncurryThis = Function_bind.bind(Function_bind.call);
-// http://jsperf.com/uncurrythis
-
-var array_slice = uncurryThis(Array.prototype.slice);
-
-var array_reduce = uncurryThis(
- Array.prototype.reduce || function (callback, basis) {
- var index = 0,
- length = this.length;
- // concerning the initial value, if one is not provided
- if (arguments.length === 1) {
- // seek to the first value in the array, accounting
- // for the possibility that is is a sparse array
- do {
- if (index in this) {
- basis = this[index++];
- break;
- }
- if (++index >= length) {
- throw new TypeError();
- }
- } while (1);
- }
- // reduce
- for (; index < length; index++) {
- // account for the possibility that the array is sparse
- if (index in this) {
- basis = callback(basis, this[index], index);
- }
- }
- return basis;
- }
-);
-
-var array_indexOf = uncurryThis(
- Array.prototype.indexOf || function (value) {
- // not a very good shim, but good enough for our one use of it
- for (var i = 0; i < this.length; i++) {
- if (this[i] === value) {
- return i;
- }
- }
- return -1;
- }
-);
-
-var array_map = uncurryThis(
- Array.prototype.map || function (callback, thisp) {
- var self = this;
- var collect = [];
- array_reduce(self, function (undefined, value, index) {
- collect.push(callback.call(thisp, value, index, self));
- }, void 0);
- return collect;
- }
-);
-
-var object_create = Object.create || function (prototype) {
- function Type() { }
- Type.prototype = prototype;
- return new Type();
-};
-
-var object_hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty);
-
-var object_keys = Object.keys || function (object) {
- var keys = [];
- for (var key in object) {
- if (object_hasOwnProperty(object, key)) {
- keys.push(key);
- }
- }
- return keys;
-};
-
-var object_toString = uncurryThis(Object.prototype.toString);
-
-function isObject(value) {
- return value === Object(value);
-}
-
-// generator related shims
-
-// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
-function isStopIteration(exception) {
- return (
- object_toString(exception) === "[object StopIteration]" ||
- exception instanceof QReturnValue
- );
-}
-
-// FIXME: Remove this helper and Q.return once ES6 generators are in
-// SpiderMonkey.
-var QReturnValue;
-if (typeof ReturnValue !== "undefined") {
- QReturnValue = ReturnValue;
-} else {
- QReturnValue = function (value) {
- this.value = value;
- };
-}
-
-// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
-// engine that has a deployed base of browsers that support generators.
-// However, SM's generators use the Python-inspired semantics of
-// outdated ES6 drafts. We would like to support ES6, but we'd also
-// like to make it possible to use generators in deployed browsers, so
-// we also support Python-style generators. At some point we can remove
-// this block.
-var hasES6Generators;
-try {
- /* jshint evil: true, nonew: false */
- new Function("(function* (){ yield 1; })");
- hasES6Generators = true;
-} catch (e) {
- hasES6Generators = false;
-}
-
-// long stack traces
-
-var STACK_JUMP_SEPARATOR = "From previous event:";
-
-function makeStackTraceLong(error, promise) {
- // If possible, transform the error stack trace by removing Node and Q
- // cruft, then concatenating with the stack trace of `promise`. See #57.
- if (hasStacks &&
- promise.stack &&
- typeof error === "object" &&
- error !== null &&
- error.stack &&
- error.stack.indexOf(STACK_JUMP_SEPARATOR) === -1
- ) {
- var stacks = [];
- for (var p = promise; !!p; p = p.source) {
- if (p.stack) {
- stacks.unshift(p.stack);
- }
- }
- stacks.unshift(error.stack);
-
- var concatedStacks = stacks.join("\n" + STACK_JUMP_SEPARATOR + "\n");
- error.stack = filterStackString(concatedStacks);
- }
-}
-
-function filterStackString(stackString) {
- var lines = stackString.split("\n");
- var desiredLines = [];
- for (var i = 0; i < lines.length; ++i) {
- var line = lines[i];
-
- if (!isInternalFrame(line) && !isNodeFrame(line) && line) {
- desiredLines.push(line);
- }
- }
- return desiredLines.join("\n");
-}
-
-function isNodeFrame(stackLine) {
- return stackLine.indexOf("(module.js:") !== -1 ||
- stackLine.indexOf("(node.js:") !== -1;
-}
-
-function getFileNameAndLineNumber(stackLine) {
- // Named functions: "at functionName (filename:lineNumber:columnNumber)"
- // In IE10 function name can have spaces ("Anonymous function") O_o
- var attempt1 = /at .+ \((.+):(\d+):(?:\d+)\)$/.exec(stackLine);
- if (attempt1) {
- return [attempt1[1], Number(attempt1[2])];
- }
-
- // Anonymous functions: "at filename:lineNumber:columnNumber"
- var attempt2 = /at ([^ ]+):(\d+):(?:\d+)$/.exec(stackLine);
- if (attempt2) {
- return [attempt2[1], Number(attempt2[2])];
- }
-
- // Firefox style: "function@filename:lineNumber or @filename:lineNumber"
- var attempt3 = /.*@(.+):(\d+)$/.exec(stackLine);
- if (attempt3) {
- return [attempt3[1], Number(attempt3[2])];
- }
-}
-
-function isInternalFrame(stackLine) {
- var fileNameAndLineNumber = getFileNameAndLineNumber(stackLine);
-
- if (!fileNameAndLineNumber) {
- return false;
- }
-
- var fileName = fileNameAndLineNumber[0];
- var lineNumber = fileNameAndLineNumber[1];
-
- return fileName === qFileName &&
- lineNumber >= qStartingLine &&
- lineNumber <= qEndingLine;
-}
-
-// discover own file name and line number range for filtering stack
-// traces
-function captureLine() {
- if (!hasStacks) {
- return;
- }
-
- try {
- throw new Error();
- } catch (e) {
- var lines = e.stack.split("\n");
- var firstLine = lines[0].indexOf("@") > 0 ? lines[1] : lines[2];
- var fileNameAndLineNumber = getFileNameAndLineNumber(firstLine);
- if (!fileNameAndLineNumber) {
- return;
- }
-
- qFileName = fileNameAndLineNumber[0];
- return fileNameAndLineNumber[1];
- }
-}
-
-function deprecate(callback, name, alternative) {
- return function () {
- if (typeof console !== "undefined" &&
- typeof console.warn === "function") {
- console.warn(name + " is deprecated, use " + alternative +
- " instead.", new Error("").stack);
- }
- return callback.apply(callback, arguments);
- };
-}
-
-// end of shims
-// beginning of real work
-
-/**
- * Constructs a promise for an immediate reference, passes promises through, or
- * coerces promises from different systems.
- * @param value immediate reference or promise
- */
-function Q(value) {
- // If the object is already a Promise, return it directly. This enables
- // the resolve function to both be used to created references from objects,
- // but to tolerably coerce non-promises to promises.
- if (isPromise(value)) {
- return value;
- }
-
- // assimilate thenables
- if (isPromiseAlike(value)) {
- return coerce(value);
- } else {
- return fulfill(value);
- }
-}
-Q.resolve = Q;
-
-/**
- * Performs a task in a future turn of the event loop.
- * @param {Function} task
- */
-Q.nextTick = nextTick;
-
-/**
- * Controls whether or not long stack traces will be on
- */
-Q.longStackSupport = false;
-
-/**
- * Constructs a {promise, resolve, reject} object.
- *
- * `resolve` is a callback to invoke with a more resolved value for the
- * promise. To fulfill the promise, invoke `resolve` with any value that is
- * not a thenable. To reject the promise, invoke `resolve` with a rejected
- * thenable, or invoke `reject` with the reason directly. To resolve the
- * promise to another thenable, thus putting it in the same state, invoke
- * `resolve` with that other thenable.
- */
-Q.defer = defer;
-function defer() {
- // if "messages" is an "Array", that indicates that the promise has not yet
- // been resolved. If it is "undefined", it has been resolved. Each
- // element of the messages array is itself an array of complete arguments to
- // forward to the resolved promise. We coerce the resolution value to a
- // promise using the `resolve` function because it handles both fully
- // non-thenable values and other thenables gracefully.
- var messages = [], progressListeners = [], resolvedPromise;
-
- var deferred = object_create(defer.prototype);
- var promise = object_create(Promise.prototype);
-
- promise.promiseDispatch = function (resolve, op, operands) {
- var args = array_slice(arguments);
- if (messages) {
- messages.push(args);
- if (op === "when" && operands[1]) { // progress operand
- progressListeners.push(operands[1]);
- }
- } else {
- nextTick(function () {
- resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
- });
- }
- };
-
- // XXX deprecated
- promise.valueOf = deprecate(function () {
- if (messages) {
- return promise;
- }
- var nearerValue = nearer(resolvedPromise);
- if (isPromise(nearerValue)) {
- resolvedPromise = nearerValue; // shorten chain
- }
- return nearerValue;
- }, "valueOf", "inspect");
-
- promise.inspect = function () {
- if (!resolvedPromise) {
- return { state: "pending" };
- }
- return resolvedPromise.inspect();
- };
-
- if (Q.longStackSupport && hasStacks) {
- try {
- throw new Error();
- } catch (e) {
- // NOTE: don't try to use `Error.captureStackTrace` or transfer the
- // accessor around; that causes memory leaks as per GH-111. Just
- // reify the stack trace as a string ASAP.
- //
- // At the same time, cut off the first line; it's always just
- // "[object Promise]\n", as per the `toString`.
- promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
- }
- }
-
- // NOTE: we do the checks for `resolvedPromise` in each method, instead of
- // consolidating them into `become`, since otherwise we'd create new
- // promises with the lines `become(whatever(value))`. See e.g. GH-252.
-
- function become(newPromise) {
- resolvedPromise = newPromise;
- promise.source = newPromise;
-
- array_reduce(messages, function (undefined, message) {
- nextTick(function () {
- newPromise.promiseDispatch.apply(newPromise, message);
- });
- }, void 0);
-
- messages = void 0;
- progressListeners = void 0;
- }
-
- deferred.promise = promise;
- deferred.resolve = function (value) {
- if (resolvedPromise) {
- return;
- }
-
- become(Q(value));
- };
-
- deferred.fulfill = function (value) {
- if (resolvedPromise) {
- return;
- }
-
- become(fulfill(value));
- };
- deferred.reject = function (reason) {
- if (resolvedPromise) {
- return;
- }
-
- become(reject(reason));
- };
- deferred.notify = function (progress) {
- if (resolvedPromise) {
- return;
- }
-
- array_reduce(progressListeners, function (undefined, progressListener) {
- nextTick(function () {
- progressListener(progress);
- });
- }, void 0);
- };
-
- return deferred;
-}
-
-/**
- * Creates a Node-style callback that will resolve or reject the deferred
- * promise.
- * @returns a nodeback
- */
-defer.prototype.makeNodeResolver = function () {
- var self = this;
- return function (error, value) {
- if (error) {
- self.reject(error);
- } else if (arguments.length > 2) {
- self.resolve(array_slice(arguments, 1));
- } else {
- self.resolve(value);
- }
- };
-};
-
-/**
- * @param resolver {Function} a function that returns nothing and accepts
- * the resolve, reject, and notify functions for a deferred.
- * @returns a promise that may be resolved with the given resolve and reject
- * functions, or rejected by a thrown exception in resolver
- */
-Q.promise = promise;
-function promise(resolver) {
- if (typeof resolver !== "function") {
- throw new TypeError("resolver must be a function.");
- }
- var deferred = defer();
- try {
- resolver(deferred.resolve, deferred.reject, deferred.notify);
- } catch (reason) {
- deferred.reject(reason);
- }
- return deferred.promise;
-}
-
-// XXX experimental. This method is a way to denote that a local value is
-// serializable and should be immediately dispatched to a remote upon request,
-// instead of passing a reference.
-Q.passByCopy = function (object) {
- //freeze(object);
- //passByCopies.set(object, true);
- return object;
-};
-
-Promise.prototype.passByCopy = function () {
- //freeze(object);
- //passByCopies.set(object, true);
- return this;
-};
-
-/**
- * If two promises eventually fulfill to the same value, promises that value,
- * but otherwise rejects.
- * @param x {Any*}
- * @param y {Any*}
- * @returns {Any*} a promise for x and y if they are the same, but a rejection
- * otherwise.
- *
- */
-Q.join = function (x, y) {
- return Q(x).join(y);
-};
-
-Promise.prototype.join = function (that) {
- return Q([this, that]).spread(function (x, y) {
- if (x === y) {
- // TODO: "===" should be Object.is or equiv
- return x;
- } else {
- throw new Error("Can't join: not the same: " + x + " " + y);
- }
- });
-};
-
-/**
- * Returns a promise for the first of an array of promises to become fulfilled.
- * @param answers {Array[Any*]} promises to race
- * @returns {Any*} the first promise to be fulfilled
- */
-Q.race = race;
-function race(answerPs) {
- return promise(function(resolve, reject) {
- // Switch to this once we can assume at least ES5
- // answerPs.forEach(function(answerP) {
- // Q(answerP).then(resolve, reject);
- // });
- // Use this in the meantime
- for (var i = 0, len = answerPs.length; i < len; i++) {
- Q(answerPs[i]).then(resolve, reject);
- }
- });
-}
-
-Promise.prototype.race = function () {
- return this.then(Q.race);
-};
-
-/**
- * Constructs a Promise with a promise descriptor object and optional fallback
- * function. The descriptor contains methods like when(rejected), get(name),
- * set(name, value), post(name, args), and delete(name), which all
- * return either a value, a promise for a value, or a rejection. The fallback
- * accepts the operation name, a resolver, and any further arguments that would
- * have been forwarded to the appropriate method above had a method been
- * provided with the proper name. The API makes no guarantees about the nature
- * of the returned object, apart from that it is usable whereever promises are
- * bought and sold.
- */
-Q.makePromise = Promise;
-function Promise(descriptor, fallback, inspect) {
- if (fallback === void 0) {
- fallback = function (op) {
- return reject(new Error(
- "Promise does not support operation: " + op
- ));
- };
- }
- if (inspect === void 0) {
- inspect = function () {
- return {state: "unknown"};
- };
- }
-
- var promise = object_create(Promise.prototype);
-
- promise.promiseDispatch = function (resolve, op, args) {
- var result;
- try {
- if (descriptor[op]) {
- result = descriptor[op].apply(promise, args);
- } else {
- result = fallback.call(promise, op, args);
- }
- } catch (exception) {
- result = reject(exception);
- }
- if (resolve) {
- resolve(result);
- }
- };
-
- promise.inspect = inspect;
-
- // XXX deprecated `valueOf` and `exception` support
- if (inspect) {
- var inspected = inspect();
- if (inspected.state === "rejected") {
- promise.exception = inspected.reason;
- }
-
- promise.valueOf = deprecate(function () {
- var inspected = inspect();
- if (inspected.state === "pending" ||
- inspected.state === "rejected") {
- return promise;
- }
- return inspected.value;
- });
- }
-
- return promise;
-}
-
-Promise.prototype.toString = function () {
- return "[object Promise]";
-};
-
-Promise.prototype.then = function (fulfilled, rejected, progressed) {
- var self = this;
- var deferred = defer();
- var done = false; // ensure the untrusted promise makes at most a
- // single call to one of the callbacks
-
- function _fulfilled(value) {
- try {
- return typeof fulfilled === "function" ? fulfilled(value) : value;
- } catch (exception) {
- return reject(exception);
- }
- }
-
- function _rejected(exception) {
- if (typeof rejected === "function") {
- makeStackTraceLong(exception, self);
- try {
- return rejected(exception);
- } catch (newException) {
- return reject(newException);
- }
- }
- return reject(exception);
- }
-
- function _progressed(value) {
- return typeof progressed === "function" ? progressed(value) : value;
- }
-
- nextTick(function () {
- self.promiseDispatch(function (value) {
- if (done) {
- return;
- }
- done = true;
-
- deferred.resolve(_fulfilled(value));
- }, "when", [function (exception) {
- if (done) {
- return;
- }
- done = true;
-
- deferred.resolve(_rejected(exception));
- }]);
- });
-
- // Progress propagator need to be attached in the current tick.
- self.promiseDispatch(void 0, "when", [void 0, function (value) {
- var newValue;
- var threw = false;
- try {
- newValue = _progressed(value);
- } catch (e) {
- threw = true;
- if (Q.onerror) {
- Q.onerror(e);
- } else {
- throw e;
- }
- }
-
- if (!threw) {
- deferred.notify(newValue);
- }
- }]);
-
- return deferred.promise;
-};
-
-/**
- * Registers an observer on a promise.
- *
- * Guarantees:
- *
- * 1. that fulfilled and rejected will be called only once.
- * 2. that either the fulfilled callback or the rejected callback will be
- * called, but not both.
- * 3. that fulfilled and rejected will not be called in this turn.
- *
- * @param value promise or immediate reference to observe
- * @param fulfilled function to be called with the fulfilled value
- * @param rejected function to be called with the rejection exception
- * @param progressed function to be called on any progress notifications
- * @return promise for the return value from the invoked callback
- */
-Q.when = when;
-function when(value, fulfilled, rejected, progressed) {
- return Q(value).then(fulfilled, rejected, progressed);
-}
-
-Promise.prototype.thenResolve = function (value) {
- return this.then(function () { return value; });
-};
-
-Q.thenResolve = function (promise, value) {
- return Q(promise).thenResolve(value);
-};
-
-Promise.prototype.thenReject = function (reason) {
- return this.then(function () { throw reason; });
-};
-
-Q.thenReject = function (promise, reason) {
- return Q(promise).thenReject(reason);
-};
-
-/**
- * If an object is not a promise, it is as "near" as possible.
- * If a promise is rejected, it is as "near" as possible too.
- * If it’s a fulfilled promise, the fulfillment value is nearer.
- * If it’s a deferred promise and the deferred has been resolved, the
- * resolution is "nearer".
- * @param object
- * @returns most resolved (nearest) form of the object
- */
-
-// XXX should we re-do this?
-Q.nearer = nearer;
-function nearer(value) {
- if (isPromise(value)) {
- var inspected = value.inspect();
- if (inspected.state === "fulfilled") {
- return inspected.value;
- }
- }
- return value;
-}
-
-/**
- * @returns whether the given object is a promise.
- * Otherwise it is a fulfilled value.
- */
-Q.isPromise = isPromise;
-function isPromise(object) {
- return isObject(object) &&
- typeof object.promiseDispatch === "function" &&
- typeof object.inspect === "function";
-}
-
-Q.isPromiseAlike = isPromiseAlike;
-function isPromiseAlike(object) {
- return isObject(object) && typeof object.then === "function";
-}
-
-/**
- * @returns whether the given object is a pending promise, meaning not
- * fulfilled or rejected.
- */
-Q.isPending = isPending;
-function isPending(object) {
- return isPromise(object) && object.inspect().state === "pending";
-}
-
-Promise.prototype.isPending = function () {
- return this.inspect().state === "pending";
-};
-
-/**
- * @returns whether the given object is a value or fulfilled
- * promise.
- */
-Q.isFulfilled = isFulfilled;
-function isFulfilled(object) {
- return !isPromise(object) || object.inspect().state === "fulfilled";
-}
-
-Promise.prototype.isFulfilled = function () {
- return this.inspect().state === "fulfilled";
-};
-
-/**
- * @returns whether the given object is a rejected promise.
- */
-Q.isRejected = isRejected;
-function isRejected(object) {
- return isPromise(object) && object.inspect().state === "rejected";
-}
-
-Promise.prototype.isRejected = function () {
- return this.inspect().state === "rejected";
-};
-
-//// BEGIN UNHANDLED REJECTION TRACKING
-
-// This promise library consumes exceptions thrown in handlers so they can be
-// handled by a subsequent promise. The exceptions get added to this array when
-// they are created, and removed when they are handled. Note that in ES6 or
-// shimmed environments, this would naturally be a `Set`.
-var unhandledReasons = [];
-var unhandledRejections = [];
-var unhandledReasonsDisplayed = false;
-var trackUnhandledRejections = true;
-function displayUnhandledReasons() {
- if (
- !unhandledReasonsDisplayed &&
- typeof window !== "undefined" &&
- !window.Touch &&
- window.console
- ) {
- console.warn("[Q] Unhandled rejection reasons (should be empty):",
- unhandledReasons);
- }
-
- unhandledReasonsDisplayed = true;
-}
-
-function logUnhandledReasons() {
- for (var i = 0; i < unhandledReasons.length; i++) {
- var reason = unhandledReasons[i];
- if (reason && typeof reason.stack !== "undefined") {
- console.warn("Unhandled rejection reason:", reason.stack);
- } else {
- console.warn("Unhandled rejection reason (no stack):", reason);
- }
- }
-}
-
-function resetUnhandledRejections() {
- unhandledReasons.length = 0;
- unhandledRejections.length = 0;
- unhandledReasonsDisplayed = false;
-
- if (!trackUnhandledRejections) {
- trackUnhandledRejections = true;
-
- // Show unhandled rejection reasons if Node exits without handling an
- // outstanding rejection. (Note that Browserify presently produces a
- // `process` global without the `EventEmitter` `on` method.)
- if (typeof process !== "undefined" && process.on) {
- process.on("exit", logUnhandledReasons);
- }
- }
-}
-
-function trackRejection(promise, reason) {
- if (!trackUnhandledRejections) {
- return;
- }
-
- unhandledRejections.push(promise);
- unhandledReasons.push(reason);
- displayUnhandledReasons();
-}
-
-function untrackRejection(promise) {
- if (!trackUnhandledRejections) {
- return;
- }
-
- var at = array_indexOf(unhandledRejections, promise);
- if (at !== -1) {
- unhandledRejections.splice(at, 1);
- unhandledReasons.splice(at, 1);
- }
-}
-
-Q.resetUnhandledRejections = resetUnhandledRejections;
-
-Q.getUnhandledReasons = function () {
- // Make a copy so that consumers can't interfere with our internal state.
- return unhandledReasons.slice();
-};
-
-Q.stopUnhandledRejectionTracking = function () {
- resetUnhandledRejections();
- if (typeof process !== "undefined" && process.on) {
- process.removeListener("exit", logUnhandledReasons);
- }
- trackUnhandledRejections = false;
-};
-
-resetUnhandledRejections();
-
-//// END UNHANDLED REJECTION TRACKING
-
-/**
- * Constructs a rejected promise.
- * @param reason value describing the failure
- */
-Q.reject = reject;
-function reject(reason) {
- var rejection = Promise({
- "when": function (rejected) {
- // note that the error has been handled
- if (rejected) {
- untrackRejection(this);
- }
- return rejected ? rejected(reason) : this;
- }
- }, function fallback() {
- return this;
- }, function inspect() {
- return { state: "rejected", reason: reason };
- });
-
- // Note that the reason has not been handled.
- trackRejection(rejection, reason);
-
- return rejection;
-}
-
-/**
- * Constructs a fulfilled promise for an immediate reference.
- * @param value immediate reference
- */
-Q.fulfill = fulfill;
-function fulfill(value) {
- return Promise({
- "when": function () {
- return value;
- },
- "get": function (name) {
- return value[name];
- },
- "set": function (name, rhs) {
- value[name] = rhs;
- },
- "delete": function (name) {
- delete value[name];
- },
- "post": function (name, args) {
- // Mark Miller proposes that post with no name should apply a
- // promised function.
- if (name === null || name === void 0) {
- return value.apply(void 0, args);
- } else {
- return value[name].apply(value, args);
- }
- },
- "apply": function (thisp, args) {
- return value.apply(thisp, args);
- },
- "keys": function () {
- return object_keys(value);
- }
- }, void 0, function inspect() {
- return { state: "fulfilled", value: value };
- });
-}
-
-/**
- * Converts thenables to Q promises.
- * @param promise thenable promise
- * @returns a Q promise
- */
-function coerce(promise) {
- var deferred = defer();
- nextTick(function () {
- try {
- promise.then(deferred.resolve, deferred.reject, deferred.notify);
- } catch (exception) {
- deferred.reject(exception);
- }
- });
- return deferred.promise;
-}
-
-/**
- * Annotates an object such that it will never be
- * transferred away from this process over any promise
- * communication channel.
- * @param object
- * @returns promise a wrapping of that object that
- * additionally responds to the "isDef" message
- * without a rejection.
- */
-Q.master = master;
-function master(object) {
- return Promise({
- "isDef": function () {}
- }, function fallback(op, args) {
- return dispatch(object, op, args);
- }, function () {
- return Q(object).inspect();
- });
-}
-
-/**
- * Spreads the values of a promised array of arguments into the
- * fulfillment callback.
- * @param fulfilled callback that receives variadic arguments from the
- * promised array
- * @param rejected callback that receives the exception if the promise
- * is rejected.
- * @returns a promise for the return value or thrown exception of
- * either callback.
- */
-Q.spread = spread;
-function spread(value, fulfilled, rejected) {
- return Q(value).spread(fulfilled, rejected);
-}
-
-Promise.prototype.spread = function (fulfilled, rejected) {
- return this.all().then(function (array) {
- return fulfilled.apply(void 0, array);
- }, rejected);
-};
-
-/**
- * The async function is a decorator for generator functions, turning
- * them into asynchronous generators. Although generators are only part
- * of the newest ECMAScript 6 drafts, this code does not cause syntax
- * errors in older engines. This code should continue to work and will
- * in fact improve over time as the language improves.
- *
- * ES6 generators are currently part of V8 version 3.19 with the
- * --harmony-generators runtime flag enabled. SpiderMonkey has had them
- * for longer, but under an older Python-inspired form. This function
- * works on both kinds of generators.
- *
- * Decorates a generator function such that:
- * - it may yield promises
- * - execution will continue when that promise is fulfilled
- * - the value of the yield expression will be the fulfilled value
- * - it returns a promise for the return value (when the generator
- * stops iterating)
- * - the decorated function returns a promise for the return value
- * of the generator or the first rejected promise among those
- * yielded.
- * - if an error is thrown in the generator, it propagates through
- * every following yield until it is caught, or until it escapes
- * the generator function altogether, and is translated into a
- * rejection for the promise returned by the decorated generator.
- */
-Q.async = async;
-function async(makeGenerator) {
- return function () {
- // when verb is "send", arg is a value
- // when verb is "throw", arg is an exception
- function continuer(verb, arg) {
- var result;
- if (hasES6Generators) {
- try {
- result = generator[verb](arg);
- } catch (exception) {
- return reject(exception);
- }
- if (result.done) {
- return result.value;
- } else {
- return when(result.value, callback, errback);
- }
- } else {
- // FIXME: Remove this case when SM does ES6 generators.
- try {
- result = generator[verb](arg);
- } catch (exception) {
- if (isStopIteration(exception)) {
- return exception.value;
- } else {
- return reject(exception);
- }
- }
- return when(result, callback, errback);
- }
- }
- // Added by Dan
- // If already a generator, use that
- if (makeGenerator.send) {
- var generator = makeGenerator;
- }
- else {
- var generator = makeGenerator.apply(this, arguments);
- }
- var callback = continuer.bind(continuer, "send");
- var errback = continuer.bind(continuer, "throw");
- return callback();
- };
-}
-
-/**
- * The spawn function is a small wrapper around async that immediately
- * calls the generator and also ends the promise chain, so that any
- * unhandled errors are thrown instead of forwarded to the error
- * handler. This is useful because it's extremely common to run
- * generators at the top-level to work with libraries.
- */
-Q.spawn = spawn;
-function spawn(makeGenerator) {
- Q.done(Q.async(makeGenerator)());
-}
-
-// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
-/**
- * Throws a ReturnValue exception to stop an asynchronous generator.
- *
- * This interface is a stop-gap measure to support generator return
- * values in older Firefox/SpiderMonkey. In browsers that support ES6
- * generators like Chromium 29, just use "return" in your generator
- * functions.
- *
- * @param value the return value for the surrounding generator
- * @throws ReturnValue exception with the value.
- * @example
- * // ES6 style
- * Q.async(function* () {
- * var foo = yield getFooPromise();
- * var bar = yield getBarPromise();
- * return foo + bar;
- * })
- * // Older SpiderMonkey style
- * Q.async(function () {
- * var foo = yield getFooPromise();
- * var bar = yield getBarPromise();
- * Q.return(foo + bar);
- * })
- */
-Q["return"] = _return;
-function _return(value) {
- throw new QReturnValue(value);
-}
-
-/**
- * The promised function decorator ensures that any promise arguments
- * are settled and passed as values (`this` is also settled and passed
- * as a value). It will also ensure that the result of a function is
- * always a promise.
- *
- * @example
- * var add = Q.promised(function (a, b) {
- * return a + b;
- * });
- * add(Q(a), Q(B));
- *
- * @param {function} callback The function to decorate
- * @returns {function} a function that has been decorated.
- */
-Q.promised = promised;
-function promised(callback) {
- return function () {
- return spread([this, all(arguments)], function (self, args) {
- return callback.apply(self, args);
- });
- };
-}
-
-/**
- * sends a message to a value in a future turn
- * @param object* the recipient
- * @param op the name of the message operation, e.g., "when",
- * @param args further arguments to be forwarded to the operation
- * @returns result {Promise} a promise for the result of the operation
- */
-Q.dispatch = dispatch;
-function dispatch(object, op, args) {
- return Q(object).dispatch(op, args);
-}
-
-Promise.prototype.dispatch = function (op, args) {
- var self = this;
- var deferred = defer();
- nextTick(function () {
- self.promiseDispatch(deferred.resolve, op, args);
- });
- return deferred.promise;
-};
-
-/**
- * Gets the value of a property in a future turn.
- * @param object promise or immediate reference for target object
- * @param name name of property to get
- * @return promise for the property value
- */
-Q.get = function (object, key) {
- return Q(object).dispatch("get", [key]);
-};
-
-Promise.prototype.get = function (key) {
- return this.dispatch("get", [key]);
-};
-
-/**
- * Sets the value of a property in a future turn.
- * @param object promise or immediate reference for object object
- * @param name name of property to set
- * @param value new value of property
- * @return promise for the return value
- */
-Q.set = function (object, key, value) {
- return Q(object).dispatch("set", [key, value]);
-};
-
-Promise.prototype.set = function (key, value) {
- return this.dispatch("set", [key, value]);
-};
-
-/**
- * Deletes a property in a future turn.
- * @param object promise or immediate reference for target object
- * @param name name of property to delete
- * @return promise for the return value
- */
-Q.del = // XXX legacy
-Q["delete"] = function (object, key) {
- return Q(object).dispatch("delete", [key]);
-};
-
-Promise.prototype.del = // XXX legacy
-Promise.prototype["delete"] = function (key) {
- return this.dispatch("delete", [key]);
-};
-
-/**
- * Invokes a method in a future turn.
- * @param object promise or immediate reference for target object
- * @param name name of method to invoke
- * @param value a value to post, typically an array of
- * invocation arguments for promises that
- * are ultimately backed with `resolve` values,
- * as opposed to those backed with URLs
- * wherein the posted value can be any
- * JSON serializable object.
- * @return promise for the return value
- */
-// bound locally because it is used by other methods
-Q.mapply = // XXX As proposed by "Redsandro"
-Q.post = function (object, name, args) {
- return Q(object).dispatch("post", [name, args]);
-};
-
-Promise.prototype.mapply = // XXX As proposed by "Redsandro"
-Promise.prototype.post = function (name, args) {
- return this.dispatch("post", [name, args]);
-};
-
-/**
- * Invokes a method in a future turn.
- * @param object promise or immediate reference for target object
- * @param name name of method to invoke
- * @param ...args array of invocation arguments
- * @return promise for the return value
- */
-// .send() disabled by Zotero for Mozilla Task.jsm compatibility
-//Q.send = // XXX Mark Miller's proposed parlance
-Q.mcall = // XXX As proposed by "Redsandro"
-Q.invoke = function (object, name /*...args*/) {
- return Q(object).dispatch("post", [name, array_slice(arguments, 2)]);
-};
-
-// .send() disabled by Zotero for Mozilla Task.jsm compatibility
-//Promise.prototype.send = // XXX Mark Miller's proposed parlance
-Promise.prototype.mcall = // XXX As proposed by "Redsandro"
-Promise.prototype.invoke = function (name /*...args*/) {
- return this.dispatch("post", [name, array_slice(arguments, 1)]);
-};
-
-/**
- * Applies the promised function in a future turn.
- * @param object promise or immediate reference for target function
- * @param args array of application arguments
- */
-Q.fapply = function (object, args) {
- return Q(object).dispatch("apply", [void 0, args]);
-};
-
-Promise.prototype.fapply = function (args) {
- return this.dispatch("apply", [void 0, args]);
-};
-
-/**
- * Calls the promised function in a future turn.
- * @param object promise or immediate reference for target function
- * @param ...args array of application arguments
- */
-Q["try"] =
-Q.fcall = function (object /* ...args*/) {
- return Q(object).dispatch("apply", [void 0, array_slice(arguments, 1)]);
-};
-
-Promise.prototype.fcall = function (/*...args*/) {
- return this.dispatch("apply", [void 0, array_slice(arguments)]);
-};
-
-/**
- * Binds the promised function, transforming return values into a fulfilled
- * promise and thrown errors into a rejected one.
- * @param object promise or immediate reference for target function
- * @param ...args array of application arguments
- */
-Q.fbind = function (object /*...args*/) {
- var promise = Q(object);
- var args = array_slice(arguments, 1);
- return function fbound() {
- return promise.dispatch("apply", [
- this,
- args.concat(array_slice(arguments))
- ]);
- };
-};
-Promise.prototype.fbind = function (/*...args*/) {
- var promise = this;
- var args = array_slice(arguments);
- return function fbound() {
- return promise.dispatch("apply", [
- this,
- args.concat(array_slice(arguments))
- ]);
- };
-};
-
-/**
- * Requests the names of the owned properties of a promised
- * object in a future turn.
- * @param object promise or immediate reference for target object
- * @return promise for the keys of the eventually settled object
- */
-Q.keys = function (object) {
- return Q(object).dispatch("keys", []);
-};
-
-Promise.prototype.keys = function () {
- return this.dispatch("keys", []);
-};
-
-/**
- * Turns an array of promises into a promise for an array. If any of
- * the promises gets rejected, the whole array is rejected immediately.
- * @param {Array*} an array (or promise for an array) of values (or
- * promises for values)
- * @returns a promise for an array of the corresponding values
- */
-// By Mark Miller
-// http://wiki.ecmascript.org/doku.php?id=strawman:concurrency&rev=1308776521#allfulfilled
-Q.all = all;
-function all(promises) {
- return when(promises, function (promises) {
- var countDown = 0;
- var deferred = defer();
- array_reduce(promises, function (undefined, promise, index) {
- var snapshot;
- if (
- isPromise(promise) &&
- (snapshot = promise.inspect()).state === "fulfilled"
- ) {
- promises[index] = snapshot.value;
- } else {
- ++countDown;
- when(
- promise,
- function (value) {
- promises[index] = value;
- if (--countDown === 0) {
- deferred.resolve(promises);
- }
- },
- deferred.reject,
- function (progress) {
- deferred.notify({ index: index, value: progress });
- }
- );
- }
- }, void 0);
- if (countDown === 0) {
- deferred.resolve(promises);
- }
- return deferred.promise;
- });
-}
-
-Promise.prototype.all = function () {
- return all(this);
-};
-
-/**
- * Waits for all promises to be settled, either fulfilled or
- * rejected. This is distinct from `all` since that would stop
- * waiting at the first rejection. The promise returned by
- * `allResolved` will never be rejected.
- * @param promises a promise for an array (or an array) of promises
- * (or values)
- * @return a promise for an array of promises
- */
-Q.allResolved = deprecate(allResolved, "allResolved", "allSettled");
-function allResolved(promises) {
- return when(promises, function (promises) {
- promises = array_map(promises, Q);
- return when(all(array_map(promises, function (promise) {
- return when(promise, noop, noop);
- })), function () {
- return promises;
- });
- });
-}
-
-Promise.prototype.allResolved = function () {
- return allResolved(this);
-};
-
-/**
- * @see Promise#allSettled
- */
-Q.allSettled = allSettled;
-function allSettled(promises) {
- return Q(promises).allSettled();
-}
-
-/**
- * Turns an array of promises into a promise for an array of their states (as
- * returned by `inspect`) when they have all settled.
- * @param {Array[Any*]} values an array (or promise for an array) of values (or
- * promises for values)
- * @returns {Array[State]} an array of states for the respective values.
- */
-Promise.prototype.allSettled = function () {
- return this.then(function (promises) {
- return all(array_map(promises, function (promise) {
- promise = Q(promise);
- function regardless() {
- return promise.inspect();
- }
- return promise.then(regardless, regardless);
- }));
- });
-};
-
-/**
- * Captures the failure of a promise, giving an oportunity to recover
- * with a callback. If the given promise is fulfilled, the returned
- * promise is fulfilled.
- * @param {Any*} promise for something
- * @param {Function} callback to fulfill the returned promise if the
- * given promise is rejected
- * @returns a promise for the return value of the callback
- */
-Q.fail = // XXX legacy
-Q["catch"] = function (object, rejected) {
- return Q(object).then(void 0, rejected);
-};
-
-Promise.prototype.fail = // XXX legacy
-Promise.prototype["catch"] = function (rejected) {
- return this.then(void 0, rejected);
-};
-
-/**
- * Attaches a listener that can respond to progress notifications from a
- * promise's originating deferred. This listener receives the exact arguments
- * passed to ``deferred.notify``.
- * @param {Any*} promise for something
- * @param {Function} callback to receive any progress notifications
- * @returns the given promise, unchanged
- */
-Q.progress = progress;
-function progress(object, progressed) {
- return Q(object).then(void 0, void 0, progressed);
-}
-
-Promise.prototype.progress = function (progressed) {
- return this.then(void 0, void 0, progressed);
-};
-
-/**
- * Provides an opportunity to observe the settling of a promise,
- * regardless of whether the promise is fulfilled or rejected. Forwards
- * the resolution to the returned promise when the callback is done.
- * The callback can return a promise to defer completion.
- * @param {Any*} promise
- * @param {Function} callback to observe the resolution of the given
- * promise, takes no arguments.
- * @returns a promise for the resolution of the given promise when
- * ``fin`` is done.
- */
-Q.fin = // XXX legacy
-Q["finally"] = function (object, callback) {
- return Q(object)["finally"](callback);
-};
-
-Promise.prototype.fin = // XXX legacy
-Promise.prototype["finally"] = function (callback) {
- callback = Q(callback);
- return this.then(function (value) {
- return callback.fcall().then(function () {
- return value;
- });
- }, function (reason) {
- // TODO attempt to recycle the rejection with "this".
- return callback.fcall().then(function () {
- throw reason;
- });
- });
-};
-
-/**
- * Terminates a chain of promises, forcing rejections to be
- * thrown as exceptions.
- * @param {Any*} promise at the end of a chain of promises
- * @returns nothing
- */
-Q.done = function (object, fulfilled, rejected, progress) {
- return Q(object).done(fulfilled, rejected, progress);
-};
-
-Promise.prototype.done = function (fulfilled, rejected, progress) {
- var onUnhandledError = function (error) {
- // forward to a future turn so that ``when``
- // does not catch it and turn it into a rejection.
- nextTick(function () {
- makeStackTraceLong(error, promise);
- if (Q.onerror) {
- Q.onerror(error);
- } else {
- throw error;
- }
- });
- };
-
- // Avoid unnecessary `nextTick`ing via an unnecessary `when`.
- var promise = fulfilled || rejected || progress ?
- this.then(fulfilled, rejected, progress) :
- this;
-
- if (typeof process === "object" && process && process.domain) {
- onUnhandledError = process.domain.bind(onUnhandledError);
- }
-
- promise.then(void 0, onUnhandledError);
-};
-
-/**
- * Causes a promise to be rejected if it does not get fulfilled before
- * some milliseconds time out.
- * @param {Any*} promise
- * @param {Number} milliseconds timeout
- * @param {String} custom error message (optional)
- * @returns a promise for the resolution of the given promise if it is
- * fulfilled before the timeout, otherwise rejected.
- */
-Q.timeout = function (object, ms, message) {
- return Q(object).timeout(ms, message);
-};
-
-Promise.prototype.timeout = function (ms, message) {
- var deferred = defer();
- var timeoutId = setTimeout(function () {
- deferred.reject(new Error(message || "Timed out after " + ms + " ms"));
- }, ms);
-
- this.then(function (value) {
- clearTimeout(timeoutId);
- deferred.resolve(value);
- }, function (exception) {
- clearTimeout(timeoutId);
- deferred.reject(exception);
- }, deferred.notify);
-
- return deferred.promise;
-};
-
-/**
- * Returns a promise for the given value (or promised value), some
- * milliseconds after it resolved. Passes rejections immediately.
- * @param {Any*} promise
- * @param {Number} milliseconds
- * @returns a promise for the resolution of the given promise after milliseconds
- * time has elapsed since the resolution of the given promise.
- * If the given promise rejects, that is passed immediately.
- */
-Q.delay = function (object, timeout) {
- if (timeout === void 0) {
- timeout = object;
- object = void 0;
- }
- return Q(object).delay(timeout);
-};
-
-Promise.prototype.delay = function (timeout) {
- return this.then(function (value) {
- var deferred = defer();
- setTimeout(function () {
- deferred.resolve(value);
- }, timeout);
- return deferred.promise;
- });
-};
-
-/**
- * Passes a continuation to a Node function, which is called with the given
- * arguments provided as an array, and returns a promise.
- *
- * Q.nfapply(FS.readFile, [__filename])
- * .then(function (content) {
- * })
- *
- */
-Q.nfapply = function (callback, args) {
- return Q(callback).nfapply(args);
-};
-
-Promise.prototype.nfapply = function (args) {
- var deferred = defer();
- var nodeArgs = array_slice(args);
- nodeArgs.push(deferred.makeNodeResolver());
- this.fapply(nodeArgs).fail(deferred.reject);
- return deferred.promise;
-};
-
-/**
- * Passes a continuation to a Node function, which is called with the given
- * arguments provided individually, and returns a promise.
- * @example
- * Q.nfcall(FS.readFile, __filename)
- * .then(function (content) {
- * })
- *
- */
-Q.nfcall = function (callback /*...args*/) {
- var args = array_slice(arguments, 1);
- return Q(callback).nfapply(args);
-};
-
-Promise.prototype.nfcall = function (/*...args*/) {
- var nodeArgs = array_slice(arguments);
- var deferred = defer();
- nodeArgs.push(deferred.makeNodeResolver());
- this.fapply(nodeArgs).fail(deferred.reject);
- return deferred.promise;
-};
-
-/**
- * Wraps a NodeJS continuation passing function and returns an equivalent
- * version that returns a promise.
- * @example
- * Q.nfbind(FS.readFile, __filename)("utf-8")
- * .then(console.log)
- * .done()
- */
-Q.nfbind =
-Q.denodeify = function (callback /*...args*/) {
- var baseArgs = array_slice(arguments, 1);
- return function () {
- var nodeArgs = baseArgs.concat(array_slice(arguments));
- var deferred = defer();
- nodeArgs.push(deferred.makeNodeResolver());
- Q(callback).fapply(nodeArgs).fail(deferred.reject);
- return deferred.promise;
- };
-};
-
-Promise.prototype.nfbind =
-Promise.prototype.denodeify = function (/*...args*/) {
- var args = array_slice(arguments);
- args.unshift(this);
- return Q.denodeify.apply(void 0, args);
-};
-
-Q.nbind = function (callback, thisp /*...args*/) {
- var baseArgs = array_slice(arguments, 2);
- return function () {
- var nodeArgs = baseArgs.concat(array_slice(arguments));
- var deferred = defer();
- nodeArgs.push(deferred.makeNodeResolver());
- function bound() {
- return callback.apply(thisp, arguments);
- }
- Q(bound).fapply(nodeArgs).fail(deferred.reject);
- return deferred.promise;
- };
-};
-
-Promise.prototype.nbind = function (/*thisp, ...args*/) {
- var args = array_slice(arguments, 0);
- args.unshift(this);
- return Q.nbind.apply(void 0, args);
-};
-
-/**
- * Calls a method of a Node-style object that accepts a Node-style
- * callback with a given array of arguments, plus a provided callback.
- * @param object an object that has the named method
- * @param {String} name name of the method of object
- * @param {Array} args arguments to pass to the method; the callback
- * will be provided by Q and appended to these arguments.
- * @returns a promise for the value or error
- */
-Q.nmapply = // XXX As proposed by "Redsandro"
-Q.npost = function (object, name, args) {
- return Q(object).npost(name, args);
-};
-
-Promise.prototype.nmapply = // XXX As proposed by "Redsandro"
-Promise.prototype.npost = function (name, args) {
- var nodeArgs = array_slice(args || []);
- var deferred = defer();
- nodeArgs.push(deferred.makeNodeResolver());
- this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
- return deferred.promise;
-};
-
-/**
- * Calls a method of a Node-style object that accepts a Node-style
- * callback, forwarding the given variadic arguments, plus a provided
- * callback argument.
- * @param object an object that has the named method
- * @param {String} name name of the method of object
- * @param ...args arguments to pass to the method; the callback will
- * be provided by Q and appended to these arguments.
- * @returns a promise for the value or error
- */
-Q.nsend = // XXX Based on Mark Miller's proposed "send"
-Q.nmcall = // XXX Based on "Redsandro's" proposal
-Q.ninvoke = function (object, name /*...args*/) {
- var nodeArgs = array_slice(arguments, 2);
- var deferred = defer();
- nodeArgs.push(deferred.makeNodeResolver());
- Q(object).dispatch("post", [name, nodeArgs]).fail(deferred.reject);
- return deferred.promise;
-};
-
-Promise.prototype.nsend = // XXX Based on Mark Miller's proposed "send"
-Promise.prototype.nmcall = // XXX Based on "Redsandro's" proposal
-Promise.prototype.ninvoke = function (name /*...args*/) {
- var nodeArgs = array_slice(arguments, 1);
- var deferred = defer();
- nodeArgs.push(deferred.makeNodeResolver());
- this.dispatch("post", [name, nodeArgs]).fail(deferred.reject);
- return deferred.promise;
-};
-
-/**
- * If a function would like to support both Node continuation-passing-style and
- * promise-returning-style, it can end its internal promise chain with
- * `nodeify(nodeback)`, forwarding the optional nodeback argument. If the user
- * elects to use a nodeback, the result will be sent there. If they do not
- * pass a nodeback, they will receive the result promise.
- * @param object a result (or a promise for a result)
- * @param {Function} nodeback a Node.js-style callback
- * @returns either the promise or nothing
- */
-Q.nodeify = nodeify;
-function nodeify(object, nodeback) {
- return Q(object).nodeify(nodeback);
-}
-
-Promise.prototype.nodeify = function (nodeback) {
- if (nodeback) {
- this.then(function (value) {
- nextTick(function () {
- nodeback(null, value);
- });
- }, function (error) {
- nextTick(function () {
- nodeback(error);
- });
- });
- } else {
- return this;
- }
-};
-
-// All code before this point will be filtered from stack traces.
-var qEndingLine = captureLine();
-
-return Q;
-
-});
diff --git a/resource/schema/triggers.sql b/resource/schema/triggers.sql
@@ -1,4 +1,4 @@
--- 17
+-- 18
-- Copyright (c) 2009 Center for History and New Media
-- George Mason University, Fairfax, Virginia, USA
@@ -52,89 +52,20 @@ CREATE TRIGGER update_date_field BEFORE UPDATE ON itemData
-- Don't allow empty creators
DROP TRIGGER IF EXISTS insert_creatorData;
-CREATE TRIGGER insert_creatorData BEFORE INSERT ON creatorData
+CREATE TRIGGER insert_creators BEFORE INSERT ON creators
FOR EACH ROW WHEN NEW.firstName='' AND NEW.lastName=''
BEGIN
SELECT RAISE (ABORT, 'Creator names cannot be empty');---
END;
DROP TRIGGER IF EXISTS update_creatorData;
-CREATE TRIGGER update_creatorData BEFORE UPDATE ON creatorData
+CREATE TRIGGER update_creators BEFORE UPDATE ON creators
FOR EACH ROW WHEN NEW.firstName='' AND NEW.lastName=''
BEGIN
SELECT RAISE (ABORT, 'Creator names cannot be empty');---
END;
---
--- Fake foreign key constraint checks using triggers
---
-
--- annotations/itemID
-DROP TRIGGER IF EXISTS fki_annotations_itemID_itemAttachments_itemID;
-CREATE TRIGGER fki_annotations_itemID_itemAttachments_itemID
- BEFORE INSERT ON annotations
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "annotations" violates foreign key constraint "fki_annotations_itemID_itemAttachments_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_annotations_itemID_itemAttachments_itemID;
-CREATE TRIGGER fku_annotations_itemID_itemAttachments_itemID
- BEFORE UPDATE OF itemID ON annotations
- FOR EACH ROW
- BEGIN
- SELECT RAISE(ABORT, 'update on table "annotations" violates foreign key constraint "fku_annotations_itemID_itemAttachments_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_annotations_itemID_itemAttachments_itemID;
-CREATE TRIGGER fkd_annotations_itemID_itemAttachments_itemID
- BEFORE DELETE ON itemAttachments
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "itemAttachments" violates foreign key constraint "fkd_annotations_itemID_itemAttachments_itemID"')
- WHERE (SELECT COUNT(*) FROM annotations WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_annotations_itemID;
-CREATE TRIGGER fku_itemAttachments_itemID_annotations_itemID
- AFTER UPDATE OF itemID ON itemAttachments
- FOR EACH ROW BEGIN
- UPDATE annotations SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
--- collections/parentCollectionID
-DROP TRIGGER IF EXISTS fki_collections_parentCollectionID_collections_collectionID;
-CREATE TRIGGER fki_collections_parentCollectionID_collections_collectionID
- BEFORE INSERT ON collections
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "collections" violates foreign key constraint "fki_collections_parentCollectionID_collections_collectionID"')
- WHERE NEW.parentCollectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.parentCollectionID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_collections_parentCollectionID_collections_collectionID;
-CREATE TRIGGER fku_collections_parentCollectionID_collections_collectionID
- BEFORE UPDATE OF parentCollectionID ON collections
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "collections" violates foreign key constraint "fku_collections_parentCollectionID_collections_collectionID"')
- WHERE NEW.parentCollectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.parentCollectionID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_collections_parentCollectionID_collections_collectionID;
-CREATE TRIGGER fkd_collections_parentCollectionID_collections_collectionID
- BEFORE DELETE ON collections
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "collections" violates foreign key constraint "fkd_collections_parentCollectionID_collections_collectionID"')
- WHERE (SELECT COUNT(*) FROM collections WHERE parentCollectionID = OLD.collectionID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_collections_collectionID_collections_parentCollectionID;
-CREATE TRIGGER fku_collections_collectionID_collections_parentCollectionID
- AFTER UPDATE OF collectionID ON collections
- FOR EACH ROW BEGIN
- UPDATE collections SET parentCollectionID=NEW.collectionID WHERE parentCollectionID=OLD.collectionID;---
- END;
-
-- Don't allow collection parents in different libraries
DROP TRIGGER IF EXISTS fki_collections_parentCollectionID_libraryID;
CREATE TRIGGER fki_collections_parentCollectionID_libraryID
@@ -142,18 +73,7 @@ CREATE TRIGGER fki_collections_parentCollectionID_libraryID
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collections" violates foreign key constraint "fki_collections_parentCollectionID_libraryID"')
WHERE NEW.parentCollectionID IS NOT NULL AND
- (
- (
- NEW.libraryID IS NULL
- AND
- (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NOT NULL
- ) OR (
- NEW.libraryID IS NOT NULL
- AND
- (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NULL
- ) OR
- NEW.libraryID != (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID)
- );---
+ NEW.libraryID != (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID);---
END;
DROP TRIGGER IF EXISTS fku_collections_parentCollectionID_libraryID;
@@ -162,83 +82,9 @@ CREATE TRIGGER fku_collections_parentCollectionID_libraryID
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collections" violates foreign key constraint "fku_collections_parentCollectionID_libraryID"')
WHERE NEW.parentCollectionID IS NOT NULL AND
- (
- (
- NEW.libraryID IS NULL
- AND
- (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NOT NULL
- ) OR (
- NEW.libraryID IS NOT NULL
- AND
- (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID) IS NULL
- ) OR
- NEW.libraryID != (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID)
- );---
- END;
-
--- collectionItems/collectionID
-DROP TRIGGER IF EXISTS fki_collectionItems_collectionID_collections_collectionID;
-CREATE TRIGGER fki_collectionItems_collectionID_collections_collectionID
- BEFORE INSERT ON collectionItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_collectionID_collections_collectionID"')
- WHERE NEW.collectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.collectionID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_collectionItems_collectionID_collections_collectionID;
-CREATE TRIGGER fku_collectionItems_collectionID_collections_collectionID
- BEFORE UPDATE OF collectionID ON collectionItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_collectionID_collections_collectionID"')
- WHERE NEW.collectionID IS NOT NULL AND (SELECT COUNT(*) FROM collections WHERE collectionID = NEW.collectionID) = 0;---
+ NEW.libraryID != (SELECT libraryID FROM collections WHERE collectionID = NEW.parentCollectionID);---
END;
-DROP TRIGGER IF EXISTS fkd_collectionItems_collectionID_collections_collectionID;
-CREATE TRIGGER fkd_collectionItems_collectionID_collections_collectionID
- BEFORE DELETE ON collections
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "collections" violates foreign key constraint "fkd_collectionItems_collectionID_collections_collectionID"')
- WHERE (SELECT COUNT(*) FROM collectionItems WHERE collectionID = OLD.collectionID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_collections_collectionID_collectionItems_collectionID;
-CREATE TRIGGER fku_collections_collectionID_collectionItems_collectionID
- AFTER UPDATE OF collectionID ON collections
- FOR EACH ROW BEGIN
- UPDATE collectionItems SET collectionID=NEW.collectionID WHERE collectionID=OLD.collectionID;---
- END;
-
--- collectionItems/itemID
-DROP TRIGGER IF EXISTS fki_collectionItems_itemID_items_itemID;
-CREATE TRIGGER fki_collectionItems_itemID_items_itemID
- BEFORE INSERT ON collectionItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_collectionItems_itemID_items_itemID;
-CREATE TRIGGER fku_collectionItems_itemID_items_itemID
- BEFORE UPDATE OF itemID ON collectionItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_collectionItems_itemID_items_itemID;
-CREATE TRIGGER fkd_collectionItems_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_collectionItems_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM collectionItems WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_collectionItems_itemID;
-CREATE TRIGGER fku_items_itemID_collectionItems_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE collectionItems SET collectionID=NEW.itemID WHERE collectionID=OLD.itemID;---
- END;
-- collectionItems libraryID
DROP TRIGGER IF EXISTS fki_collectionItems_libraryID;
@@ -246,16 +92,7 @@ CREATE TRIGGER fki_collectionItems_libraryID
BEFORE INSERT ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_libraryID"')
- WHERE (
- (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- ) OR
- (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
+ WHERE (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
END;
DROP TRIGGER IF EXISTS fku_collectionItems_libraryID;
@@ -263,540 +100,64 @@ CREATE TRIGGER fku_collectionItems_libraryID
BEFORE UPDATE ON collectionItems
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_libraryID"')
- WHERE (
- (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- ) OR
- (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
+ WHERE (SELECT libraryID FROM collections WHERE collectionID = NEW.collectionID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
END;
-- Don't allow child items to exist explicitly in collections
-DROP TRIGGER IF EXISTS fki_collectionItems_itemID_sourceItemID;
-CREATE TRIGGER fki_collectionItems_itemID_sourceItemID
+DROP TRIGGER IF EXISTS fki_collectionItems_itemID_parentItemID;
+CREATE TRIGGER fki_collectionItems_itemID_parentItemID
BEFORE INSERT ON collectionItems
FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_itemID_sourceItemID"')
- WHERE NEW.itemID IN (SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL UNION SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL);---
+ SELECT RAISE(ABORT, 'insert on table "collectionItems" violates foreign key constraint "fki_collectionItems_itemID_parentItemID"')
+ WHERE NEW.itemID IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL);---
END;
-DROP TRIGGER IF EXISTS fku_collectionItems_itemID_sourceItemID;
-CREATE TRIGGER fku_collectionItems_itemID_sourceItemID
+DROP TRIGGER IF EXISTS fku_collectionItems_itemID_parentItemID;
+CREATE TRIGGER fku_collectionItems_itemID_parentItemID
BEFORE UPDATE OF itemID ON collectionItems
FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_itemID_sourceItemID"')
- WHERE NEW.itemID IN (SELECT itemID FROM itemAttachments WHERE sourceItemID IS NOT NULL UNION SELECT itemID FROM itemNotes WHERE sourceItemID IS NOT NULL);---
+ SELECT RAISE(ABORT, 'update on table "collectionItems" violates foreign key constraint "fku_collectionItems_itemID_parentItemID"')
+ WHERE NEW.itemID IN (SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL UNION SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL);---
END;
-- When making a standalone attachment a child, remove from any collections
-DROP TRIGGER IF EXISTS fku_itemAttachments_sourceItemID_collectionItems_itemID;
-CREATE TRIGGER fku_itemAttachments_sourceItemID_collectionItems_itemID
- BEFORE UPDATE OF sourceItemID ON itemAttachments
- FOR EACH ROW WHEN OLD.sourceItemID IS NULL AND NEW.sourceItemID IS NOT NULL BEGIN
+DROP TRIGGER IF EXISTS fku_itemAttachments_parentItemID_collectionItems_itemID;
+CREATE TRIGGER fku_itemAttachments_parentItemID_collectionItems_itemID
+ BEFORE UPDATE OF parentItemID ON itemAttachments
+ FOR EACH ROW WHEN OLD.parentItemID IS NULL AND NEW.parentItemID IS NOT NULL BEGIN
DELETE FROM collectionItems WHERE itemID = NEW.itemID;---
END;
-- When making a standalone note a child, remove from any collections
-DROP TRIGGER IF EXISTS fku_itemNotes_sourceItemID_collectionItems_itemID;
-CREATE TRIGGER fku_itemNotes_sourceItemID_collectionItems_itemID
- BEFORE UPDATE OF sourceItemID ON itemNotes
- FOR EACH ROW WHEN OLD.sourceItemID IS NULL AND NEW.sourceItemID IS NOT NULL BEGIN
+DROP TRIGGER IF EXISTS fku_itemNotes_parentItemID_collectionItems_itemID;
+CREATE TRIGGER fku_itemNotes_parentItemID_collectionItems_itemID
+ BEFORE UPDATE OF parentItemID ON itemNotes
+ FOR EACH ROW WHEN OLD.parentItemID IS NULL AND NEW.parentItemID IS NOT NULL BEGIN
DELETE FROM collectionItems WHERE itemID = NEW.itemID;---
END;
--- creators/creatorDataID
-DROP TRIGGER IF EXISTS fki_creators_creatorDataID_creatorData_creatorDataID;
-CREATE TRIGGER fki_creators_creatorDataID_creatorData_creatorDataID
- BEFORE INSERT ON creators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "creators" violates foreign key constraint "fki_creators_creatorDataID_creatorData_creatorDataID"')
- WHERE NEW.creatorDataID IS NOT NULL AND (SELECT COUNT(*) FROM creatorData WHERE creatorDataID = NEW.creatorDataID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_creators_creatorDataID_creatorData_creatorDataID;
-CREATE TRIGGER fku_creators_creatorDataID_creatorData_creatorDataID
- BEFORE UPDATE OF creatorDataID ON creators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "creators" violates foreign key constraint "fku_creators_creatorDataID_creatorData_creatorDataID"')
- WHERE NEW.creatorDataID IS NOT NULL AND (SELECT COUNT(*) FROM creatorData WHERE creatorDataID = NEW.creatorDataID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_creators_creatorDataID_creatorData_creatorDataID;
-CREATE TRIGGER fkd_creators_creatorDataID_creatorData_creatorDataID
- BEFORE DELETE ON creatorData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "creatorData" violates foreign key constraint "fkd_creators_creatorDataID_creatorData_creatorDataID"')
- WHERE (SELECT COUNT(*) FROM creators WHERE creatorDataID = OLD.creatorDataID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_creatorData_creatorDataID_creators_creatorDataID;
-CREATE TRIGGER fku_creatorData_creatorDataID_creators_creatorDataID
- BEFORE UPDATE OF creatorDataID ON creatorData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "creatorData" violates foreign key constraint "fku_creatorData_creatorDataID_creators_creatorDataID"')
- WHERE (SELECT COUNT(*) FROM creators WHERE creatorDataID = OLD.creatorDataID) > 0;---
- END;
-
-
--- customBaseFieldMappings/customItemTypeID
-DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID;
-CREATE TRIGGER fki_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID
- BEFORE INSERT ON customBaseFieldMappings
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "customBaseFieldMappings" violates foreign key constraint "fki_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID"')
- WHERE NEW.customItemTypeID IS NOT NULL AND (SELECT COUNT(*) FROM customItemTypes WHERE customItemTypeID = NEW.customItemTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID;
-CREATE TRIGGER fku_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID
- BEFORE UPDATE OF customItemTypeID ON customBaseFieldMappings
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "customBaseFieldMappings" violates foreign key constraint "fku_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID"')
- WHERE NEW.customItemTypeID IS NOT NULL AND (SELECT COUNT(*) FROM customItemTypes WHERE customItemTypeID = NEW.customItemTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID;
-CREATE TRIGGER fkd_customBaseFieldMappings_customItemTypeID_customItemTypes_customItemTypeID
- BEFORE DELETE ON customItemTypes
- FOR EACH ROW BEGIN
- DELETE FROM customBaseFieldMappings WHERE customItemTypeID = OLD.customItemTypeID;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customItemTypes_customItemTypeID_customBaseFieldMappings_customItemTypeID;
-CREATE TRIGGER fku_customItemTypes_customItemTypeID_customBaseFieldMappings_customItemTypeID
- AFTER UPDATE OF customItemTypeID ON customItemTypes
- FOR EACH ROW BEGIN
- UPDATE customBaseFieldMappings SET customItemTypeID=NEW.customItemTypeID WHERE customItemTypeID=OLD.customItemTypeID;---
- END;
-
-
--- customBaseFieldMappings/baseFieldID
-DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_baseFieldID_fields_fieldID;
-CREATE TRIGGER fki_customBaseFieldMappings_baseFieldID_fields_fieldID
- BEFORE INSERT ON customBaseFieldMappings
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "customBaseFieldMappings" violates foreign key constraint "fki_customBaseFieldMappings_baseFieldID_fields_fieldID"')
- WHERE NEW.baseFieldID IS NOT NULL AND (SELECT COUNT(*) FROM fields WHERE fieldID = NEW.baseFieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_baseFieldID_fields_fieldID;
-CREATE TRIGGER fku_customBaseFieldMappings_baseFieldID_fields_fieldID
- BEFORE UPDATE OF baseFieldID ON customBaseFieldMappings
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "customBaseFieldMappings" violates foreign key constraint "fku_customBaseFieldMappings_baseFieldID_fields_fieldID"')
- WHERE NEW.baseFieldID IS NOT NULL AND (SELECT COUNT(*) FROM fields WHERE fieldID = NEW.baseFieldID) = 0;---
- END;
-
-
--- customBaseFieldMappings/customFieldID
-DROP TRIGGER IF EXISTS fki_customBaseFieldMappings_customFieldID_customFields_customFieldID;
-CREATE TRIGGER fki_customBaseFieldMappings_customFieldID_customFields_customFieldID
- BEFORE INSERT ON customBaseFieldMappings
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "customBaseFieldMappings" violates foreign key constraint "fki_customBaseFieldMappings_customFieldID_customFields_customFieldID"')
- WHERE NEW.customFieldID IS NOT NULL AND (SELECT COUNT(*) FROM customFields WHERE customFieldID = NEW.customFieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customBaseFieldMappings_customFieldID_customFields_customFieldID;
-CREATE TRIGGER fku_customBaseFieldMappings_customFieldID_customFields_customFieldID
- BEFORE UPDATE OF customFieldID ON customBaseFieldMappings
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "customBaseFieldMappings" violates foreign key constraint "fku_customBaseFieldMappings_customFieldID_customFields_customFieldID"')
- WHERE NEW.customFieldID IS NOT NULL AND (SELECT COUNT(*) FROM customFields WHERE customFieldID = NEW.customFieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_customBaseFieldMappings_customFieldID_customFields_customFieldID;
-CREATE TRIGGER fkd_customBaseFieldMappings_customFieldID_customFields_customFieldID
- BEFORE DELETE ON customFields
- FOR EACH ROW BEGIN
- DELETE FROM customBaseFieldMappings WHERE customFieldID = OLD.customFieldID;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customFields_customFieldID_customBaseFieldMappings_customFieldID;
-CREATE TRIGGER fku_customFields_customFieldID_customBaseFieldMappings_customFieldID
- AFTER UPDATE OF customFieldID ON customFields
- FOR EACH ROW BEGIN
- UPDATE customBaseFieldMappings SET customFieldID=NEW.customFieldID WHERE customFieldID=OLD.customFieldID;---
- END;
-
-
--- customItemTypeFields/customItemTypeID
-DROP TRIGGER IF EXISTS fki_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID;
-CREATE TRIGGER fki_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID
- BEFORE INSERT ON customItemTypeFields
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "customItemTypeFields" violates foreign key constraint "fki_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID"')
- WHERE NEW.customItemTypeID IS NOT NULL AND (SELECT COUNT(*) FROM customItemTypes WHERE customItemTypeID = NEW.customItemTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID;
-CREATE TRIGGER fku_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID
- BEFORE UPDATE OF customItemTypeID ON customItemTypeFields
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "customItemTypeFields" violates foreign key constraint "fku_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID"')
- WHERE NEW.customItemTypeID IS NOT NULL AND (SELECT COUNT(*) FROM customItemTypes WHERE customItemTypeID = NEW.customItemTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID;
-CREATE TRIGGER fkd_customItemTypeFields_customItemTypeID_customItemTypes_customItemTypeID
- BEFORE DELETE ON customItemTypes
- FOR EACH ROW BEGIN
- DELETE FROM customItemTypeFields WHERE customItemTypeID = OLD.customItemTypeID;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customItemTypes_customItemTypeID_customItemTypeFields_customItemTypeID;
-CREATE TRIGGER fku_customItemTypes_customItemTypeID_customItemTypeFields_customItemTypeID
- AFTER UPDATE OF customItemTypeID ON customItemTypes
- FOR EACH ROW BEGIN
- UPDATE customItemTypeFields SET customItemTypeID=NEW.customItemTypeID WHERE customItemTypeID=OLD.customItemTypeID;---
- END;
-
-
--- customItemTypeFields/fieldID
-DROP TRIGGER IF EXISTS fki_customItemTypeFields_fieldID_fields_fieldID;
-CREATE TRIGGER fki_customItemTypeFields_fieldID_fields_fieldID
- BEFORE INSERT ON customItemTypeFields
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "customItemTypeFields" violates foreign key constraint "fki_customItemTypeFields_fieldID_fields_fieldID"')
- WHERE NEW.fieldID IS NOT NULL AND (SELECT COUNT(*) FROM fields WHERE fieldID = NEW.fieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customItemTypeFields_fieldID_fields_fieldID;
-CREATE TRIGGER fku_customItemTypeFields_fieldID_fields_fieldID
- BEFORE UPDATE OF fieldID ON customItemTypeFields
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "customItemTypeFields" violates foreign key constraint "fku_customItemTypeFields_fieldID_fields_fieldID"')
- WHERE NEW.fieldID IS NOT NULL AND (SELECT COUNT(*) FROM fields WHERE fieldID = NEW.fieldID) = 0;---
- END;
-
-
--- customItemTypeFields/customFieldID
-DROP TRIGGER IF EXISTS fki_customItemTypeFields_customFieldID_customFields_customFieldID;
-CREATE TRIGGER fki_customItemTypeFields_customFieldID_customFields_customFieldID
- BEFORE INSERT ON customItemTypeFields
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "customItemTypeFields" violates foreign key constraint "fki_customItemTypeFields_customFieldID_customFields_customFieldID"')
- WHERE NEW.customFieldID IS NOT NULL AND (SELECT COUNT(*) FROM customFields WHERE customFieldID = NEW.customFieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customItemTypeFields_customFieldID_customFields_customFieldID;
-CREATE TRIGGER fku_customItemTypeFields_customFieldID_customFields_customFieldID
- BEFORE UPDATE OF customFieldID ON customItemTypeFields
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "customItemTypeFields" violates foreign key constraint "fku_customItemTypeFields_customFieldID_customFields_customFieldID"')
- WHERE NEW.customFieldID IS NOT NULL AND (SELECT COUNT(*) FROM customFields WHERE customFieldID = NEW.customFieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_customItemTypeFields_customFieldID_customFields_customFieldID;
-CREATE TRIGGER fkd_customItemTypeFields_customFieldID_customFields_customFieldID
- BEFORE DELETE ON customFields
- FOR EACH ROW BEGIN
- DELETE FROM customItemTypeFields WHERE customFieldID = OLD.customFieldID;---
- END;
-
-DROP TRIGGER IF EXISTS fku_customFields_customFieldID_customItemTypeFields_customFieldID;
-CREATE TRIGGER fku_customFields_customFieldID_customItemTypeFields_customFieldID
- AFTER UPDATE OF customFieldID ON customFields
- FOR EACH ROW BEGIN
- UPDATE customItemTypeFields SET customFieldID=NEW.customFieldID WHERE customFieldID=OLD.customFieldID;---
- END;
-
-
--- fulltextItems/itemID
-DROP TRIGGER IF EXISTS fki_fulltextItems_itemID_items_itemID;
-CREATE TRIGGER fki_fulltextItems_itemID_items_itemID
- BEFORE INSERT ON fulltextItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "fulltextItems" violates foreign key constraint "fki_fulltextItems_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_fulltextItems_itemID_items_itemID;
-CREATE TRIGGER fku_fulltextItems_itemID_items_itemID
- BEFORE UPDATE OF itemID ON fulltextItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "fulltextItems" violates foreign key constraint "fku_fulltextItems_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_fulltextItems_itemID_items_itemID;
-CREATE TRIGGER fkd_fulltextItems_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_fulltextItems_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM fulltextItems WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_fulltextItems_itemID;
-CREATE TRIGGER fku_items_itemID_fulltextItems_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE fulltextItems SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
-
--- fulltextItemWords/wordID
-DROP TRIGGER IF EXISTS fki_fulltextItemWords_wordID_fulltextWords_wordID;
-CREATE TRIGGER fki_fulltextItemWords_wordID_fulltextWords_wordID
- BEFORE INSERT ON fulltextItemWords
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "fulltextItemWords" violates foreign key constraint "fki_fulltextItemWords_wordID_fulltextWords_wordID"')
- WHERE NEW.wordID IS NOT NULL AND (SELECT COUNT(*) FROM fulltextWords WHERE wordID = NEW.wordID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_fulltextItemWords_wordID_fulltextWords_wordID;
-CREATE TRIGGER fku_fulltextItemWords_wordID_fulltextWords_wordID
- BEFORE UPDATE OF wordID ON fulltextItemWords
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "fulltextItemWords" violates foreign key constraint "fku_fulltextItemWords_wordID_fulltextWords_wordID"')
- WHERE NEW.wordID IS NOT NULL AND (SELECT COUNT(*) FROM fulltextWords WHERE wordID = NEW.wordID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_fulltextItemWords_wordID_fulltextWords_wordID;
-CREATE TRIGGER fkd_fulltextItemWords_wordID_fulltextWords_wordID
- BEFORE DELETE ON fulltextWords
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "fulltextWords" violates foreign key constraint "fkd_fulltextItemWords_wordID_fulltextWords_wordID"')
- WHERE (SELECT COUNT(*) FROM fulltextItemWords WHERE wordID = OLD.wordID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_fulltextWords_wordID_fulltextItemWords_wordID;
-CREATE TRIGGER fku_fulltextWords_wordID_fulltextItemWords_wordID
- BEFORE UPDATE OF wordID ON fulltextWords
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "fulltextWords" violates foreign key constraint "fku_fulltextWords_wordID_fulltextItemWords_wordID"')
- WHERE (SELECT COUNT(*) FROM fulltextItemWords WHERE wordID = OLD.wordID) > 0;---
- END;
-
--- fulltextItemWords/itemID
-DROP TRIGGER IF EXISTS fki_fulltextItemWords_itemID_items_itemID;
-CREATE TRIGGER fki_fulltextItemWords_itemID_items_itemID
- BEFORE INSERT ON fulltextItemWords
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "fulltextItemWords" violates foreign key constraint "fki_fulltextItemWords_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_fulltextItemWords_itemID_items_itemID;
-CREATE TRIGGER fku_fulltextItemWords_itemID_items_itemID
- BEFORE UPDATE OF itemID ON fulltextItemWords
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "fulltextItemWords" violates foreign key constraint "fku_fulltextItemWords_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_fulltextItemWords_itemID_items_itemID;
-CREATE TRIGGER fkd_fulltextItemWords_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_fulltextItemWords_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM fulltextItemWords WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_fulltextItemWords_itemID;
-CREATE TRIGGER fku_items_itemID_fulltextItemWords_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE fulltextItemWords SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
--- groups/libraryID
-DROP TRIGGER IF EXISTS fki_groups_libraryID_libraries_libraryID;
-CREATE TRIGGER fki_groups_libraryID_libraries_libraryID
- BEFORE INSERT ON groups
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "groups" violates foreign key constraint "fki_groups_libraryID_libraries_libraryID"')
- WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_groups_libraryID_libraries_libraryID;
-CREATE TRIGGER fku_groups_libraryID_libraries_libraryID
- BEFORE UPDATE OF libraryID ON groups
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "groups" violates foreign key constraint "fku_groups_libraryID_libraries_libraryID"')
- WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_groups_libraryID_libraries_libraryID;
-CREATE TRIGGER fkd_groups_libraryID_libraries_libraryID
- BEFORE DELETE ON libraries
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "libraries" violates foreign key constraint "fkd_groups_libraryID_libraries_libraryID"')
- WHERE (SELECT COUNT(*) FROM groups WHERE libraryID = OLD.libraryID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_libraries_libraryID_groups_libraryID;
-CREATE TRIGGER fku_libraries_libraryID_groups_libraryID
- AFTER UPDATE OF libraryID ON libraries
- FOR EACH ROW BEGIN
- UPDATE groups SET libraryID=NEW.libraryID WHERE libraryID=OLD.libraryID;---
- END;
-
--- groupItems/createdByUserID
-DROP TRIGGER IF EXISTS fki_groupItems_createdByUserID_users_userID;
-CREATE TRIGGER fki_groupItems_createdByUserID_users_userID
- BEFORE INSERT ON groupItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "groupItems" violates foreign key constraint "fki_groupItems_createdByUserID_users_userID"')
- WHERE NEW.createdByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.createdByUserID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_groupItems_createdByUserID_users_userID;
-CREATE TRIGGER fku_groupItems_createdByUserID_users_userID
- BEFORE UPDATE OF createdByUserID ON groupItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "groupItems" violates foreign key constraint "fku_groupItems_createdByUserID_users_userID"')
- WHERE NEW.createdByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.createdByUserID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_groupItems_createdByUserID_users_userID;
-CREATE TRIGGER fkd_groupItems_createdByUserID_users_userID
- BEFORE DELETE ON users
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "users" violates foreign key constraint "fkd_groupItems_createdByUserID_users_userID"')
- WHERE (SELECT COUNT(*) FROM groupItems WHERE createdByUserID = OLD.userID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_users_userID_groupItems_createdByUserID;
-CREATE TRIGGER fku_users_userID_groupItems_createdByUserID
- AFTER UPDATE OF userID ON users
- FOR EACH ROW BEGIN
- UPDATE groupItems SET createdByUserID=NEW.userID WHERE createdByUserID=OLD.userID;---
- END;
-
--- groupItems/lastModifiedByUserID
-DROP TRIGGER IF EXISTS fki_groupItems_lastModifiedByUserID_users_userID;
-CREATE TRIGGER fki_groupItems_lastModifiedByUserID_users_userID
- BEFORE INSERT ON groupItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "groupItems" violates foreign key constraint "fki_groupItems_lastModifiedByUserID_users_userID"')
- WHERE NEW.lastModifiedByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.lastModifiedByUserID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_groupItems_lastModifiedByUserID_users_userID;
-CREATE TRIGGER fku_groupItems_lastModifiedByUserID_users_userID
- BEFORE UPDATE OF lastModifiedByUserID ON groupItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "groupItems" violates foreign key constraint "fku_groupItems_lastModifiedByUserID_users_userID"')
- WHERE NEW.lastModifiedByUserID IS NOT NULL AND (SELECT COUNT(*) FROM users WHERE userID = NEW.lastModifiedByUserID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_groupItems_lastModifiedByUserID_users_userID;
-CREATE TRIGGER fkd_groupItems_lastModifiedByUserID_users_userID
- BEFORE DELETE ON users
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "users" violates foreign key constraint "fkd_groupItems_lastModifiedByUserID_users_userID"')
- WHERE (SELECT COUNT(*) FROM groupItems WHERE lastModifiedByUserID = OLD.userID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_users_userID_groupItems_lastModifiedByUserID;
-CREATE TRIGGER fku_users_userID_groupItems_lastModifiedByUserID
- AFTER UPDATE OF userID ON users
- FOR EACH ROW BEGIN
- UPDATE groupItems SET lastModifiedByUserID=NEW.userID WHERE lastModifiedByUserID=OLD.userID;---
- END;
-
--- highlights/itemID
-DROP TRIGGER IF EXISTS fki_highlights_itemID_itemAttachments_itemID;
-CREATE TRIGGER fki_highlights_itemID_itemAttachments_itemID
- BEFORE INSERT ON highlights
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "highlights" violates foreign key constraint "fki_highlights_itemID_itemAttachments_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_highlights_itemID_itemAttachments_itemID;
-CREATE TRIGGER fku_highlights_itemID_itemAttachments_itemID
- BEFORE UPDATE OF itemID ON highlights
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "highlights" violates foreign key constraint "fku_highlights_itemID_itemAttachments_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM itemAttachments WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_highlights_itemID_itemAttachments_itemID;
-CREATE TRIGGER fkd_highlights_itemID_itemAttachments_itemID
- BEFORE DELETE ON itemAttachments
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "itemAttachments" violates foreign key constraint "fkd_highlights_itemID_itemAttachments_itemID"')
- WHERE (SELECT COUNT(*) FROM highlights WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_highlights_itemID;
-CREATE TRIGGER fku_itemAttachments_itemID_highlights_itemID
- AFTER UPDATE OF itemID ON itemAttachments
- FOR EACH ROW BEGIN
- UPDATE highlights SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
--- itemAttachments/itemID
-DROP TRIGGER IF EXISTS fki_itemAttachments_itemID_items_itemID;
-CREATE TRIGGER fki_itemAttachments_itemID_items_itemID
- BEFORE INSERT ON itemAttachments
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemAttachments" violates foreign key constraint "fki_itemAttachments_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemAttachments_itemID_items_itemID;
-CREATE TRIGGER fku_itemAttachments_itemID_items_itemID
- BEFORE UPDATE OF itemID ON itemAttachments
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemAttachments" violates foreign key constraint "fku_itemAttachments_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemAttachments_itemID_items_itemID;
-CREATE TRIGGER fkd_itemAttachments_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemAttachments_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemAttachments WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemAttachments_itemID;
-CREATE TRIGGER fku_items_itemID_itemAttachments_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemAttachments SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
-- itemAttachments
DROP TRIGGER IF EXISTS fki_itemAttachments;
CREATE TRIGGER fki_itemAttachments
BEFORE INSERT ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemAttachments" violates foreign key constraint "fki_itemAttachments"')
- WHERE
- NEW.sourceItemID IS NOT NULL AND (
- (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
- ) OR
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
- );---
+ WHERE NEW.parentItemID IS NOT NULL AND
+ (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.parentItemID);---
-- Make sure this is an attachment item
- SELECT RAISE(ABORT, 'item is not an attachment') WHERE
- (SELECT itemTypeID FROM items WHERE itemID = NEW.itemID) != 14;---
+ SELECT RAISE(ABORT, 'item is not an attachment')
+ WHERE (SELECT itemTypeID FROM items WHERE itemID = NEW.itemID) != 14;---
-- Make sure parent is a regular item
- SELECT RAISE(ABORT, 'parent is not a regular item') WHERE
- NEW.sourceItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.sourceItemID) IN (1,14);---
+ SELECT RAISE(ABORT, 'parent is not a regular item')
+ WHERE NEW.parentItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.parentItemID) IN (1,14);---
-- If child, make sure attachment is not in a collection
- SELECT RAISE(ABORT, 'collection item must be top level') WHERE
- NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM collectionItems WHERE itemID=NEW.itemID)>0;---
+ SELECT RAISE(ABORT, 'collection item must be top level')
+ WHERE NEW.parentItemID IS NOT NULL AND (SELECT COUNT(*) FROM collectionItems WHERE itemID=NEW.itemID)>0;---
END;
DROP TRIGGER IF EXISTS fku_itemAttachments;
@@ -804,303 +165,12 @@ CREATE TRIGGER fku_itemAttachments
BEFORE UPDATE ON itemAttachments
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemAttachments" violates foreign key constraint "fku_itemAttachments"')
- WHERE
- NEW.sourceItemID IS NOT NULL AND (
- (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
- ) OR
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
- );---
+ WHERE NEW.parentItemID IS NOT NULL AND
+ (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.parentItemID);---
-- Make sure parent is a regular item
- SELECT RAISE(ABORT, 'parent is not a regular item') WHERE
- NEW.sourceItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.sourceItemID) IN (1,14);---
- END;
-
--- itemAttachments/sourceItemID
-DROP TRIGGER IF EXISTS fki_itemAttachments_sourceItemID_items_itemID;
-CREATE TRIGGER fki_itemAttachments_sourceItemID_items_itemID
- BEFORE INSERT ON itemAttachments
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemAttachments" violates foreign key constraint "fki_itemAttachments_sourceItemID_items_sourceItemID"')
- WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemAttachments_sourceItemID_items_itemID;
-CREATE TRIGGER fku_itemAttachments_sourceItemID_items_itemID
- BEFORE UPDATE OF sourceItemID ON itemAttachments
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemAttachments" violates foreign key constraint "fku_itemAttachments_sourceItemID_items_sourceItemID"')
- WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemAttachments_sourceItemID_items_itemID;
-CREATE TRIGGER fkd_itemAttachments_sourceItemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemAttachments_sourceItemID_items_sourceItemID"')
- WHERE (SELECT COUNT(*) FROM itemAttachments WHERE sourceItemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemAttachments_sourceItemID;
-CREATE TRIGGER fku_items_itemID_itemAttachments_sourceItemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemAttachments SET sourceItemID=NEW.itemID WHERE sourceItemID=OLD.itemID;---
- END;
-
--- itemCreators/itemID
-DROP TRIGGER IF EXISTS fki_itemCreators_itemID_items_itemID;
-CREATE TRIGGER fki_itemCreators_itemID_items_itemID
- BEFORE INSERT ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemCreators_itemID_items_itemID;
-CREATE TRIGGER fku_itemCreators_itemID_items_itemID
- BEFORE UPDATE OF itemID ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemCreators_itemID_items_itemID;
-CREATE TRIGGER fkd_itemCreators_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemCreators_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemCreators WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemCreators_itemID;
-CREATE TRIGGER fku_items_itemID_itemCreators_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemCreators SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
--- itemCreators/creatorID
-DROP TRIGGER IF EXISTS fki_itemCreators_creatorID_creators_creatorID;
-CREATE TRIGGER fki_itemCreators_creatorID_creators_creatorID
- BEFORE INSERT ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_creatorID_creators_creatorID"')
- WHERE NEW.creatorID IS NOT NULL AND (SELECT COUNT(*) FROM creators WHERE creatorID = NEW.creatorID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemCreators_creatorID_creators_creatorID;
-CREATE TRIGGER fku_itemCreators_creatorID_creators_creatorID
- BEFORE UPDATE OF creatorID ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_creatorID_creators_creatorID"')
- WHERE NEW.creatorID IS NOT NULL AND (SELECT COUNT(*) FROM creators WHERE creatorID = NEW.creatorID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemCreators_creatorID_creators_creatorID;
-CREATE TRIGGER fkd_itemCreators_creatorID_creators_creatorID
- BEFORE DELETE ON creators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "creators" violates foreign key constraint "fkd_itemCreators_creatorID_creators_creatorID"')
- WHERE (SELECT COUNT(*) FROM itemCreators WHERE creatorID = OLD.creatorID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_creators_creatorID_itemCreators_creatorID;
-CREATE TRIGGER fku_creators_creatorID_itemCreators_creatorID
- AFTER UPDATE OF creatorID ON creators
- FOR EACH ROW BEGIN
- UPDATE itemCreators SET creatorID=NEW.creatorID WHERE creatorID=OLD.creatorID;---
- END;
-
--- itemCreators/creatorTypeID
-DROP TRIGGER IF EXISTS fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID;
-CREATE TRIGGER fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID
- BEFORE INSERT ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_creatorTypeID_creatorTypes_creatorTypeID"')
- WHERE NEW.creatorTypeID IS NOT NULL AND (SELECT COUNT(*) FROM creatorTypes WHERE creatorTypeID = NEW.creatorTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID;
-CREATE TRIGGER fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID
- BEFORE UPDATE OF creatorTypeID ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_creatorTypeID_creatorTypes_creatorTypeID"')
- WHERE NEW.creatorTypeID IS NOT NULL AND (SELECT COUNT(*) FROM creatorTypes WHERE creatorTypeID = NEW.creatorTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID;
-CREATE TRIGGER fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID
- BEFORE DELETE ON creatorTypes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "creatorTypes" violates foreign key constraint "fkd_itemCreators_creatorTypeID_creatorTypes_creatorTypeID"')
- WHERE (SELECT COUNT(*) FROM itemCreators WHERE creatorTypeID = OLD.creatorTypeID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_creatorTypes_creatorTypeID_itemCreators_creatorTypeID;
-CREATE TRIGGER fku_creatorTypes_creatorTypeID_itemCreators_creatorTypeID
- BEFORE UPDATE OF creatorTypeID ON creatorTypes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "creatorTypes" violates foreign key constraint "fku_creatorTypes_creatorTypeID_itemCreators_creatorTypeID"')
- WHERE (SELECT COUNT(*) FROM itemCreators WHERE creatorTypeID = OLD.creatorTypeID) > 0;---
- END;
-
-
--- itemCreators libraryID
-DROP TRIGGER IF EXISTS fki_itemCreators_libraryID;
-CREATE TRIGGER fki_itemCreators_libraryID
- BEFORE INSERT ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemCreators" violates foreign key constraint "fki_itemCreators_libraryID"')
- WHERE (
- (SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- ) OR
- (SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemCreators_libraryID;
-CREATE TRIGGER fku_itemCreators_libraryID
- BEFORE UPDATE ON itemCreators
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemCreators" violates foreign key constraint "fku_itemCreators_libraryID"')
- WHERE (
- (SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- ) OR
- (SELECT libraryID FROM creators WHERE creatorID = NEW.creatorID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
- END;
-
-
--- itemData/itemID
-DROP TRIGGER IF EXISTS fki_itemData_itemID_items_itemID;
-CREATE TRIGGER fki_itemData_itemID_items_itemID
- BEFORE INSERT ON itemData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemData" violates foreign key constraint "fki_itemData_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemData_itemID_items_itemID;
-CREATE TRIGGER fku_itemData_itemID_items_itemID
- BEFORE UPDATE OF itemID ON itemData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemData" violates foreign key constraint "fku_itemData_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemData_itemID_items_itemID;
-CREATE TRIGGER fkd_itemData_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemData_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemData WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemData_itemID;
-CREATE TRIGGER fku_items_itemID_itemData_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemData SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
--- itemData/fieldID
-DROP TRIGGER IF EXISTS fki_itemData_fieldID_fields_fieldID;
-CREATE TRIGGER fki_itemData_fieldID_fields_fieldID
- BEFORE INSERT ON itemData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemData" violates foreign key constraint "fki_itemData_fieldID_fieldsCombined_fieldID"')
- WHERE NEW.fieldID IS NOT NULL AND (SELECT COUNT(*) FROM fieldsCombined WHERE fieldID = NEW.fieldID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemData_fieldID_fields_fieldID;
-CREATE TRIGGER fku_itemData_fieldID_fields_fieldID
- BEFORE UPDATE OF fieldID ON itemData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemData" violates foreign key constraint "fku_itemData_fieldID_fieldsCombined_fieldID"')
- WHERE NEW.fieldID IS NOT NULL AND (SELECT COUNT(*) FROM fieldsCombined WHERE fieldID = NEW.fieldID) = 0;---
- END;
-
--- itemData/valueID
-DROP TRIGGER IF EXISTS fki_itemData_valueID_itemDataValues_valueID;
-CREATE TRIGGER fki_itemData_valueID_itemDataValues_valueID
- BEFORE INSERT ON itemData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemData" violates foreign key constraint "fki_itemData_valueID_itemDataValues_valueID"')
- WHERE NEW.valueID IS NOT NULL AND (SELECT COUNT(*) FROM itemDataValues WHERE valueID = NEW.valueID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemData_valueID_itemDataValues_valueID;
-CREATE TRIGGER fku_itemData_valueID_itemDataValues_valueID
- BEFORE UPDATE OF valueID ON itemData
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemData" violates foreign key constraint "fku_itemData_valueID_itemDataValues_valueID"')
- WHERE NEW.valueID IS NOT NULL AND (SELECT COUNT(*) FROM itemDataValues WHERE valueID = NEW.valueID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemData_valueID_itemDataValues_valueID;
-CREATE TRIGGER fkd_itemData_valueID_itemDataValues_valueID
- BEFORE DELETE ON itemDataValues
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "itemDataValues" violates foreign key constraint "fkd_itemData_valueID_itemDataValues_valueID"')
- WHERE (SELECT COUNT(*) FROM itemData WHERE valueID = OLD.valueID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemDataValues_valueID_itemData_valueID;
-CREATE TRIGGER fku_itemDataValues_valueID_itemData_valueID
- BEFORE UPDATE OF valueID ON itemDataValues
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemDataValues" violates foreign key constraint "fku_itemDataValues_valueID_itemData_valueID"')
- WHERE (SELECT COUNT(*) FROM itemData WHERE valueID = OLD.valueID) > 0;---
- END;
-
--- itemNotes/itemID
-DROP TRIGGER IF EXISTS fki_itemNotes_itemID_items_itemID;
-CREATE TRIGGER fki_itemNotes_itemID_items_itemID
- BEFORE INSERT ON itemNotes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemNotes" violates foreign key constraint "fki_itemNotes_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemNotes_itemID_items_itemID;
-CREATE TRIGGER fku_itemNotes_itemID_items_itemID
- BEFORE UPDATE OF itemID ON itemNotes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemNotes" violates foreign key constraint "fku_itemNotes_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemNotes_itemID_items_itemID;
-CREATE TRIGGER fkd_itemNotes_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemNotes_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemNotes WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemNotes_itemID;
-CREATE TRIGGER fku_items_itemID_itemNotes_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemNotes SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
+ SELECT RAISE(ABORT, 'parent is not a regular item')
+ WHERE NEW.parentItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.parentItemID) IN (1,14);---
END;
@@ -1110,19 +180,8 @@ CREATE TRIGGER fki_itemNotes
BEFORE INSERT ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemNotes" violates foreign key constraint "fki_itemNotes_libraryID"')
- WHERE
- NEW.sourceItemID IS NOT NULL AND (
- (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
- ) OR
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
- );---
+ WHERE NEW.parentItemID IS NOT NULL AND
+ (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.parentItemID);---
-- Make sure this is a note or attachment item
SELECT RAISE(ABORT, 'item is not a note or attachment') WHERE
@@ -1130,11 +189,11 @@ CREATE TRIGGER fki_itemNotes
-- Make sure parent is a regular item
SELECT RAISE(ABORT, 'parent is not a regular item') WHERE
- NEW.sourceItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.sourceItemID) IN (1,14);---
+ NEW.parentItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.parentItemID) IN (1,14);---
-- If child, make sure note is not in a collection
SELECT RAISE(ABORT, 'collection item must be top level') WHERE
- NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM collectionItems WHERE itemID=NEW.itemID)>0;---
+ NEW.parentItemID IS NOT NULL AND (SELECT COUNT(*) FROM collectionItems WHERE itemID=NEW.itemID)>0;---
END;
DROP TRIGGER IF EXISTS fku_itemNotes;
@@ -1142,152 +201,12 @@ CREATE TRIGGER fku_itemNotes
BEFORE UPDATE ON itemNotes
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemNotes" violates foreign key constraint "fku_itemNotes"')
- WHERE
- NEW.sourceItemID IS NOT NULL AND (
- (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID) IS NULL
- ) OR
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.sourceItemID)
- );---
+ WHERE NEW.parentItemID IS NOT NULL AND
+ (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.parentItemID);---
-- Make sure parent is a regular item
SELECT RAISE(ABORT, 'parent is not a regular item') WHERE
- NEW.sourceItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.sourceItemID) IN (1,14);---
- END;
-
-
--- itemNotes/sourceItemID
-DROP TRIGGER IF EXISTS fki_itemNotes_sourceItemID_items_itemID;
-CREATE TRIGGER fki_itemNotes_sourceItemID_items_itemID
- BEFORE INSERT ON itemNotes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemNotes" violates foreign key constraint "fki_itemNotes_sourceItemID_items_itemID"')
- WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemNotes_sourceItemID_items_itemID;
-CREATE TRIGGER fku_itemNotes_sourceItemID_items_itemID
- BEFORE UPDATE OF sourceItemID ON itemNotes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemNotes" violates foreign key constraint "fku_itemNotes_sourceItemID_items_itemID"')
- WHERE NEW.sourceItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.sourceItemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemNotes_sourceItemID_items_itemID;
-CREATE TRIGGER fkd_itemNotes_sourceItemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemNotes_sourceItemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemNotes WHERE sourceItemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemNotes_sourceItemID;
-CREATE TRIGGER fku_items_itemID_itemNotes_sourceItemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemNotes SET sourceItemID=NEW.itemID WHERE sourceItemID=OLD.itemID;---
- END;
-
--- items/libraryID
-DROP TRIGGER IF EXISTS fki_items_libraryID_libraries_libraryID;
-CREATE TRIGGER fki_items_libraryID_libraries_libraryID
- BEFORE INSERT ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "items" violates foreign key constraint "fki_items_libraryID_libraries_libraryID"')
- WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_libraryID_libraries_libraryID;
-CREATE TRIGGER fku_items_libraryID_libraries_libraryID
- BEFORE UPDATE OF libraryID ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "items" violates foreign key constraint "fku_items_libraryID_libraries_libraryID"')
- WHERE NEW.libraryID IS NOT NULL AND (SELECT COUNT(*) FROM libraries WHERE libraryID = NEW.libraryID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_items_libraryID_libraries_libraryID;
-CREATE TRIGGER fkd_items_libraryID_libraries_libraryID
- BEFORE DELETE ON libraries
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "libraries" violates foreign key constraint "fkd_items_libraryID_libraries_libraryID"')
- WHERE (SELECT COUNT(*) FROM items WHERE libraryID = OLD.libraryID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_libraries_libraryID_items_libraryID;
-CREATE TRIGGER fku_libraries_libraryID_items_libraryID
- AFTER UPDATE OF libraryID ON libraries
- FOR EACH ROW BEGIN
- UPDATE items SET libraryID=NEW.libraryID WHERE libraryID=OLD.libraryID;---
- END;
-
--- itemSeeAlso/itemID
-DROP TRIGGER IF EXISTS fki_itemSeeAlso_itemID_items_itemID;
-CREATE TRIGGER fki_itemSeeAlso_itemID_items_itemID
- BEFORE INSERT ON itemSeeAlso
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemSeeAlso" violates foreign key constraint "fki_itemSeeAlso_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemSeeAlso_itemID_items_itemID;
-CREATE TRIGGER fku_itemSeeAlso_itemID_items_itemID
- BEFORE UPDATE OF itemID ON itemSeeAlso
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemSeeAlso" violates foreign key constraint "fku_itemSeeAlso_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemSeeAlso_itemID_items_itemID;
-CREATE TRIGGER fkd_itemSeeAlso_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemSeeAlso_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemSeeAlso WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemSeeAlso_itemID;
-CREATE TRIGGER fku_items_itemID_itemSeeAlso_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemSeeAlso SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
--- itemSeeAlso/linkedItemID
-DROP TRIGGER IF EXISTS fki_itemSeeAlso_linkedItemID_items_itemID;
-CREATE TRIGGER fki_itemSeeAlso_linkedItemID_items_itemID
- BEFORE INSERT ON itemSeeAlso
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemSeeAlso" violates foreign key constraint "fki_itemSeeAlso_linkedItemID_items_itemID"')
- WHERE NEW.linkedItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.linkedItemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemSeeAlso_linkedItemID_items_itemID;
-CREATE TRIGGER fku_itemSeeAlso_linkedItemID_items_itemID
- BEFORE UPDATE OF linkedItemID ON itemSeeAlso
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemSeeAlso" violates foreign key constraint "fku_itemSeeAlso_linkedItemID_items_itemID"')
- WHERE NEW.linkedItemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.linkedItemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemSeeAlso_linkedItemID_items_itemID;
-CREATE TRIGGER fkd_itemSeeAlso_linkedItemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemSeeAlso_linkedItemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemSeeAlso WHERE linkedItemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_itemSeeAlso_linkedItemID;
-CREATE TRIGGER fku_items_itemID_itemSeeAlso_linkedItemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemSeeAlso SET linkedItemID=NEW.itemID WHERE linkedItemID=OLD.itemID;---
+ NEW.parentItemID IS NOT NULL AND (SELECT itemTypeID FROM items WHERE itemID = NEW.parentItemID) IN (1,14);---
END;
@@ -1297,16 +216,7 @@ CREATE TRIGGER fki_itemSeeAlso_libraryID
BEFORE INSERT ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemSeeAlso" violates foreign key constraint "fki_itemSeeAlso_libraryID"')
- WHERE (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NULL
- ) OR
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID);---
+ WHERE (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID);---
END;
DROP TRIGGER IF EXISTS fku_itemSeeAlso_libraryID;
@@ -1314,51 +224,10 @@ CREATE TRIGGER fku_itemSeeAlso_libraryID
BEFORE UPDATE ON itemSeeAlso
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemSeeAlso" violates foreign key constraint "fku_itemSeeAlso_libraryID"')
- WHERE (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID) IS NULL
- ) OR
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID);---
+ WHERE (SELECT libraryID FROM items WHERE itemID = NEW.itemID) != (SELECT libraryID FROM items WHERE itemID = NEW.linkedItemID);---
END;
--- itemTags/itemID
-DROP TRIGGER IF EXISTS fki_itemTags_itemID_items_itemID;
-CREATE TRIGGER fki_itemTags_itemID_items_itemID
- BEFORE INSERT ON itemTags
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemTags_itemID_items_itemID;
-CREATE TRIGGER fku_itemTags_itemID_items_itemID
- BEFORE UPDATE OF itemID ON itemTags
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_itemID_items_itemID"')
- WHERE NEW.itemID IS NOT NULL AND (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemTags_itemID_items_itemID;
-CREATE TRIGGER fkd_itemTags_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_itemTags_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM itemTags WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_items_itemID_itemTags_itemID;
-CREATE TRIGGER fkd_items_itemID_itemTags_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE itemTags SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
-- itemTags libraryID
DROP TRIGGER IF EXISTS fki_itemTags_libraryID;
@@ -1366,16 +235,7 @@ CREATE TRIGGER fki_itemTags_libraryID
BEFORE INSERT ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_libraryID"')
- WHERE (
- (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- ) OR
- (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
+ WHERE (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
END;
DROP TRIGGER IF EXISTS fku_itemTags_libraryID;
@@ -1383,183 +243,10 @@ CREATE TRIGGER fku_itemTags_libraryID
BEFORE UPDATE ON itemTags
FOR EACH ROW BEGIN
SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_libraryID"')
- WHERE (
- (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NOT NULL
- ) OR (
- (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) IS NOT NULL
- AND
- (SELECT libraryID FROM items WHERE itemID = NEW.itemID) IS NULL
- ) OR
- (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
+ WHERE (SELECT libraryID FROM tags WHERE tagID = NEW.tagID) != (SELECT libraryID FROM items WHERE itemID = NEW.itemID);---
END;
--- itemTags/tagID
-DROP TRIGGER IF EXISTS fki_itemTags_tagID_tags_tagID;
-CREATE TRIGGER fki_itemTags_tagID_tags_tagID
- BEFORE INSERT ON itemTags
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "itemTags" violates foreign key constraint "fki_itemTags_tagID_tags_tagID"')
- WHERE NEW.tagID IS NOT NULL AND (SELECT COUNT(*) FROM tags WHERE tagID = NEW.tagID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_itemTags_tagID_tags_tagID;
-CREATE TRIGGER fku_itemTags_tagID_tags_tagID
- BEFORE UPDATE OF tagID ON itemTags
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "itemTags" violates foreign key constraint "fku_itemTags_tagID_tags_tagID"')
- WHERE NEW.tagID IS NOT NULL AND (SELECT COUNT(*) FROM tags WHERE tagID = NEW.tagID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_itemTags_tagID_tags_tagID;
-CREATE TRIGGER fkd_itemTags_tagID_tags_tagID
- BEFORE DELETE ON tags
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "tags" violates foreign key constraint "fkd_itemTags_tagID_tags_tagID"')
- WHERE (SELECT COUNT(*) FROM itemTags WHERE tagID = OLD.tagID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_tags_tagID_itemTags_tagID;
-CREATE TRIGGER fku_tags_tagID_itemTags_tagID
- AFTER UPDATE OF tagID ON tags
- FOR EACH ROW BEGIN
- UPDATE itemTags SET tagID=NEW.tagID WHERE tagID=OLD.tagID;---
- END;
-
-
--- savedSearchConditions/savedSearchID
-DROP TRIGGER IF EXISTS fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
-CREATE TRIGGER fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
- BEFORE INSERT ON savedSearchConditions
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "savedSearchConditions" violates foreign key constraint "fki_savedSearchConditions_savedSearchID_savedSearches_savedSearchID"')
- WHERE (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.savedSearchID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
-CREATE TRIGGER fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
- BEFORE UPDATE OF savedSearchID ON savedSearchConditions
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "savedSearchConditions" violates foreign key constraint "fku_savedSearchConditions_savedSearchID_savedSearches_savedSearchID"')
- WHERE (SELECT COUNT(*) FROM savedSearches WHERE savedSearchID = NEW.savedSearchID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID;
-CREATE TRIGGER fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID
- BEFORE DELETE ON savedSearches
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "savedSearches" violates foreign key constraint "fkd_savedSearchConditions_savedSearchID_savedSearches_savedSearchID"')
- WHERE (SELECT COUNT(*) FROM savedSearchConditions WHERE savedSearchID = OLD.savedSearchID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_savedSearches_savedSearchID_savedSearchConditions_savedSearchID;
-CREATE TRIGGER fku_savedSearches_savedSearchID_savedSearchConditions_savedSearchID
- AFTER UPDATE OF savedSearchID ON savedSearches
- FOR EACH ROW BEGIN
- UPDATE savedSearchConditions SET savedSearchID=NEW.savedSearchID WHERE savedSearchID=OLD.savedSearchID;---
- END;
-
--- deletedItems/itemID
-DROP TRIGGER IF EXISTS fki_deletedItems_itemID_items_itemID;
-CREATE TRIGGER fki_deletedItems_itemID_items_itemID
- BEFORE INSERT ON deletedItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "deletedItems" violates foreign key constraint "fki_deletedItems_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_deletedItems_itemID_items_itemID;
-CREATE TRIGGER fku_deletedItems_itemID_items_itemID
- BEFORE UPDATE OF itemID ON deletedItems
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "deletedItems" violates foreign key constraint "fku_deletedItems_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM items WHERE itemID = NEW.itemID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_deletedItems_itemID_items_itemID;
-CREATE TRIGGER fkd_deletedItems_itemID_items_itemID
- BEFORE DELETE ON items
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "items" violates foreign key constraint "fkd_deletedItems_itemID_items_itemID"')
- WHERE (SELECT COUNT(*) FROM deletedItems WHERE itemID = OLD.itemID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_items_itemID_deletedItems_itemID;
-CREATE TRIGGER fku_items_itemID_deletedItems_itemID
- AFTER UPDATE OF itemID ON items
- FOR EACH ROW BEGIN
- UPDATE deletedItems SET itemID=NEW.itemID WHERE itemID=OLD.itemID;---
- END;
-
-
--- syncDeleteLog/syncObjectTypeID
-DROP TRIGGER IF EXISTS fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
-CREATE TRIGGER fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
- BEFORE INSERT ON syncDeleteLog
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "syncDeleteLog" violates foreign key constraint "fki_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID"')
- WHERE (SELECT COUNT(*) FROM syncObjectTypes WHERE syncObjectTypeID = NEW.syncObjectTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
-CREATE TRIGGER fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
- BEFORE UPDATE OF syncObjectTypeID ON syncDeleteLog
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "syncDeleteLog" violates foreign key constraint "fku_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID"')
- WHERE (SELECT COUNT(*) FROM syncObjectTypes WHERE syncObjectTypeID = NEW.syncObjectTypeID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID;
-CREATE TRIGGER fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID
- BEFORE DELETE ON syncObjectTypes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "syncObjectTypes" violates foreign key constraint "fkd_syncDeleteLog_syncObjectTypeID_syncObjectTypes_syncObjectTypeID"')
- WHERE (SELECT COUNT(*) FROM syncDeleteLog WHERE syncObjectTypeID = OLD.syncObjectTypeID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_syncObjectTypes_syncObjectTypeID_syncDeleteLog_syncObjectTypeID;
-CREATE TRIGGER fku_syncObjectTypes_syncObjectTypeID_syncDeleteLog_syncObjectTypeID
- BEFORE DELETE ON syncObjectTypes
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "syncObjectTypes" violates foreign key constraint "fku_syncObjectTypes_syncObjectTypeID_syncDeleteLog_syncObjectTypeID"')
- WHERE (SELECT COUNT(*) FROM syncDeleteLog WHERE syncObjectTypeID = OLD.syncObjectTypeID) > 0;---
- END;
-
--- proxyHosts/proxyID
-DROP TRIGGER IF EXISTS fki_proxyHosts_proxyID_proxies_proxyID;
-CREATE TRIGGER fki_proxyHosts_proxyID_proxies_proxyID
-BEFORE INSERT ON proxyHosts
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'insert on table "proxyHosts" violates foreign key constraint "fki_proxyHosts_proxyID_proxies_proxyID"')
- WHERE (SELECT COUNT(*) FROM proxies WHERE proxyID = NEW.proxyID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_proxyHosts_proxyID_proxies_proxyID;
-CREATE TRIGGER fku_proxyHosts_proxyID_proxies_proxyID
- BEFORE UPDATE OF proxyID ON proxyHosts
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "proxyHosts" violates foreign key constraint "fku_proxyHosts_proxyID_proxies_proxyID"')
- WHERE (SELECT COUNT(*) FROM proxies WHERE proxyID = NEW.proxyID) = 0;---
- END;
-
-DROP TRIGGER IF EXISTS fkd_proxyHosts_proxyID_proxies_proxyID;
-CREATE TRIGGER fkd_proxyHosts_proxyID_proxies_proxyID
- BEFORE DELETE ON proxies
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'delete on table "proxies" violates foreign key constraint "fkd_proxyHosts_proxyID_proxies_proxyID"')
- WHERE (SELECT COUNT(*) FROM proxyHosts WHERE proxyID = OLD.proxyID) > 0;---
- END;
-
-DROP TRIGGER IF EXISTS fku_proxies_proxyID_proxyHosts_proxyID;
-CREATE TRIGGER fku_proxies_proxyID_proxyHosts_proxyID
- BEFORE DELETE ON proxies
- FOR EACH ROW BEGIN
- SELECT RAISE(ABORT, 'update on table "proxies" violates foreign key constraint "fku_proxies_proxyID_proxyHosts_proxyID"')
- WHERE (SELECT COUNT(*) FROM proxyHosts WHERE proxyID = OLD.proxyID) > 0;---
- END;
-
-- Make sure tags aren't empty
DROP TRIGGER IF EXISTS fki_tags;
CREATE TRIGGER fki_tags
@@ -1576,4 +263,3 @@ CREATE TRIGGER fku_tags
SELECT RAISE(ABORT, 'Tag cannot be blank')
WHERE TRIM(NEW.name)='';---
END;
-
diff --git a/resource/schema/userdata.sql b/resource/schema/userdata.sql
@@ -1,4 +1,4 @@
--- 79
+-- 80
-- Copyright (c) 2009 Center for History and New Media
-- George Mason University, Fairfax, Virginia, USA
@@ -45,22 +45,25 @@ CREATE TABLE syncedSettings (
value NOT NULL,
version INT NOT NULL DEFAULT 0,
synced INT NOT NULL DEFAULT 0,
- PRIMARY KEY (setting, libraryID)
+ PRIMARY KEY (setting, libraryID),
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
--- The foundational table; every item collected has a unique record here
+-- 'items' view and triggers created in triggers.sql
CREATE TABLE items (
itemID INTEGER PRIMARY KEY,
itemTypeID INT NOT NULL,
dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- libraryID INT,
+ libraryID INT NOT NULL,
key TEXT NOT NULL,
+ version INT NOT NULL DEFAULT 0,
+ synced INT NOT NULL DEFAULT 0,
UNIQUE (libraryID, key),
- FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
-
+CREATE INDEX items_synced ON items(synced);
CREATE TABLE itemDataValues (
valueID INTEGER PRIMARY KEY,
@@ -73,8 +76,8 @@ CREATE TABLE itemData (
fieldID INT,
valueID,
PRIMARY KEY (itemID, fieldID),
- FOREIGN KEY (itemID) REFERENCES items(itemID),
- FOREIGN KEY (fieldID) REFERENCES fields(fieldID),
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (fieldID) REFERENCES fieldsCombined(fieldID),
FOREIGN KEY (valueID) REFERENCES itemDataValues(valueID)
);
CREATE INDEX itemData_fieldID ON itemData(fieldID);
@@ -82,147 +85,133 @@ CREATE INDEX itemData_fieldID ON itemData(fieldID);
-- Note data for note and attachment items
CREATE TABLE itemNotes (
itemID INTEGER PRIMARY KEY,
- sourceItemID INT,
+ parentItemID INT,
note TEXT,
title TEXT,
- FOREIGN KEY (itemID) REFERENCES items(itemID),
- FOREIGN KEY (sourceItemID) REFERENCES items(itemID)
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE
);
-CREATE INDEX itemNotes_sourceItemID ON itemNotes(sourceItemID);
+CREATE INDEX itemNotes_parentItemID ON itemNotes(parentItemID);
-- Metadata for attachment items
CREATE TABLE itemAttachments (
itemID INTEGER PRIMARY KEY,
- sourceItemID INT,
+ parentItemID INT,
linkMode INT,
- mimeType TEXT,
+ contentType TEXT,
charsetID INT,
path TEXT,
originalPath TEXT,
syncState INT DEFAULT 0,
storageModTime INT,
storageHash TEXT,
- FOREIGN KEY (itemID) REFERENCES items(itemID),
- FOREIGN KEY (sourceItemID) REFERENCES items(itemID)
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (parentItemID) REFERENCES items(itemID) ON DELETE CASCADE
);
-CREATE INDEX itemAttachments_sourceItemID ON itemAttachments(sourceItemID);
-CREATE INDEX itemAttachments_mimeType ON itemAttachments(mimeType);
+CREATE INDEX itemAttachments_parentItemID ON itemAttachments(parentItemID);
+CREATE INDEX itemAttachments_contentType ON itemAttachments(contentType);
CREATE INDEX itemAttachments_syncState ON itemAttachments(syncState);
CREATE TABLE tags (
tagID INTEGER PRIMARY KEY,
- name TEXT NOT NULL COLLATE NOCASE,
- type INT NOT NULL,
- dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- libraryID INT,
- key TEXT NOT NULL,
- UNIQUE (libraryID, name, type),
- UNIQUE (libraryID, key)
+ libraryID INT NOT NULL,
+ name TEXT NOT NULL,
+ UNIQUE (libraryID, name)
);
CREATE TABLE itemTags (
- itemID INT,
- tagID INT,
+ itemID INT NOT NULL,
+ tagID INT NOT NULL,
+ type INT NOT NULL,
PRIMARY KEY (itemID, tagID),
- FOREIGN KEY (itemID) REFERENCES items(itemID),
- FOREIGN KEY (tagID) REFERENCES tags(tagID)
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (tagID) REFERENCES tags(tagID) ON DELETE CASCADE
);
CREATE INDEX itemTags_tagID ON itemTags(tagID);
CREATE TABLE itemSeeAlso (
- itemID INT,
- linkedItemID INT,
+ itemID INT NOT NULL,
+ linkedItemID INT NOT NULL,
PRIMARY KEY (itemID, linkedItemID),
- FOREIGN KEY (itemID) REFERENCES items(itemID),
- FOREIGN KEY (linkedItemID) REFERENCES items(itemID)
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (linkedItemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX itemSeeAlso_linkedItemID ON itemSeeAlso(linkedItemID);
CREATE TABLE creators (
creatorID INTEGER PRIMARY KEY,
- creatorDataID INT NOT NULL,
- dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- libraryID INT,
- key TEXT NOT NULL,
- UNIQUE (libraryID, key),
- FOREIGN KEY (creatorDataID) REFERENCES creatorData(creatorDataID)
-);
-CREATE INDEX creators_creatorDataID ON creators(creatorDataID);
-
--- Unique creator data, which can be associated with more than one creator
-CREATE TABLE creatorData (
- creatorDataID INTEGER PRIMARY KEY,
firstName TEXT,
lastName TEXT,
- shortName TEXT,
fieldMode INT,
- birthYear INT
+ UNIQUE (lastName, firstName, fieldMode)
);
-CREATE INDEX creatorData_name ON creatorData(lastName, firstName);
CREATE TABLE itemCreators (
- itemID INT,
- creatorID INT,
- creatorTypeID INT DEFAULT 1,
- orderIndex INT DEFAULT 0,
+ itemID INT NOT NULL,
+ creatorID INT NOT NULL,
+ creatorTypeID INT NOT NULL DEFAULT 1,
+ orderIndex INT NOT NULL DEFAULT 0,
PRIMARY KEY (itemID, creatorID, creatorTypeID, orderIndex),
- FOREIGN KEY (itemID) REFERENCES items(itemID),
- FOREIGN KEY (creatorID) REFERENCES creators(creatorID)
+ UNIQUE (itemID, orderIndex),
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (creatorID) REFERENCES creators(creatorID) ON DELETE CASCADE,
FOREIGN KEY (creatorTypeID) REFERENCES creatorTypes(creatorTypeID)
);
+CREATE INDEX itemCreators_creatorTypeID ON itemCreators(creatorTypeID);
CREATE TABLE collections (
collectionID INTEGER PRIMARY KEY,
collectionName TEXT NOT NULL,
parentCollectionID INT DEFAULT NULL,
- dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- libraryID INT,
+ libraryID INT NOT NULL,
key TEXT NOT NULL,
+ version INT NOT NULL DEFAULT 0,
+ synced INT NOT NULL DEFAULT 0,
UNIQUE (libraryID, key),
- FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID)
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,
+ FOREIGN KEY (parentCollectionID) REFERENCES collections(collectionID) ON DELETE CASCADE
);
+CREATE INDEX collections_synced ON collections(synced);
CREATE TABLE collectionItems (
- collectionID INT,
- itemID INT,
- orderIndex INT DEFAULT 0,
+ collectionID INT NOT NULL,
+ itemID INT NOT NULL,
+ orderIndex INT NOT NULL DEFAULT 0,
PRIMARY KEY (collectionID, itemID),
- FOREIGN KEY (collectionID) REFERENCES collections(collectionID),
- FOREIGN KEY (itemID) REFERENCES items(itemID)
+ FOREIGN KEY (collectionID) REFERENCES collections(collectionID) ON DELETE CASCADE,
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
-CREATE INDEX itemID ON collectionItems(itemID);
+CREATE INDEX collectionItems_itemID ON collectionItems(itemID);
CREATE TABLE savedSearches (
savedSearchID INTEGER PRIMARY KEY,
savedSearchName TEXT NOT NULL,
- dateAdded TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- dateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- libraryID INT,
+ libraryID INT NOT NULL,
key TEXT NOT NULL,
- UNIQUE (libraryID, key)
+ version INT NOT NULL DEFAULT 0,
+ synced INT NOT NULL DEFAULT 0,
+ UNIQUE (libraryID, key),
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
+CREATE INDEX savedSearches_synced ON savedSearches(synced);
CREATE TABLE savedSearchConditions (
- savedSearchID INT,
- searchConditionID INT,
- condition TEXT,
+ savedSearchID INT NOT NULL,
+ searchConditionID INT NOT NULL,
+ condition TEXT NOT NULL,
operator TEXT,
value TEXT,
required NONE,
PRIMARY KEY (savedSearchID, searchConditionID),
- FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID)
+ FOREIGN KEY (savedSearchID) REFERENCES savedSearches(savedSearchID) ON DELETE CASCADE
);
CREATE TABLE deletedItems (
itemID INTEGER PRIMARY KEY,
- dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL
+ dateDeleted DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX deletedItems_dateDeleted ON deletedItems(dateDeleted);
@@ -232,13 +221,15 @@ CREATE TABLE relations (
predicate TEXT NOT NULL,
object TEXT NOT NULL,
clientDateModified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (subject, predicate, object)
+ PRIMARY KEY (subject, predicate, object),
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX relations_object ON relations(object);
CREATE TABLE libraries (
libraryID INTEGER PRIMARY KEY,
- libraryType TEXT NOT NULL
+ libraryType TEXT NOT NULL,
+ version INT NOT NULL DEFAULT 0
);
CREATE TABLE users (
@@ -253,15 +244,17 @@ CREATE TABLE groups (
description TEXT NOT NULL,
editable INT NOT NULL,
filesEditable INT NOT NULL,
- FOREIGN KEY (libraryID) REFERENCES libraries(libraryID)
+ etag TEXT NOT NULL DEFAULT '',
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE TABLE groupItems (
itemID INTEGER PRIMARY KEY,
- createdByUserID INT NOT NULL,
- lastModifiedByUserID INT NOT NULL,
- FOREIGN KEY (createdByUserID) REFERENCES users(userID),
- FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID)
+ createdByUserID INT,
+ lastModifiedByUserID INT,
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
+ FOREIGN KEY (createdByUserID) REFERENCES users(userID) ON DELETE SET NULL,
+ FOREIGN KEY (lastModifiedByUserID) REFERENCES users(userID) ON DELETE SET NULL
);
CREATE TABLE fulltextItems (
@@ -272,7 +265,7 @@ CREATE TABLE fulltextItems (
indexedChars INT,
totalChars INT,
synced INT DEFAULT 0,
- FOREIGN KEY (itemID) REFERENCES items(itemID)
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX fulltextItems_version ON fulltextItems(version);
@@ -286,31 +279,44 @@ CREATE TABLE fulltextItemWords (
itemID INT,
PRIMARY KEY (wordID, itemID),
FOREIGN KEY (wordID) REFERENCES fulltextWords(wordID),
- FOREIGN KEY (itemID) REFERENCES items(itemID)
+ FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE
);
CREATE INDEX fulltextItemWords_itemID ON fulltextItemWords(itemID);
+CREATE TABLE syncCache (
+ libraryID INT NOT NULL,
+ key TEXT NOT NULL,
+ syncObjectTypeID INT NOT NULL,
+ version INT NOT NULL,
+ data TEXT,
+ PRIMARY KEY (libraryID, key, syncObjectTypeID),
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE,
+ FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
+);
+
CREATE TABLE syncDeleteLog (
syncObjectTypeID INT NOT NULL,
libraryID INT NOT NULL,
key TEXT NOT NULL,
timestamp INT NOT NULL,
UNIQUE (syncObjectTypeID, libraryID, key),
- FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID)
+ FOREIGN KEY (syncObjectTypeID) REFERENCES syncObjectTypes(syncObjectTypeID),
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX syncDeleteLog_timestamp ON syncDeleteLog(timestamp);
CREATE TABLE storageDeleteLog (
- libraryID INT,
+ libraryID INT NOT NULL,
key TEXT NOT NULL,
timestamp INT NOT NULL,
- PRIMARY KEY (libraryID, key)
+ PRIMARY KEY (libraryID, key),
+ FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
);
CREATE INDEX storageDeleteLog_timestamp ON storageDeleteLog(timestamp);
CREATE TABLE annotations (
annotationID INTEGER PRIMARY KEY,
- itemID INT,
+ itemID INT NOT NULL,
parent TEXT,
textNode INT,
offset INT,
@@ -321,13 +327,13 @@ CREATE TABLE annotations (
text TEXT,
collapsed BOOL,
dateModified DATE,
- FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
+ FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE
);
CREATE INDEX annotations_itemID ON annotations(itemID);
CREATE TABLE highlights (
highlightID INTEGER PRIMARY KEY,
- itemID INTEGER,
+ itemID INT NOT NULL,
startParent TEXT,
startTextNode INT,
startOffset INT,
@@ -335,7 +341,7 @@ CREATE TABLE highlights (
endTextNode INT,
endOffset INT,
dateModified DATE,
- FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID)
+ FOREIGN KEY (itemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE
);
CREATE INDEX highlights_itemID ON highlights(itemID);