Skip to content

Commit

Permalink
ES2015ify
Browse files Browse the repository at this point in the history
* Remove `readable-stream` (only needed for `v0.10`)
* Remove `mkdirp` `'EEXIST'` checks
* Remove `rewire` in favor of `clear-require` and `require-uncached`

Closes: #13
  • Loading branch information
schnittstabil committed Oct 4, 2016
1 parent 2ec76c4 commit f83ae73
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 332 deletions.
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);
};
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -44,14 +44,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
35 changes: 35 additions & 0 deletions progress-emitter.js
@@ -0,0 +1,35 @@
'use strict';
const EventEmitter = require('events');

const written = new WeakMap();

class ProgressEmitter extends EventEmitter {
constructor(src, dest) {
super();
this.src = src;
this.dest = dest;
}

set written(value) {
written.set(this, value);
this.emitProgress();
}

get written() {
return written.get(this);
}

emitProgress() {
const size = this.size;
const written = this.written;
this.emit('progress', {
src: this.src,
dest: this.dest,
size,
written,
percent: written === size ? 1 : written / size
});
}
}

module.exports = ProgressEmitter;

0 comments on commit f83ae73

Please sign in to comment.