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.
This commit is contained in:
Ben Parr 2009-08-13 23:08:05 +00:00
parent ac79b1d05f
commit 233b51d3e1
4 changed files with 249 additions and 52 deletions

View file

@ -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()) {

View file

@ -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>
</overlay>

View file

@ -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();
}
}

View file

@ -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 headers = {
"Content-Type": data.mimeType,
"Content-Length": content.length,
"x-amz-meta-creator": "Zotero Commons"
};
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 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