commit 233b51d3e12667ae31359ae298d28277e2734de1
parent ac79b1d05f4551898f68415507c3d4b5d14ce982
Author: Ben Parr <cheeseisgood626@gmail.com>
Date: Thu, 13 Aug 2009 23:08:05 +0000
Fixes #1541, shows "Remove Bucket from List" option (can't actually delete a bucket in IA)
Also adds "Create Bucket", and "Sync Bucket List with IA" features, available by right clicking the "Commons" header.
Diffstat:
4 files changed, 249 insertions(+), 53 deletions(-)
diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js
@@ -1263,6 +1263,33 @@ var ZoteroPane = new function()
Zotero.Items.emptyTrash();
}
}
+
+ this.createBucket = 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.removeBucket = 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()
@@ -1551,7 +1578,10 @@ var ZoteroPane = new function()
createBibCollection: 8,
exportFile: 9,
loadReport: 10,
- emptyTrash: 11
+ emptyTrash: 11,
+ createBucket: 12,
+ syncBucketList: 13,
+ removeBucket: 14
};
var itemGroup = this.collectionsView._getItemAtRow(this.collectionsView.selection.currentIndex);
@@ -1621,7 +1651,12 @@ var ZoteroPane = new function()
}
// Header
else if (itemGroup.isHeader()) {
-
+ if (itemGroup.ref.id == 'commons-header') {
+ show = [m.createBucket, m.syncBucketList];
+ }
+ }
+ else if (itemGroup.isBucket()) {
+ show = [m.removeBucket];
}
// Group
else if (itemGroup.isGroup()) {
diff --git a/chrome/content/zotero/overlay.xul b/chrome/content/zotero/overlay.xul
@@ -102,6 +102,9 @@
<menuitem label="&zotero.toolbar.export.label;" oncommand="Zotero_File_Interface.exportFile()"/>
<menuitem oncommand="Zotero_Report_Interface.loadCollectionReport()"/>
<menuitem label="&zotero.toolbar.emptyTrash.label;" oncommand="ZoteroPane.emptyTrash();"/>
+ <menuitem label="Create Bucket" oncommand="ZoteroPane.createBucket();"/><!--TODO localize -->
+ <menuitem label="Sync Bucket List with IA" oncommand="Zotero.Commons.syncBucketList();"/><!--TODO localize -->
+ <menuitem label="Remove Bucket from List" oncommand="ZoteroPane.removeBucket();"/><!--TODO localize -->
</popup>
<popup id="zotero-itemmenu" onpopupshowing="ZoteroPane.buildItemContextMenu();">
<menuitem label="&zotero.items.menu.showInLibrary;" oncommand="ZoteroPane.selectItem(this.parentNode.getAttribute('itemID'), true)"/>
@@ -527,4 +530,4 @@
oncommand="ZoteroPane.toggleDisplay();"
modifiers="accel alt" />
</keyset>
-</overlay>
-\ No newline at end of file
+</overlay>
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -36,7 +36,7 @@ Zotero.CollectionTreeView = function()
this._treebox = null;
this.itemToSelect = null;
this._highlightedRows = {};
- this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group']);
+ this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'bucket']);
this.showDuplicates = false;
}
@@ -226,7 +226,7 @@ Zotero.CollectionTreeView.prototype.refresh = function()
}
var buckets = Zotero.Commons.buckets;
- if(buckets.length) {
+ if(buckets) {
this._showItem(new Zotero.ItemGroup('separator', false));
var header = {
id: "commons-header",
@@ -410,6 +410,9 @@ Zotero.CollectionTreeView.prototype.notify = function(action, type, ids)
// Groups can only be created during sync
this.rememberSelection(savedSelection);
break;
+
+ case 'bucket':
+ this.reload();
}
}
diff --git a/chrome/content/zotero/xpcom/commons.js b/chrome/content/zotero/xpcom/commons.js
@@ -20,13 +20,20 @@
***** END LICENSE BLOCK *****
*/
+//TODO localize
Zotero.Commons = new function() {
+ this.createBucket = createBucket;
+ this.syncBucketList = syncBucketList;
+ this.removeBucket = removeBucket;
+ this._createAuthenticatedRequest = _createAuthenticatedRequest;
+ this._createUnauthenticatedRequest = _createUnauthenticatedRequest;
this.uri = 'http://www.archive.org/';
+ this.apiUrl = 'http://s3.us.archive.org';
this.__defineGetter__('buckets', function () {
if(!Zotero.Prefs.get("commons.enabled")) {
- return [];
+ return false;
}
var accessKey = Zotero.Prefs.get("commons.accessKey");
@@ -40,6 +47,190 @@ Zotero.Commons = new function() {
}
return buckets;
});
+
+ function createBucket(bucketName) {
+ var accessKey = Zotero.Prefs.get("commons.accessKey");
+ var secretKey = Zotero.Prefs.get("commons.secretKey");
+
+ var req = this._createAuthenticatedRequest(
+ "PUT", "/" + bucketName, {}, accessKey, secretKey
+ );
+
+ req.onreadystatechange = function() {
+ if(req.readyState == 4) {
+ if(req.status < 400) {
+ // add bucketName to preference if isn't already there
+ var prefBucketNames = Zotero.Prefs.get("commons.buckets").split(',');
+ if(!Zotero.inArray(bucketName, prefBucketNames)) {
+ prefBucketNames.push(bucketName);
+ prefBucketNames.sort();
+ Zotero.Prefs.set("commons.buckets", prefBucketNames.join(','));
+ Zotero.Notifier.trigger('add', 'bucket', true);
+ }
+ }
+ else if(req.status == 403) {
+ alert("Bucket creation failed: authentication failed.");
+ }
+ else if(req.status == 409) {
+ alert("Bucket creation failed: bucket name already taken.");
+ }
+ else if(req.status == 503) {
+ alert("Bucket creation failed: server unavailable.");
+ }
+ else {
+ alert("Bucket creation failed: server error " + req.status);
+ }
+ }
+ }
+
+ req.send(null);
+ }
+
+ function syncBucketList() {
+ var accessKey = Zotero.Prefs.get("commons.accessKey");
+ var secretKey = Zotero.Prefs.get("commons.secretKey");
+
+ // get list of buckets from IA
+ var req = this._createAuthenticatedRequest(
+ "GET", "/", {}, accessKey, secretKey
+ );
+
+ req.onreadystatechange = function() {
+ if (req.readyState == 4) {
+ if(req.status < 400) {
+ var zu = new Zotero.Utilities;
+ var prompt = Components.classes["@mozilla.org/network/default-prompt;1"]
+ .getService(Components.interfaces.nsIPrompt);
+
+ var prefChanged = false;
+ var prefBuckets = Zotero.Prefs.get("commons.buckets");
+ var prefBucketNames = (prefBuckets) ? prefBuckets.split(',').sort() : [];
+ var newPrefBucketNames = [];
+ var iaBucketNames = [];
+ var buckets = req.responseXML.getElementsByTagName("Bucket");
+ for(var i = 0, len = buckets.length; i < len; i++) {
+ var bucketName = buckets[i].getElementsByTagName('Name')[0].textContent;
+ iaBucketNames.push(bucketName);
+ if(Zotero.inArray(bucketName, prefBucketNames)) {
+ newPrefBucketNames.push(bucketName);
+ }
+ }
+ iaBucketNames.sort();
+
+ // newPrefBucketNames currently contains intersection
+ // of prefBucketNames and iaBucketNames
+ var askToAddBuckets = zu.arrayDiff(newPrefBucketNames, iaBucketNames);
+ var askToRemoveBuckets = zu.arrayDiff(newPrefBucketNames, prefBucketNames);
+
+ // prompt user about adding buckets
+ for(var i = 0, len = askToAddBuckets.length; i < len; i++) {
+ var result = prompt.confirm("", "'" + askToAddBuckets[i] + "' is associated with "
+ + "your IA account, but is not in the Zotero list of buckets\n\n"
+ + "Add bucket '" + askToAddBuckets[i] + "'?");
+ if (result) {
+ newPrefBucketNames.push(askToAddBuckets[i]);
+ prefChanged = true;
+ }
+ }
+
+ // prompt user about removing buckets
+ for(var i = 0, len = askToRemoveBuckets.length; i < len; i++) {
+ var result = prompt.confirm("", "'" + askToRemoveBuckets[i] + "' is in your "
+ + "Zotero list of buckets, but is not associated with your IA account\n\n"
+ + "Remove bucket '" + askToRemoveBuckets[i] + "'?");
+ if (result) {
+ prefChanged = true;
+ }
+ else {
+ newPrefBucketNames.push(askToRemoveBuckets[i]);
+ }
+ }
+
+ newPrefBucketNames.sort();
+ Zotero.Prefs.set("commons.buckets", newPrefBucketNames.join(','));
+
+ // refresh left pane if local bucket list changed
+ if(prefChanged) {
+ Zotero.Notifier.trigger('add', 'bucket', true);
+ }
+
+ // give user feedback if no difference between lists
+ // (don't leave user wondering if nothing happened)
+ if(askToAddBuckets.length == 0 && askToRemoveBuckets.length == 0) {
+ alert("No differences between local bucket list and IA bucket list found.");
+ }
+ }
+ else if(req.status == 503) {
+ alert("Bucket list sync failed: server unavailable.");
+ }
+ else {
+ alert("Bucket list sync failed: server error " + req.status);
+ }
+ }
+ }
+
+ req.send(null);
+ }
+
+ // remove bucketName from preference, and refresh left pane in Zotero
+ function removeBucket(bucketName) {
+ var prefBucketNames = Zotero.Prefs.get("commons.buckets").split(',');
+ var newPrefBucketNames = [];
+ for(var i = 0, len = prefBucketNames.length; i < len; i++) {
+ if(bucketName != prefBucketNames[i]) {
+ newPrefBucketNames.push(prefBucketNames[i]);
+ }
+ }
+ newPrefBucketNames.sort();
+ Zotero.Prefs.set("commons.buckets", newPrefBucketNames.join(','));
+ Zotero.Notifier.trigger('add', 'bucket', true);
+ }
+
+ function _createAuthenticatedRequest(method, resource, headers, accessKey, secretKey) {
+ var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ req.open(method, Zotero.Commons.apiUrl + resource, true);
+
+ var d = new Date();
+ headers["Date"] = d.toUTCString();
+
+ var signatureData = method + '\n' +
+ ((headers['Content-MD5']) ? headers['Content-MD5'] : '') + '\n' +
+ ((headers['Content-Type']) ? headers['Content-Type'] : '') + '\n' +
+ ((headers['Date']) ? headers['Date'] : '') + '\n';
+
+ // add x-amz- headers in alphabetic order
+ var amz = [];
+ for(header in headers) {
+ if(header.indexOf("x-amz-") == 0) {
+ amz.push(header + ":" + headers[header] + '\n');
+ }
+ }
+ signatureData += amz.sort().join('');
+
+ signatureData += resource;
+ var signature = Zotero.Commons.SHA1.b64_hmac_sha1(secretKey, signatureData) + '=';
+ headers["Authorization"] = "AWS " + accessKey + ":" + signature;
+ //headers["Authorization"] = "LOW " + accessKey + ":" + secretKey;
+
+ for(var header in headers) {
+ req.setRequestHeader(header, headers[header]);
+ }
+
+ return req;
+ }
+
+ function _createUnauthenticatedRequest(method, resource, headers) {
+ var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ req.open(method, Zotero.Commons.apiUrl + resource, true);
+
+ for(var header in headers) {
+ req.setRequestHeader(header, headers[header]);
+ }
+
+ return req;
+ }
}
@@ -54,8 +245,6 @@ Zotero.Commons.Bucket = function(name, accessKey, secretKey) {
}
-Zotero.Commons.Bucket.prototype.apiUrl = 'http://s3.us.archive.org';
-
Zotero.Commons.Bucket.prototype.RDF_TRANSLATOR = {
'label': 'Zotero RDF',
'target': 'rdf',
@@ -92,11 +281,7 @@ Zotero.Commons.Bucket.prototype.getItems = function() {
this._requestingItems = true;
// get a list of keys associated with this bucket
- var method = "GET";
- var resource = '/' + this.name + '/';
- var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
- .createInstance(Components.interfaces.nsIXMLHttpRequest);
- req.open(method, this.apiUrl + resource, true);
+ var req = Zotero.Commons._createUnauthenticatedRequest("GET", '/' + this.name + '/', {});
//req.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
var self = this;
@@ -193,6 +378,7 @@ Zotero.Commons.Bucket.prototype._translateCallback = function(translation, succe
data.bucket._zipDirectory(data.bucket, dir, dir, zw);
data.uploadFile = zipFile;
+ data.mimetype = "application/zip";
// add observer so _putKey is called on zip completion
var observer = new Zotero.Commons.ZipWriterObserver(zw, data.bucket._putKey, data);
zw.processQueue(observer, null);
@@ -205,30 +391,20 @@ Zotero.Commons.Bucket.prototype._translateCallback = function(translation, succe
// Does the put call to IA, puting data.uploadFile into the bucket
Zotero.Commons.Bucket.prototype._putKey = function(data) {
var self = data.bucket;
- var method = "PUT";
- var mimeType = 'application/zip';
var key = data.uploadFile.leafName;
+ var method = "PUT";
var resource = '/' + self.name + '/' + key;
var content = self._readFileContents(data.uploadFile);
- var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
- .createInstance(Components.interfaces.nsIXMLHttpRequest);
- req.open(method, self.apiUrl + resource, true);
-
- var d = new Date();
- var headers = {};
- headers["Content-Type"] = mimeType;
- headers["Content-Length"] = content.length;
- headers["Date"] = d.toUTCString();
- headers["x-amz-meta-creator"] = "Zotero Commons";
-
- for(var header in headers) {
- req.setRequestHeader(header, headers[header]);
- }
+ var headers = {
+ "Content-Type": data.mimeType,
+ "Content-Length": content.length,
+ "x-amz-meta-creator": "Zotero Commons"
+ };
- var signature = self._generateSignature(method, resource, headers, self._secretKey);
- req.setRequestHeader("Authorization", "AWS " + self._accessKey + ":" + signature);
- //req.setRequestHeader("Authorization", "LOW " + self._accessKey + ":" + self._secretKey);
+ var req = Zotero.Commons._createAuthenticatedRequest(
+ method, resource, headers, self._accessKey, self._secretKey
+ );
req.onreadystatechange = function() {
if (req.readyState == 4) {
@@ -301,26 +477,6 @@ Zotero.Commons.Bucket.prototype._zipDirectory = function(self, rootDir, dir, zip
}
}
-Zotero.Commons.Bucket.prototype._generateSignature = function(method, resource, headers, secretKey) {
- var data = method + '\n' +
- ((headers['Content-MD5']) ? headers['Content-MD5'] : '') + '\n' +
- ((headers['Content-Type']) ? headers['Content-Type'] : '') + '\n' +
- ((headers['Date']) ? headers['Date'] : '') + '\n';
-
- // add x-amz- headers in alphabetic order
- var amz = [];
- for(header in headers) {
- if(header.indexOf("x-amz-") == 0) {
- amz.push(header + ":" + headers[header] + '\n');
- }
- }
- data += amz.sort().join('');
-
- data += resource;
-
- return Zotero.Commons.SHA1.b64_hmac_sha1(secretKey, data) + '=';
-}
-
// Implements nsIRequestObserver