git ssb

0+

Rômulo Alves / sync



Commit ce27463164a9c7dab3164e59e793ace39b8ac23d

Improves copy

Rômulo Alves committed on 4/6/2018, 7:19:25 PM
Parent: fc4f77ebcef3769ee736b18653f7712ef8169884

Files changed

index.jschanged
utils.jschanged
copy.jsadded
index.jsView
@@ -1,10 +1,49 @@
1 +const { createCipher, createDecipher } = require('crypto');
2 +const { createGzip, createGunzip } = require('zlib');
3 +const combiner = require('stream-combiner');
4 +
5 +const copy = require('./copy');
16 const utils = require('./utils');
27
38 module.exports = function sync(source, target, crypt, decrypt) {
49 return new Promise(async (resolve, reject) => {
10 +
11 + // Generate crypt and decrypt streams
12 + const transformStreams = [];
13 +
14 + if (crypt) {
15 +
16 + // Set stream to zip
17 + transformStreams.push(createGzip());
18 +
19 + // Set stream to crypt
20 + transformStreams.push(createCipher(crypt.algorithm, crypt.password));
21 + } else if (decrypt) {
22 +
23 + // Set stream to decrypt
24 + transformStreams.push(createDecipher(decrypt.algorithm, decrypt.password));
25 +
26 + // Set stream to unzip
27 + transformStreams.push(createGunzip());
28 + }
29 +
30 + const combinedStreams = combiner(transformStreams);
31 +
532 try {
33 + await syncAction(source, target, combinedStreams);
634
35 + return resolve();
36 + } catch (err) {
37 + return reject(err);
38 + }
39 + });
40 +};
41 +
42 +function syncAction(source, target, cryptDecryptTransform) {
43 + return new Promise(async (resolve, reject) => {
44 + try {
45 +
746 // Get stats of source and target
847 const [ statSource, statTarget ] = await Promise.all([ utils.getStats(source), utils.getStats(target) ]);
948
1049 // Source does not exists and target exists, delete target
@@ -15,9 +54,9 @@
1554 }
1655
1756 // Dost not exists in the target, copy
1857 if (statSource !== null && statTarget === null) {
19- await utils.copy(source, target, crypt, decrypt);
58 + await copy(source, target, cryptDecryptTransform);
2059
2160 return resolve();
2261 }
2362
@@ -29,22 +68,22 @@
2968 for (let index = 0; index < nextPaths.length; index++) {
3069 const [ nextSource, nextTarget ] = nextPaths[index];
3170
3271 // Start to sync next paths
33- await sync(nextSource, nextTarget, crypt, decrypt);
72 + await syncAction(nextSource, nextTarget, cryptDecryptTransform);
3473 }
3574 } else if (statSource.isFile() && statTarget.isFile()) {
3675
3776 // Verify if both are files
3877 if (statSource.mtime <= statTarget.mtime) {
3978
4079 // Source is newer, copy the new version to the target
41- await utils.copy(source, target, crypt, decrypt);
80 + await copy(source, target, cryptDecryptTransform);
4281 }
4382 }
4483
4584 return resolve();
4685 } catch (err) {
4786 return reject(err);
4887 }
4988 });
50-};
89 +}
utils.jsView
@@ -1,14 +1,8 @@
1-const { PassThrough } = require('stream');
21 const { stat, access } = require('fs');
32 const fs = require('fs-extra');
4-const { createReadStream, createWriteStream, utimes } = require('fs');
5-const { createCipher, createDecipher } = require('crypto');
63 const { join } = require('path');
7-const { createGzip, createGunzip } = require('zlib');
84
9-const combiner = require('stream-combiner');
10-
115 /**
126 * @function getStats
137 * @description Returns the directory/file stat
148 * @param {String} path Path to verify
@@ -57,75 +51,8 @@
5751 return fs.remove(path, () => resolve());
5852 });
5953
6054 /**
61- * @function copy
62- * @description Copies the file from the source to the target path
63- * @param {String} source File path to copy
64- * @param {String} target Path to put the file
65- * @param {String} crypt The cipher algorithm and key
66- * @param {String} decrypt The decipher algorithm and key
67- * @return {Promise<Object>} Resolves if was OK or rejects with the error object
68- */
69-module.exports.copy = (source, target, crypt, decrypt) =>
70- new Promise(async (resolve, reject) => {
71- const [ statSource, statTarget ] = await Promise.all([ getStats(source), getStats(target) ]);
72-
73- if (!statSource) {
74- return reject(new Error('Source does not exists.'));
75- }
76-
77- if (!statSource.isFile() || (statTarget && !statTarget.isFile())) {
78- return reject(new Error(`It's possible just to copy files.`));
79- }
80-
81- let cryptOrDecryptFn = null;
82- let zipFn = null;
83- let unzipFn = null;
84-
85- if (crypt) {
86-
87- // Set function to crypt
88- cryptOrDecryptFn = createCipher(crypt.algorithm, crypt.password);
89- zipFn = createGzip();
90- unzipFn = new PassThrough();
91- } else if (decrypt) {
92-
93- // Set function to decrypt
94- cryptOrDecryptFn = createDecipher(decrypt.algorithm, decrypt.password);
95- zipFn = new PassThrough();
96- unzipFn = createGunzip();
97- } else {
98-
99- // Set empty stream just to pipe
100- cryptOrDecryptFn = new PassThrough();
101- zipFn = new PassThrough();
102- unzipFn = new PassThrough();
103- }
104-
105- const destStream = createWriteStream(target);
106- destStream.on('finish', () => {
107-
108- // Change target modificate time
109- return utimes(target, statSource.atime, statSource.mtime, () => {
110- return resolve();
111- });
112- });
113- destStream.on('error', err => reject(err));
114-
115- const combinedStreams = combiner([
116- zipFn,
117- cryptOrDecryptFn,
118- unzipFn
119- ]);
120-
121- // Read the file > crypt/decrypt > write to the target > resolve the promise
122- return createReadStream(source)
123- .pipe(combinedStreams)
124- .pipe(destStream);
125- });
126-
127-/**
12855 * @function readRecursive
12956 * @description Reads recursivelly a path
13057 * @param {String} source Path to copy
13158 * @param {String} target Target path
@@ -144,6 +71,8 @@
14471 const pathsParsed = files.map(f => [
14572 join(source, f),
14673 join(target, f)
14774 ]);
75 +
76 + return resolve(pathsParsed);
14877 });
14978 });
copy.jsView
@@ -1,0 +1,318 @@
1 +const fs = require('graceful-fs');
2 +const path = require('path');
3 +const utils = require('./utils');
4 +const { mkdirs, utimes } = require('fs-extra');
5 +
6 +const notExist = Symbol('notExist');
7 +const existsReg = Symbol('existsReg');
8 +
9 +module.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 +
29 +function 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 +
53 +function 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 +
65 +function 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 +
91 +function 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 +
115 +function 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 +
133 +function 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 +
156 +function 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 +
174 +function 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 +
206 +function 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 +
228 +function 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 +
252 +function 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 +
270 +function 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
289 +const 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
312 +const 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