You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
9.2 KiB
298 lines
9.2 KiB
// Copyright 2012 The Obvious Corporation. |
|
|
|
/* |
|
* This simply fetches the right version of phantom for the current platform. |
|
*/ |
|
|
|
'use strict' |
|
|
|
var AdmZip = require('adm-zip') |
|
var cp = require('child_process') |
|
var fs = require('fs') |
|
var helper = require('./lib/phantomjs') |
|
var http = require('http') |
|
var kew = require('kew') |
|
var ncp = require('ncp') |
|
var npmconf = require('npmconf') |
|
var mkdirp = require('mkdirp') |
|
var path = require('path') |
|
var rimraf = require('rimraf').sync |
|
var url = require('url') |
|
var util = require('util') |
|
var which = require('which') |
|
|
|
var downloadUrl = 'http://phantomjs.googlecode.com/files/phantomjs-' + helper.version + '-' |
|
|
|
var originalPath = process.env.PATH |
|
|
|
// NPM adds bin directories to the path, which will cause `which` to find the |
|
// bin for this package not the actual phantomjs bin. Also help out people who |
|
// put ./bin on their path |
|
process.env.PATH = helper.cleanPath(originalPath) |
|
|
|
var libPath = path.join(__dirname, 'lib') |
|
var pkgPath = path.join(libPath, 'phantom') |
|
var phantomPath = null |
|
var tmpPath = null |
|
|
|
var whichDeferred = kew.defer() |
|
which('phantomjs', whichDeferred.makeNodeResolver()) |
|
whichDeferred.promise |
|
.then(function (path) { |
|
phantomPath = path |
|
|
|
// Horrible hack to avoid problems during global install. We check to see if |
|
// the file `which` found is our own bin script. |
|
// See: https://github.com/Obvious/phantomjs/issues/85 |
|
if (/NPM_INSTALL_MARKER/.test(fs.readFileSync(phantomPath, 'utf8'))) { |
|
console.log('Looks like an `npm install -g`; unable to check for already installed version.') |
|
throw new Error('Global install') |
|
|
|
} else { |
|
var checkVersionDeferred = kew.defer() |
|
cp.execFile(phantomPath, ['--version'], checkVersionDeferred.makeNodeResolver()) |
|
return checkVersionDeferred.promise |
|
} |
|
}) |
|
.then(function (stdout) { |
|
var version = stdout.trim() |
|
if (helper.version == version) { |
|
writeLocationFile(phantomPath) |
|
console.log('PhantomJS is already installed at', phantomPath + '.') |
|
exit(0) |
|
|
|
} else { |
|
console.log('PhantomJS detected, but wrong version', stdout.trim(), '@', phantomPath + '.') |
|
throw new Error('Wrong version') |
|
} |
|
}) |
|
.fail(function (err) { |
|
// Trying to use a local file failed, so initiate download and install |
|
// steps instead. |
|
var npmconfDeferred = kew.defer() |
|
npmconf.load(npmconfDeferred.makeNodeResolver()) |
|
return npmconfDeferred.promise |
|
}) |
|
.then(function (conf) { |
|
tmpPath = findSuitableTempDirectory(conf) |
|
|
|
// Can't use a global version so start a download. |
|
if (process.platform === 'linux' && process.arch === 'x64') { |
|
downloadUrl += 'linux-x86_64.tar.bz2' |
|
} else if (process.platform === 'linux') { |
|
downloadUrl += 'linux-i686.tar.bz2' |
|
} else if (process.platform === 'darwin' || process.platform === 'openbsd') { |
|
downloadUrl += 'macosx.zip' |
|
} else if (process.platform === 'win32') { |
|
downloadUrl += 'windows.zip' |
|
} else { |
|
console.log('Unexpected platform or architecture:', process.platform, process.arch) |
|
exit(1) |
|
} |
|
|
|
var fileName = downloadUrl.split('/').pop() |
|
var downloadedFile = path.join(tmpPath, fileName) |
|
|
|
// Start the install. |
|
if (!fs.existsSync(downloadedFile)) { |
|
console.log('Downloading', downloadUrl) |
|
console.log('Saving to', downloadedFile) |
|
return requestBinary(getRequestOptions(conf.get('proxy')), downloadedFile) |
|
} else { |
|
console.log('Download already available at', downloadedFile) |
|
return downloadedFile |
|
} |
|
}) |
|
.then(function (downloadedFile) { |
|
return extractDownload(downloadedFile) |
|
}) |
|
.then(function (extractedPath) { |
|
return copyIntoPlace(extractedPath, pkgPath) |
|
}) |
|
.then(function () { |
|
var location = process.platform === 'win32' ? |
|
path.join(pkgPath, 'phantomjs.exe') : |
|
path.join(pkgPath, 'bin' ,'phantomjs') |
|
var relativeLocation = path.relative(libPath, location) |
|
writeLocationFile(relativeLocation) |
|
console.log('Done. Phantomjs binary available at', location) |
|
exit(0) |
|
}) |
|
.fail(function (err) { |
|
console.error('Phantom installation failed', err, err.stack) |
|
exit(1) |
|
}) |
|
|
|
|
|
function writeLocationFile(location) { |
|
console.log('Writing location.js file') |
|
if (process.platform === 'win32') { |
|
location = location.replace(/\\/g, '\\\\') |
|
} |
|
fs.writeFileSync(path.join(libPath, 'location.js'), |
|
'module.exports.location = "' + location + '"') |
|
} |
|
|
|
|
|
function exit(code) { |
|
process.env.PATH = originalPath |
|
process.exit(code || 0) |
|
} |
|
|
|
|
|
function findSuitableTempDirectory(npmConf) { |
|
var now = Date.now() |
|
var candidateTmpDirs = [ |
|
process.env.TMPDIR || process.env.TEMP || '/tmp', |
|
npmConf.get('tmp'), |
|
path.join(process.cwd(), 'tmp') |
|
] |
|
|
|
for (var i = 0; i < candidateTmpDirs.length; i++) { |
|
var candidatePath = path.join(candidateTmpDirs[i], 'phantomjs') |
|
|
|
try { |
|
mkdirp.sync(candidatePath, '0777') |
|
// Make double sure we have 0777 permissions; some operating systems |
|
// default umask does not allow write by default. |
|
fs.chmodSync(candidatePath, '0777') |
|
var testFile = path.join(candidatePath, now + '.tmp') |
|
fs.writeFileSync(testFile, 'test') |
|
fs.unlinkSync(testFile) |
|
return candidatePath |
|
} catch (e) { |
|
console.log(candidatePath, 'is not writable:', e.message) |
|
} |
|
} |
|
|
|
console.error('Can not find a writable tmp directory, please report issue ' + |
|
'on https://github.com/Obvious/phantomjs/issues/59 with as much ' + |
|
'information as possible.') |
|
exit(1) |
|
} |
|
|
|
|
|
function getRequestOptions(proxyUrl) { |
|
if (proxyUrl) { |
|
var options = url.parse(proxyUrl) |
|
options.path = downloadUrl |
|
options.headers = { Host: url.parse(downloadUrl).host } |
|
// If going through proxy, spoof the User-Agent, since may commerical proxies block blank or unknown agents in headers |
|
options.headers['User-Agent'] = 'curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5' |
|
// Turn basic authorization into proxy-authorization. |
|
if (options.auth) { |
|
options.headers['Proxy-Authorization'] = 'Basic ' + new Buffer(options.auth).toString('base64') |
|
delete options.auth |
|
} |
|
|
|
return options |
|
} else { |
|
return url.parse(downloadUrl) |
|
} |
|
} |
|
|
|
|
|
function requestBinary(requestOptions, filePath) { |
|
var deferred = kew.defer() |
|
|
|
var count = 0 |
|
var notifiedCount = 0 |
|
var writePath = filePath + '-download-' + Date.now() |
|
var outFile = fs.openSync(writePath, 'w') |
|
|
|
var client = http.get(requestOptions, function (response) { |
|
var status = response.statusCode |
|
console.log('Receiving...') |
|
|
|
if (status === 200) { |
|
response.addListener('data', function (data) { |
|
fs.writeSync(outFile, data, 0, data.length, null) |
|
count += data.length |
|
if ((count - notifiedCount) > 800000) { |
|
console.log('Received ' + Math.floor(count / 1024) + 'K...') |
|
notifiedCount = count |
|
} |
|
}) |
|
|
|
response.addListener('end', function () { |
|
console.log('Received ' + Math.floor(count / 1024) + 'K total.') |
|
fs.closeSync(outFile) |
|
fs.renameSync(writePath, filePath) |
|
deferred.resolve(filePath) |
|
}) |
|
|
|
} else { |
|
client.abort() |
|
console.error('Error requesting archive') |
|
deferred.reject(new Error('Error with http request: ' + util.inspect(response.headers))) |
|
} |
|
}) |
|
|
|
return deferred.promise |
|
} |
|
|
|
|
|
function extractDownload(filePath) { |
|
var deferred = kew.defer() |
|
// extract to a unique directory in case multiple processes are |
|
// installing and extracting at once |
|
var extractedPath = filePath + '-extract-' + Date.now() |
|
var options = {cwd: extractedPath} |
|
|
|
mkdirp.sync(extractedPath, '0777') |
|
// Make double sure we have 0777 permissions; some operating systems |
|
// default umask does not allow write by default. |
|
fs.chmodSync(extractedPath, '0777') |
|
|
|
if (filePath.substr(-4) === '.zip') { |
|
console.log('Extracting zip contents') |
|
|
|
try { |
|
var zip = new AdmZip(filePath) |
|
zip.extractAllTo(extractedPath, true) |
|
deferred.resolve(extractedPath) |
|
} catch (err) { |
|
console.error('Error extracting archive') |
|
deferred.reject(err) |
|
} |
|
|
|
} else { |
|
console.log('Extracting tar contents (via spawned process)') |
|
cp.execFile('tar', ['jxf', filePath], options, function (err, stdout, stderr) { |
|
if (err) { |
|
console.error('Error extracting archive') |
|
deferred.reject(err) |
|
} else { |
|
deferred.resolve(extractedPath) |
|
} |
|
}) |
|
} |
|
return deferred.promise |
|
} |
|
|
|
|
|
function copyIntoPlace(extractedPath, targetPath) { |
|
rimraf(targetPath) |
|
|
|
var deferred = kew.defer() |
|
// Look for the extracted directory, so we can rename it. |
|
var files = fs.readdirSync(extractedPath) |
|
for (var i = 0; i < files.length; i++) { |
|
var file = path.join(extractedPath, files[i]) |
|
if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) != -1) { |
|
console.log('Copying extracted folder', file, '->', targetPath) |
|
ncp(file, targetPath, deferred.makeNodeResolver()) |
|
break |
|
} |
|
} |
|
|
|
// Cleanup extracted directory after it's been copied |
|
return deferred.promise.then(function() { |
|
try { |
|
return rimraf(extractedPath) |
|
} catch (e) { |
|
console.warn('Unable to remove temporary files at "' + extractedPath + |
|
'", see https://github.com/Obvious/phantomjs/issues/108 for details.') |
|
} |
|
}); |
|
}
|
|
|