Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES2015ify #14

Merged
merged 1 commit into from Oct 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions cp-file-error.js
@@ -0,0 +1,12 @@
'use strict';
const NestedError = require('nested-error-stacks');

class CpFileError extends NestedError {
constructor(message, nested) {
super(message, nested);
Object.assign(this, nested);
this.name = 'CpFileError';
}
}

module.exports = CpFileError;
97 changes: 97 additions & 0 deletions fs.js
@@ -0,0 +1,97 @@
'use strict';
const fs = require('graceful-fs');
const mkdirp = require('mkdirp');
const pify = require('pify');
const CpFileError = require('./cp-file-error');

const fsP = pify(fs);
const mkdirpP = pify(mkdirp);

exports.closeSync = fs.closeSync.bind(fs);
exports.createWriteStream = fs.createWriteStream.bind(fs);

exports.createReadStream = (path, options) => new Promise((resolve, reject) => {
const read = fs.createReadStream(path, options);

read.on('error', err => {
reject(new CpFileError(`cannot read from \`${path}\`: ${err.message}`, err));
});

read.on('readable', () => {
resolve(read);
});

read.on('end', () => {
resolve(read);
});
});

exports.stat = path => fsP.stat(path).catch(err => {
throw new CpFileError(`cannot stat path \`${path}\`: ${err.message}`, err);
});

exports.lstat = path => fsP.lstat(path).catch(err => {
throw new CpFileError(`lstat \`${path}\` failed: ${err.message}`, err);
});

exports.utimes = (path, atime, mtime) => fsP.utimes(path, atime, mtime).catch(err => {
throw new CpFileError(`utimes \`${path}\` failed: ${err.message}`, err);
});

exports.openSync = (path, flags, mode) => {
try {
return fs.openSync(path, flags, mode);
} catch (err) {
if (flags.includes('w')) {
throw new CpFileError(`cannot write to \`${path}\`: ${err.message}`, err);
}

throw new CpFileError(`cannot open \`${path}\`: ${err.message}`, err);
}
};

// eslint-disable-next-line max-params
exports.readSync = (fd, buffer, offset, length, position, path) => {
try {
return fs.readSync(fd, buffer, offset, length, position);
} catch (err) {
throw new CpFileError(`cannot read from \`${path}\`: ${err.message}`, err);
}
};

// eslint-disable-next-line max-params
exports.writeSync = (fd, buffer, offset, length, position, path) => {
try {
return fs.writeSync(fd, buffer, offset, length, position);
} catch (err) {
throw new CpFileError(`cannot write to \`${path}\`: ${err.message}`, err);
}
};

exports.fstatSync = (fd, path) => {
try {
return fs.fstatSync(fd);
} catch (err) {
throw new CpFileError(`stat \`${path}\` failed: ${err.message}`, err);
}
};

exports.futimesSync = (fd, atime, mtime, path) => {
try {
return fs.futimesSync(fd, atime, mtime, path);
} catch (err) {
throw new CpFileError(`utimes \`${path}\` failed: ${err.message}`, err);
}
};

exports.mkdirp = path => mkdirpP(path, {fs}).catch(err => {
throw new CpFileError(`cannot create directory \`${path}\`: ${err.message}`, err);
});

exports.mkdirpSync = path => {
try {
mkdirp.sync(path, {fs});
} catch (err) {
throw new CpFileError(`cannot create directory \`${path}\`: ${err.message}`, err);
}
};
182 changes: 39 additions & 143 deletions index.js
@@ -1,120 +1,53 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
var path = require('path');
var util = require('util');
var stream = require('readable-stream');
var pify = require('pify');
var fs = require('graceful-fs');
var mkdirp = require('mkdirp');
var NestedError = require('nested-error-stacks');
const path = require('path');
const CpFileError = require('./cp-file-error');
const fs = require('./fs');
const ProgressEmitter = require('./progress-emitter');

var mkdirpP = pify(mkdirp, Promise);
var fsP = pify(fs, Promise);

function CpFileError(message, nested) {
NestedError.call(this, message, nested);
Object.assign(this, nested);
}

util.inherits(CpFileError, NestedError);

CpFileError.prototype.name = 'CpFileError';

