git ssb

0+

Rômulo Alves / sync



Tree: ce27463164a9c7dab3164e59e793ace39b8ac23d

Files: ce27463164a9c7dab3164e59e793ace39b8ac23d / copy.js

7862 bytesRaw
1const fs = require('graceful-fs');
2const path = require('path');
3const utils = require('./utils');
4const { mkdirs, utimes } = require('fs-extra');
5
6const notExist = Symbol('notExist');
7const existsReg = Symbol('existsReg');
8
9module.exports = function copy(src, dest, cryptDecryptStream) {
10 return new Promise(async (resolve, reject) => {
11 src = path.resolve(src);
12 dest = path.resolve(dest);
13
14 // Do not allow src and dest to be the same
15 if (src === dest) {
16 return reject(new Error('Source and destination must not be the same.'));
17 }
18
19 try {
20 await checkParentDir(src, dest, cryptDecryptStream);
21
22 return resolve();
23 } catch (err) {
24 return reject(err);
25 }
26 });
27};
28
29function checkParentDir (src, dest, cryptDecryptStream) {
30 return new Promise(async (resolve, reject) => {
31 const destParent = path.dirname(dest);
32
33 try {
34
35 // Verify if dir exists
36 const dirExists = await utils.exists(destParent);
37
38 // Create dir
39 if (!dirExists) {
40 await mkdirs(destParent);
41 }
42
43 // Copy items
44 await startCopy(src, dest, cryptDecryptStream);
45
46 return resolve();
47 } catch (err) {
48 return reject(err);
49 }
50 });
51}
52
53function startCopy (src, dest, cryptDecryptStream) {
54 return new Promise(async (resolve, reject) => {
55 try {
56 await getStats(src, dest, cryptDecryptStream);
57
58 return resolve();
59 } catch (err) {
60 return reject(err);
61 }
62 });
63}
64
65function getStats (src, dest, cryptDecryptStream) {
66 return new Promise((resolve, reject) => {
67 return fs.lstat(src, async (err, st) => {
68 try {
69 if (err) {
70 return reject(err);
71 }
72
73 if (st.isDirectory()) {
74 await onDir(st, src, dest, cryptDecryptStream);
75 } else if (st.isFile() ||
76 st.isCharacterDevice() ||
77 st.isBlockDevice()) {
78 await onFile(st, src, dest, cryptDecryptStream);
79 } else if (st.isSymbolicLink()) {
80 return reject(new Error('Symbolic links are not supported.'));
81 }
82
83 return resolve();
84 } catch (err) {
85 return reject(err);
86 }
87 });
88 });
89}
90
91function onFile (srcStat, src, dest, cryptDecryptStream) {
92 return new Promise(async (resolve, reject) => {
93 try {
94 const resolvedPath = await checkDest(dest);
95
96 if (resolvedPath === notExist) {
97 await copyFile(srcStat, src, dest, cryptDecryptStream);
98 } else if (resolvedPath === existsReg) {
99 await mayCopyFile(srcStat, src, dest, cryptDecryptStream);
100 } else {
101 if (src === resolvedPath) {
102 return resolve();
103 }
104
105 await mayCopyFile(srcStat, src, dest, cryptDecryptStream);
106 }
107 } catch (err) {
108 return reject(err);
109 }
110
111 return resolve();
112 });
113}
114
115function mayCopyFile (srcStat, src, dest, cryptDecryptStream) {
116 return new Promise((resolve, reject) => {
117 return fs.unlink(dest, async err => {
118 if (err) {
119 return reject(err);
120 }
121
122 try {
123 await copyFile(srcStat, src, dest, cryptDecryptStream);
124 } catch (err) {
125 return reject(err);
126 }
127
128 return resolve();
129 });
130 });
131}
132
133function copyFile (srcStat, src, dest, cryptDecryptStream) {
134 return new Promise((resolve, reject) => {
135 const rs = fs.createReadStream(src);
136
137 rs.on('error', err => reject(err))
138 .once('open', () => {
139 const ws = fs.createWriteStream(dest, { mode: srcStat.mode });
140
141 ws.on('error', err => reject(err))
142 .on('open', () => rs.pipe(cryptDecryptStream).pipe(ws))
143 .once('close', async () => {
144 try {
145 await setDestModeAndTimestamps(srcStat, dest);
146 } catch (err) {
147 return resolve();
148 }
149
150 return resolve();
151 });
152 });
153 });
154}
155
156function setDestModeAndTimestamps (srcStat, dest) {
157 return new Promise((resolve, reject) => {
158 return fs.chmod(dest, srcStat.mode, err => {
159 if (err) {
160 return reject(err);
161 }
162
163 return utimes(dest, srcStat.atime, srcStat.mtime, () => {
164 if (err) {
165 return reject(err);
166 }
167
168 return resolve();
169 });
170 });
171 });
172}
173
174function onDir (srcStat, src, dest, cryptDecryptStream) {
175 return new Promise(async (resolve, reject) => {
176 try {
177 const resolvedPath = await checkDest(dest);
178
179 if (resolvedPath === notExist) {
180 if (isSrcSubdir(src, dest)) {
181 return reject(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`));
182 }
183
184 await mkDirAndCopy(srcStat, src, dest, cryptDecryptStream);
185 } else if (resolvedPath === existsReg) {
186 if (isSrcSubdir(src, dest)) {
187 return reject(new Error(`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`));
188 }
189
190 await mayCopyDir(src, dest, cryptDecryptStream);
191 } else {
192 if (src === resolvedPath) {
193 return reject();
194 }
195
196 await copyDir(src, dest, cryptDecryptStream);
197 }
198 } catch (err) {
199 return reject(err);
200 }
201
202 return resolve();
203 });
204}
205
206function mayCopyDir (src, dest, cryptDecryptStream) {
207 return new Promise((resolve, reject) => {
208 return fs.stat(dest, async (err, st) => {
209 if (err) {
210 return reject(err);
211 }
212
213 if (!st.isDirectory()) {
214 return reject(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`));
215 }
216
217 try {
218 await copyDir(src, dest, cryptDecryptStream);
219 } catch (err) {
220 return reject(err);
221 }
222
223 return resolve();
224 });
225 });
226}
227
228function mkDirAndCopy (srcStat, src, dest, cryptDecryptStream) {
229 return new Promise((resolve, reject) => {
230 return fs.mkdir(dest, srcStat.mode, err => {
231 if (err) {
232 return reject(err);
233 }
234
235 return fs.chmod(dest, srcStat.mode, async err => {
236 if (err) {
237 return reject(err);
238 }
239
240 try {
241 await copyDir(src, dest, cryptDecryptStream);
242 } catch (err) {
243 return reject(err);
244 }
245
246 return resolve();
247 });
248 });
249 });
250}
251
252function copyDir (src, dest, cryptDecryptStream) {
253 return new Promise((resolve, reject) => {
254 return fs.readdir(src, async (err, items) => {
255 if (err) {
256 return reject(err);
257 }
258
259 try {
260 await copyDirItems(items, src, dest, cryptDecryptStream);
261 } catch (err) {
262 return reject(err);
263 }
264
265 return resolve();
266 });
267 });
268}
269
270function copyDirItems (items, src, dest, cryptDecryptStream) {
271 return new Promise(async (resolve, reject) => {
272 const item = items.pop();
273
274 if (!item) {
275 return reject();
276 }
277
278 try {
279 await startCopy(path.join(src, item), path.join(dest, item), cryptDecryptStream);
280
281 return copyDirItems(items, src, dest, cryptDecryptStream);
282 } catch (err) {
283 return reject(err);
284 }
285 });
286}
287
288// Check if dest exists and/or is a symlink
289const checkDest = dest =>
290 new Promise((resolve, reject) => {
291 return fs.readlink(dest, (err, resolvedPath) => {
292 if (err) {
293 if (err.code === 'ENOENT') {
294 return resolve(notExist);
295 }
296
297 // Dest exists and is a regular file or directory, Windows may throw UNKNOWN error
298 if (err.code === 'EINVAL' || err.code === 'UNKNOWN') {
299 return resolve(existsReg);
300 }
301
302 return reject(err);
303 }
304
305 // Dest exists and is a symlink
306 return resolve(resolvedPath);
307 });
308 });
309
310// Return true if dest is a subdir of src, otherwise false
311// Extract dest base dir and check if that is the same as src basename
312const isSrcSubdir = (src, dest) => {
313 const srcArray = src.split(path.sep);
314 const destArray = dest.split(path.sep);
315
316 return srcArray.reduce((acc, current, i) =>
317 acc && destArray[i] === current, true);
318};

Built with git-ssb-web