techno-express/node-unpack-all

View on GitHub
index.js

Summary

Maintainability
C
7 hrs
Test Coverage
'use strict';

// see http://unarchiver.c3.cx/commandline
// unar and lsar
const path = require('path');
const exec = require('child_process').exec;
const os = require('os');

const hasOwn = Object.prototype.hasOwnProperty;

/**
 * Create a new array by applying `callback` to each element in `xs`.
 *
 * @param {*} xs
 * @param {*} callback
 *
 * @returns Array
 */
function array_map(xs, callback) {
    if (xs.array_map) return xs.array_map(callback);
    let res = [];
    for (let i = 0; i < xs.length; i++) {
        let x = xs[i];
        if (hasOwn.call(xs, i)) res.push(callback(x, i, xs));
    }

    return res;
};

// from http://github.com/substack/node-shell-quote, needed to remove more escaping
function quote(xs) {
    return array_map(xs, function (s) {
        return String(s).replace(/([#!"$&'(),;<=>?@\[\\\]^`{|}])/g, '\\$1');
    }).join(' ');
};
//

let archiveTypePattern = /: [A-Z,7]*$/g;

let escapeFileName = function (s) {
    return '"' + s + '"';
    //if (isWindows()) return '"'+s+'"';
    //// '"'+cmd.replace(/(["\s'$`\\])/g,'\\$1')+'"'
    //return s;
};

const isInt = function isInt(x) {
    return !isNaN(x) && eval(x).toString().length == parseInt(eval(x)).toString().length;
};


unpackAll.defaultListFilter = function defaultListFilter(s) {
    return s && s != '' &&
        s.indexOf('\r') == -1 &&
        s.indexOf('\n') == -1 &&
        !s.match(archiveTypePattern);
};

function exec_unar(runCmd, extractDir, doCallback) {
    exec(runCmd, function (err, stdout, stderr) {
        if (err) return doCallback(Error(err), null);
        if (stderr && stderr.length > 0) return doCallback(Error('Error: ' + stderr), null);
        if (stdout && stdout.length > 0) {
            if (stdout.indexOf('No files extracted') >= 1) return doCallback(Error('Error: No files extracted'), null);
        }
        return doCallback(null, extractDir, stdout);
    });
}

unpackAll.unpack = function unpack(archiveFile, options, callback) {
    if (!callback) return new Error('No callback function');
    if (!archiveFile) archiveFile = options.archiveFile;
    if (!archiveFile) return callback(Error("Error: archiveFile or options.archiveFile missing."), null);
    if (!options) options = {};

    // Unar command:
    let unar = options.unar;
    if (!unar) unar = (process.platform != "linux") ? path.join(__dirname, 'unar') : 'unar';
    let ar = [unar];

    // Archive file (source):
    ar.push('SOURCEFILE');
    //ar.push(archiveFile);

    // -output-directory (-o) <string>: The directory to write the contents of the archive to. Defaults to the current directory.
    ar.push('-o');
    var targetDir = options.targetDir;
    if (!targetDir) targetDir = path.join(os.tmpdir(), 'tmp');
    ar.push(targetDir);

    // -force-overwrite (-f): Always overwrite files when a file to be unpacked already exists on disk. By default, the program asks the user if possible, otherwise skips the file.
    if (options.forceOverwrite) ar.push('-f');

    // -force-rename (-r): Always rename files when a file to be unpacked already exists on disk.
    if (options.forceRename) ar.push('-r');

    // -force-skip (-s): Always skip files when a file to be unpacked already exists on disk.
    if (options.forceSkip) ar.push('-s');

    // -force-directory (-d): Always create a containing directory for the contents of the unpacked archive. By default, a directory is created if there is more than one top-level file or folder.
    if (options.forceDirectory) ar.push('-d');

    // -no-directory (-D): Never create a containing directory for the contents of the unpacked archive.
    if (options.noDirectory) ar.push('-D');

    // -no-recursion (-nr): Do not attempt to extract archives contained in other archives. For instance, when unpacking a .tar.gz file, only unpack the .gz file and not its contents.
    if (options.noRecursion) ar.push('-nr');

    // -copy-time (-t): Copy the file modification time from the archive file to the containing directory, if one is created.
    if (options.copyTime) ar.push('-t');

    // -quiet (-q): Run in quiet mode.
    if (options.quiet) ar.push('-q');

    // -password (-p) <string>: The password to use for decrypting protected archives.
    if (options.password) {
        ar.push('-p');
        ar.push(options.password);
    }
    // -password-encoding (-E) <name>: The encoding to use for the password for the archive, when it is not known. If not specified, then either the encoding given by the -encoding option or the auto-detected encoding is used.
    if (options.passwordEncoding) {
        ar.push('-E');
        ar.push(options.passwordEncoding);
    }

    // -encoding (-e) <encoding name>: The encoding to use for filenames in the archive, when it is not known. If not specified, the program attempts to auto-detect the encoding used. Use "help" or "list" as the argument to give
    if (options.encoding) {
        ar.push('-e');
        ar.push(options.encoding);
    }

    if (options.indexes) {
        // -indexes (-i): Instead of specifying the files to unpack as filenames or wildcard patterns, specify them as indexes, as output by lsar.
        if (Array.isArray(options.indexes)) {
            options.indexes.forEach(function (idx) {
                ar.push('-i');
                ar.push('' + idx); // string!
            });
        } else if (isInt(options.indexes)) {
            console.error('options.indexes must be an array of integer, but it is: ' + JSON.stringify(options.indexes))
        }
    } else if (options.files) {
        if (Array.isArray(options.files)) {
            options.files.forEach(function (s) {
                ar.push(s);
            });
        } else {
            ar.push(options.files);
        }
    }

    if (!options.quiet) console.info('command', quote(ar));

    let cmd = quote(ar).replace('SOURCEFILE', escapeFileName(archiveFile));
    if (!options.quiet) console.info('cmd', cmd);
    exec_unar(cmd, targetDir, callback);
}; // unpackAll.unpack

unpackAll.unpackonly = function unpackonly(archiveFile, unpackDir, unpackOnly, callback) {
    if (!callback) return new Error('No callback function');
    if (!archiveFile) return callback(Error("Error: archiveFile missing."), null);
    if (!unpackDir) return callback(Error("Error: target Directory missing."), null);
    if (!unpackOnly) return callback(Error("Error: files or directory to extract form archive missing."), null);

    // Unar command:
    let unar = (process.platform != "linux") ? path.join(__dirname, 'unar') : 'unar';
    let ar = [unar];

    // Archive file (source):
    ar.push('SOURCEFILE');
    //ar.push(archiveFile);

    // -output-directory (-o) <string>: The directory to write the contents of the archive to. Defaults to the current directory.
    ar.push('-o');
    ar.push(unpackDir);

    // -force-overwrite (-f): Always overwrite files when a file to be unpacked already exists on disk. By default, the program asks the user if possible, otherwise skips the file.
    ar.push('-f');

    // -no-directory (-D): Never create a containing directory for the contents of the unpacked archive.
    ar.push('-D');

    // -copy-time (-t): Copy the file modification time from the archive file to the containing directory, if one is created.
    ar.push('-t');

    if (unpackOnly) {
        if (Array.isArray(unpackOnly)) {
            unpackOnly.forEach(function (s) {
                ar.push(s);
            });
        } else {
            ar.push(unpackOnly);
        }
    }

    let cmd = quote(ar).replace('SOURCEFILE', escapeFileName(archiveFile));
    console.info('cmd', cmd);
    exec_unar(cmd, unpackDir, callback);
}; // unpackAll.unpackonly

unpackAll.list = function list(archiveFile, options, callback) {
    if (!callback) return new Error('No callback function');
    if (!archiveFile) archiveFile = options.archiveFile;
    if (!archiveFile) return callback(Error("Error: archiveFile or options.archiveFile missing."), null);

    if (!options) options = {};

    // Usar command:
    let lsar = options.lsar;
    if (!lsar) lsar = (process.platform != "linux") ? path.join(__dirname, 'lsar') : 'lsar';
    let ar = [lsar];

    // Archive file (source):
    ar.push('SOURCEFILE');

    // -no-recursion (-nr): Do not attempt to extract archives contained in other archives. For instance, when unpacking a .tar.gz file, only unpack the .gz file and not its contents.
    if (options.noRecursion) ar.push('-nr');

    // -password (-p) <string>: The password to use for decrypting protected archives.
    if (options.password) {
        ar.push('-p');
        ar.push(options.password);
    }
    // -password-encoding (-E) <name>: The encoding to use for the password for the archive, when it is not known. If not specified, then either the encoding given by the -encoding option or the auto-detected encoding is used.
    if (options.passwordEncoding) {
        ar.push('-E');
        ar.push(options.passwordEncoding);
    }

    // -encoding (-e) <encoding name>: The encoding to use for filenames in the archive, when it is not known. If not specified, the program attempts to auto-detect the encoding used. Use "help" or "list" as the argument to give
    if (options.encoding) {
        ar.push('-e');
        ar.push(options.encoding);
    }

    // -print-encoding (-pe): Print the auto-detected encoding and the confidence factor after the file list
    if (options.printEncoding) {
        ar.push('-pe');
        ar.push(options.printEncoding);
    }

    // -json (-j): Print the listing in JSON format.
    if (options.json) ar.push('-j');

    // -json-ascii (-ja): Print the listing in JSON format, encoded as pure ASCII text.
    if (options.jsonAscii) ar.push('-ja');

    let cmd = quote(ar).replace('SOURCEFILE', escapeFileName(archiveFile));
    if (!options.quiet) console.info('cmd', cmd);
    exec(cmd, function (err, stdout, stderr) {
        if (err) return callback(Error(err), null);
        if (stderr && stderr.length > 0) return callback(Error('Error: ' + stderr), null);

        let lines = stdout.split(/(\r?\n)/g);
        if (lines.length > 0) {
            let files = lines.filter(unpackAll.defaultListFilter);
            return callback(null, files);

        } else {
            return callback(Error('Error: no files found in archive. ' + stderr), null);
        }
    });
}; // unpackAll.list

function unpackAll() {}

module.exports = exports = unpackAll;
exports.default = exports;