module.exports = function (src, dest, opts) {
module.exports = (src, dest, opts) => {
if (!src || !dest) {
return Promise.reject(new CpFileError('`src` and `dest` required'));
}

opts = Object.assign({overwrite: true}, opts);

var size = 0;
var progressEmitter = new EventEmitter();

var absoluteSrc = path.resolve(src);
var absoluteDest = path.resolve(dest);

var promise = fsP.stat(src).then(function (stat) {
size = stat.size;
}).catch(function (err) {
throw new CpFileError('cannot stat path `' + src + '`: ' + err.message, err);
}).then(function () {
var read = new stream.Readable().wrap(fs.createReadStream(src));
return new Promise(function (resolve, reject) {
read.on('error', function (err) {
reject(new CpFileError('cannot read from `' + src + '`: ' + err.message, err));
});

read.on('readable', function () {
resolve(read);
});

read.on('end', function () {
resolve(read);
});
});
}).then(function mkdirpDestDirectory(read) {
return mkdirpP(path.dirname(dest)).then(function () {
return read;
}).catch(function (err) {
if (err.code !== 'EEXIST') {
throw new CpFileError('cannot create directory `' + path.dirname(dest) + '`: ' + err.message, err);
}
const progressEmitter = new ProgressEmitter(path.resolve(src), path.resolve(dest));

return read;
});
}).then(function (read) {
return new Promise(function pipeToDest(resolve, reject) {
var write = fs.createWriteStream(dest, {flags: opts.overwrite ? 'w' : 'wx'});
const promise = fs
.stat(src)
.then(stat => {
progressEmitter.size = stat.size;
})
.then(() => fs.createReadStream(src))
.then(read => fs.mkdirp(path.dirname(dest)).then(() => read))
.then(read => new Promise((resolve, reject) => {
const write = fs.createWriteStream(dest, {flags: opts.overwrite ? 'w' : 'wx'});

read.on('data', function () {
if (size === 0) {
return;
}
var written = write.bytesWritten;
var percent = written / size;
progressEmitter.emit('progress', {
src: absoluteSrc,
dest: absoluteDest,
size: size,
written: written,
percent: percent
});
read.on('data', () => {
progressEmitter.written = write.bytesWritten;
});

write.on('error', function (err) {
write.on('error', err => {
if (!opts.overwrite && err.code === 'EEXIST') {
resolve(false);
return;
}

reject(new CpFileError('cannot write to `' + dest + '`: ' + err.message, err));
reject(new CpFileError(`cannot write to \`${dest}\`: ${err.message}`, err));
});

write.on('close', function () {
progressEmitter.emit('progress', {
src: absoluteSrc,
dest: absoluteDest,
size: size,
written: size,
percent: 1
});

write.on('close', () => {
progressEmitter.written = progressEmitter.size;
resolve(true);
});

read.pipe(write);
}))
.then(updateTimes => {
if (updateTimes) {
return fs.lstat(src).then(stats => fs.utimes(dest, stats.atime, stats.mtime));
}
});
}).then(function (updateTimes) {
if (updateTimes) {
return fsP.lstat(src).catch(function (err) {
throw new CpFileError('lstat `' + src + '` failed: ' + err.message, err);
}).then(function (stats) {
return fsP.utimes(dest, stats.atime, stats.mtime).catch(function (err) {
throw new CpFileError('utimes `' + dest + '` failed: ' + err.message, err);
});
});
}
});

promise.on = function () {
progressEmitter.on.apply(progressEmitter, arguments);
Expand All @@ -131,45 +64,18 @@ module.exports.sync = function (src, dest, opts) {

opts = Object.assign({overwrite: true}, opts);

var read;
var bytesRead;
var pos;
var write;
var stat;
var BUF_LENGTH = 100 * 1024;
var buf = new Buffer(BUF_LENGTH);

function readSync(pos) {
try {
return fs.readSync(read, buf, 0, BUF_LENGTH, pos);
} catch (err) {
throw new CpFileError('cannot read from `' + src + '`: ' + err.message, err);
}
}

function writeSync() {
try {
fs.writeSync(write, buf, 0, bytesRead);
} catch (err) {
throw new CpFileError('cannot write to `' + dest + '`: ' + err.message, err);
}
}

try {
read = fs.openSync(src, 'r');
} catch (err) {
throw new CpFileError('cannot open `' + src + '`: ' + err.message, err);
}
let read;
let bytesRead;
let pos;
let write;
const BUF_LENGTH = 100 * 1024;
const buf = new Buffer(BUF_LENGTH);
const readSync = pos => fs.readSync(read, buf, 0, BUF_LENGTH, pos, src);
const writeSync = () => fs.writeSync(write, buf, 0, bytesRead, undefined, dest);

read = fs.openSync(src, 'r');
pos = bytesRead = readSync(0);

try {
mkdirp.sync(path.dirname(dest));
} catch (err) {
if (err.code !== 'EEXIST') {
throw new CpFileError('cannot create directory `' + path.dirname(dest) + '`: ' + err.message, err);
}
}
fs.mkdirpSync(path.dirname(dest));

try {
write = fs.openSync(dest, opts.overwrite ? 'w' : 'wx');
Expand All @@ -178,7 +84,7 @@ module.exports.sync = function (src, dest, opts) {
return;
}

throw new CpFileError('cannot write to `' + dest + '`: ' + err.message, err);
throw err;
}

writeSync();
Expand All @@ -189,18 +95,8 @@ module.exports.sync = function (src, dest, opts) {
pos += bytesRead;
}

try {
stat = fs.fstatSync(read);
} catch (err) {
throw new CpFileError('stat `' + src + '` failed: ' + err.message, err);
}

try {
fs.futimesSync(write, stat.atime, stat.mtime);
} catch (err) {
throw new CpFileError('utimes `' + dest + '` failed: ' + err.message, err);
}

const stat = fs.fstatSync(read, src);
fs.futimesSync(write, stat.atime, stat.mtime, dest);
fs.closeSync(read);
fs.closeSync(write);
};
11 changes: 7 additions & 4 deletions package.json
Expand Up @@ -23,7 +23,10 @@
"test": "xo && nyc ava"
},
"files": [
"index.js"
"cp-file-error.js",
"fs.js",
"index.js",
"progress-emitter.js"
],
"keywords": [
"copy",
Expand All @@ -44,14 +47,14 @@
"graceful-fs": "^4.1.2",
"mkdirp": "^0.5.0",
"nested-error-stacks": "^1.0.1",
"pify": "^2.3.0",
"readable-stream": "^2.1.4"
"pify": "^2.3.0"
},
"devDependencies": {
"ava": "*",
"coveralls": "^2.11.14",
"clear-require": "^1.0.1",
"nyc": "^8.3.0",
"rewire": "^2.3.1",
"require-uncached": "^1.0.2",
"rimraf": "^2.3.2",
"uuid": "^2.0.3",
"xo": "*"
Expand Down