'use strict';
|
const path = require('path');
|
const {constants: fsConstants} = require('fs');
|
const {Buffer} = require('safe-buffer');
|
const CpFileError = require('./cp-file-error');
|
const fs = require('./fs');
|
const ProgressEmitter = require('./progress-emitter');
|
|
const cpFile = (source, destination, options) => {
|
if (!source || !destination) {
|
return Promise.reject(new CpFileError('`source` and `destination` required'));
|
}
|
|
options = Object.assign({overwrite: true}, options);
|
|
const progressEmitter = new ProgressEmitter(path.resolve(source), path.resolve(destination));
|
|
const promise = fs
|
.stat(source)
|
.then(stat => {
|
progressEmitter.size = stat.size;
|
})
|
.then(() => fs.createReadStream(source))
|
.then(read => fs.makeDir(path.dirname(destination)).then(() => read))
|
.then(read => new Promise((resolve, reject) => {
|
const write = fs.createWriteStream(destination, {flags: options.overwrite ? 'w' : 'wx'});
|
|
read.on('data', () => {
|
progressEmitter.written = write.bytesWritten;
|
});
|
|
write.on('error', error => {
|
if (!options.overwrite && error.code === 'EEXIST') {
|
resolve(false);
|
return;
|
}
|
|
reject(new CpFileError(`Cannot write to \`${destination}\`: ${error.message}`, error));
|
});
|
|
write.on('close', () => {
|
progressEmitter.written = progressEmitter.size;
|
resolve(true);
|
});
|
|
read.pipe(write);
|
}))
|
.then(updateStats => {
|
if (updateStats) {
|
return fs.lstat(source).then(stats => Promise.all([
|
fs.utimes(destination, stats.atime, stats.mtime),
|
fs.chmod(destination, stats.mode),
|
fs.chown(destination, stats.uid, stats.gid)
|
]));
|
}
|
});
|
|
promise.on = (...args) => {
|
progressEmitter.on(...args);
|
return promise;
|
};
|
|
return promise;
|
};
|
|
module.exports = cpFile;
|
// TODO: Remove this for the next major release
|
module.exports.default = cpFile;
|
|
const checkSourceIsFile = (stat, source) => {
|
if (stat.isDirectory()) {
|
throw Object.assign(new CpFileError(`EISDIR: illegal operation on a directory '${source}'`), {
|
errno: -21,
|
code: 'EISDIR',
|
source
|
});
|
}
|
};
|
|
const fixupAttributes = (destination, stat) => {
|
fs.chmodSync(destination, stat.mode);
|
fs.chownSync(destination, stat.uid, stat.gid);
|
};
|
|
const copySyncNative = (source, destination, options) => {
|
const stat = fs.statSync(source);
|
checkSourceIsFile(stat, source);
|
fs.makeDirSync(path.dirname(destination));
|
|
const flags = options.overwrite ? null : fsConstants.COPYFILE_EXCL;
|
try {
|
fs.copyFileSync(source, destination, flags);
|
} catch (error) {
|
if (!options.overwrite && error.code === 'EEXIST') {
|
return;
|
}
|
|
throw error;
|
}
|
|
fs.utimesSync(destination, stat.atime, stat.mtime);
|
fixupAttributes(destination, stat);
|
};
|
|
const copySyncFallback = (source, destination, options) => {
|
let bytesRead;
|
let position;
|
let read; // eslint-disable-line prefer-const
|
let write;
|
const BUF_LENGTH = 100 * 1024;
|
const buffer = Buffer.alloc(BUF_LENGTH);
|
const readSync = position => fs.readSync(read, buffer, 0, BUF_LENGTH, position, source);
|
const writeSync = () => fs.writeSync(write, buffer, 0, bytesRead, undefined, destination);
|
|
read = fs.openSync(source, 'r');
|
bytesRead = readSync(0);
|
position = bytesRead;
|
fs.makeDirSync(path.dirname(destination));
|
|
try {
|
write = fs.openSync(destination, options.overwrite ? 'w' : 'wx');
|
} catch (error) {
|
if (!options.overwrite && error.code === 'EEXIST') {
|
return;
|
}
|
|
throw error;
|
}
|
|
writeSync();
|
|
while (bytesRead === BUF_LENGTH) {
|
bytesRead = readSync(position);
|
writeSync();
|
position += bytesRead;
|
}
|
|
const stat = fs.fstatSync(read, source);
|
fs.futimesSync(write, stat.atime, stat.mtime, destination);
|
fs.closeSync(read);
|
fs.closeSync(write);
|
fixupAttributes(destination, stat);
|
};
|
|
module.exports.sync = (source, destination, options) => {
|
if (!source || !destination) {
|
throw new CpFileError('`source` and `destination` required');
|
}
|
|
options = Object.assign({overwrite: true}, options);
|
|
if (fs.copyFileSync) {
|
copySyncNative(source, destination, options);
|
} else {
|
copySyncFallback(source, destination, options);
|
}
|
};
|