Support unmodified Xpdf binaries

Use stdout redirection scripts for pdfinfo and, on Windows, a script to
run pdftotext hidden, which together allow for all unmodified binaries
(including, probably, symlinked system ones, though I didn't test that).

On Windows, using a .vbs does cause a brief wait cursor. The stock
pdfinfo needs the redirection script anyway, so that's unavoidable, but
on the async branch I think we'll be able to switch to pdf.js for the
page count, at which point maybe I'll try to remember how I modified the
Windows binaries to be hidden and use a modified version of pdftotext to
avoid VBScript. (We use the stock pdftotext elsewhere already.)
This commit is contained in:
Dan Stillman 2015-04-01 02:03:57 -04:00
parent aa7c1b6fd6
commit a9ca6e0857
6 changed files with 267 additions and 133 deletions

View file

@ -162,139 +162,121 @@ Zotero_Preferences.Search = {
* if a newer version is available
*/
checkPDFToolsDownloadVersion: function () {
var url = Zotero.Fulltext.pdfToolsDownloadBaseURL
+ Zotero.platform.replace(' ', '-') + '.latest';
var url = Zotero.Fulltext.pdfToolsDownloadBaseURL + 'latest.json';
// Find latest version for this platform
var self = this;
var sent = Zotero.HTTP.doGet(url, function (xmlhttp) {
try {
if (xmlhttp.status == 200) {
var converterIsRegistered = Zotero.Fulltext.pdfConverterIsRegistered();
var infoIsRegistered = Zotero.Fulltext.pdfInfoIsRegistered();
var bothRegistered = converterIsRegistered && infoIsRegistered;
if (xmlhttp.status != 200) {
throw new Error("Unexpected response code " + xmlhttp.status);
}
var platform = Zotero.platform.replace(/\s/g, '-');
var json = JSON.parse(xmlhttp.responseText);
var latestVersion = json[platform] || json['default'];
Zotero.debug("Latest PDF tools version for " + platform + " is " + latestVersion);
var converterIsRegistered = Zotero.Fulltext.pdfConverterIsRegistered();
var infoIsRegistered = Zotero.Fulltext.pdfInfoIsRegistered();
var bothRegistered = converterIsRegistered && infoIsRegistered;
// Install if not installed, version unknown, outdated, or
// Xpdf 3.02/3.04 (to upgrade to Poppler),
var converterVersionAvailable = (!converterIsRegistered ||
Zotero.Fulltext.pdfConverterVersion == 'UNKNOWN'
|| latestVersion > Zotero.Fulltext.pdfConverterVersion
|| (latestVersion != '3.02' && Zotero.Fulltext.pdfConverterVersion == '3.02')
|| (latestVersion != '3.02' && latestVersion != '3.04' && Zotero.Fulltext.pdfConverterVersion == '3.04'));
var infoVersionAvailable = (!infoIsRegistered ||
Zotero.Fulltext.pdfInfoVersion == 'UNKNOWN'
|| latestVersion > Zotero.Fulltext.pdfInfoVersion
|| (latestVersion != '3.02' && Zotero.Fulltext.pdfInfoVersion == '3.02')
|| (latestVersion != '3.02' && latestVersion != '3.04' && Zotero.Fulltext.pdfInfoVersion == '3.04'));
var bothAvailable = converterVersionAvailable && infoVersionAvailable;
// Up to date -- disable update button
if (!converterVersionAvailable && !infoVersionAvailable) {
var button = document.getElementById('pdftools-update-button');
button.setAttribute('label', Zotero.getString('zotero.preferences.update.upToDate'));
button.setAttribute('disabled', true);
}
// New version available -- display update prompt
else {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
var converterVersion = xmlhttp.responseText.split(/\s/)[0];
var infoVersion = xmlhttp.responseText.split(/\s/)[1];
var msg = Zotero.getString('zotero.preferences.search.pdf.available'
+ ((converterIsRegistered || infoIsRegistered) ? 'Updates' : 'Downloads'),
[Zotero.platform, 'zotero.org']) + '\n\n';
// Install if not installed, version unknown, Xpdf 3.02 (to upgrade to Poppler),
// or outdated
var converterVersionAvailable = converterVersion &&
(!converterIsRegistered ||
Zotero.Fulltext.pdfConverterVersion == 'UNKNOWN'
|| (converterVersion != '3.02' && Zotero.Fulltext.pdfConverterVersion == '3.02')
|| converterVersion > Zotero.Fulltext.pdfConverterVersion);
var infoVersionAvailable = infoVersion &&
(!infoIsRegistered ||
Zotero.Fulltext.pdfInfoVersion == 'UNKNOWN'
|| (infoVersion != '3.02' && Zotero.Fulltext.pdfInfoVersion == '3.02')
|| infoVersion > Zotero.Fulltext.pdfInfoVersion);
var bothAvailable = converterVersionAvailable && infoVersionAvailable;
/*
Zotero.debug(converterIsRegistered);
Zotero.debug(infoIsRegistered);
Zotero.debug(converterVersion);
Zotero.debug(infoVersion);
Zotero.debug(Zotero.Fulltext.pdfConverterVersion);
Zotero.debug(Zotero.Fulltext.pdfInfoVersion);
Zotero.debug(converterVersionAvailable);
Zotero.debug(infoVersionAvailable);
*/
// Up to date -- disable update button
if (!converterVersionAvailable && !infoVersionAvailable) {
var button = document.getElementById('pdftools-update-button');
button.setAttribute('label', Zotero.getString('zotero.preferences.update.upToDate'));
button.setAttribute('disabled', true);
if (converterVersionAvailable) {
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfConverterName, latestVersion]);
msg += '- ' + tvp + '\n';
}
// New version available -- display update prompt
else {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL);
if (infoVersionAvailable) {
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfInfoName, latestVersion]);
msg += '- ' + tvp + '\n';
}
msg += '\n';
msg += Zotero.getString('zotero.preferences.search.pdf.zoteroCanInstallVersion'
+ (bothAvailable ? 's' : ''));
var index = ps.confirmEx(null,
converterIsRegistered ?
Zotero.getString('general.updateAvailable') : '',
msg,
buttonFlags,
converterIsRegistered ?
Zotero.getString('general.upgrade') :
Zotero.getString('general.install'),
null, null, null, {});
if (index == 0) {
document.getElementById('pdftools-update-button').disabled = true;
var str = Zotero.getString('zotero.preferences.search.pdf.downloading');
document.getElementById('pdftools-update-button').setAttribute('label', str);
var msg = Zotero.getString('zotero.preferences.search.pdf.available'
+ ((converterIsRegistered || infoIsRegistered) ? 'Updates' : 'Downloads'),
[Zotero.platform, 'zotero.org']) + '\n\n';
if (converterVersionAvailable) {
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfConverterName, converterVersion]);
msg += '- ' + tvp + '\n';
}
if (infoVersionAvailable) {
let tvp = Zotero.getString('zotero.preferences.search.pdf.toolVersionPlatform',
[Zotero.Fulltext.pdfInfoName, infoVersion]);
msg += '- ' + tvp + '\n';
}
msg += '\n';
msg += Zotero.getString('zotero.preferences.search.pdf.zoteroCanInstallVersion'
+ (bothAvailable ? 's' : ''));
var index = ps.confirmEx(null,
converterIsRegistered ?
Zotero.getString('general.updateAvailable') : '',
msg,
buttonFlags,
converterIsRegistered ?
Zotero.getString('general.upgrade') :
Zotero.getString('general.install'),
null, null, null, {});
if (index == 0) {
var installVersions = {
converter: converterVersionAvailable ?
converterVersion : null,
info: infoVersionAvailable ?
infoVersion : null
};
document.getElementById('pdftools-update-button').disabled = true;
var str = Zotero.getString('zotero.preferences.search.pdf.downloading');
document.getElementById('pdftools-update-button').setAttribute('label', str);
if (converterVersionAvailable && infoVersionAvailable) {
Zotero.Fulltext.downloadPDFTool('converter', converterVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdftotext");
return;
}
Zotero.Fulltext.downloadPDFTool('info', infoVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdfinfo");
return;
}
self.updatePDFToolsStatus();
});
});
}
else if (converterVersionAvailable) {
Zotero.Fulltext.downloadPDFTool('converter', converterVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdftotext");
return;
}
self.updatePDFToolsStatus();
});
}
else {
Zotero.Fulltext.downloadPDFTool('info', infoVersion, function (success) {
if (converterVersionAvailable && infoVersionAvailable) {
Zotero.Fulltext.downloadPDFTool('converter', latestVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdftotext");
return;
}
Zotero.Fulltext.downloadPDFTool('info', latestVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdfinfo");
return;
}
self.updatePDFToolsStatus();
});
}
});
}
else if (converterVersionAvailable) {
Zotero.Fulltext.downloadPDFTool('converter', latestVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdftotext");
return;
}
self.updatePDFToolsStatus();
});
}
else {
Zotero.Fulltext.downloadPDFTool('info', latestVersion, function (success) {
if (!success) {
self.onPDFToolsDownloadError("Error downloading pdfinfo");
return;
}
self.updatePDFToolsStatus();
});
}
}
}
// Version not found for platform
else if (xmlhttp.status == 404) {
self.onPDFToolsDownloadError(404);
}
}
catch (e) {
self.onPDFToolsDownloadError(e);

View file

@ -55,7 +55,7 @@ Zotero.Fulltext = new function(){
//this.clearItemContent = clearItemContent;
this.purgeUnusedWords = purgeUnusedWords;
this.__defineGetter__("pdfToolsDownloadBaseURL", function() { return 'http://www.zotero.org/download/xpdf/'; });
this.__defineGetter__("pdfToolsDownloadBaseURL", function() { return 'https://www.zotero.org/download/xpdf/'; });
this.__defineGetter__("pdfToolsName", function() { return 'Xpdf'; });
this.__defineGetter__("pdfToolsURL", function() { return 'http://www.foolabs.com/xpdf/'; });
this.__defineGetter__("pdfConverterName", function() { return 'pdftotext'; });
@ -82,9 +82,11 @@ Zotero.Fulltext = new function(){
var _pdfConverterVersion = null;
var _pdfConverterFileName = null;
var _pdfConverterScript = null; // nsIFile of hidden window script on Windows
var _pdfConverter = null; // nsIFile to executable
var _pdfInfoVersion = null;
var _pdfInfoFileName = null;
var _pdfInfoScript = null; // nsIFile of redirection script
var _pdfInfo = null; // nsIFile to executable
var _idleObserverIsRegistered = false;
@ -179,7 +181,7 @@ Zotero.Fulltext = new function(){
var fileName = this.pdfInfoFileName;
}
var spec = this.pdfToolsDownloadBaseURL + fileName + '-' + version;
var spec = this.pdfToolsDownloadBaseURL + version + "/" + fileName;
var uri = ioService.newURI(spec, null, null);
var file = Zotero.getTempDirectory();
@ -202,15 +204,43 @@ Zotero.Fulltext = new function(){
Zotero.File.putContentsAsync(file, is)
.then(function () {
var scriptExt = _getScriptExtension();
// On Windows, write out script to hide pdftotext console window
if (tool == 'converter') {
if (Zotero.isWin) {
var content = Zotero.File.getContentsFromURL('resource://zotero/hide.' + scriptExt);
var scriptFile = Zotero.getTempDirectory();
scriptFile.append('pdftotext.' + scriptExt);
Zotero.File.putContents(scriptFile, content);
}
}
// Write out output redirection script for pdfinfo
else if (tool == 'info') {
var content = Zotero.File.getContentsFromURL('resource://zotero/redirect.' + scriptExt);
var scriptFile = Zotero.getTempDirectory();
scriptFile.append('pdfinfo.' + scriptExt);
Zotero.File.putContents(scriptFile, content);
}
// Set permissions to 755
if (Zotero.isMac) {
file.permissions = 33261;
if (scriptFile) {
scriptFile.permissions = 33261;
}
}
else if (Zotero.isLinux) {
file.permissions = 493;
if (scriptFile) {
scriptFile.permissions = 493;
}
}
var destDir = Zotero.getZoteroDirectory()
// Move redirect script and executable into data dir
if (scriptFile) {
scriptFile.moveTo(destDir, null);
}
file.moveTo(destDir, null);
// Write the version number to a file
@ -223,6 +253,11 @@ Zotero.Fulltext = new function(){
if (callback) {
callback(true);
}
})
.catch(function (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
callback(false);
});
});
}
@ -274,10 +309,42 @@ Zotero.Fulltext = new function(){
return false;
}
// If scripts exist, use those instead
switch (tool) {
case 'converter':
if (Zotero.isWin) {
var script = Zotero.getZoteroDirectory();
script.append('pdftotext.' + _getScriptExtension())
if (script.exists()) {
Zotero.debug(script.leafName + " registered");
_pdfConverterScript = script;
}
}
break;
case 'info':
var script = Zotero.getZoteroDirectory();
script.append('pdfinfo.' + _getScriptExtension())
// The redirection script is necessary to run pdfinfo
if (!script.exists()) {
Zotero.debug(script.leafName + " not found -- PDF statistics disabled");
return false;
}
Zotero.debug(toolName + " redirection script registered");
_pdfInfoScript = script;
break;
}
var versionFile = exec.parent;
versionFile.append(fileName + '.version');
if (versionFile.exists()) {
var version = Zotero.File.getSample(versionFile).split(/[\r\n\s]/)[0];
try {
var version = Zotero.File.getSample(versionFile).split(/[\r\n\s]/)[0];
}
catch (e) {
Zotero.debug(e, 1);
Components.utils.reportError(e);
}
}
if (!version) {
var version = 'UNKNOWN';
@ -295,7 +362,7 @@ Zotero.Fulltext = new function(){
break;
}
Zotero.debug(toolName + ' version ' + version + ' registered at ' + exec.path);
Zotero.debug(toolName + ' version ' + version + ' registered');
return true;
}
@ -529,16 +596,19 @@ Zotero.Fulltext = new function(){
}
cacheFile.append(this.pdfConverterCacheFile);
if (_pdfInfo) {
if (_pdfInfoScript) {
var infoFile = cacheFile.parent;
infoFile.append(this.pdfInfoCacheFile);
Zotero.debug('Running pdfinfo "' + file.path + '" "' + infoFile.path + '"');
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(_pdfInfo);
var args = [_pdfInfo.path, file.path, infoFile.path];
Zotero.debug("Running " + _pdfInfoScript.path + ' '
+ args.map(arg => "'" + arg + "'").join(' '));
var proc = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
proc.init(_pdfInfoScript);
var args = [file.path, infoFile.path];
try {
proc.runw(true, args, args.length);
var totalPages = this.getTotalPagesFromFile(itemID);
@ -551,15 +621,12 @@ Zotero.Fulltext = new function(){
Zotero.debug(this.pdfInfoName + " is not available");
}
Zotero.debug('Running pdftotext -enc UTF-8 -nopgbrk '
+ (allPages ? '' : '-l ' + maxPages) + ' "' + file.path + '" "'
+ cacheFile.path + '"');
var args = []
if (_pdfConverterScript) {
args.push(_pdfConverter.path);
}
args.push('-enc', 'UTF-8', '-nopgbrk');
var proc = Components.classes["@mozilla.org/process/util;1"].
createInstance(Components.interfaces.nsIProcess);
proc.init(_pdfConverter);
var args = ['-enc', 'UTF-8', '-nopgbrk'];
if (allPages) {
if (totalPages) {
var pagesIndexed = totalPages;
@ -570,6 +637,20 @@ Zotero.Fulltext = new function(){
var pagesIndexed = Math.min(maxPages, totalPages);
}
args.push(file.path, cacheFile.path);
var proc = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
if (_pdfConverterScript) {
Zotero.debug("Running " + _pdfConverterScript.path + ' '
+ args.map(arg => "'" + arg + "'").join(' '));
proc.init(_pdfConverterScript);
}
else {
Zotero.debug("Running " + _pdfConverter.path + ' '
+ args.map(arg => "'" + arg + "'").join(' '));
proc.init(_pdfConverter);
}
try {
proc.runw(true, args, args.length);
}
@ -1679,4 +1760,9 @@ Zotero.Fulltext = new function(){
return w;
});
}
function _getScriptExtension() {
return Zotero.isWin ? 'vbs' : 'sh';
}
}

View file

@ -1590,6 +1590,9 @@ Zotero.Schema = new function(){
var styleUpdates = xmlhttp.responseXML.getElementsByTagName('style');
var updatePDFTools = function () {
// No updates for PPC
if (Zotero.platform == 'MacPPC') return;
let pdfToolsUpdates = xmlhttp.responseXML.getElementsByTagName('pdftools');
if (pdfToolsUpdates.length) {
let availableVersion = pdfToolsUpdates[0].getAttribute('version');

24
resource/hide.vbs Normal file
View file

@ -0,0 +1,24 @@
Option Explicit
Dim WshShell, fso, exe, args, I
Set WshShell = Wscript.CreateObject("Wscript.Shell")
Set fso = WScript.CreateObject("Scripting.FileSystemObject")
If WScript.Arguments.Count = 0 Then
WScript.Echo "Usage: hidden.vbs program.exe [args...]"
WScript.Quit 1
End If
exe = WScript.Arguments(0)
If Not(fso.FileExists(exe)) Then
WScript.Echo "Executable not found: " & exe
WScript.Quit 1
End If
args = ""
For I = 1 to WScript.Arguments.Count - 1
args = args & " " & chr(34) & WScript.Arguments(I) & chr(34)
Next
WshShell.Run exe & args, 0, true

6
resource/redirect.sh Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo "Usage: $0 cmd source output.txt"
exit 1
fi
"$1" "$2" > "$3"

33
resource/redirect.vbs Normal file
View file

@ -0,0 +1,33 @@
Option Explicit
Dim WshShell
Dim fso
Dim exe, src, dest
Set WshShell = Wscript.CreateObject("Wscript.Shell")
Set fso = WScript.CreateObject("Scripting.FileSystemObject")
If Not(Wscript.Arguments.Count = 3) Then
Wscript.Echo "Usage: redirect.vbs <.exe file> <source file> <text file>"
WScript.Quit 1
End If
exe = WScript.Arguments(0)
src = WScript.Arguments(1)
dest = WScript.Arguments(2)
If Not(fso.FileExists(exe)) Then
WScript.Echo "Executable not found: " & exe
WScript.Quit 1
End If
If Not(fso.FileExists(src)) Then
WScript.Echo "Source file not found: " & src
WScript.Quit 1
End If
If Not(fso.FolderExists(Left(dest, InstrRev(dest, "\")))) Then
WScript.Echo "Destination folder not found: " & Left(dest, InstrRev(dest, "\"))
WScript.Quit 1
End If
WshShell.Run "%comspec% /c " & exe & " " & chr(34) & src & chr(34) & " > " & chr(34) & dest & chr(34), 0, true