zotero/chrome/content/zotero/xpcom/commons.js

1421 lines
36 KiB
JavaScript

/*
***** 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 *****
*/
//TODO localize
Zotero.Commons = new function() {
this.uri = 'http://www.archive.org/';
this.apiUrl = 'http://s3.us.archive.org';
this.postCreateBucketDelay = 2000;
this.__defineGetter__('enabled', function () {
return Zotero.Prefs.get("commons.enabled");
});
this.__defineSetter__('enabled', function (val) {
return Zotero.Prefs.set("commons.enabled", !!val);
});
this.__defineGetter__('userIdentifier', function () {
return Zotero.Prefs.get("commons.accessKey");
});
this.__defineGetter__('accessKey', function () {
return Zotero.Prefs.get("commons.accessKey");
});
this.__defineSetter__('accessKey', function (val) {
return Zotero.Prefs.set("commons.accessKey", val);
});
this.__defineGetter__('secretKey', function () {
return Zotero.Prefs.get("commons.secretKey");
});
this.__defineSetter__('secretKey', function (val) {
// TODO: use login manager
return Zotero.Prefs.set("commons.secretKey", val);
});
this.__defineGetter__('userNameSlug', function () {
if (!_userNameSlug) {
throw ("Username not set in Zotero.Commons.userNameSlug getter");
}
return _userNameSlug;
});
this.RDF_TRANSLATOR = {
'label': 'Zotero RDF',
'target': 'rdf',
'translatorID': '14763d24-8ba0-45df-8f52-b8d1108e7ac9',
'displayOptions': {
'exportFileData': true,
'exportNotes': true
}
};
this.RDF_IMPORT_TRANSLATOR = {
'translatorID': '5e3ad958-ac79-463d-812b-a86a9235c28f',
}
this.ERROR_BUCKET_EXISTS = 1;
this.refreshNeeded = true;
var _userName;
var _userNameSlug;
var _buckets = {};
var _bucketsLoading = false;
var _bucketsLoaded = false;
var _requestingItems = false;
this.getBuckets = function (callback) {
if (!this.enabled) {
if (callback) {
callback(_buckets);
}
return;
}
var accessKey = this.accessKey;
var secretKey = this.secretKey;
if (_bucketsLoaded) {
if (callback) {
callback(_buckets);
}
return;
}
if (_bucketsLoading) {
Zotero.debug("Already loading buckets");
if (callback) {
callback(_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("Unable to retrieve bucket list from the Internet Archive: server unavailable.");
}
else {
alert("Unable to retrieve bucket list from the Internet Archive: server error " + req.status);
}
_bucketsLoading = false;
return;
}
Zotero.debug(req.responseText);
_userName = req.responseXML.getElementsByTagName('DisplayName')[0].textContent;
_userNameSlug = Zotero.Commons.slugify(_userName);
var currentBuckets = [];
var IABuckets = [];
for (var name in _buckets) {
currentBuckets.push(name);
}
currentBuckets.sort();
Zotero.debug('==========');
Zotero.debug("CURRENT BUCKETS");
Zotero.debug(currentBuckets);
var buckets = req.responseXML.getElementsByTagName("Bucket");
for (var i=0, len=buckets.length; i<len; i++) {
var bucketName = buckets[i].getElementsByTagName('Name')[0].textContent;
IABuckets.push(bucketName);
}
IABuckets.sort();
Zotero.debug("IA BUCKETS");
Zotero.debug(IABuckets);
var addBuckets = Zotero.Utilities.arrayDiff(IABuckets, currentBuckets);
var removeBuckets = Zotero.Utilities.arrayDiff(currentBuckets, IABuckets);
Zotero.debug("ADD");
Zotero.debug(addBuckets);
Zotero.debug("REMOVE");
Zotero.debug(removeBuckets);
for each(var name in removeBuckets) {
delete _buckets[name];
}
var ids = [];
var refresh = false;
for each(var name in addBuckets) {
refresh = true;
var bucket = new Zotero.Commons.Bucket(name);
_buckets[name] = bucket;
ids.push(bucket.id);
}
_bucketsLoading = false;
_bucketsLoaded = true;
// refresh left pane if local bucket list changed
if (refresh) {
Zotero.Notifier.trigger('add', 'bucket', ids);
}
if (callback) {
callback(_buckets);
}
};
this.createAuthenticatedRequest(
"GET", "/", {}, accessKey, secretKey, syncCallback, null, false, true
);
};
this.isValidBucketTitle = function (title) {
if (!title) {
return false;
}
if (title.constructor.name != 'String') {
return false;
}
return title.length <= 255;
}
this.isValidBucketName = function (name) {
if (!name) {
return false;
}
if (name.constructor.name != 'String') {
return false;
}
return name.match(/^[a-z0-9_-]{0,32}$/); // TODO: check IA pattern
}
this.createBucket = function (name, title, onBucketCreated) {
if (!_userName) {
throw new Exception("Username not set in Zotero.Commons.createBucket()");
}
name = this.userNameSlug + "-" + name;
var headers = {
"x-archive-auto-make-bucket":"1",
"x-archive-meta01-collection":"zoterocommons",
"x-archive-meta02-collection":"scholarworkspaces",
"x-archive-meta-sponsor":"Andrew W. Mellon Foundation"
};
if (!this.isValidBucketName(name)) {
throw ("Bucket name '" + name + "' must be ASCII in Zotero.Commons.createBucket()");
}
if (!title) {
title = name;
}
if (!this.isValidBucketTitle(title)) {
throw ("Invalid title '" + title + "' in Zotero.Commons.createBucket()");
}
headers["x-archive-meta-title"] = title;
headers["x-archive-meta-mediatype"] = "texts";
Zotero.Commons.createAuthenticatedRequest(
"PUT", "/" + name, headers, this.accessKey, this.secretKey,
function (req) {
Zotero.debug(req.status);
if (req.status == 201) {
var bucket = new Zotero.Commons.Bucket(name);
_buckets[name] = bucket;
Zotero.Notifier.trigger('add', 'bucket', [name]);
if (onBucketCreated) {
onBucketCreated();
}
}
else {
Zotero.debug(req.status);
Zotero.debug(req.responseText);
Zotero.Commons.error("Error creating bucket '" + name + "'");
}
}
);
}
this.createAuthenticatedRequest = function (method, resource, headers, accessKey, secretKey, callback, data, sendAsBinary, noCache) {
var apiURL = Zotero.Commons.apiUrl;
var url = apiURL + resource;
Zotero.debug("Commons HTTP " + method + ": " + url);
var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Components.interfaces.nsIXMLHttpRequest);
req.open(method, url, 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 += req.channel.URI.spec.substr(apiURL.length);
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]);
}
if (noCache) {
req.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
}
if (data) {
if (sendAsBinary) {
req.sendAsBinary(data);
}
else {
req.send(data);
}
}
else {
req.send(null);
}
if (callback) {
req.onreadystatechange = function() {
if (req.readyState == 4) {
callback(req);
}
};
}
}
// Recursively add files and directories to zipWriter
this.zipDirectory = function (rootDir, dir, zipWriter) {
dir = dir.directoryEntries;
while(dir.hasMoreElements()) {
var file = dir.getNext();
file.QueryInterface(Components.interfaces.nsILocalFile);
var fileName = file.getRelativeDescriptor(rootDir);
if(fileName.indexOf('.') == 0) {
Zotero.debug('Skipping file ' + fileName);
continue;
}
// addEntryFile works for both files and directories
zipWriter.addEntryFile(
fileName,
Components.interfaces.nsIZipWriter.COMPRESSION_DEFAULT,
file,
true
);
if (file.isDirectory()) {
Zotero.Commons.zipDirectory(rootDir, file, zipWriter);
continue;
}
}
}
this.error = function (message) {
Components.utils.reportError(message);
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
ps.alert(null, "Zotero Commons Error", message);
}
this.slugify = function (input) {
var slug = Zotero.Utilities.trim(input)
.toLowerCase()
.replace(/[^a-z0-9 ._-]/g, "")
//.replace(/ /g, "_");
.replace(/ /g, "-");
return slug;
}
}
Zotero.Commons.Bucket = function (name) {
this.id = Zotero.ID.getBigInt(); // assign a random ID to the bucket for this session
this.name = name;
this.accessKey = Zotero.Prefs.get("commons.accessKey");
this.secretKey = Zotero.Prefs.get("commons.secretKey");
this._items = null;
this._requestingItems = false;
this._needRefresh = false;
this._items = {};
this._itemsLoading = false;
this._itemsLoaded = false;
this._lastLoad = null;
this._metadataLoading = false;
this._itemDataLoaded = false;
this._itemDataLoading = false;
}
Zotero.Commons.Bucket.prototype.__defineGetter__('uri', function () {
return 'http://www.archive.org/details/' + this.name;
});
Zotero.Commons.Bucket.prototype.__defineGetter__('downloadURI', function () {
return 'http://www.archive.org/download/' + this.name;
});
Zotero.Commons.Bucket.prototype.__defineGetter__('metadataURI', function () {
return this.downloadURI + '/' + this.name + '_meta.xml';
});
Zotero.Commons.Bucket.prototype.__defineGetter__('apiPath', function() {
return '/' + this.name;
});
Zotero.Commons.Bucket.prototype.__defineGetter__('apiURI', function() {
return Zotero.Commons.apiUrl + this.apiPath;
});
Zotero.Commons.Bucket.prototype.relationPredicate = "owl:sameAs";
Zotero.Commons.Bucket.prototype.reloadSeconds = 60;
Zotero.Commons.Bucket.prototype.exists = function (callback, maxTries, tries) {
if (!tries) {
tries = 0;
}
var self = this;
Zotero.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");
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.getItems = function (callback) {
if (this._itemsLoading ||
// Reload if data is too old
(this._itemsLoaded && (!this._lastLoad || (new Date - this._lastLoad) < (this.reloadSeconds * 1000)))) {
Zotero.debug("Using cached items");
return this._getCachedItems();
}
Zotero.debug("Loading items from IA");
this._itemsLoading = true;
var method = "GET";
var uri = this.downloadURI + "/" + this.name + "_files.xml";
var self = this;
var progressWin = null;
var progressWinIcon = 'chrome://zotero/skin/treeitem-attachment-pdf.png';
var req = Zotero.HTTP.doGet(uri, function (xmlhttp) {
if (xmlhttp.status != 200) {
Zotero.debug(xmlhttp.status);
Zotero.debug(xmlhttp.responseText);
Zotero.debug("Commons: Error retrieving bucket contents", 2);
self._itemsLoading = false;
return;
}
Zotero.debug(xmlhttp.responseText);
try {
// Strip XML declaration and convert to E4X
var xml = new XML(xmlhttp.responseText.replace(/<\?xml.*\?>/, ''));
}
catch (e) {
Zotero.debug("Commons: Invalid response retrieving bucket contents", 2);
this._itemsLoading = false;
return;
}
try {
var zipsXML = xml.file.(@source == 'original').(typeof format != 'undefined' && format == 'Zotero ZIP Item');
}
catch (e) {
Zotero.debug("Commons: Invalid XML retrieving bucket contents", 2);
this._itemsLoading = false;
return;
}
Zotero.debug(zipsXML);
var zips = [];
// Parse files XML to get RDF and OCRed PDFs
var ocrOriginals = {};
var ocrPDFXML = xml.file.(@source == 'derivative').(format == 'Additional Text PDF').@name;
for each(var pdf in ocrPDFXML) {
var fn = pdf.toString().replace(/_text\.pdf$/, '.pdf');
ocrOriginals[fn] = true;
}
for each(var zipXML in zipsXML) {
var key = zipXML.@name.toString();
var title = zipXML.title.toString();
var childrenXML = xml.file.(typeof zipsource != 'undefined').(zipsource == key);
var rdf;
var children = [];
for each(var childXML in childrenXML) {
var childKey = childXML.@name.toString();
// Pull out RDF filename
if (childXML.format == 'Zotero RDF') {
rdf = childKey;
continue;
}
children.push({
key: childKey,
title: childXML.title.toString(),
ocr: ocrOriginals[childKey] ? true : false
});
}
/*
// See if we already have this item
if (self._items[key]) {
continue;
}
*/
zips.push({
key: key,
title: title,
rdf: rdf,
children: children
});
}
self._itemsLoading = false;
self._itemsLoaded = true;
self._lastLoad = new Date;
Zotero.debug(zips);
Zotero.Notifier.trigger('refresh', 'bucket', self.id);
// Get RDF for new items, pulling off a stack
var process = function (zips) {
if (!zips.length) {
Zotero.debug("Commons: No more ZIPs to process");
if (callback) {
callback();
}
return;
}
let zip = zips.shift();
// See if we already have this item
if (self._items[zip.key]) {
process(zips);
return;
}
var rdfURI = self.downloadURI + '/' + zip.rdf;
Zotero.HTTP.doGet(rdfURI, function (xmlhttp) {
// If RDF not available, skip item
if (xmlhttp.status != 200) {
Zotero.debug("RDF not found at " + xmlhttp.channel.originalURI.spec);
process(zips);
return;
}
if (!xmlhttp.responseText) {
Zotero.debug("RDF file is empty at " + xmlhttp.channel.originalURI.spec);
process(zips);
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();
// Add item data to virtual item
for (var field in item) {
// Skip empty fields
if (!item[field]) {
continue;
}
var fieldID = Zotero.ItemFields.getID(field);
if (!fieldID) {
continue;
}
try {
newItem.setField(fieldID, item[field]);
}
catch (e) {
Zotero.debug(e);
}
}
// Add creators to virtual item
for (var i=0; i<item.creators.length; i++) {
try {
var creator = new Zotero.Creator;
creator.setFields(item.creators[i]);
newItem.setCreator(i, creator, item.creators[i].creatorType);
}
catch (e) {
Zotero.debug(e);
}
}
self._items[zip.key] = newItem;
Zotero.Notifier.trigger('refresh', 'bucket', self.id);
// If item exists locally, check for OCRed attachments
var localItem = self.getLocalItem(newItem);
if (localItem) {
for each(var child in zip.children) {
if (!child.ocr) {
continue;
}
var iaFileName = child.key.replace(/\.pdf$/, '_text.pdf');
var iaFileURI = self.downloadURI + '/' + iaFileName;
var rels = Zotero.Relations.getByURIs(null, self.relationPredicate, iaFileURI);
if (rels.length) {
Zotero.debug("Commons: " + iaFileName + " has already been downloaded -- skipping");
continue;
}
Zotero.debug("Downloading OCRed PDF " + iaFileName);
var baseName = child.title.replace(/\.pdf$/, '')
baseName += ' (OCR)';
var title = baseName + '.pdf';
if (!progressWin) {
progressWin = new Zotero.ProgressWindow();
progressWin.changeHeadline("Downloading OCRed PDFs"); // TODO: localize
}
progressWin.addLines([title], [progressWinIcon]);
progressWin.show();
progressWin.startCloseTimer(8000);
var newAttachment = Zotero.Attachments.importFromURL(
iaFileURI, localItem.id, title, baseName, null, 'application/pdf'
);
if (!(newAttachment instanceof Zotero.Item)) {
throw (newAttachment + " is not a Zotero.Item in Zotero.Commons.Bucket.getItems()");
}
// Add a relation linking the new attachment to the IA file
var uri = Zotero.URI.getItemURI(newAttachment);
Zotero.Relations.add(null, uri, self.relationPredicate, iaFileURI);
}
}
process(zips);
});
translate.translate(false, false);
});
}
process(zips);
});
// Browser offline
if (!req) {
this._itemsLoading = false;
}
// List isn't yet available
return this._getCachedItems();
}
Zotero.Commons.Bucket.prototype.refreshItems = function (callback) {
if (this._itemsLoading) {
Zotero.debug("Items already loading in Zotero.Commons.Bucket.refreshItems()", 2);
if (callback) {
callback()
}
return;
}
Zotero.debug("Loading items for bucket '" + this.name + "'");
this._itemsLoaded = false;
this.getItems(callback);
}
Zotero.Commons.Bucket.prototype.uploadItems = function (ids) {
var items = Zotero.Items.get(ids);
if (!items) {
Zotero.debug("No items to upload");
return;
}
var itemsToUpload = [];
itemLoop:
for (var i=0, len=items.length; i<len; i++) {
if (items[i].isRegularItem()) {
// Item must have a title
if (!items[i].getDisplayTitle()) {
continue;
}
var attachmentIDs = items[i].getAttachments();
if (!attachmentIDs) {
continue;
}
// Make sure there's at least one valid attachment
for each(var attachmentID in attachmentIDs) {
var attachment = Zotero.Items.get(attachmentID);
if (attachment && attachment.getFile()) {
itemsToUpload.push(items[i]);
continue itemLoop;
}
}
}
}
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
var validItemsMessage = "Only titled items with bibliographic metadata and at least one attached file can be added to the Zotero Commons.";
if (itemsToUpload.length == 0) {
Zotero.debug("No regular items to upload");
ps.alert(null, "", validItemsMessage);
return;
}
if (itemsToUpload.length != items.length) {
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
var index = ps.confirmEx(
null,
"",
"Some of the dragged items will not be uploaded."
+ "\n\n"
+ validItemsMessage,
buttonFlags,
"Continue",
null, null, null, {}
);
// If user chooses 'Cancel', exit
if (index != 0) {
return;
}
}
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
var index = ps.confirmEx(
null,
"Zotero Commons Upload",
"By uploading items to Zotero Commons you agree to the terms of use at zotero.org and archive.org. "
+ "Please make sure metadata for your items is set properly."
+ "\n\n"
+ "Note that there may be a delay while the Internet Archive processes "
+ "your items before they appear in the Commons collection in Zotero."
+ "\n\n"
+ "Continue uploading items to Zotero Commons?",
buttonFlags,
"Upload",
null, null, null, {}
);
// If user chooses 'Cancel', exit
if (index != 0) {
return;
}
var progressWin = new Zotero.ProgressWindow();
var tmpDir = Zotero.getTempDirectory();
var self = this;
// Upload items one at a time, pulling items off a stack
var process = function (items) {
if (!items.length) {
Zotero.debug("Commons: No more items to upload");
return;
}
let item = items.shift();
// TODO: check relations table to see if this item already has a bucket
// TODO: localize
progressWin.changeHeadline("Uploading Items to IA");
progressWin.addLines([item.getDisplayTitle()], [item.getImageSrc()]);
progressWin.show();
self.uploadItem(
item,
function () {
Zotero.debug(items.length);
if (items.length) {
// Process next item
process(items);
}
else {
progressWin.startCloseTimer(5000);
}
}
);
}
process(itemsToUpload);
}
/**
* Export item and attachments to RDF and files and upload to IA
*/
Zotero.Commons.Bucket.prototype.uploadItem = function (item, callback) {
var key = item.key;
var outputDir = Zotero.getTempDirectory();
outputDir.append(key);
var bucket = this;
var translation = new Zotero.Translate("export");
translation.setItems([item]);
translation.setTranslator(Zotero.Commons.RDF_TRANSLATOR.translatorID);
translation.setDisplayOptions(Zotero.Commons.RDF_TRANSLATOR.displayOptions);
translation.setHandler("done", function (translation, success) {
if (!success) {
alert("Commons: Translation failed for " + translation);
return;
}
try {
// Rename RDF file
var rdfFile = outputDir.clone();
rdfFile.append(key + ".rdf");
rdfFile.moveTo(null, "zotero.rdf");
// Then create ZIP file from item
var zipFile = Zotero.getTempDirectory();
var title = Zotero.File.getValidFileName(item.getDisplayTitle());
zipFile.append(title + '-' + key + '.zip');
var zw = Components.classes["@mozilla.org/zipwriter;1"]
.createInstance(Components.interfaces.nsIZipWriter);
zw.open(zipFile, 0x04 | 0x08 | 0x20); // open rw, create, truncate
Zotero.Commons.zipDirectory(outputDir, outputDir, zw);
// Upload file after zipping
var observer = new Zotero.Commons.ZipWriterObserver(zw, function () {
bucket.putFile(zipFile, "application/zip", function (uri) {
// Link item to new bucket
var url1 = Zotero.URI.getItemURI(item);
var predicate = bucket.relationPredicate;
var url2 = bucket.getItemURI(item);
// TEMP?
if (Zotero.Relations.getByURIs(url1, predicate, url2).length == 0) {
Zotero.Relations.add(null, url1, predicate, url2);
}
if (callback) {
callback();
}
});
});
zw.processQueue(observer, null);
}
catch (e) {
alert("Zotero Commons upload failed:\n\n" + e);
}
});
translation.setLocation(outputDir);
translation.translate(); // synchronous
}
/**
* Delete selected items from IA
*/
Zotero.Commons.Bucket.prototype.deleteItems = function (ids) {
var ids = Zotero.flattenArguments(ids);
// Get the ZIP filenames from the ids
var keysToDelete = [];
for each(var id in ids) {
var key = this._getIAKeyByItemID(id);
if (key) {
keysToDelete.push(key);
}
}
if (!keysToDelete.length) {
Zotero.debug("No items to delete");
return;
}
var method = "DELETE";
// Delete extracted files as well
var headers = {
"x-archive-cascade-delete":"1"
};
var resource = '/' + this.name;
var bucket = this;
for each(let key in keysToDelete) {
let path = resource + '/' + key;
Zotero.Commons.createAuthenticatedRequest(
method, path, headers, this.accessKey, this.secretKey, function (req) {
if (req.status == 204) {
Zotero.debug('---=---------');
if (!bucket._items[key]) {
Zotero.debug('NO ITEM!');
Zotero.debug(key);
Zotero.debug(bucket._items);
}
// Delete any relations linked to the IA item
var uri = bucket.getItemURI(bucket._items[key]);
var relations = Zotero.Relations.getByURIs(
null, bucket.relationPredicate, uri
);
if (relations) {
Zotero.DB.beginTransaction();
for each(var relation in relations) {
Zotero.Relations.erase(relation.id);
}
Zotero.DB.commitTransaction();
}
delete bucket._items[key];
Zotero.debug("Commons: " + path + " was deleted successfully.");
Zotero.Notifier.trigger('refresh', 'bucket', bucket.id);
}
else {
Zotero.debug(req.status);
Zotero.debug(req.responseText);
if (req.status == 403) {
alert("Failed to delete " + path + " at IA: authentication failed.");
}
else if (req.status == 503) {
alert("Failed to delete " + path + " at IA: server unavailable.");
}
else {
alert("Failed to delete " + path + " at IA.");
Zotero.debug("Commons: delete failed with status code: " + req.status);
}
}
});
}
}
// UNUSED
Zotero.Commons.Bucket.prototype.updateMetadata = function(action, item, callback) {
Zotero.debug("Updating bucket metadata");
var method = "PUT";
var headers = {
"x-archive-ignore-preexisting-bucket":"1",
"x-archive-meta01-collection":"zoterocommons",
"x-archive-meta02-collection":"scholarworkspaces",
"x-archive-meta-mediatype":"texts",
"x-archive-meta-sponsor":"Andrew W. Mellon Foundation"
};
var meta = null;
var resource = encodeURI('http://archive.org/download/' + this.name + '/' + this.name + '_meta.xml');
var self = this;
// get previous metadata. multiple language support difficult via IA s3.
Zotero.HTTP.doGet(resource, function (xmlhttp) {
if (xmlhttp.status == 404 || (xmlhttp.status == 200 && !xmlhttp.responseXML)) {
Zotero.Commons.error("Error updating bucket metadata");
return;
}
if (itemTitle) {
headers["x-archive-meta-title"] = itemTitle;
}
// recreate headers of languages already specified in metadata
var languages = xmlhttp.responseXML.getElementsByTagName("metadata")[0].getElementsByTagName("language");
var itemLanguage = item.getField('language');
for (var i = 0, len = languages.length; i < len; i++) {
meta = "x-archive-meta0"+(i+1)+"-language";
headers[meta] = languages[i].textContent;
}
Zotero.debug(headers);
resource = "/" + this.name;
var updateCallback = function (req) {
Zotero.debug('========');
Zotero.debug("UPDATE");
Zotero.debug(req.status);
if(req.status < 202) {
Zotero.debug("Commons: " + resource + " metadata updated successfully.");
if (callback) {
callback();
}
}
else {
Zotero.debug(req.status);
Zotero.debug(req.responseText);
if (req.status == 403) {
alert("Failed to change " + key + " metadata: authentication failed.");
}
else if (req.status == 503) {
alert("Failed to change " + key + " metadata: server unavailable.");
}
else {
alert("Failed to change " + key + " metadata. Status code: " + req.status);
}
}
};
Zotero.Commons.createAuthenticatedRequest(
method, resource, headers, self.accessKey, self.secretKey, updateCallback
);
});
}
Zotero.Commons.Bucket.prototype.putFile = function (file, mimeType, callback) {
var fileName = file.leafName;
var fileNameHyphened = fileName.replace(/ /g,'-');
var method = "PUT";
var resource = this.apiPath + '/' + fileName;
var content = Zotero.File.getBinaryContents(file);
var headers = {};
var self = this;
var putCallback = function (req) {
Zotero.debug(req.responseText);
// Success
if (req.status == 201) {
Zotero.debug("Commons: " + fileName + " was uploaded successfully.");
if (callback) {
callback(req.channel.URI.spec);
}
return;
}
// Error
Zotero.debug(req.status);
Zotero.debug(req.responseText);
if (req.status == 404) {
alert("Failed to upload " + fileName + " to IA: bucket not found");
}
else if (req.status == 403) {
alert("Failed to upload " + fileName + " to IA: authentication failed.");
}
else if (req.status == 503) {
alert("Failed to upload " + fileName + " to IA: server unavailable.");
}
else {
alert("Failed to upload " + fileName + " to IA. status is " + req.status);
}
};
Zotero.Commons.createAuthenticatedRequest(
method, resource, headers, this.accessKey, this.secretKey, putCallback, content, true
);
}
Zotero.Commons.Bucket.prototype.getItemURI = function (item) {
// TODO: this won't work for non-English systems
return this.uri + '#' + encodeURIComponent(item.getDisplayTitle());
}
Zotero.Commons.Bucket.prototype.getLocalItem = function (item) {
var uri = this.getItemURI(item);
var rels = Zotero.Relations.getByURIs(null, this.relationPredicate, uri);
if (!rels.length) {
Zotero.debug("No local item linked to remote URI " + uri, 2);
return false;
}
if (rels.length > 1) {
Zotero.debug("More than one local item linked to remote item " + uri, 2);
return false;
}
var item = Zotero.URI.getURIItem(rels[0].subject);
if (!item) {
Zotero.debug("Linked local item not found for remote URI " + uri, 2);
return false;
}
return item;
}
Zotero.Commons.Bucket.prototype._getCachedItems = function () {
var items = [];
for each(var item in this._items) {
items.push(item);
}
return items;
}
Zotero.Commons.Bucket.prototype._getIAKeyByItemID = function (id) {
for (var key in this._items) {
if (this._items[key].id == id) {
return key;
}
}
return false;
}
// Implements nsIRequestObserver
Zotero.Commons.ZipWriterObserver = function (zipWriter, callback) {
this._zipWriter = zipWriter;
this._callback = callback;
}
Zotero.Commons.ZipWriterObserver.prototype = {
onStartRequest: function () {},
onStopRequest: function(req, context, status) {
this._zipWriter.close();
this._callback();
}
}
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for details.
*/
Zotero.Commons.SHA1 = new function() {
// added by Ben Parr to expose function for Zotero
this.hex_sha1 = hex_sha1;
this.b64_sha1 = b64_sha1;
this.str_sha1 = str_sha1;
this.hex_hmac_sha1 = hex_hmac_sha1;
this.b64_hmac_sha1 = b64_hmac_sha1;
this.str_hmac_sha1 = str_hmac_sha1;
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
/*
* Perform a simple self-test to see if the VM is working
*/
function sha1_vm_test()
{
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for(var j = 0; j < 80; j++)
{
if(j < 16) w[j] = x[i + j];
else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
safe_add(safe_add(e, w[j]), sha1_kt(j)));
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function sha1_ft(t, b, c, d)
{
if(t < 20) return (b & c) | ((~b) & d);
if(t < 40) return b ^ c ^ d;
if(t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function sha1_kt(t)
{
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
(t < 60) ? -1894007588 : -899497514;
}
/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_sha1(key, data)
{
var bkey = str2binb(key);
if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
return core_sha1(opad.concat(hash), 512 + 160);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str)
{
var bin = Array();
var mask = (1 << chrsz) - 1;
for(var i = 0; i < str.length * chrsz; i += chrsz)
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
return bin;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin)
{
var str = "";
var mask = (1 << chrsz) - 1;
for(var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
return str;
}
/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray)
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8)) & 0xF);
}
return str;
}
/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray)
{
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for(var i = 0; i < binarray.length * 4; i += 3)
{
var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
| (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
| ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
}
}
return str;
}
}