UNPKG

ljswitchboard-package_loader

Version:

A small node/node-webkit project loader for ljswitchboard that loads files into the global.ljswitchboard namespace

213 lines (182 loc) 7.33 kB
var fs = require('fs'); var path = require('path'); var async = require('async'); var yauzl = require('yauzl'); var mkdirp = require('fs-extra').mkdirp; var concat = require('concat-stream'); var q = require('q'); var enableDebugging = false; var debug = function() { if(enableDebugging) { console.log.apply(this, arguments); } }; var enableDebuggingFileExtraction = false; var debugFileExtract = function() { if(enableDebuggingFileExtraction) { console.log.apply(this, arguments); } }; var logError = function() { console.error.apply(this, arguments); }; function extractWithYauzl (bundle, self, EVENTS) { var from = bundle.chosenUpgrade.location; var to = bundle.currentPackage.location; debug('opening', from, 'extracting to', to); // Emit events indicating that a zip file extraction has started self.emit(EVENTS.STARTING_EXTRACTION, bundle); self.emit(EVENTS.STARTING_ZIP_FILE_EXTRACTION, bundle); var defered = q.defer(); var resolveError = function(err) { logError(' - Error performZipFileUpgrade', err, bundle.name); var msg = 'Error performing a .zip file upgrade. Verify ' + 'the user-permissions for the directory and .zip file: ' + upgradeZipFilePath + ', and ' + destinationPath; bundle.resultMessages.push({ 'step': 'performDirectoryUpgrade-copyRecursive', 'message': msg, 'isError': true, 'error': JSON.stringify(err) }); bundle.overallResult = false; bundle.isError = true; // Emit events indicating that a zip file extraction has finished // w/ an error self.emit(EVENTS.FINISHED_EXTRACTION_ERROR, bundle); self.emit(EVENTS.FINISHED_ZIP_FILE_EXTRACTION_ERROR, bundle); defered.resolve(bundle); }; var resolveSuccess = function() { // Emit events indicating that a zip file extraction has finished self.emit(EVENTS.FINISHED_EXTRACTION, bundle); self.emit(EVENTS.FINISHED_ZIP_FILE_EXTRACTION, bundle); defered.resolve(bundle); }; yauzl.open(from, {autoClose: false}, function(err, zipfile) { if (err) { console.error('Error opening zip', err); resolveError(err); return; } var cancelled = false; var finished = false; var asyncQueue = async.queue(extractEntry, 1); function drain() { if (!finished){ return; } debug('zip extraction complete'); resolveSuccess(); }; try { asyncQueue.drain(drain); asyncQueue.error(function(err, task) { console.error('!!! Task experienced an error', err, task); resolveError(err); }) } catch(err) { asyncQueue.drain = drain; } zipfile.on("entry", function(entry) { debug('zipfile entry', entry.fileName); if (/\/$/.test(entry.fileName)) { // directory file names end with '/' return; } if (/^__MACOSX\//.test(entry.fileName)) { // dir name starts with __MACOSX/ return; } asyncQueue.push(entry, function(err) { debug('finished processing', entry.fileName, {err: err}); }); }); zipfile.on('error', function(err) { console.error('Error opening zip', err); resolveError(err); }); zipfile.on('end', function() { finished = true; }); function extractEntry(entry, done) { if (cancelled) { debug('skipping entry', entry.fileName, {cancelled: cancelled}); return setImmediate(done); } else { debug('extracting entry', entry.fileName, to); } var dest = path.join(to, entry.fileName); var destDir = path.dirname(dest); // convert external file attr int into a fs stat mode int var mode = (entry.externalFileAttributes >> 16) & 0xFFFF; // check if it's a symlink or dir (using stat mode constants) var IFMT = 61440; var IFDIR = 16384; var IFLNK = 40960; var symlink = (mode & IFMT) === IFLNK; var isDir = (mode & IFMT) === IFDIR; // if no mode then default to readable if (mode === 0) { if (isDir) mode = 0555; else mode = 0444; } // reverse umask first (~) var umask = ~process.umask(); // & with processes umask to override invalid perms var procMode = mode & umask; zipfile.openReadStream(entry, function(err, readStream) { debugFileExtract('openReadStream opened', err); if (err) { logError('openReadStream error', err); cancelled = true; return done(err); } readStream.on('error', function(err) { logError('readStream error', err); }); mkdirp(destDir, function(err) { debugFileExtract('made directory'); if (err) { logError('mkdirp error', destDir, {error: err}); cancelled = true; return done(err); } if (symlink) writeSymlink(); else writeStream(); }); function writeStream() { debugFileExtract('writing file', dest); var createdWriteStream = fs.createWriteStream(dest, {mode: procMode}); readStream.pipe(createdWriteStream); createdWriteStream.on('finish', function() { try { readStream.destroy(); } catch(err) { // ignore this error. } done(); }); createdWriteStream.on('error', function(err) { logError('write error', {error: err}); cancelled = true; return done(err); }); } // AFAICT the content of the symlink file itself is the symlink target filename string function writeSymlink() { readStream.pipe(concat(function(data) { var link = data.toString(); debug('creating symlink', link, dest); fs.symlink(link, dest, function(err) { if (err) cancelled = true; done(err); }); })); } }); } }); return defered.promise; } exports.extractWithYauzl = extractWithYauzl;