365 lines
10 KiB
JavaScript
365 lines
10 KiB
JavaScript
/**
|
|
* @module vow-fs
|
|
* @author Filatov Dmitry <dfilatov@yandex-team.ru>
|
|
* @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();
|