alinex-fs
Version:
Extension of nodes filesystem tools.
278 lines (251 loc) • 8.45 kB
JavaScript
/*
Remove Files
=================================================
This method will remove the given `path` entry and if it is a directory it
will also remove any containing data or only the selection of files.
The option `maxdepth` is only supported in the search, but if a directory is
matched everything within will be deleted.
This method will remove the given `path` entry and if it is a directory it
will also remove any containing data. If some filter given you can also delete#
selectively.
To select which files to remove the following options may be used:
- `filter` - `Array<Object>|Object` {@link filter.coffee}
- `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.remove '/tmp/some/directory', (err, removed) ->
return console.error err if err
if removed
console.log "Directory '"+removed+"' was removed with all it's contents."
console.log "Directory no longer exists!"
```
*/
(function() {
var PARALLEL, async, debug, filter, fs, path, posix, removeFile, removeSync;
debug = require('debug')('fs:remove');
path = require('path');
async = require('async');
fs = require('fs');
posix = require('posix');
filter = require('../helper/filter');
PARALLEL = Math.floor(posix.getrlimit('nofile').soft / 2);
/*
@param {String} source directory or file to be deleted
@param {Object} [options] specifications for check defining which files to remove
@param {function(Error, Array<String>)} [cb] callback which is called after done with possible
`Èrror` or with the files/directories been deleted
*/
module.exports.remove = function(source, options, cb) {
var list, queue, ref;
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.setImmediate(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 && (err.code === 'ENOENT' || options.ignoreErrors)) {
return cb();
}
if (err) {
return cb((options != null ? options.ignoreErrors : void 0) ? null : err);
}
if (ok) {
removeFile(task.source, options, function(err) {
if (err && (err.code === 'ENOENT' || options.ignoreErrors)) {
return cb();
}
return cb(err);
});
return list.push(task.source);
}
if (!stats.isDirectory()) {
return cb(null, list);
}
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
});
}
return cb();
});
});
});
});
}, (ref = options.parallel) != null ? ref : PARALLEL);
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} path directory or file to be deleted
@param {Object} [options] specifications for check defining which files to remove
@return {Array<String>} the files or directories been deleted
@throws {Error} if domething went wrong
@internal The `depth` parameter is only used internally.
@param {Integer} [depth=0] current depth in file tree
*/
removeSync = module.exports.removeSync = function(file, options, depth) {
var dir, error, files, i, len, list, ok, stat, stats;
if (options == null) {
options = {};
}
if (depth == null) {
depth = 0;
}
stat = options.dereference != null ? fs.statSync : fs.lstatSync;
list = [];
try {
stats = stat(file);
} catch (error1) {
error = error1;
if (error.code === 'ENOENT' || options.ignoreErrors) {
return list;
}
throw error;
}
ok = filter.filterSync(file, depth, options);
if (ok) {
list.push(file);
}
if (stats.isFile()) {
if (!ok) {
return list;
}
if (debug.enabled) {
debug("removing file " + file);
}
fs.unlinkSync(file);
} else if (stats.isSymbolicLink()) {
if (!ok) {
return list;
}
if (debug.enabled) {
debug("removing link " + file);
}
fs.unlinkSync(file);
} else if (stats.isDirectory()) {
dir = file;
depth++;
if (debug.enabled) {
debug("removing directory " + file);
}
if (ok) {
options = {};
}
files = fs.readdirSync(file);
for (i = 0, len = files.length; i < len; i++) {
file = files[i];
list = list.concat(removeSync(path.join(dir, file), options, depth));
}
if (!ok) {
return;
}
try {
fs.rmdirSync(dir);
} catch (error1) {}
try {
fs.unlinkSync(dir);
} catch (error1) {}
} else {
throw new Error("Entry '" + file + "' is no directory, file or symbolic link.");
}
list.sort();
return list;
};
removeFile = function(source, options, cb) {
var stat;
stat = options.dereference != null ? fs.stat : fs.lstat;
return stat(source, function(err, stats) {
if (err && (err.code === 'ENOENT' || options.ignoreErrors)) {
return cb();
}
if (stats.isFile()) {
if (debug.enabled) {
debug("removing " + source + " (file)");
}
return fs.unlink(source, cb);
} else if (stats.isSymbolicLink()) {
if (debug.enabled) {
debug("removing " + source + " (link)");
}
return fs.unlink(source, cb);
} else if (stats.isDirectory()) {
if (debug.enabled) {
debug("removing " + source + " (directory)");
}
return fs.readdir(source, function(err, files) {
var ref;
if (err) {
return cb(err);
}
return async.eachLimit(files, (ref = options.parallel) != null ? ref : PARALLEL, function(file, cb) {
return removeFile(source + "/" + file, options, cb);
}, function(err) {
if (err) {
return cb(err);
}
return fs.rmdir(source, function(err) {
if ((err != null ? err.code : void 0) === 'ENOTDIR') {
return fs.unlink(source, cb);
}
return cb(err);
});
});
});
} else {
return cb(new Error("Entry '" + source + "' is no directory, file or symbolic link."));
}
});
};
/*
Debugging
---------------------------------------------------------
This module uses the {@link debug} module so you may anytime call your app with
the environment setting `DEBUG=fs:remove` for the output of this method only.
Because there are `mkdirs` subcalls here you see the output of `DEBUG=fs:*` while
removeing a small directory:
fs:remove check test/temp/dir1 +29ms
fs:filter skip test/temp/dir1 because not in specified depth +1ms
fs:remove going deeper into test/temp/dir1 directory +0ms
fs:remove check test/temp/dir1/file11 +0ms
fs:remove removing test/temp/dir1/file11 (file) +0ms
*/
}).call(this);
//# sourceMappingURL=remove.map