1 | 'use strict';
|
2 | const path = require('path');
|
3 | const {constants: fsConstants} = require('fs');
|
4 | const pEvent = require('p-event');
|
5 | const CpFileError = require('./cp-file-error');
|
6 | const fs = require('./fs');
|
7 | const ProgressEmitter = require('./progress-emitter');
|
8 |
|
9 | const 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 |
|
53 | const 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 |
|
73 | module.exports = cpFile;
|
74 |
|
75 | const 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 |
|
85 | const fixupAttributes = (destination, stat) => {
|
86 | fs.chmodSync(destination, stat.mode);
|
87 | fs.chownSync(destination, stat.uid, stat.gid);
|
88 | };
|
89 |
|
90 | module.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 | };
|