alinex-fs
Version:
Extension of nodes filesystem tools.
344 lines (316 loc) • 11.4 kB
JavaScript
/*
Copy Files
=================================================
This will copy a single file, complete directory or selection from directory.
It will make exact copies of the files as far as possible including times, ownership
and access modes. But if some of this rights are not possible to set it will be ignored
without an explicit error.
To select which files to copy and how to work you can use the following options:
- `filter` - `Array<Object>|Object` {@link filter.coffee}
- `overwrite` - `Boolean` if set to `true` it will not fail if destination file
already exists and overwrite it
- `ignore` - `Boolean` it will not fail if destination file already exists
but skip this and go on with the next file
- `noempty` - `Boolean` set to `true to don't create empty directories while no
files to copy into`
- `dereference` - `Boolean` dereference symbolic links and go into them
- `ìgnoreErrors` - `Boolean` go on and ignore IO errors
- `parallel` - `Integer` number of maximum parallel calls in asynchronous run
(defaults to half of open files limit per process on the system)
__Example:__
``` coffee
fs = require 'alinex-fs'
fs.copy '/tmp/some/directory', '/new/destination', (err) ->
return console.error err if err
console.log "Directory copied!"
```
Or to copy all js files and overwrite existing:
``` coffee
fs = require 'alinex-fs'
fs.copy '/tmp/some/directory', '/new/destination',
filter:
include: '*.js'
overwrite: true
, (err) ->
return console.error err.message if err
console.log "Directory copied!"
```
*/
(function() {
var async, copyFile, copyFileSync, copySync, debug, filter, fs, mkdirs, parallel, path;
debug = require('debug')('fs:copy');
fs = require('fs');
path = require('path');
async = require('async');
mkdirs = require('./mkdirs');
filter = require('../helper/filter');
parallel = require('../helper/parallel');
/*
@param {String} source path or file to be copied
@param {String} target file or directory to copy to
@param {Object} [options] specifications for check defining which files
to copy
@param {function(Error, Array<String>)} [cb] callback with list of newly created
files and directly created directories or possible `Èrror`:
- Target file already exists: xxxxx
*/
module.exports.copy = function(source, target, options, cb) {
var list, queue;
if (debug.enabled) {
debug("start copy: " + source + " -> " + target);
}
if (cb == null) {
cb = function() {};
}
if (typeof options === 'function' || !options) {
cb = options != null ? options : function() {};
options = {};
}
list = [];
queue = async.queue(function(task, cb) {
if (debug.enabled) {
debug("check " + task.source);
}
return async.nextTick(function() {
return filter.filter(task.source, task.depth, options, function(ok) {
var stat;
if (ok === void 0) {
return cb();
}
stat = options.dereference != null ? fs.stat : fs.lstat;
return stat(task.source, function(err, stats) {
if (err) {
return cb((options != null ? options.ignoreErrors : void 0) ? null : err);
}
task.target = target + task.source.slice(source.length);
if (stats.isFile()) {
if (!ok) {
return cb();
}
return mkdirs.mkdirs(path.dirname(task.target), function(err) {
if (err) {
return cb(err);
}
return fs.exists(task.target, function(exists) {
if (exists && !(options.overwrite || options.ignore)) {
return cb(new Error("Target file already exists: " + task.target));
}
if (!(!exists || options.overwrite)) {
return cb();
}
if (debug.enabled) {
debug("copying file " + task.source + " to " + task.target);
}
list.push(task.target);
return copyFile(task.source, stats, task.target, cb);
});
});
} else if (stats.isSymbolicLink()) {
if (!ok) {
return cb();
}
return mkdirs.mkdirs(path.dirname(task.target), function(err) {
if (err) {
return cb(err);
}
return fs.exists(task.target, function(exists) {
if (exists && !(options.overwrite || options.ignore)) {
return cb(new Error("Target file already exists: " + task.target));
}
if (!(!exists || options.overwrite)) {
return cb();
}
if (debug.enabled) {
debug("copying link " + task.source + " to " + task.target);
}
return fs.readlink(task.source, function(err, resolvedPath) {
if (err) {
return cb(err);
}
list.push(task.target);
return fs.symlink(resolvedPath, task.target, cb);
});
});
});
} else {
if (debug.enabled) {
debug("going deeper into " + task.source + " directory");
}
task.depth++;
return fs.readdir(task.source, function(err, files) {
var file, i, len;
if (err) {
return cb(err);
}
for (i = 0, len = files.length; i < len; i++) {
file = files[i];
queue.push({
source: task.source + "/" + file,
depth: task.depth
});
}
if (options.noempty) {
return cb();
}
if (!ok) {
return cb();
}
return fs.exists(task.target, function(exists) {
if (exists && !(options.overwrite || options.ignore)) {
return cb(new Error("Target file already exists: " + task.target));
}
if (!(!exists || options.overwrite)) {
return cb();
}
list.push(task.target);
return mkdirs.mkdirs(task.target, cb);
});
});
}
});
});
});
}, parallel(options));
queue.push({
source: source,
depth: 0
});
queue.drain = function() {
list.sort();
return cb(null, list);
};
return queue.error = function(err) {
queue.kill();
cb(err);
return cb = function() {};
};
};
/*
@param {String} source path or file to be copied
@param {String} target file or directory to copy to
@param {Object} [options] specifications for check defining which files to copy
@throws {Error} if anything out of order happened
- Target file already exists: xxxxxxxxxxxxxxxxx
@return {Array<String>} list of newly created files and directly created directories
@internal The `depth` parameter is only used internally.
@param {Integer} [depth=0] current depth in file tree
*/
copySync = module.exports.copySync = function(source, target, options, depth) {
var error, exists, file, i, len, list, ok, ref, resolvedPath, stat, stats;
if (options == null) {
options = {};
}
if (depth == null) {
depth = 0;
}
if (debug.enabled) {
debug("start copy: " + source + " -> " + target);
}
stat = options.dereference != null ? fs.statSync : fs.lstatSync;
list = [];
try {
stats = stat(source);
} catch (error1) {
error = error1;
if (options.ignoreErrors) {
return list;
}
throw error;
}
ok = filter.filterSync(source, depth, options);
if (ok) {
list.push(target);
}
if (stats.isFile()) {
if (!ok) {
return list;
}
mkdirs.mkdirsSync(path.dirname(target));
exists = fs.existsSync(target);
if (exists && !(options.overwrite || options.ignore)) {
throw new Error("Target file already exists: " + target);
}
if (!exists || options.overwrite) {
if (debug.enabled) {
debug("copying file " + source + " to " + target);
}
copyFileSync(source, stats, target);
}
} else if (stats.isSymbolicLink()) {
if (!ok) {
return list;
}
mkdirs.mkdirsSync(path.dirname(target));
resolvedPath = fs.readlinkSync(source);
if (debug.enabled) {
debug("copying link " + source + " to " + target);
}
list.push(target);
fs.symlinkSync(resolvedPath, target);
} else {
depth++;
if (ok && !options.noempty) {
mkdirs.mkdirsSync(target, stats.mode);
}
if (debug.enabled) {
debug("copying directory " + source + " to " + target);
}
ref = fs.readdirSync(source);
for (i = 0, len = ref.length; i < len; i++) {
file = ref[i];
list = list.concat(copySync(path.join(source, file), path.join(target, file), options, depth));
}
}
list.sort();
return list;
};
copyFile = function(source, stats, target, cb) {
var done, rs, ws;
done = function(err) {
var cbCalled;
if (!cbCalled) {
if (err) {
return cb(err);
}
fs.utimes(target, stats.atime, stats.mtime, function() {
return fs.chown(target, stats.uid, stats.gid, function() {
return fs.chmod(target, stats.mode, function() {
return cb();
});
});
});
}
return cbCalled = true;
};
rs = fs.createReadStream(source);
ws = fs.createWriteStream(target, {
mode: stats.mode
});
ws.on('error', done);
ws.on('close', done);
return rs.pipe(ws);
};
copyFileSync = function(source, stats, target) {
fs.writeFileSync(target, fs.readFileSync(source));
fs.utimesSync(target, stats.atime, stats.mtime);
fs.chownSync(target, stats.uid, stats.gid);
return fs.chmodSync(target, stats.mode);
};
/*
Debugging
---------------------------------------------------------
This module uses the {@link debug} module so you may anytime call your app with
the environment setting `DEBUG=fs:copy` for the output of this method only.
Because there are `mkdirs` subcalls here you see the output of `DEBUG=fs:*` while
copying a small directory:
fs:copy check test/temp/dir3 +32ms
fs:copy going deeper into test/temp/dir3 directory +1ms
fs:mkdirs directory /home/alex/github/node-fs/test/temp/dir4? +0ms
fs:copy check test/temp/dir3/file11 +0ms
fs:mkdirs directory /home/alex/github/node-fs/test/temp/dir4 created +0ms
fs:mkdirs directory /home/alex/github/node-fs/test/temp/dir4? +0ms
fs:mkdirs -> directory /home/alex/github/node-fs/test/temp/dir4 was already there +0ms
fs:copy copying file test/temp/dir3/file11 to test/temp/dir4/file11 +0ms
*/
}).call(this);
//# sourceMappingURL=copy.map