kitchensink
Version:
Dispatch's awesome components and style guide
365 lines (331 loc) • 10.5 kB
JavaScript
/**
* @module vow-fs
* @author Filatov Dmitry <dfilatov@yandex-team.ru>
* @version 0.3.5
* @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('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]
* @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]
* @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]
* @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.setParams({ weightLimit : opts.openFileLimit });
}
}
};
openFilesQueue.start();