UNPKG

3.01 kBJavaScriptView Raw
1'use strict';
2const path = require('path');
3const {constants: fsConstants} = require('fs');
4const pEvent = require('p-event');
5const CpFileError = require('./cp-file-error');
6const fs = require('./fs');
7const ProgressEmitter = require('./progress-emitter');
8
9const cpFileAsync = async (source, destination, options, progressEmitter) => {
10 let readError;
11 const stat = await fs.stat(source);
12 progressEmitter.size = stat.size;
13
14 const read = await fs.createReadStream(source);
15 await fs.makeDir(path.dirname(destination));
16 const write = fs.createWriteStream(destination, {flags: options.overwrite ? 'w' : 'wx'});
17 read.on('data', () => {
18 progressEmitter.written = write.bytesWritten;
19 });
20 read.once('error', error => {
21 readError = new CpFileError(`Cannot read from \`${source}\`: ${error.message}`, error);
22 write.end();
23 });
24
25 let updateStats = false;
26 try {
27 const writePromise = pEvent(write, 'close');
28 read.pipe(write);
29 await writePromise;
30 progressEmitter.written = progressEmitter.size;
31 updateStats = true;
32 } catch (error) {
33 if (options.overwrite || error.code !== 'EEXIST') {
34 throw new CpFileError(`Cannot write to \`${destination}\`: ${error.message}`, error);
35 }
36 }
37
38 if (readError) {
39 throw readError;
40 }
41
42 if (updateStats) {
43 const stats = await fs.lstat(source);
44
45 return Promise.all([
46 fs.utimes(destination, stats.atime, stats.mtime),
47 fs.chmod(destination, stats.mode),
48 fs.chown(destination, stats.uid, stats.gid)
49 ]);
50 }
51};
52
53const cpFile = (source, destination, options) => {
54 if (!source || !destination) {
55 return Promise.reject(new CpFileError('`source` and `destination` required'));
56 }
57
58 options = {
59 overwrite: true,
60 ...options
61 };
62
63 const progressEmitter = new ProgressEmitter(path.resolve(source), path.resolve(destination));
64 const promise = cpFileAsync(source, destination, options, progressEmitter);
65 promise.on = (...args) => {
66 progressEmitter.on(...args);
67 return promise;
68 };
69
70 return promise;
71};
72
73module.exports = cpFile;
74
75const checkSourceIsFile = (stat, source) => {
76 if (stat.isDirectory()) {
77 throw Object.assign(new CpFileError(`EISDIR: illegal operation on a directory '${source}'`), {
78 errno: -21,
79 code: 'EISDIR',
80 source
81 });
82 }
83};
84
85const fixupAttributes = (destination, stat) => {
86 fs.chmodSync(destination, stat.mode);
87 fs.chownSync(destination, stat.uid, stat.gid);
88};
89
90module.exports.sync = (source, destination, options) => {
91 if (!source || !destination) {
92 throw new CpFileError('`source` and `destination` required');
93 }
94
95 options = {
96 overwrite: true,
97 ...options
98 };
99
100 const stat = fs.statSync(source);
101 checkSourceIsFile(stat, source);
102 fs.makeDirSync(path.dirname(destination));
103
104 const flags = options.overwrite ? null : fsConstants.COPYFILE_EXCL;
105 try {
106 fs.copyFileSync(source, destination, flags);
107 } catch (error) {
108 if (!options.overwrite && error.code === 'EEXIST') {
109 return;
110 }
111
112 throw error;
113 }
114
115 fs.utimesSync(destination, stat.atime, stat.mtime);
116 fixupAttributes(destination, stat);
117};