/** * @module vow-fs * @author Filatov Dmitry * @version 0.3.2 * @license * Dual licensed under the MIT and GPL licenses: * * http://www.opensource.org/licenses/mit-license.php * * http://www.gnu.org/licenses/gpl.html */ var vow = require('vow'), Queue = require('vow-queue'), openFilesQueue = new Queue(), fs = require('fs'), path = require('path'), os = require('os'), uuid = require('node-uuid'), glob = require('glob'), slice = Array.prototype.slice, promisify = function(nodeFn) { return function() { var defer = vow.defer(), args = slice.call(arguments); args.push(function(err) { err? defer.reject(err) : defer.resolve(arguments[1]); }); try { nodeFn.apply(fs, args); } catch(err) { defer.reject(err); } return defer.promise(); }; }, tmpDir = os.tmpdir || os.tmpDir || function() { return '/tmp'; }, makeDir = promisify(fs.mkdir), removeDir = promisify(fs.rmdir), lstat = promisify(fs.lstat), emfileTimeout = 1, emfileFixWrapper = function(method, weight) { var wrapper = function() { var callArgs = arguments; return openFilesQueue.enqueue(function() { return method.apply(vfs, callArgs).then( function(res) { emfileTimeout = Math.max(1, emfileTimeout / 2); return res; }, function(err) { if(err.code === 'EMFILE') { emfileTimeout++; return vow.delay(null, emfileTimeout).then(function() { return wrapper.apply(vfs, callArgs); }); } else { throw err; } }); }, weight); }; return wrapper; }, undef; var vfs = module.exports = { /** * Reads file by given path * @param {String} path * @param {String} [encoding=utf8] * @returns {vow:Promise} */ read : emfileFixWrapper(promisify(fs.readFile)), /** * Writes data to file by given path * @param {String} path * @param {String|Buffer} data * @param {String} [encoding=utf8] * @returns {vow:Promise} */ write : emfileFixWrapper(promisify(fs.writeFile)), /** * Appends data to file by given path * @param {String} path * @param {String|Buffer} data * @param {String} [encoding=utf8] * @returns {vow:Promise} */ append : emfileFixWrapper(promisify(fs.appendFile)), /** * Removes file at given path * @param {String} pathToRemove * @returns {vow:Promise} */ remove : promisify(fs.unlink), /** * Copies file from sourcePath to targetPath * @param {String} sourcePath * @param {String} targetPath * @returns {vow:Promise} */ copy : emfileFixWrapper(function(sourcePath, targetPath) { return this.isFile(sourcePath).then(function(isFile) { if(!isFile) { var err = Error(); err.errno = 28; err.code = 'EISDIR'; err.path = sourcePath; throw err; } var defer = vow.defer(), sourceStream = fs.createReadStream(sourcePath), errFn = function(err) { defer.reject(err); }; sourceStream .on('error', errFn) .on('open', function() { var targetStream = fs.createWriteStream(targetPath); sourceStream.pipe( targetStream .on('error', errFn) .on('close', function() { defer.resolve(); })); }); return defer.promise(); }); }, 2), /** * Moves from sourcePath to targetPath * @param {String} sourcePath * @param {String} targetPath * @returns {vow:Promise} */ move : promisify(fs.rename), /** * Extracts fs.Stats about a given path * @param {String} path * @returns {vow:Promise} */ stat : promisify(fs.stat), /** * Tests whether or not the given path exists * @param {String} path * @returns {vow:Promise} */ exists : fs.exists? function(path) { var defer = vow.defer(); fs.exists(path, function(exists) { defer.resolve(exists); }); return defer.promise(); } : function(path) { var defer = vow.defer(); fs.stat(path, function(err) { defer.resolve(!err); }); return defer.promise(); }, /** * Creates a hard link from the sourcePath to targetPath * @param {String} sourcePath * @param {String} targetPath * @returns {vow:Promise} */ link : promisify(fs.link), /** * Creates a relative symbolic link from the sourcePath to targetPath * @param {String} sourcePath * @param {String} targetPath * @param {String} [type=file] can be either 'dir', 'file', or 'junction' * @returns {vow:Promise} */ symLink : promisify(fs.symlink), /** * Changes the owner for a given path using Unix user-id and group-id numbers * @param {String} path * @param uid * @param gid * @returns {vow:Promise} */ chown : promisify(fs.chown), /** * Changes the Unix mode for a path. Returns a promise * @param {String} path * @param mode * @returns {vow:Promise} */ chmod : promisify(fs.chmod), /** * Normalizes a given path to absolute path * @param {String} path * @returns {vow:Promise} */ absolute : promisify(fs.realpath), /** * Checks whether the given path is a file * @param {String} path * @returns {vow:Promise} */ isFile : function(path) { return this.stat(path).then(function(stats) { return stats.isFile(); }); }, /** * Checks whether the given path is a directory * @param {String} path * @returns {vow:Promise} */ isDir : function(path) { return this.stat(path).then(function(stats) { return stats.isDirectory(); }); }, /** * Checks whether the given path is a socket * @param {String} path * @returns {vow:Promise} */ isSocket : function(path) { return this.stat(path).then(function(stats) { return stats.isSocket(); }); }, /** * Checks whether the given path is a symbolic link * @param {String} path * @returns {vow:Promise} */ isSymLink : function(path) { return lstat(path).then(function(stats) { return stats.isSymbolicLink(); }); }, /** * Makes a temporary file * @param {Object} options * @param {String} [options.prefix] * @param {String} [options.dir=os.tmpdir()] * @param {String} [options.ext=tmp] * @returns {vow:Promise} */ makeTmpFile : function(options) { options || (options = {}); var filePath = path.join( options.dir || tmpDir(), (options.prefix || '') + uuid.v4() + (options.ext || '.tmp')); return vfs.write(filePath, '').then(function() { return filePath; }); }, /** * Reads the contents of a directory by given path * @param {String} path * @returns {vow:Promise} */ listDir : emfileFixWrapper(promisify(fs.readdir)), /** * Makes a directory at a given path, recursively creating any branches that doesn't exist * @param {String} dirPath * @param [mode=0777] * @param [failIfExist=false] * @returns {vow:Promise} */ makeDir : function(dirPath, mode, failIfExist) { if(typeof mode === 'boolean') { failIfExist = mode; mode = undef; } var dirName = path.dirname(dirPath), onFailed = function(e) { if(e.code !== 'EEXIST' || failIfExist) { throw e; } return vfs.isDir(dirPath).then(function(isDir) { if(!isDir) { throw e; } }); }; return vfs.exists(dirName).then(function(exists) { if(exists) { return makeDir(dirPath, mode).fail(onFailed); } else { failIfExist = false; return vfs.makeDir(dirName, mode).then(function() { return makeDir(dirPath, mode).fail(onFailed); }); } }); }, /** * Removes directory * @param {String} dirPath * @returns {vow:Promise} */ removeDir : function(dirPath) { return vfs.listDir(dirPath) .then(function(list) { return list.length && vow.all( list.map(function(file) { var fullPath = path.join(dirPath, file); return vfs.isFile(fullPath).then(function(isFile) { return isFile? vfs.remove(fullPath) : vfs.removeDir(fullPath); }); })); }) .then(function() { return removeDir(dirPath); }); }, /** * Matches files using the patterns, see https://github.com/isaacs/node-glob for details * @param {String} pattern to be matched * @param {Object} [options] options * @returns {vow:Promise} */ glob : promisify(glob), options : function(opts) { if(typeof opts.openFileLimit !== 'undefined') { openFilesQueue.params({ weightLimit : opts.openFileLimit }); } } }; openFilesQueue.start();