123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- /*
- Copyright (c) 2012, Yahoo! Inc. All rights reserved.
- Code licensed under the BSD License:
- http://yuilibrary.com/license/
- */
- var fs = require('graceful-fs');
- var Stack = require('./stack').Stack;
- var path = require('path');
- var rimraf = require('rimraf');
- var mkdirp = require('mkdirp');
- var getTree = function(from, options, callback) {
- var stack = new Stack(),
- errors = [],
- results = {};
- options.stats = options.stats || {};
- options.toHash = options.toHash || {};
- fs.readdir(from, stack.add(function(err, dirs) {
- if (!dirs.length) {
- results[from] = true;
- fs.stat(from, stack.add(function(err, stat) {
- /*istanbul ignore next*/
- if (err) {
- return errors.push(err);
- }
- options.stats[from] = stat;
- options.toHash[from] = path.join(options.to, path.relative(options.from, from));
- }));
- }
- dirs.forEach(function (dir) {
- var base = path.join(from, dir);
- fs.stat(base, stack.add(function(err, stat) {
- options.stats[base] = stat;
- options.toHash[base] = path.join(options.to, path.relative(options.from, base));
- /*istanbul ignore next*/
- if (err) {
- return errors.push(err);
- }
- if (stat.isDirectory()) {
- getTree(base, options, stack.add(function(errs, tree) {
- /*istanbul ignore next*/
- if (errs && errs.length) {
- errs.forEach(function(item) {
- errors.push(item);
- });
- }
- //tree is always an Array
- tree.forEach(function(item) {
- results[item] = true;
- });
- }));
- } else {
- results[base] = true;
- }
- }));
- });
- }));
- stack.done(function() {
- callback(errors, Object.keys(results).sort());
- });
- };
- var filterTree = function (tree, options, callback) {
- var t = tree;
- if (options.filter) {
- if (typeof options.filter === 'function') {
- t = tree.filter(options.filter);
- } else if (options.filter instanceof RegExp) {
- t = tree.filter(function(item) {
- return !options.filter.test(item);
- });
- }
- }
- callback(null, t);
- };
- var splitTree = function (tree, options, callback) {
- var files = {},
- dirs = {};
- tree.forEach(function(item) {
- var to = options.toHash[item];
- if (options.stats[item] && options.stats[item].isDirectory()) {
- dirs[item] = true;
- } else {
- dirs[path.dirname(item)] = true;
- options.stats[path.dirname(item)] = fs.statSync(path.dirname(item));
- options.toHash[path.dirname(item)] = path.dirname(to);
- }
- });
- tree.forEach(function(item) {
- if (!dirs[item]) {
- files[item] = true;
- }
- });
- callback(Object.keys(dirs).sort(), Object.keys(files).sort());
- };
- var createDirs = function(dirs, to, options, callback) {
- var stack = new Stack();
- dirs.forEach(function(dir) {
- var stat = options.stats[dir],
- to = options.toHash[dir];
- /*istanbul ignore else*/
- if (to && typeof to === 'string') {
- fs.stat(to, stack.add(function(err, s) {
- if (s && !s.isDirectory()) {
- /*istanbul ignore next*/
- err = new Error(to + ' exists and is not a directory, can not create');
- /*istanbul ignore next*/
- err.code = 'ENOTDIR';
- /*istanbul ignore next*/
- err.errno = 27;
- options.errors.push(err);
- } else {
- mkdirp(to, stat.mode, stack.add(function(err) {
- /*istanbul ignore next*/
- if (err) {
- options.errors.push(err);
- }
- }));
- }
- }));
- }
- });
- stack.done(function() {
- callback();
- });
- };
- var copyFile = function(from, to, options, callback) {
- var dir = path.dirname(to);
- mkdirp(dir, function() {
- fs.stat(to, function(statError) {
- var err;
- if(!statError && options.overwrite !== true) {
- /*istanbul ignore next*/
- err = new Error('File '+to+' exists');
- /*istanbul ignore next*/
- err.code = 'EEXIST';
- /*istanbul ignore next*/
- err.errno = 47;
- return callback(err);
- }
- var fromFile = fs.createReadStream(from),
- toFile = fs.createWriteStream(to, {
- mode: options.stats[from].mode
- }),
- called = false,
- cb = function(e) {
- /*istanbul ignore next - This catches a hard to trap race condition */
- if (!called) {
- callback(e);
- called = true;
- }
- },
- /*istanbul ignore next*/
- onError = function (e) {
- err = e;
- cb(e);
- };
- fromFile.on('error', onError);
- toFile.on('error', onError);
- toFile.once('finish', function() {
- cb(err);
- });
- fromFile.pipe(toFile);
- });
- });
- };
- var createFiles = function(files, to, options, callback) {
- var next = process.nextTick,
- complete = 0,
- count = files.length,
- check = function() {
- /*istanbul ignore else - Shouldn't need this if graceful-fs does it's job*/
- if (count === complete) {
- callback();
- }
- },
- copy = function() {
- var from = files.pop(),
- to = options.toHash[from],
- bail;
- if (!from) {
- return check();
- }
- copyFile(from, to, options, function(err) {
- /*istanbul ignore next*/
- //This shouldn't happen with graceful-fs, but just in case
- if (/EMFILE/.test(err)) {
- bail = true;
- files.push(from);
- } else if (err) {
- options.errors.push(err);
- }
- /*istanbul ignore next*/
- if (!bail) {
- complete++;
- }
- next(copy);
- });
- };
- copy();
- };
- var confirm = function(files, options, callback) {
- var stack = new Stack(),
- errors = [],
- f = [],
- filtered = files;
- if (options.filter) {
- if (typeof options.filter === 'function') {
- filtered = files.filter(options.filter);
- } else if (options.filter instanceof RegExp) {
- filtered = files.filter(function(item) {
- return !options.filter.test(item);
- });
- }
- }
- /*istanbul ignore else - filtered should be an array, but just in case*/
- if (filtered.length) {
- filtered.forEach(function(file) {
- fs.stat(file, stack.add(function(err, stat) {
- /*istanbul ignore next*/
- if (err) {
- errors.push(err);
- } else {
- if (stat && (stat.isFile() || stat.isDirectory())) {
- f.push(file);
- }
- }
- }));
- });
- }
- stack.done(function() {
- /*istanbul ignore next */
- callback(((errors.length) ? errors : null), f.sort());
- });
- };
- var cpr = function(from, to, opts, callback) {
- if (typeof opts === 'function') {
- callback = opts;
- opts = {};
- }
- var options = {},
- proc;
- /*istanbul ignore next - in case a callback isn't provided*/
- callback = callback || function () {};
- Object.keys(opts).forEach(function(key) {
- options[key] = opts[key];
- });
- options.from = from;
- options.to = to;
- options.errors = [];
- proc = function() {
- getTree(options.from, options, function(err, tree) {
- filterTree(tree, options, function(err, t) {
- splitTree(t, options, function(dirs, files) {
- if (!dirs.length && !files.length) {
- return callback(new Error('No files to copy'));
- }
- createDirs(dirs, to, options, function() {
- createFiles(files, to, options, function() {
- var out = [], err;
- Object.keys(options.toHash).forEach(function(k) {
- out.push(options.toHash[k]);
- });
- if (options.confirm) {
- confirm(out, options, callback);
- } else if (!options.errors.length) {
- callback(null, out.sort());
- } else {
- /*istanbul ignore next*/
- err = new Error('Unable to copy directory' + (out.length ? ' entirely' : ''));
- /*istanbul ignore next*/
- err.list = options.errors;
- /*istanbul ignore next*/
- callback(err, out.sort());
- }
- });
- });
- });
- });
- });
- };
- fs.stat(options.from, function(err, stat) {
- if (err) {
- return callback(new Error('From should be a file or directory'));
- }
- if (stat && stat.isDirectory()) {
- if (options.deleteFirst) {
- rimraf(to, function() {
- proc();
- });
- } else {
- proc();
- }
- } else {
- if (stat.isFile()) {
- var dirRegex = new RegExp(path.sep + '$');
- if (dirRegex.test(to)) { // Create directory if has trailing separator
- to = path.join(to, path.basename(options.from));
- }
- // ensure copyFile() can access cached stat
- options.stats = options.stats || {};
- options.stats[from] = stat;
- return copyFile(options.from, to, options, callback);
- }
- callback(new Error('From should be a file or directory'));
- }
- });
- };
- //Preserve backward compatibility
- cpr.cpr = cpr;
- //Export a function
- module.exports = cpr;
|