
- Group file sync via Zotero File Storage - Split file syncing into separate modules for ZFS and WebDAV - Dragging items between libraries copies child notes, snapshots/files, and links based on checkboxes for each (enabled by default) in the Zotero preferences - Sync errors now trigger an exclamation/error icon separate from the sync icon, with a popup window displaying the error and an option to report it - Various errors that could cause perpetual sync icon spinning now stop the sync properly - Zotero.Utilities.md5(str) is now md5(strOrFile, base64) - doPost(), doHead(), and retrieveSource() now takes a headers parameter instead of requestContentType - doHead() can now accept an nsIURI (with login credentials), is a background request, and isn't cached - When library access or file writing access is denied during sync, display a warning and then reset local group to server version - Perform additional steps (e.g., removing local groups) when switching sync users to prevent errors - Compare hash as well as mod time when checking for modified local files - Don't trigger notifications when removing groups from the client - Clear relation links to items in removed groups - Zotero.Item.attachmentHash property to get file MD5 - importFromFile() now takes libraryID as a third parameter - Zotero.Attachments.getNumFiles() returns the number of files in the attachment directory - Zotero.Attachments.copyAttachmentToLibrary() copies an attachment item, including files, to another library - Removed Zotero.File.getFileHash() in favor of updated Zotero.Utilities.md5() - Zotero.File.copyDirectory(dir, newDir) copies all files from dir into newDir - Preferences shuffling: OpenURL to Advanced, import/export character set options to Export, "Include URLs of paper articles in references" to Styles - Other stuff I don't remember Suffice it to say, this could use testing.
307 lines
9.0 KiB
JavaScript
307 lines
9.0 KiB
JavaScript
/*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (c) 2006 Center for History and New Media
|
|
George Mason University, Fairfax, Virginia, USA
|
|
http://chnm.gmu.edu
|
|
|
|
Licensed under the Educational Community License, Version 1.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.opensource.org/licenses/ecl1.php
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
*/
|
|
|
|
Zotero.File = new function(){
|
|
this.getExtension = getExtension;
|
|
this.getClosestDirectory = getClosestDirectory;
|
|
this.getSample = getSample;
|
|
this.getContents = getContents;
|
|
this.getContentsFromURL = getContentsFromURL;
|
|
this.putContents = putContents;
|
|
this.getValidFileName = getValidFileName;
|
|
this.copyToUnique = this.copyToUnique;
|
|
this.getCharsetFromFile = getCharsetFromFile;
|
|
this.addCharsetListener = addCharsetListener;
|
|
|
|
|
|
function getExtension(file){
|
|
var pos = file.leafName.lastIndexOf('.');
|
|
return pos==-1 ? '' : file.leafName.substr(pos+1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Traverses up the filesystem from a file until it finds an existing
|
|
* directory, or false if it hits the root
|
|
*/
|
|
function getClosestDirectory(file) {
|
|
var dir = file.parent;
|
|
|
|
while (dir && !dir.exists()) {
|
|
var dir = dir.parent;
|
|
}
|
|
|
|
if (dir && dir.exists()) {
|
|
return dir;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the first 128 bytes of the file as a string (multibyte-safe)
|
|
*/
|
|
function getSample(file){
|
|
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
|
createInstance(Components.interfaces.nsIFileInputStream);
|
|
fis.init(file, 0x01, 0664, 0);
|
|
|
|
const replacementChar
|
|
= Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
|
|
var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIConverterInputStream);
|
|
is.init(fis, "UTF-8", 128, replacementChar);
|
|
var str = {};
|
|
var numChars = is.readString(128, str);
|
|
is.close();
|
|
|
|
return str.value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get contents of a binary file
|
|
*/
|
|
this.getBinaryContents = function(file) {
|
|
var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIFileInputStream);
|
|
iStream.init(file, 0x01, 0664, 0);
|
|
var bStream = Components.classes["@mozilla.org/binaryinputstream;1"]
|
|
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
|
bStream.setInputStream(iStream);
|
|
var string = bStream.readBytes(file.fileSize);
|
|
iStream.close();
|
|
return string;
|
|
}
|
|
|
|
|
|
function getContents(file, charset, maxLength){
|
|
var fis = Components.classes["@mozilla.org/network/file-input-stream;1"].
|
|
createInstance(Components.interfaces.nsIFileInputStream);
|
|
fis.init(file, 0x01, 0664, 0);
|
|
|
|
if (charset){
|
|
charset = Zotero.CharacterSets.getName(charset);
|
|
}
|
|
|
|
if (!charset){
|
|
charset = "UTF-8";
|
|
}
|
|
|
|
const replacementChar
|
|
= Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
|
|
var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
|
.createInstance(Components.interfaces.nsIConverterInputStream);
|
|
is.init(fis, charset, 4096, replacementChar);
|
|
var chars = 4096;
|
|
|
|
var contents = [], str = {};
|
|
while (is.readString(4096, str) != 0) {
|
|
if (maxLength) {
|
|
chars += 4096;
|
|
if (chars >= maxLength) {
|
|
Zotero.debug('Stopping at ' + (chars - 4096)
|
|
+ ' characters in File.getContents()');
|
|
break;
|
|
}
|
|
}
|
|
|
|
contents.push(str.value);
|
|
}
|
|
|
|
is.close();
|
|
|
|
return contents.join('');
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the contents of a URL as a string
|
|
*
|
|
* Runs synchronously, so should only be run on local (e.g. chrome) URLs
|
|
*/
|
|
function getContentsFromURL(url) {
|
|
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
|
.createInstance();
|
|
xmlhttp.open('GET', url, false);
|
|
xmlhttp.send(null);
|
|
return xmlhttp.responseText;
|
|
}
|
|
|
|
|
|
/*
|
|
* Write string to a file, overwriting existing file if necessary
|
|
*/
|
|
function putContents(file, str) {
|
|
if (file.exists()) {
|
|
file.remove(null);
|
|
}
|
|
var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
|
|
createInstance(Components.interfaces.nsIFileOutputStream);
|
|
fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
|
|
|
|
var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
|
|
.createInstance(Components.interfaces.nsIConverterOutputStream);
|
|
os.init(fos, "UTF-8", 4096, "?".charCodeAt(0));
|
|
os.writeString(str);
|
|
os.close();
|
|
|
|
fos.close();
|
|
}
|
|
|
|
|
|
function copyToUnique(file, newFile) {
|
|
newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
|
|
var newName = newFile.leafName;
|
|
newFile.remove(null);
|
|
|
|
// Copy file to unique name
|
|
file.copyTo(newFile.parent, newName);
|
|
return file;
|
|
}
|
|
|
|
|
|
/**
|
|
* Copies all files from dir into newDir
|
|
*/
|
|
this.copyDirectory = function (dir, newDir) {
|
|
var otherFiles = dir.directoryEntries;
|
|
while (otherFiles.hasMoreElements()) {
|
|
var file = otherFiles.getNext();
|
|
file.QueryInterface(Components.interfaces.nsIFile);
|
|
file.copyTo(newDir, null);
|
|
}
|
|
}
|
|
|
|
|
|
this.createDirectoryIfMissing = function (dir) {
|
|
if (!dir.exists() || !dir.isDirectory()) {
|
|
if (dir.exists() && !dir.isDirectory()) {
|
|
dir.remove(null);
|
|
}
|
|
dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
|
|
}
|
|
}
|
|
|
|
|
|
// Strip potentially invalid characters
|
|
// See http://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
|
function getValidFileName(fileName) {
|
|
// TODO: use space instead, and figure out what's doing extra
|
|
// URL encode when saving attachments that trigger this
|
|
fileName = fileName.replace(/[\/\\\?%\*:|"<>]/g, '');
|
|
// Replace newlines (which shouldn't be in the string in the first place) with spaces
|
|
fileName = fileName.replace(/\n/g, ' ');
|
|
// Strip characters not valid in XML, since they won't sync and they're probably unwanted
|
|
fileName = fileName.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '');
|
|
// Don't allow blank filename
|
|
if (!fileName) {
|
|
fileName = '_';
|
|
}
|
|
return fileName;
|
|
}
|
|
|
|
|
|
/*
|
|
* Not implemented, but it'd sure be great if it were
|
|
*/
|
|
function getCharsetFromByteArray(arr) {
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* An extraordinarily inelegant way of getting the character set of a
|
|
* text file using a hidden browser
|
|
*
|
|
* I'm quite sure there's a better way
|
|
*
|
|
* Note: This is for text files -- don't run on other files
|
|
*
|
|
* 'callback' is the function to pass the charset (and, if provided, 'args')
|
|
* to after detection is complete
|
|
*/
|
|
function getCharsetFromFile(file, mimeType, callback, args){
|
|
if (!file || !file.exists()){
|
|
return false;
|
|
}
|
|
|
|
if (mimeType.substr(0, 5)!='text/' ||
|
|
!Zotero.MIME.hasInternalHandler(mimeType, this.getExtension(file))){
|
|
return false;
|
|
}
|
|
|
|
var browser = Zotero.Browser.createHiddenBrowser();
|
|
|
|
var url = Components.classes["@mozilla.org/network/protocol;1?name=file"]
|
|
.getService(Components.interfaces.nsIFileProtocolHandler)
|
|
.getURLSpecFromFile(file);
|
|
|
|
this.addCharsetListener(browser, function (charset, args) {
|
|
callback(charset, args);
|
|
Zotero.Browser.deleteHiddenBrowser(browser);
|
|
}, args);
|
|
|
|
browser.loadURI(url);
|
|
}
|
|
|
|
|
|
/*
|
|
* Attach a load listener to a browser object to perform charset detection
|
|
*
|
|
* We make sure the universal character set detector is set to the
|
|
* universal_charset_detector (temporarily changing it if not--shhhh)
|
|
*
|
|
* 'callback' is the function to pass the charset (and, if provided, 'args')
|
|
* to after detection is complete
|
|
*/
|
|
function addCharsetListener(browser, callback, args){
|
|
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
|
.getService(Components.interfaces.nsIPrefBranch);
|
|
var oldPref = prefService.getCharPref('intl.charset.detector');
|
|
var newPref = 'universal_charset_detector';
|
|
//Zotero.debug("Default character detector is " + (oldPref ? oldPref : '(none)'));
|
|
|
|
if (oldPref != newPref){
|
|
//Zotero.debug('Setting character detector to universal_charset_detector');
|
|
prefService.setCharPref('intl.charset.detector', 'universal_charset_detector');
|
|
}
|
|
|
|
var onpageshow = function(){
|
|
// ignore spurious about:blank loads
|
|
if(browser.contentDocument.location.href == "about:blank") return;
|
|
|
|
browser.removeEventListener("pageshow", onpageshow, false);
|
|
|
|
var charset = browser.contentDocument.characterSet;
|
|
Zotero.debug("Detected character set '" + charset + "'");
|
|
|
|
//Zotero.debug('Resetting character detector to ' + (oldPref ? oldPref : '(none)'));
|
|
prefService.setCharPref('intl.charset.detector', oldPref);
|
|
|
|
callback(charset, args);
|
|
};
|
|
|
|
browser.addEventListener("pageshow", onpageshow, false);
|
|
}
|
|
}
|