From 453fed88bd175d3b1b0cefe7f349cd92fcdb5dcb Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 27 Apr 2010 08:03:08 +0000 Subject: [PATCH] Zotero Commons updates: - Store one item per IA bucket, with attachments stored as objects - Use proper mediatype field based on Zotero item type - Commons list is now pulled dynamically based on RDF stored at IA, without need for corresponding local item (which may have been deleted, etc.) - Once available, OCRed PDFs can be pulled down by right-clicking on Commons and selecting Refresh - Downloaded OCRed PDFs are now named the same as the existing attachment, with "(OCR)" appended - The relations table is used to link downloaded OCRed PDFs to the IA file, so the downloaded file can be renamed without triggering another download - The Commons view is marked for automatic refresh after an item is uploaded - Added some progress notifications, though more are probably needed - Other things Also: - Added Zotero.File.getBinaryContents(file) - Erase an item's relations when the item is deleted, and purge orphaned ones - Zotero.URI.eraseByPathPrefix(prefix) no longer prepends 'http://zotero.org' (which has been moved to Zotero.URI.defaultPrefix) - New function Zotero.URI.eraseByURI(prefix) Known Issues: - Slow (some IA changes should be able to speed it up) - Identifier format is likely temporary - Sometimes it stops during setTimeout() calls for no apparent reason whatsoever - Didn't test items with multiple attachments - Not sure if Commons view will auto-refresh if you switch to it before the upload is done - IA translator not yet updated - Deleting items not supported by IA - Date Added/Date Modified don't show up properly in Zotero for Commons items --- chrome/content/zotero/overlay.js | 102 +- chrome/content/zotero/overlay.xul | 5 +- .../zotero/xpcom/collectionTreeView.js | 79 +- chrome/content/zotero/xpcom/commons.js | 1501 ++++++++++------- chrome/content/zotero/xpcom/data/group.js | 2 +- chrome/content/zotero/xpcom/data/item.js | 9 +- chrome/content/zotero/xpcom/data/relations.js | 35 +- chrome/content/zotero/xpcom/file.js | 15 + chrome/content/zotero/xpcom/itemTreeView.js | 21 +- chrome/content/zotero/xpcom/uri.js | 13 +- chrome/content/zotero/xpcom/zotero.js | 4 + defaults/preferences/zotero.js | 1 - 12 files changed, 1084 insertions(+), 703 deletions(-) diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js index 2e5f7ea62..a3590e339 100644 --- a/chrome/content/zotero/overlay.js +++ b/chrome/content/zotero/overlay.js @@ -1343,7 +1343,7 @@ var ZoteroPane = new function() } var itemGroup = this.itemsView._itemGroup; - if (!itemGroup.isTrash() && !this.canEdit()) { + if (!itemGroup.isTrash() && !itemGroup.isCommons() && !this.canEdit()) { this.displayCannotEditLibraryMessage(); return; } @@ -1383,6 +1383,9 @@ var ZoteroPane = new function() else if (itemGroup.isShare()) { return; } + else if (itemGroup.isCommons()) { + var prompt = toDelete; + } // Do nothing in trash view if any non-deleted items are selected else if (itemGroup.isTrash()) { var start = {}; @@ -1462,47 +1465,25 @@ var ZoteroPane = new function() } } - this.createCommonsBucket = function() { - var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] - .getService(Components.interfaces.nsIPromptService); - - var bucketName = { value: '' }; - // TODO localize - var result = promptService.prompt(window, - "New Bucket", - "Enter a name for this bucket:", bucketName, "", {}); - - if (result && bucketName.value) { - Zotero.Commons.createBucket(bucketName.value); - } - } - - this.refreshCommonsBucket = function() { + this.refreshCommons = function() { if (this.collectionsView && this.collectionsView.selection - && this.collectionsView.selection.count > 0 + && this.collectionsView.selection.count == 1 && this.collectionsView.selection.currentIndex != -1) { - var bucket = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); - if (bucket && bucket.isBucket()) { - this.itemsView._itemGroup.ref._items = null; - this.itemsView.refresh(); + var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); + if (itemGroup && itemGroup.isCommons()) { + var self = this; + Zotero.Commons.syncBucketList(function () { + self.itemsView.refresh(); + self.itemsView.sort(); + + // On a manual refresh, also check for new OCRed files + Zotero.Commons.syncFiles(); + }); } } } - this.removeCommonsBucket = function() { - if (this.collectionsView - && this.collectionsView.selection - && this.collectionsView.selection.count > 0 - && this.collectionsView.selection.currentIndex != -1) { - var bucket = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); - if (bucket && bucket.isBucket()) { - Zotero.Commons.removeBucket(bucket.getName()); - } - } - } - - function editSelectedCollection() { if (!this.canEdit()) { @@ -1802,10 +1783,7 @@ var ZoteroPane = new function() exportFile: 9, loadReport: 10, emptyTrash: 11, - createCommonsBucket: 12, - syncBucketList: 13, - removeCommonsBucket: 14, - refreshCommonsBucket: 15 + refreshCommons: 12 }; var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex); @@ -1873,14 +1851,8 @@ var ZoteroPane = new function() else if (itemGroup.isTrash()) { show = [m.emptyTrash]; } - // Header - else if (itemGroup.isHeader()) { - if (itemGroup.ref.id == 'commons-header') { - show = [m.createCommonsBucket, m.syncBucketList]; - } - } - else if (itemGroup.isBucket()) { - show = [m.removeCommonsBucket, m.refreshCommonsBucket]; + else if (itemGroup.isCommons()) { + show = [m.refreshCommons]; } // Group else if (itemGroup.isGroup()) { @@ -2261,7 +2233,7 @@ var ZoteroPane = new function() return; } - if (tree.id == 'zotero-collections-tree') { + if (tree.id == 'zotero-collections-tree') { // Ignore triple clicks for collections if (event.detail != 2) { return; @@ -2295,15 +2267,12 @@ var ZoteroPane = new function() ZoteroPane.loadURI(uri); event.stopPropagation(); } - else if(itemGroup.ref.id == 'commons-header') { - ZoteroPane.loadURI(Zotero.Commons.uri); - event.stopPropagation(); - } return; } - if (itemGroup.isBucket()) { - ZoteroPane.loadURI(itemGroup.ref.uri); + if (itemGroup.isCommons()) { + // TODO: take to a search of the user's buckets? + //ZoteroPane.loadURI(itemGroup.ref.uri); event.stopPropagation(); } } @@ -2325,6 +2294,18 @@ var ZoteroPane = new function() var item = ZoteroPane.getSelectedItems()[0]; if (item) { if (item.isRegularItem()) { + // Double-click on Commons item should load IA page + var itemGroup = ZoteroPane.collectionsView._getItemAtRow( + ZoteroPane.collectionsView.selection.currentIndex + ); + + if (itemGroup.isCommons()) { + var bucket = Zotero.Commons.getBucketFromItem(item); + ZoteroPane.loadURI(bucket.uri); + event.stopPropagation(); + return; + } + if (!viewOnDoubleClick) { return; } @@ -2879,6 +2860,8 @@ var ZoteroPane = new function() return this.addItemFromPage(itemType, saveSnapshot, row); } + var self = this; + Zotero.MIME.getMIMETypeFromURL(url, function (mimeType, hasNativeHandler) { // If native type, save using a hidden browser if (hasNativeHandler) { @@ -2943,7 +2926,16 @@ var ZoteroPane = new function() var collectionID = false; } - Zotero.Attachments.importFromURL(url, false, false, false, collectionID, null, libraryID); + var attachmentItem = Zotero.Attachments.importFromURL(url, false, false, false, collectionID, mimeType, libraryID); + + // importFromURL() doesn't trigger the notifier until + // after download is complete + // + // TODO: add a callback to importFromURL() + setTimeout(function () { + self.selectItem(attachmentItem.id); + }, 1001); + return; } } diff --git a/chrome/content/zotero/overlay.xul b/chrome/content/zotero/overlay.xul index fd1a522fd..58a52b866 100644 --- a/chrome/content/zotero/overlay.xul +++ b/chrome/content/zotero/overlay.xul @@ -107,10 +107,7 @@ - - - - + diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 283fe037d..b19503cdd 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -39,7 +39,7 @@ Zotero.CollectionTreeView = function() this._treebox = null; this.itemToSelect = null; this._highlightedRows = {}; - this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']); + this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'commons']); this.showDuplicates = false; } @@ -194,7 +194,7 @@ Zotero.CollectionTreeView.prototype.refresh = function() var groups = Zotero.Groups.getAll(); if (groups.length) { - this._showItem(new Zotero.ItemGroup('separator', false)); + this._showItem(new Zotero.ItemGroup('separator')); var header = { id: "group-libraries-header", label: "Group Libraries", // TODO: localize @@ -234,30 +234,15 @@ Zotero.CollectionTreeView.prototype.refresh = function() var shares = Zotero.Zeroconf.instances; if (shares.length) { - this._showItem(new Zotero.ItemGroup('separator', false)); + this._showItem(new Zotero.ItemGroup('separator')); for each(var share in shares) { this._showItem(new Zotero.ItemGroup('share', share)); } } - var buckets = Zotero.Commons.buckets; - if(buckets) { - this._showItem(new Zotero.ItemGroup('separator', false)); - var header = { - id: "commons-header", - label: "Commons", // TODO: localize - expand: function (buckets) { - if (!buckets) { - var buckets = Zotero.Commons.buckets; - } - - for(var i = 0, len = buckets.length; i < len; i++) { - self._showItem(new Zotero.ItemGroup('bucket', buckets[i]), 1); - } - } - }; - this._showItem(new Zotero.ItemGroup('header', header), null, null, true); - header.expand(buckets); + if (Zotero.Commons.enabled) { + this._showItem(new Zotero.ItemGroup('separator')); + this._showItem(new Zotero.ItemGroup('commons'), null, null, true); } this._refreshHashMap(); @@ -278,7 +263,11 @@ Zotero.CollectionTreeView.prototype.reload = function() for (var i=0; i 3) { + alert("Upload failed"); + return; + } + + id = Zotero.Commons.getNewBucketName(); + Zotero.Commons.createAuthenticatedRequest( + "PUT", "/" + id, headers, this.accessKey, this.secretKey, + function (req) { + requestCallback(req, id, tries); + } + ); + } else { - alert("Bucket creation failed: server error " + req.status); + Zotero.debug(req.status); + Zotero.debug(req.responseText); + + alert("Upload failed"); } } - } + }; - var req = this.createAuthenticatedRequest( - "PUT", "/" + bucketName, headers, accessKey, secretKey, callback + var id = Zotero.Commons.getNewBucketName(); + Zotero.Commons.createAuthenticatedRequest( + "PUT", "/" + id, headers, this.accessKey, this.secretKey, + function (req) { + requestCallback(req, id, 1); + } ); } - - this.syncBucketList = function () { + + + this.syncBucketList = function (callback) { var accessKey = Zotero.Prefs.get("commons.accessKey"); var secretKey = Zotero.Prefs.get("commons.secretKey"); - - // get list of buckets from IA - var callback = function (req) { + + if (_bucketsLoading) { + Zotero.debug("Already loading buckets"); + return; + } + + _bucketsLoading = true; + + var syncCallback = function (req) { // Error if (req.status != 200) { Zotero.debug(req.status); Zotero.debug(req.responseText); if (req.status == 503) { - alert("Bucket list sync failed: server unavailable."); + alert("Unable to retrieve items list from the Internet Archive: server unavailable."); } else { - alert("Bucket list sync failed: server error " + req.status); + alert("Unable to retrieve items list from the Internet Archive: server error " + req.status); } + _bucketsLoading = false; + return; } Zotero.debug(req.responseText); - var zu = new Zotero.Utilities; - var prompt = Components.classes["@mozilla.org/network/default-prompt;1"] - .getService(Components.interfaces.nsIPrompt); + var currentBuckets = []; + var IABuckets = []; - var prefChanged = false; - var prefBuckets = Zotero.Prefs.get("commons.buckets"); - var prefBucketNames = prefBuckets ? prefBuckets.split(',').sort() : []; + for (var name in _buckets) { + currentBuckets.push(name); + } + currentBuckets.sort(); + + Zotero.debug('=========='); + Zotero.debug("CURRENT BUCKETS"); + Zotero.debug(currentBuckets); - // Remove any duplicate buckets that got into the pref somehow - var len = prefBucketNames.length; - var hash = {}; - for each(var val in prefBucketNames) { - hash[val] = true; - } - for (var key in hash) { - prefBucketNames.push(key); - } - if (prefBucketNames.length != len) { - prefChanged = true; - } - var newPrefBucketNames = []; - var iaBucketNames = []; var buckets = req.responseXML.getElementsByTagName("Bucket"); - for(var i = 0, len = buckets.length; i < len; i++) { + for (var i=0, len=buckets.length; i.ZIP file - var items = Zotero.Items.get(ids); - - for(var i = 0, len = items.length; i < len; i++) { - var item = items[i]; - var zipName = item.key + ".zip"; - - // since cascade delete is enabled, delete the ZIP and derived files should follow. - // this does not, however, delete the RDF file. - var resource = '/' + self.name + '/' + zipName; - Zotero.debug("Commons: Deleting: " + resource); - - // Delete IA items - var callback = function (req) { - if (req.status == 204) { - Zotero.debug("Commons: " + resource + " was deleted successfully."); - this._needRefresh = true; - Zotero.Notifier.trigger('refresh', 'bucket', ids); - - //respecify metadata - self.updateMetadata(item.key,"delete",null); - } - else { - Zotero.debug(req.status); - Zotero.debug(req.responseText); - - if (req.status == 403) { - alert("Failed to delete " + resource + " at IA: authentication failed."); - } - else if (req.status == 503) { - alert("Failed to delete " + resource + " at IA: server unavailable."); - } - else { - alert("Failed to delete " + resource + " at IA."); - Zotero.debug("Commons: delete failed with status code: " + req.status); - } - } - }; - - Zotero.Commons.createAuthenticatedRequest( - method, resource, headers, self.accessKey, self.secretKey, callback - ); - - // Delete Zotero RDF file - zipName = item.key + ".rdf"; - resource = '/' + self.name + '/' + zipName; - Zotero.debug("Commons: Deleting: " + resource); - - Zotero.Commons.createAuthenticatedRequest( - method, resource, headers, self.accessKey, self.secretKey - ); +Zotero.Commons.Bucket.prototype.exists = function (callback, maxTries, tries) { + if (!tries) { + tries = 0; } + + var self = this; + + Zotero.Utilities.HTTP.doHead(this.uri, function (xmlhttp) { + switch (xmlhttp.status) { + case 200: + callback(1); + return; + + case 404: + case 503: // IA returns this for missing buckets + tries++; + + if (tries >= maxTries) { + callback(0); + return; + } + + var delay = Zotero.Commons.postCreateBucketDelay * tries; + var seconds = delay / 1000; + Zotero.debug("Bucket " + self.name + " doesn't yet exist -- retrying in " + seconds + " seconds"); + + Zotero.debug('---------'); + Zotero.debug(setTimeout); + + setTimeout(function () { + self.exists(callback, maxTries, tries); + }, delay); + return; + + default: + Zotero.debug(xmlhttp.status); + Zotero.debug(xmlhttp.responseText); + callback(-1); + return; + } + }); } -Zotero.Commons.Bucket.prototype.updateMetadata = function(key, action, data) { - Zotero.debug("updating metadata..."); - var method = "PUT"; - self = this; +/** + * Get OCRed PDFs from files in this bucket + * + * @param {Function} callback Function to run after adding each file -- + * function is passed the new attachment item + * (which due to a Zotero.Attachments.importFromURL() + * limitation will still be downloading) + */ +Zotero.Commons.Bucket.prototype.syncFiles = function (callback) { + // Find local item if it exists + var rels = Zotero.Relations.getByURIs(null, this.relationPredicate, this.uri); + if (!rels.length) { + Zotero.debug("No local items linked to URI " + this.uri); + return; + } + if (rels.length > 1) { + throw ("Commons: More than one local item linked to remote bucket " + this.name); + } + var item = Zotero.URI.getURIItem(rels[0].subject); + if (!item) { + Zotero.debug("Linked local item not found for URI " + this.uri, 2); + return; + } - var headers2 = { + // Get array of possible attachment names + var pdfFileNames = []; + var pdfTitles = []; + var ids = item.getAttachments(); + for each(var id in ids) { + var attachment = Zotero.Items.get(id); + var fileName = attachment.getFilename(); + if (!fileName || !fileName.match(/.+\.pdf$/)) { + continue; + } + pdfFileNames.push(fileName); + pdfTitles.push(attachment.getField('title')); + } + + if (this._requestingItems) { + throw ("Commons: Already requesting items for bucket"); + } + + this._requestingItems = true; + + var self = this; + + // Check for a full-text ("derived" in IA terms) OCRed version of the PDF, + // and if found add it as an attachment to the corresponding Zotero client item + Zotero.Utilities.HTTP.doGet(this.apiURI, function(xmlhttp) { + if (xmlhttp.status != 200) { + Zotero.debug(xmlhttp.status); + Zotero.debug(xmlhttp.responseText); + alert("Error loading data from the Internet Archive"); + self._requestingItems = false; + return; + } + + Zotero.debug(xmlhttp.responseText); + + // TODO: replace original PDF? + + var contents = xmlhttp.responseXML.getElementsByTagName("Contents"); + + // loop through files listed in bucket contents file + for(var i=0, len=contents.length; i'); - meta = "x-archive-meta-" + zitemp[0].substr(1); - Zotero.debug("Commons: found old zotero key: " + meta + " = " + zitemp[1]); - - // if action is delete, don't add - if (action != "delete" && meta.substr(9).toUpperCase() != key) - headers2[meta] = zitemp[1]; - } - - // adding headers in this way allows for easy scraping from zotero commons pages. - if (action == "add") { - meta = "x-archive-meta-" + "zoterokey" + key; - headers2[meta] = data.items[0].getField('title')+"|"+key+".ZIP"; - } - - Zotero.debug(headers2); - resource2 = '/' + self.name; - - var callback = function (req) { + var updateCallback = function (req) { + Zotero.debug('========'); + Zotero.debug("UPDATE"); + Zotero.debug(req.status); if(req.status < 202) { - Zotero.debug("Commons: " + resource2 + " metadata updated successfully."); - // if adding item, upload file - data.bucket.putKeyCallback(data); + Zotero.debug("Commons: " + resource + " metadata updated successfully."); + + if (callback) { + callback(); + } } else { Zotero.debug(req.status); @@ -474,286 +1018,25 @@ Zotero.Commons.Bucket.prototype.updateMetadata = function(key, action, data) { }; Zotero.Commons.createAuthenticatedRequest( - method, resource2, headers2, self.accessKey, self.secretKey, callback + method, resource, headers, self.accessKey, self.secretKey, updateCallback ); }); } -// return an array of items currently stored in this bucket -Zotero.Commons.Bucket.prototype.getItems = function() { - var method = "GET"; - var resource = '/' + this.name; - - if(this._items && !this._needRefresh) { - Zotero.debug("Commons: items already set. Returing existing items set"); - return this._items; - } - else { - Zotero.debug("Commons: items need refresh. re-getting..."); - } - - // avoid multiple requests to IA - if(this._requestingItems) { - Zotero.debug("Commons: already requesting items"); - return []; - } - - this._requestingItems = true; - var self = this; - self._items = []; - - // get a list of keys (files) associated with this bucket - var req = Zotero.Utilities.HTTP.doGet(Zotero.Commons.apiUrl + resource, function(xmlhttp) { - if (xmlhttp.status != 200) { - Zotero.debug(xmlhttp.status); - Zotero.debug(xmlhttp.responseText); - alert("Error loading data from the Internet Archive"); - self._requestingItems = false; - return; - } - - Zotero.debug(xmlhttp.responseText); - - var itemIDs = []; - - // While looking for Zotero exported items in the bucket, - // check for a full-text ("derived" in IA terms) OCR'd version of the PDF. - // If so, get it and add it as an attachment to the corresponding Zotero client item. - - // TODO: replace original PDF? - - var contents = null; - contents = xmlhttp.responseXML.getElementsByTagName("Contents"); - - // loop through files listed in bucket contents file - for(var i = 0, len = contents.length; i < len; i++) { - var keyParts = contents[i].getElementsByTagName('Key')[0].textContent.split('.'); - - // if key file is Zotero ZIP export item - // TODO: check to see if really a zotero item, not just a IA zip - if(keyParts.length == 2 && keyParts[1] == 'zip') { - var key = keyParts[0]; - Zotero.debug("Commons: found key in IA response: " + key); - - // see if the ZIP item corresponds to a zotero item (ZIP name = item key) - // This of course only works for the creator of the bucket. - // Others will get Zotero items from the IA bucket via a translator. - var item = Zotero.Items.getByLibraryAndKey(null, key); - - if(item) { - Zotero.debug("Commons: found item:" + item.id); - itemIDs.push(item.id); - this._needRefresh = false; - - // loop through attachments of this item and look for missing OCR'd PDFs - var attachmentIDs = item.getAttachments(); - for(var j = 0, len2 = attachmentIDs.length; j < len2; j++) { - var attachedItem = Zotero.Items.get(attachmentIDs[j]); - var fileName = attachedItem.getFilename(); - - // Since we have to upload all files without spaces in the name (or they won't be OCR'd), - // we need to look for the hyphenated version of actual attachment name. - // A space next to a hyphen should be deleted, not repleaced with another hyphen - if (fileName && fileName.substr(fileName.length-9) != "_text.pdf") { - var haveOCRVersion = false; - var IAfileName = fileName.substr(0,fileName.length-4).replace(/ /g,'-') + "_text.pdf"; - IAfileName = IAfileName.replace(/-+/g,'-'); - Zotero.debug("Commons: OCR'd file for this attachment would be: " + IAfileName); - - // check to see if we already have the OCR'd PDF attached to the zotero item - for(var k = 0, len3 = attachmentIDs.length; k < len3; k++) { - var attachedItem = Zotero.Items.get(attachmentIDs[k]); - if (attachedItem.getFilename() == IAfileName) - haveOCRVersion = true; - } - - // if we need to get the OCR version... - if (!haveOCRVersion) { - - // set up new attachment - var attachmentUri = "http://s3.us.archive.org/"+self.name+"/" + IAfileName; - var mimeType = "application/pdf"; - - // scan bucket contents to see if the OCR'd PDF is available - for(var con = 0, len = contents.length; con < len; con++) { - var keys = contents[con].getElementsByTagName("Key"); - - for(var l = 0, len4 = keys.length; l < len4; l++) { - if (keys[l].textContent == IAfileName) { - Zotero.debug("Commons: about to get OCR file from: " + attachmentUri); - Zotero.Attachments.importFromURL(attachmentUri, item.id, null, null, null, mimeType, null); - } - else { - //Zotero.debug("Commons: no OCR'd PDF of this attachment is available."); - } - } // for each key - } // for each contents - } // end if need to get PDF - else { - Zotero.debug("Commons: do not need OCR'd PDF for attachment " + fileName); - } - } - } - - self._items.push(item); - } - } - } - - Zotero.Notifier.trigger('refresh', 'bucket', itemIDs); - - self._requestingItems = false; - }); - - // Browser offline - if (!req) { - self._requestingItems = false; - } - - // List isn't yet available - return []; -} - - -// upload zipped Zotero RDF output of items to this bucket -Zotero.Commons.Bucket.prototype.uploadItems = function(ids) { - this._items = null; - var items = Zotero.Items.get(ids); - - if (!items) { - Zotero.debug("No items to upload"); - return; - } - - var itemsToUpload = false; - for (var i=0, len=items.length; i/, '')); + var title = xml.title; + if (!self._item) { + self._item = new Zotero.Item("document"); + self._item.id = Zotero.ID.getBigInt(); + } + self._item.setField('title', title); + + self._metadataLoading = false; + + if (callback) { + callback(); + } + });*/ + return; + } + + Zotero.debug(xmlhttp.responseText); + + var translate = new Zotero.Translate("import"); + translate.setString(xmlhttp.responseText); + translate.getTranslators() + translate.setTranslator(Zotero.Commons.RDF_IMPORT_TRANSLATOR.translatorID); + translate.setHandler("itemDone", function (translation, item) { + var typeID = Zotero.ItemTypes.getID(item.itemType); + var newItem = new Zotero.Item(typeID); + newItem.id = Zotero.ID.getBigInt(); + + for (var field in item) { + // Skip empty fields + if (!item[field]) { + continue; + } + var fieldID = Zotero.ItemFields.getID(field); + if (!fieldID) { + continue; + } + Zotero.debug('setting field ' + fieldID + ' to ' + item[field]); + try { + newItem.setField(fieldID, item[field]); + } + catch(e) { Zotero.debug(e); } + } + + self._item = newItem; + self._metadataLoading = false; + + if (callback) { + callback(); + } + return; + }); + translate.translate(false, false); + }); +} + + // Implements nsIRequestObserver -Zotero.Commons.ZipWriterObserver = function (zipWriter, callback, callbackData) { +Zotero.Commons.ZipWriterObserver = function (zipWriter, callback) { this._zipWriter = zipWriter; this._callback = callback; - this._callbackData = callbackData; } Zotero.Commons.ZipWriterObserver.prototype = { @@ -856,7 +1203,7 @@ Zotero.Commons.ZipWriterObserver.prototype = { onStopRequest: function(req, context, status) { this._zipWriter.close(); - this._callback(this._callbackData); + this._callback(); } } diff --git a/chrome/content/zotero/xpcom/data/group.js b/chrome/content/zotero/xpcom/data/group.js index f1f3815ec..7a06a88ed 100644 --- a/chrome/content/zotero/xpcom/data/group.js +++ b/chrome/content/zotero/xpcom/data/group.js @@ -338,7 +338,7 @@ Zotero.Group.prototype.erase = function() { Zotero.DB.query(sql, this.libraryID); var prefix = "groups/" + this.id; - Zotero.Relations.eraseByPathPrefix(prefix); + Zotero.Relations.eraseByURIPrefix(Zotero.URI.defaultPrefix + prefix); // Delete group sql = "DELETE FROM groups WHERE groupID=?"; diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index af05f64df..1c57189b1 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -741,7 +741,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) { } if (!Zotero.ItemFields.isValidForType(fieldID, this.itemTypeID)) { - var msg = '"' + field + "' is not a valid field for type " + this.itemTypeID; + var msg = "'" + field + "' is not a valid field for type " + this.itemTypeID; if (loadIn) { Zotero.debug(msg + " -- ignoring value '" + value + "'", 2); @@ -1240,7 +1240,7 @@ Zotero.Item.prototype.save = function() { if (Zotero.ItemFields.getID('accessDate') == fieldID && this.getField(fieldID) == 'CURRENT_TIMESTAMP') { - value = Zotero.DB.transactionDateTime; + value = Zotero.DB.transactionDateTime; } var dataType = ZU.getSQLDataType(value); @@ -3944,7 +3944,10 @@ Zotero.Item.prototype.erase = function() { //Zotero.Fulltext.clearItemContent(this.id); } - + // Remove relations + var relation = Zotero.URI.getItemURI(this); + Zotero.Relations.eraseByURIPrefix(relation); + 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); diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js index 3e2d1c288..febeafa97 100644 --- a/chrome/content/zotero/xpcom/data/relations.js +++ b/chrome/content/zotero/xpcom/data/relations.js @@ -31,8 +31,6 @@ Zotero.Relations = new function () { owl: 'http://www.w3.org/2002/07/owl#' }; - var _prefix = "http://zotero.org/"; - this.get = function (id) { if (typeof id != 'number') { throw ("id '" + id + "' must be an integer in Zotero.Relations.get()"); @@ -165,13 +163,40 @@ Zotero.Relations = new function () { } - this.eraseByPathPrefix = function (prefix) { - prefix = _prefix + prefix + '%'; + this.eraseByURIPrefix = function (prefix) { + prefix = prefix + '%'; sql = "DELETE FROM relations WHERE subject LIKE ? OR object LIKE ?"; Zotero.DB.query(sql, [prefix, prefix]); } + this.eraseByURI = function (uri) { + sql = "DELETE FROM relations WHERE subject=? OR object=?"; + Zotero.DB.query(sql, [uri, uri]); + } + + + this.purge = function () { + var sql = "SELECT subject FROM relations UNION SELECT object FROM relations"; + var uris = Zotero.DB.columnQuery(sql); + 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 (!Zotero.URI.getURIItem(uri)) { + this.eraseByURI(uri); + } + } + Zotero.DB.commitTransaction(); + } + } + + this.xmlToRelation = function (xml) { var relation = new Zotero.Relation; var libraryID = xml.@libraryID.toString(); @@ -196,7 +221,7 @@ Zotero.Relations = new function () { var [prefix, value] = uri.split(':'); if (prefix && value) { if (!_namespaces[prefix]) { - throw ("Invalid prefix '" + prefix + "' in Zotero.Relations.add()"); + throw ("Invalid prefix '" + prefix + "' in Zotero.Relations._getPrefixAndValue()"); } return [prefix, value]; } diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index d8f1889ef..74234f673 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -137,6 +137,21 @@ Zotero.File = new function(){ } + + /** + * Return the contents of a file as a byte array + */ + this.getBinaryContents = function (bfile) { + var istream = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.nsIFileInputStream); + istream.init(bfile, -1, -1, false); + var bstream = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + bstream.setInputStream(istream); + return bstream.readBytes(bstream.available()); + } + + /* * Return the contents of a URL as a string * diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js index 2ae1f4abd..a8c16facb 100644 --- a/chrome/content/zotero/xpcom/itemTreeView.js +++ b/chrome/content/zotero/xpcom/itemTreeView.js @@ -52,7 +52,7 @@ Zotero.ItemTreeView = function(itemGroup, sourcesOnly) this._dataItems = []; this.rowCount = 0; - this._unregisterID = Zotero.Notifier.registerObserver(this, ['item', 'collection-item', 'share-items', 'bucket']); + this._unregisterID = Zotero.Notifier.registerObserver(this, ['item', 'collection-item', 'share-items', 'commons']); } @@ -260,6 +260,7 @@ Zotero.ItemTreeView.prototype.refresh = function() */ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) { + Zotero.debug('============='); if (!this._treebox || !this._treebox.treeBody) { Components.utils.reportError("Treebox didn't exist in itemTreeView.notify()"); return; @@ -269,7 +270,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) Zotero.debug("Item row map didn't exist in itemTreeView.notify()"); return; } - + Zotero.debug(1); var itemGroup = this._itemGroup; var madeChanges = false; @@ -284,9 +285,10 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) this.refresh(); } } - else if (type == 'bucket') { - if (itemGroup.isBucket()) { + else if (type == 'commons') { + if (itemGroup.isCommons()) { this.refresh(); + this.sort(); } } else if (savedSelection.length == 1 && savedSelection[0] == ids[0]) { @@ -455,6 +457,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) } else if(action == 'add') { + Zotero.debug(2); // If saved search or trash, just re-run search if (itemGroup.isSearch() || itemGroup.isTrash()) { this.refresh(); @@ -499,6 +502,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) if(madeChanges) { + Zotero.debug(3); // If adding and this is the active window, select the item if(action == 'add' && ids.length===1 && activeWindow) { @@ -511,7 +515,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) // Reset to Info tab this._ownerDocument.getElementById('zotero-view-tabbox').selectedIndex = 0; - + Zotero.debug(4); this.selectItem(ids[0]); } // If single item is selected and was modified @@ -540,6 +544,7 @@ Zotero.ItemTreeView.prototype.notify = function(action, type, ids, extraData) } else { + Zotero.debug(4); var previousRow = this._itemRowMap[ids[0]]; if (sort) { @@ -1112,6 +1117,8 @@ Zotero.ItemTreeView.prototype.sort = function(itemID) */ Zotero.ItemTreeView.prototype.selectItem = function(id, expand, noRecurse) { + Zotero.debug('============='); + Zotero.debug(Zotero.suppressUIUpdates); // Don't change selection if UI updates are disabled (e.g., during sync) if (Zotero.suppressUIUpdates) { return; @@ -1267,8 +1274,8 @@ Zotero.ItemTreeView.prototype.deleteSelection = function (force) else if (itemGroup.isCollection()) { itemGroup.ref.removeItems(ids); } - else if (itemGroup.isBucket()) { - itemGroup.ref.deleteItems(ids); + else if (itemGroup.isCommons()) { + Zotero.Commons.deleteItems(ids); } this._treebox.endUpdateBatch(); } diff --git a/chrome/content/zotero/xpcom/uri.js b/chrome/content/zotero/xpcom/uri.js index c5590e1c7..6169c174d 100644 --- a/chrome/content/zotero/xpcom/uri.js +++ b/chrome/content/zotero/xpcom/uri.js @@ -25,6 +25,8 @@ Zotero.URI = new function () { + this.__defineGetter__('defaultPrefix', function () 'http://zotero.org/'); + var _baseURI = ZOTERO_CONFIG.BASE_URI; var _apiURI = ZOTERO_CONFIG.API_URI; @@ -166,12 +168,12 @@ Zotero.URI = new function () { if (itemURI.indexOf(localUserURI) == 0) { itemURI = itemURI.substr(localUserURI.length); var libraryType = 'user'; - var libraryTypeID = null; + var libraryID = null; } } */ var libraryType = 'user'; - var libraryTypeID = null; + var libraryID = null; } // If not found, try global URI @@ -186,7 +188,7 @@ Zotero.URI = new function () { throw ("Invalid library URI '" + itemURI + "' in Zotero.URI.getURIItem()"); } var libraryType = matches[1].substr(0, matches[1].length-1); - var libraryTypeID = matches[2]; + var libraryID = matches[2]; itemURI = itemURI.replace(typeRE, ''); } @@ -202,7 +204,10 @@ Zotero.URI = new function () { } if (libraryType == 'group') { - var libraryID = Zotero.Groups.getLibraryIDFromGroupID(libraryTypeID); + if (!Zotero.Libraries.exists(libraryID)) { + return false; + } + var libraryID = Zotero.Groups.getLibraryIDFromGroupID(libraryID); return Zotero.Items.getByLibraryAndKey(libraryID, itemKey); } } diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 9da1fe6b4..a0e921cab 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1033,6 +1033,8 @@ var Zotero = new function(){ * Returns true if an object (or associative array) has at least one value */ function hasValues(obj) { + Zotero.debug("WARNING: Zotero.isEmpty() is deprecated! Use Zotero.Utilities.isEmpty(obj)", 2); + for (var i in obj) { return true; } @@ -1239,6 +1241,8 @@ var Zotero = new function(){ Zotero.Tags.purge(); Zotero.Fulltext.purgeUnusedWords(); Zotero.Items.purge(); + // DEBUG: this might not need to be permanent + Zotero.Relations.purge(); if (!skipStoragePurge && Zotero.Utilities.prototype.probability(10)) { Zotero.Sync.Storage.purgeDeletedStorageFiles('zfs'); diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js index 899c60697..72e35783d 100644 --- a/defaults/preferences/zotero.js +++ b/defaults/preferences/zotero.js @@ -110,7 +110,6 @@ pref("extensions.zotero.zeroconf.server.enabled", false); // Zotero Commons pref("extensions.zotero.commons.enabled", false); -pref("extensions.zotero.commons.buckets", ''); pref("extensions.zotero.commons.accessKey", ''); pref("extensions.zotero.commons.secretKey", '');