fix: explicitly promisify fs methods

`promisifyAll` will throw if a method with the suffix `Async` already
exists.

Fixes: https://github.com/trs/ftp-srv/pull/159
This commit is contained in:
Tyler Stewart
2019-08-13 17:16:06 -06:00
parent 484409d2eb
commit 0b9167e1e4
2 changed files with 37 additions and 18 deletions

View File

@@ -2,13 +2,14 @@ const _ = require('lodash');
const nodePath = require('path'); const nodePath = require('path');
const uuid = require('uuid'); const uuid = require('uuid');
const Promise = require('bluebird'); const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs')); const {createReadStream, createWriteStream, constants} = require('fs');
const fsAsync = require('./helpers/fs-async');
const errors = require('./errors'); const errors = require('./errors');
class FileSystem { class FileSystem {
constructor(connection, {root, cwd} = {}) { constructor(connection, {root, cwd} = {}) {
this.connection = connection; this.connection = connection;
this.cwd = cwd ? nodePath.join(nodePath.sep, cwd) : nodePath.sep; this.cwd = nodePath.normalize(cwd ? nodePath.join(nodePath.sep, cwd) : nodePath.sep);
this._root = nodePath.resolve(root || process.cwd()); this._root = nodePath.resolve(root || process.cwd());
} }
@@ -28,7 +29,7 @@ class FileSystem {
const fsPath = (() => { const fsPath = (() => {
const resolvedPath = nodePath.join(this.root, clientPath); const resolvedPath = nodePath.join(this.root, clientPath);
return nodePath.resolve(nodePath.join(resolvedPath)); return nodePath.resolve(nodePath.normalize(nodePath.join(resolvedPath)));
})(); })();
return { return {
@@ -43,19 +44,19 @@ class FileSystem {
get(fileName) { get(fileName) {
const {fsPath} = this._resolvePath(fileName); const {fsPath} = this._resolvePath(fileName);
return fs.statAsync(fsPath) return fsAsync.stat(fsPath)
.then((stat) => _.set(stat, 'name', fileName)); .then((stat) => _.set(stat, 'name', fileName));
} }
list(path = '.') { list(path = '.') {
const {fsPath} = this._resolvePath(path); const {fsPath} = this._resolvePath(path);
return fs.readdirAsync(fsPath) return fsAsync.readdir(fsPath)
.then((fileNames) => { .then((fileNames) => {
return Promise.map(fileNames, (fileName) => { return Promise.map(fileNames, (fileName) => {
const filePath = nodePath.join(fsPath, fileName); const filePath = nodePath.join(fsPath, fileName);
return fs.accessAsync(filePath, fs.constants.F_OK) return fsAsync.access(filePath, constants.F_OK)
.then(() => { .then(() => {
return fs.statAsync(filePath) return fsAsync.stat(filePath)
.then((stat) => _.set(stat, 'name', fileName)); .then((stat) => _.set(stat, 'name', fileName));
}) })
.catch(() => null); .catch(() => null);
@@ -66,7 +67,7 @@ class FileSystem {
chdir(path = '.') { chdir(path = '.') {
const {fsPath, clientPath} = this._resolvePath(path); const {fsPath, clientPath} = this._resolvePath(path);
return fs.statAsync(fsPath) return fsAsync.stat(fsPath)
.tap((stat) => { .tap((stat) => {
if (!stat.isDirectory()) throw new errors.FileSystemError('Not a valid directory'); if (!stat.isDirectory()) throw new errors.FileSystemError('Not a valid directory');
}) })
@@ -78,8 +79,8 @@ class FileSystem {
write(fileName, {append = false, start = undefined} = {}) { write(fileName, {append = false, start = undefined} = {}) {
const {fsPath, clientPath} = this._resolvePath(fileName); const {fsPath, clientPath} = this._resolvePath(fileName);
const stream = fs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start}); const stream = createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
stream.once('error', () => fs.unlinkAsync(fsPath)); stream.once('error', () => fsAsync.unlink(fsPath));
stream.once('close', () => stream.end()); stream.once('close', () => stream.end());
return { return {
stream, stream,
@@ -89,12 +90,12 @@ class FileSystem {
read(fileName, {start = undefined} = {}) { read(fileName, {start = undefined} = {}) {
const {fsPath, clientPath} = this._resolvePath(fileName); const {fsPath, clientPath} = this._resolvePath(fileName);
return fs.statAsync(fsPath) return fsAsync.stat(fsPath)
.tap((stat) => { .tap((stat) => {
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory'); if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
}) })
.then(() => { .then(() => {
const stream = fs.createReadStream(fsPath, {flags: 'r', start}); const stream = createReadStream(fsPath, {flags: 'r', start});
return { return {
stream, stream,
clientPath clientPath
@@ -104,28 +105,28 @@ class FileSystem {
delete(path) { delete(path) {
const {fsPath} = this._resolvePath(path); const {fsPath} = this._resolvePath(path);
return fs.statAsync(fsPath) return fsAsync.stat(fsPath)
.then((stat) => { .then((stat) => {
if (stat.isDirectory()) return fs.rmdirAsync(fsPath); if (stat.isDirectory()) return fsAsync.rmdir(fsPath);
else return fs.unlinkAsync(fsPath); else return fsAsync.unlink(fsPath);
}); });
} }
mkdir(path) { mkdir(path) {
const {fsPath} = this._resolvePath(path); const {fsPath} = this._resolvePath(path);
return fs.mkdirAsync(fsPath) return fsAsync.mkdir(fsPath)
.then(() => fsPath); .then(() => fsPath);
} }
rename(from, to) { rename(from, to) {
const {fsPath: fromPath} = this._resolvePath(from); const {fsPath: fromPath} = this._resolvePath(from);
const {fsPath: toPath} = this._resolvePath(to); const {fsPath: toPath} = this._resolvePath(to);
return fs.renameAsync(fromPath, toPath); return fsAsync.rename(fromPath, toPath);
} }
chmod(path, mode) { chmod(path, mode) {
const {fsPath} = this._resolvePath(path); const {fsPath} = this._resolvePath(path);
return fs.chmodAsync(fsPath, mode); return fsAsync.chmod(fsPath, mode);
} }
getUniqueName() { getUniqueName() {

18
src/helpers/fs-async.js Normal file
View File

@@ -0,0 +1,18 @@
const fs = require('fs');
const {promisify} = require('bluebird');
const methods = [
'stat',
'readdir',
'access',
'unlink',
'rmdir',
'mkdir',
'rename',
'chmod'
];
module.exports = methods.reduce((obj, method) => {
obj[method] = promisify(fs[method]);
return obj;
}, {});