1421 lines
36 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
|