ssh2-sftp-client 9.0.4 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +431 -363
- package/README.org +44 -91
- package/package.json +3 -3
- package/src/index.js +587 -543
- package/src/utils.js +22 -5
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const concat = require('concat-stream');
|
|
|
6
6
|
const promiseRetry = require('promise-retry');
|
|
7
7
|
const { join, parse } = require('path');
|
|
8
8
|
const {
|
|
9
|
+
globalListener,
|
|
9
10
|
addTempListeners,
|
|
10
11
|
removeTempListeners,
|
|
11
12
|
haveConnection,
|
|
@@ -26,42 +27,12 @@ class SftpClient {
|
|
|
26
27
|
this.errorHandled = false;
|
|
27
28
|
this.closeHandled = false;
|
|
28
29
|
this.endHandled = false;
|
|
29
|
-
this.remotePathSep = '/';
|
|
30
30
|
this.remotePlatform = 'unix';
|
|
31
31
|
this.debug = undefined;
|
|
32
32
|
|
|
33
|
-
this.client.on('close', ()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.debugMsg('Global: Ignoring handled close event');
|
|
37
|
-
} else {
|
|
38
|
-
this.debugMsg('Global: Handling unexpected close event');
|
|
39
|
-
this.sftp = undefined;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
this.client.on('end', () => {
|
|
44
|
-
if (this.endCalled || this.errorHandled || this.endHandled) {
|
|
45
|
-
// end event expected or handled elsewhere
|
|
46
|
-
this.debugMsg('Global: Ignoring hanlded end event');
|
|
47
|
-
} else {
|
|
48
|
-
this.debugMsg('Global: Handling unexpected end event');
|
|
49
|
-
this.sftp = undefined;
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
this.client.on('error', (err) => {
|
|
54
|
-
if (this.endCalled || this.errorHandled) {
|
|
55
|
-
// error event expected or handled elsewhere
|
|
56
|
-
this.debugMsg(`Global: Ignoring handled error: ${err.message}`);
|
|
57
|
-
} else {
|
|
58
|
-
this.debugMsg(`Global; Handling unexpected error; ${err.message}`);
|
|
59
|
-
this.sftp = undefined;
|
|
60
|
-
console.log(
|
|
61
|
-
`ssh2-sftp-client: Unexpected error: ${err.message}. Error code: ${err.code}`
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
33
|
+
this.client.on('close', globalListener(this, 'close'));
|
|
34
|
+
this.client.on('end', globalListener(this, 'end'));
|
|
35
|
+
this.client.on('error', globalListener(this, 'error'));
|
|
65
36
|
}
|
|
66
37
|
|
|
67
38
|
debugMsg(msg, obj) {
|
|
@@ -175,7 +146,6 @@ class SftpClient {
|
|
|
175
146
|
if (err) {
|
|
176
147
|
reject(this.fmtError(err, 'getSftpChannel', err.code));
|
|
177
148
|
} else {
|
|
178
|
-
this.debugMsg('getSftpChannel: SFTP channel established');
|
|
179
149
|
this.sftp = sftp;
|
|
180
150
|
resolve(sftp);
|
|
181
151
|
}
|
|
@@ -261,41 +231,36 @@ class SftpClient {
|
|
|
261
231
|
* Returns undefined if the path does not exists.
|
|
262
232
|
*
|
|
263
233
|
* @param {String} remotePath - remote path, may be relative
|
|
234
|
+
* @param {Boolean} addListeners - (Optional) add event listeners. Default = true
|
|
264
235
|
* @returns {Promise<String>} - remote absolute path or ''
|
|
265
236
|
*/
|
|
266
|
-
|
|
237
|
+
realPath(remotePath, addListeners = true) {
|
|
238
|
+
let listeners;
|
|
267
239
|
return new Promise((resolve, reject) => {
|
|
268
|
-
|
|
269
|
-
|
|
240
|
+
if (addListeners) {
|
|
241
|
+
listeners = addTempListeners(this, 'realPath', reject);
|
|
242
|
+
}
|
|
243
|
+
this.debugMsg(`realPath -> ${remotePath}`);
|
|
244
|
+
this.sftp.realpath(remotePath, (err, absPath) => {
|
|
270
245
|
if (err) {
|
|
271
246
|
if (err.code === 2) {
|
|
272
|
-
this.debugMsg('
|
|
247
|
+
this.debugMsg('realPath <- ""');
|
|
273
248
|
resolve('');
|
|
274
249
|
} else {
|
|
275
|
-
|
|
250
|
+
this.debugMsg(`${err.message} ${remotePath}`, 'realPath');
|
|
251
|
+
reject(this.fmtError(`${err.message} ${remotePath}`, 'realPath', err.code));
|
|
276
252
|
}
|
|
277
253
|
}
|
|
278
|
-
this.debugMsg(`
|
|
254
|
+
this.debugMsg(`realPath <- ${absPath}`);
|
|
279
255
|
resolve(absPath);
|
|
280
256
|
});
|
|
257
|
+
}).finally(() => {
|
|
258
|
+
if (addListeners) {
|
|
259
|
+
removeTempListeners(this, listeners, 'realPath');
|
|
260
|
+
}
|
|
281
261
|
});
|
|
282
262
|
}
|
|
283
263
|
|
|
284
|
-
async realPath(remotePath) {
|
|
285
|
-
let listeners;
|
|
286
|
-
try {
|
|
287
|
-
listeners = addTempListeners(this, 'realPath');
|
|
288
|
-
haveConnection(this, 'realPath');
|
|
289
|
-
return await this._realPath(remotePath);
|
|
290
|
-
} catch (e) {
|
|
291
|
-
throw e.custom
|
|
292
|
-
? e
|
|
293
|
-
: this.fmtError(`${e.message} ${remotePath}`, 'realPath', e.code);
|
|
294
|
-
} finally {
|
|
295
|
-
removeTempListeners(this, listeners, 'realPath');
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
264
|
/**
|
|
300
265
|
* @async
|
|
301
266
|
*
|
|
@@ -308,20 +273,23 @@ class SftpClient {
|
|
|
308
273
|
}
|
|
309
274
|
|
|
310
275
|
/**
|
|
311
|
-
* Retrieves attributes for path
|
|
276
|
+
* Retrieves attributes for path using cmd, which is either
|
|
277
|
+
* this.sftp.stat or this.sftp.lstat
|
|
312
278
|
*
|
|
279
|
+
* @param {Function} cmd - either this.sftp.stat or this.sftp.lstat
|
|
313
280
|
* @param {String} remotePath - a string containing the path to a file
|
|
281
|
+
* @param {Boolean} addListeners - (Optional) if true add event listeners. Default true.
|
|
314
282
|
* @return {Promise<Object>} stats - attributes info
|
|
315
283
|
*/
|
|
316
|
-
|
|
284
|
+
_xstat(cmd, aPath, addListeners = true) {
|
|
285
|
+
let listeners;
|
|
317
286
|
return new Promise((resolve, reject) => {
|
|
318
|
-
|
|
319
|
-
this.sftp.stat(aPath, (err, stats) => {
|
|
287
|
+
let cb = (err, stats) => {
|
|
320
288
|
if (err) {
|
|
321
289
|
if (err.code === 2 || err.code === 4) {
|
|
322
|
-
reject(this.fmtError(`No such file: ${aPath}`, '
|
|
290
|
+
reject(this.fmtError(`No such file: ${aPath}`, '_xstat', errorCode.notexist));
|
|
323
291
|
} else {
|
|
324
|
-
reject(this.fmtError(`${err.message} ${aPath}`, '
|
|
292
|
+
reject(this.fmtError(`${err.message} ${aPath}`, '_xstat', err.code));
|
|
325
293
|
}
|
|
326
294
|
} else {
|
|
327
295
|
const result = {
|
|
@@ -339,24 +307,62 @@ class SftpClient {
|
|
|
339
307
|
isFIFO: stats.isFIFO(),
|
|
340
308
|
isSocket: stats.isSocket(),
|
|
341
309
|
};
|
|
342
|
-
this.debugMsg('
|
|
310
|
+
this.debugMsg('_xstat: result = ', result);
|
|
343
311
|
resolve(result);
|
|
344
312
|
}
|
|
345
|
-
}
|
|
313
|
+
};
|
|
314
|
+
if (addListeners) {
|
|
315
|
+
listeners = addTempListeners(this, '_xstat', reject);
|
|
316
|
+
}
|
|
317
|
+
if (cmd === 'stat') {
|
|
318
|
+
this.sftp.stat(aPath, cb);
|
|
319
|
+
} else {
|
|
320
|
+
this.sftp.lstat(aPath, cb);
|
|
321
|
+
}
|
|
322
|
+
}).finally(() => {
|
|
323
|
+
if (addListeners) {
|
|
324
|
+
removeTempListeners(this, listeners, '_xstat');
|
|
325
|
+
}
|
|
346
326
|
});
|
|
347
327
|
}
|
|
348
328
|
|
|
329
|
+
/*
|
|
330
|
+
* Use the stat command to obtain attributes associated with a remote path.
|
|
331
|
+
* THe difference between stat and lstat is that stat, in the case of symbolic
|
|
332
|
+
* links, will return the attributes associated with the target of the link. With
|
|
333
|
+
* lstat, attributes associated with the symbolic link rather than the target are
|
|
334
|
+
* returned.
|
|
335
|
+
*
|
|
336
|
+
* @param {String} remotePath - path to an object on the remote server
|
|
337
|
+
* @return {Promise<Object>} stats - attributes info
|
|
338
|
+
*
|
|
339
|
+
*/
|
|
349
340
|
async stat(remotePath) {
|
|
350
|
-
let listeners;
|
|
351
341
|
try {
|
|
352
|
-
listeners = addTempListeners(this, 'stat');
|
|
353
342
|
haveConnection(this, 'stat');
|
|
354
|
-
|
|
355
|
-
return await this._stat(absPath);
|
|
343
|
+
return await this._xstat('stat', remotePath);
|
|
356
344
|
} catch (err) {
|
|
357
345
|
throw err.custom ? err : this.fmtError(err, 'stat', err.code);
|
|
358
|
-
}
|
|
359
|
-
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/*
|
|
350
|
+
* Use the lstat command to obtain attributes associated with a remote path.
|
|
351
|
+
* THe difference between stat and lstat is that stat, in the case of symbolic
|
|
352
|
+
* links, will return the attributes associated with the target of the link. With
|
|
353
|
+
* lstat, attributes associated with the symbolic link rather than the target are
|
|
354
|
+
* returned.
|
|
355
|
+
*
|
|
356
|
+
* @param {String} remotePath - path to an object on the remote server
|
|
357
|
+
* @return {Promise<Object>} stats - attributes info
|
|
358
|
+
*
|
|
359
|
+
*/
|
|
360
|
+
async lstat(remotePath) {
|
|
361
|
+
try {
|
|
362
|
+
haveConnection(this, 'lstat');
|
|
363
|
+
return await this._xstat('lstat', remotePath);
|
|
364
|
+
} catch (err) {
|
|
365
|
+
throw err.custom ? err : this.fmtError(err, 'lstat', err.code);
|
|
360
366
|
}
|
|
361
367
|
}
|
|
362
368
|
|
|
@@ -371,51 +377,31 @@ class SftpClient {
|
|
|
371
377
|
* @return {Promise<Boolean|String>} returns false if object does not exist. Returns type of
|
|
372
378
|
* object if it does
|
|
373
379
|
*/
|
|
374
|
-
async
|
|
380
|
+
async exists(remotePath) {
|
|
381
|
+
this.debugMsg(`exists: remotePath = ${remotePath}`);
|
|
375
382
|
try {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
383
|
+
if (remotePath === '.') {
|
|
384
|
+
return 'd';
|
|
385
|
+
}
|
|
386
|
+
const info = await this.lstat(remotePath);
|
|
379
387
|
this.debugMsg('exists: <- ', info);
|
|
380
388
|
if (info.isDirectory) {
|
|
381
|
-
this.debugMsg(`exists: ${rPath} = d`);
|
|
382
389
|
return 'd';
|
|
383
|
-
}
|
|
384
|
-
if (info.isSymbolicLink) {
|
|
385
|
-
this.debugMsg(`exists: ${rPath} = l`);
|
|
390
|
+
} else if (info.isSymbolicLink) {
|
|
386
391
|
return 'l';
|
|
387
|
-
}
|
|
388
|
-
if (info.isFile) {
|
|
389
|
-
this.debugMsg(`exists: ${rPath} = -`);
|
|
392
|
+
} else if (info.isFile) {
|
|
390
393
|
return '-';
|
|
394
|
+
} else {
|
|
395
|
+
return false;
|
|
391
396
|
}
|
|
392
|
-
this.debugMsg(`exists: ${rPath} = false`);
|
|
393
|
-
return false;
|
|
394
397
|
} catch (err) {
|
|
395
398
|
if (err.code === errorCode.notexist) {
|
|
396
|
-
this.debugMsg(`exists: ${rPath} = false errorCode = ${err.code}`);
|
|
397
399
|
return false;
|
|
398
400
|
}
|
|
399
401
|
throw err.custom ? err : this.fmtError(err.message, 'exists', err.code);
|
|
400
402
|
}
|
|
401
403
|
}
|
|
402
404
|
|
|
403
|
-
async exists(remotePath) {
|
|
404
|
-
let listeners;
|
|
405
|
-
try {
|
|
406
|
-
listeners = addTempListeners(this, 'exists');
|
|
407
|
-
haveConnection(this, 'exists');
|
|
408
|
-
if (remotePath === '.') {
|
|
409
|
-
return 'd';
|
|
410
|
-
}
|
|
411
|
-
return await this._exists(remotePath);
|
|
412
|
-
} catch (err) {
|
|
413
|
-
throw err.custom ? err : this.fmtError(err, 'exists', err.code);
|
|
414
|
-
} finally {
|
|
415
|
-
removeTempListeners(this, listeners, 'exists');
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
405
|
/**
|
|
420
406
|
* @async
|
|
421
407
|
*
|
|
@@ -427,55 +413,53 @@ class SftpClient {
|
|
|
427
413
|
*
|
|
428
414
|
* @param {String} remotePath - path to remote directory
|
|
429
415
|
* @param {function} filter - a filter function used to select return entries
|
|
416
|
+
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
|
|
430
417
|
* @returns {Promise<Array>} array of file description objects
|
|
431
418
|
*/
|
|
432
|
-
|
|
419
|
+
list(remotePath, filter, addListeners = true) {
|
|
420
|
+
let listeners;
|
|
433
421
|
return new Promise((resolve, reject) => {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
type: item.longname.slice(0, 1),
|
|
442
|
-
name: item.filename,
|
|
443
|
-
size: item.attrs.size,
|
|
444
|
-
modifyTime: item.attrs.mtime * 1000,
|
|
445
|
-
accessTime: item.attrs.atime * 1000,
|
|
446
|
-
rights: {
|
|
447
|
-
user: item.longname.slice(1, 4).replace(reg, ''),
|
|
448
|
-
group: item.longname.slice(4, 7).replace(reg, ''),
|
|
449
|
-
other: item.longname.slice(7, 10).replace(reg, ''),
|
|
450
|
-
},
|
|
451
|
-
owner: item.attrs.uid,
|
|
452
|
-
group: item.attrs.gid,
|
|
453
|
-
longname: item.longname,
|
|
454
|
-
};
|
|
455
|
-
});
|
|
456
|
-
if (filter) {
|
|
457
|
-
resolve(newList.filter((item) => filter(item)));
|
|
422
|
+
if (addListeners) {
|
|
423
|
+
listeners = addTempListeners(this, 'list', reject);
|
|
424
|
+
}
|
|
425
|
+
if (haveConnection(this, 'list', reject)) {
|
|
426
|
+
this.sftp.readdir(remotePath, (err, fileList) => {
|
|
427
|
+
if (err) {
|
|
428
|
+
reject(this.fmtError(`${err.message} ${remotePath}`, 'list', err.code));
|
|
458
429
|
} else {
|
|
459
|
-
|
|
430
|
+
const reg = /-/gi;
|
|
431
|
+
const newList = fileList.map((item) => {
|
|
432
|
+
return {
|
|
433
|
+
type: item.longname.slice(0, 1),
|
|
434
|
+
name: item.filename,
|
|
435
|
+
size: item.attrs.size,
|
|
436
|
+
modifyTime: item.attrs.mtime * 1000,
|
|
437
|
+
accessTime: item.attrs.atime * 1000,
|
|
438
|
+
rights: {
|
|
439
|
+
user: item.longname.slice(1, 4).replace(reg, ''),
|
|
440
|
+
group: item.longname.slice(4, 7).replace(reg, ''),
|
|
441
|
+
other: item.longname.slice(7, 10).replace(reg, ''),
|
|
442
|
+
},
|
|
443
|
+
owner: item.attrs.uid,
|
|
444
|
+
group: item.attrs.gid,
|
|
445
|
+
longname: item.longname,
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
if (filter) {
|
|
449
|
+
resolve(newList.filter((item) => filter(item)));
|
|
450
|
+
} else {
|
|
451
|
+
resolve(newList);
|
|
452
|
+
}
|
|
460
453
|
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}).finally(() => {
|
|
457
|
+
if (addListeners) {
|
|
458
|
+
removeTempListeners(this, listeners, 'list');
|
|
459
|
+
}
|
|
463
460
|
});
|
|
464
461
|
}
|
|
465
462
|
|
|
466
|
-
async list(remotePath, filter) {
|
|
467
|
-
let listeners;
|
|
468
|
-
try {
|
|
469
|
-
listeners = addTempListeners(this, 'list');
|
|
470
|
-
haveConnection(this, 'list');
|
|
471
|
-
return await this._list(remotePath, filter);
|
|
472
|
-
} catch (e) {
|
|
473
|
-
throw e.custom ? e : this.fmtError(`${e.message} ${remotePath}`, 'list', e.code);
|
|
474
|
-
} finally {
|
|
475
|
-
removeTempListeners(this, listeners, 'list');
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
463
|
/**
|
|
480
464
|
* get file
|
|
481
465
|
*
|
|
@@ -488,6 +472,7 @@ class SftpClient {
|
|
|
488
472
|
* @param {string|stream|undefined} dst - data destination
|
|
489
473
|
* @param {Object} options - options object with supported properties of readStreamOptions,
|
|
490
474
|
* writeStreamOptions and pipeOptions.
|
|
475
|
+
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
|
|
491
476
|
*
|
|
492
477
|
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls
|
|
493
478
|
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been
|
|
@@ -495,83 +480,78 @@ class SftpClient {
|
|
|
495
480
|
*
|
|
496
481
|
* @return {Promise<String|Stream|Buffer>}
|
|
497
482
|
*/
|
|
498
|
-
|
|
499
|
-
let rdr, wtr;
|
|
483
|
+
get(remotePath, dst, options, addListeners = true) {
|
|
484
|
+
let listeners, rdr, wtr;
|
|
500
485
|
return new Promise((resolve, reject) => {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
this.debugMsg('get resolving buffer of data');
|
|
517
|
-
wtr = concat((buff) => {
|
|
518
|
-
resolve(buff);
|
|
486
|
+
if (addListeners) {
|
|
487
|
+
listeners = addTempListeners(this, 'get', reject);
|
|
488
|
+
}
|
|
489
|
+
if (haveConnection(this, 'get', reject)) {
|
|
490
|
+
options = {
|
|
491
|
+
readStreamOptions: { ...options?.readStreamOptions, autoClose: true },
|
|
492
|
+
writeStreamOptions: { ...options?.writeStreamOptions, autoClose: true },
|
|
493
|
+
pipeOptions: { ...options?.pipeOptions, end: true },
|
|
494
|
+
};
|
|
495
|
+
rdr = this.sftp.createReadStream(remotePath, options.readStreamOptions);
|
|
496
|
+
rdr.once('error', (err) => {
|
|
497
|
+
if (dst && typeof dst !== 'string' && !dst.destroyed) {
|
|
498
|
+
dst.destroy();
|
|
499
|
+
}
|
|
500
|
+
reject(this.fmtError(`${err.message} ${remotePath}`, 'get', err.code));
|
|
519
501
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
502
|
+
if (dst === undefined) {
|
|
503
|
+
// no dst specified, return buffer of data
|
|
504
|
+
this.debugMsg('get resolving buffer of data');
|
|
505
|
+
wtr = concat((buff) => {
|
|
506
|
+
resolve(buff);
|
|
507
|
+
});
|
|
508
|
+
} else if (typeof dst === 'string') {
|
|
509
|
+
// dst local file path
|
|
510
|
+
this.debugMsg('get returning local file');
|
|
511
|
+
const localCheck = haveLocalCreate(dst);
|
|
512
|
+
if (!localCheck.status) {
|
|
513
|
+
reject(
|
|
514
|
+
this.fmtError(
|
|
515
|
+
`Bad path: ${dst}: ${localCheck.details}`,
|
|
516
|
+
'get',
|
|
517
|
+
localCheck.code
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
return;
|
|
521
|
+
} else {
|
|
522
|
+
wtr = fs.createWriteStream(dst, options.writeStreamOptions);
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
525
|
+
this.debugMsg('get: returning data into supplied stream');
|
|
526
|
+
wtr = dst;
|
|
527
|
+
}
|
|
528
|
+
wtr.once('error', (err) => {
|
|
525
529
|
reject(
|
|
526
530
|
this.fmtError(
|
|
527
|
-
|
|
531
|
+
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`,
|
|
528
532
|
'get',
|
|
529
|
-
|
|
533
|
+
err.code
|
|
530
534
|
)
|
|
531
535
|
);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
});
|
|
549
|
-
rdr.once('end', () => {
|
|
550
|
-
if (typeof dst === 'string') {
|
|
551
|
-
this.debugMsg('get: resolving with dst filename');
|
|
552
|
-
resolve(dst);
|
|
553
|
-
} else if (dst !== undefined) {
|
|
554
|
-
this.debugMsg('get: resolving with writer stream object');
|
|
555
|
-
resolve(wtr);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
rdr.pipe(wtr, opts.pipeOptions);
|
|
536
|
+
});
|
|
537
|
+
rdr.once('end', () => {
|
|
538
|
+
if (typeof dst === 'string') {
|
|
539
|
+
this.debugMsg('get: resolving with dst filename');
|
|
540
|
+
resolve(dst);
|
|
541
|
+
} else if (dst !== undefined) {
|
|
542
|
+
this.debugMsg('get: resolving with writer stream object');
|
|
543
|
+
resolve(wtr);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
rdr.pipe(wtr, options.pipeOptions);
|
|
547
|
+
}
|
|
548
|
+
}).finally(() => {
|
|
549
|
+
if (addListeners) {
|
|
550
|
+
removeTempListeners(this, listeners, 'get');
|
|
551
|
+
}
|
|
559
552
|
});
|
|
560
553
|
}
|
|
561
554
|
|
|
562
|
-
async get(remotePath, dst, options) {
|
|
563
|
-
let listeners;
|
|
564
|
-
try {
|
|
565
|
-
listeners = addTempListeners(this, 'get');
|
|
566
|
-
haveConnection(this, 'get');
|
|
567
|
-
return await this._get(remotePath, dst, options);
|
|
568
|
-
} catch (e) {
|
|
569
|
-
throw e.custom ? e : this.fmtError(`${e.message} ${remotePath}`, 'get', e.code);
|
|
570
|
-
} finally {
|
|
571
|
-
removeTempListeners(this, listeners, 'get');
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
555
|
/**
|
|
576
556
|
* Use SSH2 fastGet for downloading the file.
|
|
577
557
|
* Downloads a file at remotePath to localPath using parallel reads
|
|
@@ -582,22 +562,29 @@ class SftpClient {
|
|
|
582
562
|
* @param {Object} options
|
|
583
563
|
* @return {Promise<String>} the result of downloading the file
|
|
584
564
|
*/
|
|
585
|
-
_fastGet(rPath, lPath, opts) {
|
|
565
|
+
_fastGet(rPath, lPath, opts, addListeners = true) {
|
|
566
|
+
let listeners;
|
|
586
567
|
return new Promise((resolve, reject) => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
568
|
+
if (addListeners) {
|
|
569
|
+
listeners = addTempListeners(this, '_fastGet', reject);
|
|
570
|
+
}
|
|
571
|
+
if (haveConnection(this, '_fastGet', reject)) {
|
|
572
|
+
this.sftp.fastGet(rPath, lPath, opts, (err) => {
|
|
573
|
+
if (err) {
|
|
574
|
+
reject(this.fmtError(`${err.message} Remote: ${rPath} Local: ${lPath}`));
|
|
575
|
+
}
|
|
576
|
+
resolve(`${rPath} was successfully download to ${lPath}!`);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}).finally(() => {
|
|
580
|
+
if (addListeners) {
|
|
581
|
+
removeTempListeners(this, listeners, '_fastGet');
|
|
582
|
+
}
|
|
593
583
|
});
|
|
594
584
|
}
|
|
595
585
|
|
|
596
586
|
async fastGet(remotePath, localPath, options) {
|
|
597
|
-
let listeners;
|
|
598
587
|
try {
|
|
599
|
-
listeners = addTempListeners(this, 'fastGet');
|
|
600
|
-
haveConnection(this, 'fastGet');
|
|
601
588
|
const ftype = await this.exists(remotePath);
|
|
602
589
|
if (ftype !== '-') {
|
|
603
590
|
const msg = `${!ftype ? 'No such file ' : 'Not a regular file'} ${remotePath}`;
|
|
@@ -614,8 +601,6 @@ class SftpClient {
|
|
|
614
601
|
return await this._fastGet(remotePath, localPath, options);
|
|
615
602
|
} catch (err) {
|
|
616
603
|
throw this.fmtError(err, 'fastGet');
|
|
617
|
-
} finally {
|
|
618
|
-
removeTempListeners(this, listeners, 'fastGet');
|
|
619
604
|
}
|
|
620
605
|
}
|
|
621
606
|
|
|
@@ -627,34 +612,42 @@ class SftpClient {
|
|
|
627
612
|
* See 'fastPut' at
|
|
628
613
|
* https://github.com/mscdex/ssh2-streams/blob/master/SFTPStream.md
|
|
629
614
|
*
|
|
630
|
-
* @param {String} localPath
|
|
631
|
-
* @param {String} remotePath
|
|
632
|
-
* @param {Object} options
|
|
615
|
+
* @param {String} localPath - path to local file to put
|
|
616
|
+
* @param {String} remotePath - destination path for put file
|
|
617
|
+
* @param {Object} options - additonal fastPut options
|
|
618
|
+
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true.
|
|
633
619
|
* @return {Promise<String>} the result of downloading the file
|
|
634
620
|
*/
|
|
635
|
-
_fastPut(lPath, rPath, opts) {
|
|
621
|
+
_fastPut(lPath, rPath, opts, addListeners = true) {
|
|
622
|
+
let listeners;
|
|
636
623
|
return new Promise((resolve, reject) => {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
624
|
+
if (addListeners) {
|
|
625
|
+
listeners = addTempListeners(this, '_fastPut', reject);
|
|
626
|
+
}
|
|
627
|
+
if (haveConnection(this, '_fastPut', reject)) {
|
|
628
|
+
this.sftp.fastPut(lPath, rPath, opts, (err) => {
|
|
629
|
+
if (err) {
|
|
630
|
+
reject(
|
|
631
|
+
this.fmtError(
|
|
632
|
+
`${err.message} Local: ${lPath} Remote: ${rPath}`,
|
|
633
|
+
'fastPut',
|
|
634
|
+
err.code
|
|
635
|
+
)
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
resolve(`${lPath} was successfully uploaded to ${rPath}!`);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}).finally(() => {
|
|
642
|
+
if (addListeners) {
|
|
643
|
+
removeTempListeners(this, listeners, '_fastPut');
|
|
644
|
+
}
|
|
649
645
|
});
|
|
650
646
|
}
|
|
651
647
|
|
|
652
648
|
async fastPut(localPath, remotePath, options) {
|
|
653
|
-
let listeners;
|
|
654
649
|
try {
|
|
655
|
-
listeners = addTempListeners(this, 'fastPut');
|
|
656
650
|
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
657
|
-
haveConnection(this, 'fastPut');
|
|
658
651
|
const localCheck = haveLocalAccess(localPath);
|
|
659
652
|
if (!localCheck.status) {
|
|
660
653
|
throw this.fmtError(
|
|
@@ -672,8 +665,6 @@ class SftpClient {
|
|
|
672
665
|
return await this._fastPut(localPath, remotePath, options);
|
|
673
666
|
} catch (e) {
|
|
674
667
|
throw e.custom ? e : this.fmtError(e.message, 'fastPut', e.code);
|
|
675
|
-
} finally {
|
|
676
|
-
removeTempListeners(this, listeners, 'fastPut');
|
|
677
668
|
}
|
|
678
669
|
}
|
|
679
670
|
|
|
@@ -694,49 +685,58 @@ class SftpClient {
|
|
|
694
685
|
*
|
|
695
686
|
* @return {Promise<String>}
|
|
696
687
|
*/
|
|
697
|
-
_put(lPath, rPath, opts) {
|
|
698
|
-
let wtr, rdr;
|
|
688
|
+
_put(lPath, rPath, opts, addListeners = true) {
|
|
689
|
+
let listeners, wtr, rdr;
|
|
699
690
|
return new Promise((resolve, reject) => {
|
|
691
|
+
if (addListeners) {
|
|
692
|
+
listeners = addTempListeners(this, '_put', reject);
|
|
693
|
+
}
|
|
700
694
|
opts = {
|
|
701
|
-
...opts,
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
pipeOptions: { end: true },
|
|
695
|
+
readStreamOptions: { ...opts?.readStreamOptions, autoClose: true },
|
|
696
|
+
writeStreamOptions: { ...opts?.writeStreamOptions, autoClose: true },
|
|
697
|
+
pipeOptions: { ...opts?.pipeOptions, end: true },
|
|
705
698
|
};
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
});
|
|
710
|
-
wtr.once('close', () => {
|
|
711
|
-
resolve(`Uploaded data stream to ${rPath}`);
|
|
712
|
-
});
|
|
713
|
-
if (lPath instanceof Buffer) {
|
|
714
|
-
this.debugMsg('put source is a buffer');
|
|
715
|
-
wtr.end(lPath);
|
|
716
|
-
} else {
|
|
717
|
-
rdr =
|
|
718
|
-
typeof lPath === 'string'
|
|
719
|
-
? fs.createReadStream(lPath, opts.readStreamOptions)
|
|
720
|
-
: lPath;
|
|
721
|
-
rdr.once('error', (err) => {
|
|
699
|
+
if (haveConnection(this, '_put', reject)) {
|
|
700
|
+
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
|
|
701
|
+
wtr.once('error', (err) => {
|
|
722
702
|
reject(
|
|
723
|
-
this.fmtError(
|
|
724
|
-
`${err.message} ${typeof lPath === 'string' ? lPath : '<stream>'}`,
|
|
725
|
-
'_put',
|
|
726
|
-
err.code
|
|
727
|
-
)
|
|
703
|
+
this.fmtError(`Write stream error: ${err.message} ${rPath}`, '_put', err.code)
|
|
728
704
|
);
|
|
729
705
|
});
|
|
730
|
-
|
|
706
|
+
wtr.once('close', () => {
|
|
707
|
+
resolve(`Uploaded data stream to ${rPath}`);
|
|
708
|
+
});
|
|
709
|
+
if (lPath instanceof Buffer) {
|
|
710
|
+
this.debugMsg('put source is a buffer');
|
|
711
|
+
wtr.end(lPath);
|
|
712
|
+
} else {
|
|
713
|
+
rdr =
|
|
714
|
+
typeof lPath === 'string'
|
|
715
|
+
? fs.createReadStream(lPath, opts.readStreamOptions)
|
|
716
|
+
: lPath;
|
|
717
|
+
rdr.once('error', (err) => {
|
|
718
|
+
reject(
|
|
719
|
+
this.fmtError(
|
|
720
|
+
`Read stream error: ${err.message} ${
|
|
721
|
+
typeof lPath === 'string' ? lPath : '<stream>'
|
|
722
|
+
}`,
|
|
723
|
+
'_put',
|
|
724
|
+
err.code
|
|
725
|
+
)
|
|
726
|
+
);
|
|
727
|
+
});
|
|
728
|
+
rdr.pipe(wtr, opts.pipeOptions);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}).finally(() => {
|
|
732
|
+
if (addListeners) {
|
|
733
|
+
removeTempListeners(this, listeners, '_put');
|
|
731
734
|
}
|
|
732
735
|
});
|
|
733
736
|
}
|
|
734
737
|
|
|
735
738
|
async put(localSrc, remotePath, options) {
|
|
736
|
-
let listeners;
|
|
737
739
|
try {
|
|
738
|
-
listeners = addTempListeners(this, 'put');
|
|
739
|
-
haveConnection(this, 'put');
|
|
740
740
|
if (typeof localSrc === 'string') {
|
|
741
741
|
const localCheck = haveLocalAccess(localSrc);
|
|
742
742
|
if (!localCheck.status) {
|
|
@@ -749,9 +749,7 @@ class SftpClient {
|
|
|
749
749
|
}
|
|
750
750
|
return await this._put(localSrc, remotePath, options);
|
|
751
751
|
} catch (e) {
|
|
752
|
-
throw e.custom ? e : this.fmtError(e.message
|
|
753
|
-
} finally {
|
|
754
|
-
removeTempListeners(this, listeners, 'put');
|
|
752
|
+
throw e.custom ? e : this.fmtError(`Re-thrown: ${e.message}`, 'put', e.code);
|
|
755
753
|
}
|
|
756
754
|
}
|
|
757
755
|
|
|
@@ -763,30 +761,38 @@ class SftpClient {
|
|
|
763
761
|
* @param {Object} options
|
|
764
762
|
* @return {Promise<String>}
|
|
765
763
|
*/
|
|
766
|
-
_append(input, rPath, opts) {
|
|
764
|
+
_append(input, rPath, opts, addListeners = true) {
|
|
765
|
+
let listeners;
|
|
767
766
|
return new Promise((resolve, reject) => {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
stream.
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
input
|
|
767
|
+
if (addListeners) {
|
|
768
|
+
listeners = addTempListeners(this, '_append', reject);
|
|
769
|
+
}
|
|
770
|
+
if (haveConnection(this, '_append', reject)) {
|
|
771
|
+
this.debugMsg(`append -> remote: ${rPath} `, opts);
|
|
772
|
+
opts.flags = 'a';
|
|
773
|
+
const stream = this.sftp.createWriteStream(rPath, opts);
|
|
774
|
+
stream.on('error', (err) => {
|
|
775
|
+
reject(this.fmtError(`${err.message} ${rPath}`, 'append', err.code));
|
|
776
|
+
});
|
|
777
|
+
stream.on('close', () => {
|
|
778
|
+
resolve(`Appended data to ${rPath}`);
|
|
779
|
+
});
|
|
780
|
+
if (input instanceof Buffer) {
|
|
781
|
+
stream.write(input);
|
|
782
|
+
stream.end();
|
|
783
|
+
} else {
|
|
784
|
+
input.pipe(stream);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}).finally(() => {
|
|
788
|
+
if (addListeners) {
|
|
789
|
+
removeTempListeners(this, listeners, '_append');
|
|
782
790
|
}
|
|
783
791
|
});
|
|
784
792
|
}
|
|
785
793
|
|
|
786
794
|
async append(input, remotePath, options = {}) {
|
|
787
|
-
let listeners;
|
|
788
795
|
try {
|
|
789
|
-
listeners = addTempListeners(this, 'append');
|
|
790
796
|
if (typeof input === 'string') {
|
|
791
797
|
throw this.fmtError(
|
|
792
798
|
'Cannot append one file to another',
|
|
@@ -794,7 +800,6 @@ class SftpClient {
|
|
|
794
800
|
errorCode.badPath
|
|
795
801
|
);
|
|
796
802
|
}
|
|
797
|
-
haveConnection(this, 'append');
|
|
798
803
|
const fileType = await this.exists(remotePath);
|
|
799
804
|
if (fileType && fileType === 'd') {
|
|
800
805
|
throw this.fmtError(
|
|
@@ -806,8 +811,6 @@ class SftpClient {
|
|
|
806
811
|
await this._append(input, remotePath, options);
|
|
807
812
|
} catch (e) {
|
|
808
813
|
throw e.custom ? e : this.fmtError(e.message, 'append', e.code);
|
|
809
|
-
} finally {
|
|
810
|
-
removeTempListeners(this, listeners, 'append');
|
|
811
814
|
}
|
|
812
815
|
}
|
|
813
816
|
|
|
@@ -820,8 +823,12 @@ class SftpClient {
|
|
|
820
823
|
* @param {boolean} recursive - if true, recursively create directories
|
|
821
824
|
* @return {Promise<String>}
|
|
822
825
|
*/
|
|
823
|
-
_doMkdir(p) {
|
|
826
|
+
_doMkdir(p, addListeners = true) {
|
|
827
|
+
let listeners;
|
|
824
828
|
return new Promise((resolve, reject) => {
|
|
829
|
+
if (addListeners) {
|
|
830
|
+
listeners = addTempListeners(this, '_doMkdir', reject);
|
|
831
|
+
}
|
|
825
832
|
this.sftp.mkdir(p, (err) => {
|
|
826
833
|
if (err) {
|
|
827
834
|
if (err.code === 4) {
|
|
@@ -848,6 +855,10 @@ class SftpClient {
|
|
|
848
855
|
resolve(`${p} directory created`);
|
|
849
856
|
}
|
|
850
857
|
});
|
|
858
|
+
}).finally(() => {
|
|
859
|
+
if (addListeners) {
|
|
860
|
+
removeTempListeners(this, listeners, '_doMkdir');
|
|
861
|
+
}
|
|
851
862
|
});
|
|
852
863
|
}
|
|
853
864
|
|
|
@@ -889,15 +900,11 @@ class SftpClient {
|
|
|
889
900
|
}
|
|
890
901
|
|
|
891
902
|
async mkdir(remotePath, recursive = false) {
|
|
892
|
-
let listeners;
|
|
893
903
|
try {
|
|
894
|
-
listeners = addTempListeners(this, '_mkdir');
|
|
895
904
|
haveConnection(this, 'mkdir');
|
|
896
905
|
return await this._mkdir(remotePath, recursive);
|
|
897
906
|
} catch (err) {
|
|
898
907
|
throw this.fmtError(`${err.message}`, 'mkdir', err.code);
|
|
899
|
-
} finally {
|
|
900
|
-
removeTempListeners(this, listeners, 'append');
|
|
901
908
|
}
|
|
902
909
|
}
|
|
903
910
|
|
|
@@ -911,69 +918,85 @@ class SftpClient {
|
|
|
911
918
|
* directory
|
|
912
919
|
* @return {Promise<String>}
|
|
913
920
|
*/
|
|
914
|
-
async rmdir(
|
|
915
|
-
const _rmdir = (
|
|
921
|
+
async rmdir(remoteDir, recursive = false) {
|
|
922
|
+
const _rmdir = (dir) => {
|
|
923
|
+
let listeners;
|
|
916
924
|
return new Promise((resolve, reject) => {
|
|
917
|
-
this
|
|
918
|
-
this.
|
|
925
|
+
listeners = addTempListeners(this, '_rmdir', reject);
|
|
926
|
+
this.debugMsg(`_rmdir: dir = ${dir}`);
|
|
927
|
+
this.sftp.rmdir(dir, (err) => {
|
|
919
928
|
if (err) {
|
|
920
|
-
reject(this.fmtError(`${err.message} ${
|
|
929
|
+
reject(this.fmtError(`${err.message} ${dir}`, 'rmdir', err.code));
|
|
921
930
|
}
|
|
922
931
|
resolve('Successfully removed directory');
|
|
923
932
|
});
|
|
933
|
+
}).finally(() => {
|
|
934
|
+
removeTempListeners(this, listeners, '_rmdir');
|
|
924
935
|
});
|
|
925
936
|
};
|
|
926
937
|
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
this.debugMsg('rmdir contents (dirs): ', dirs);
|
|
936
|
-
for (const d of dirs) {
|
|
937
|
-
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true);
|
|
938
|
-
}
|
|
939
|
-
const promiseList = [];
|
|
940
|
-
for (const f of files) {
|
|
941
|
-
promiseList.push(this._delete(`${p}${this.remotePathSep}${f.name}`));
|
|
942
|
-
}
|
|
943
|
-
await Promise.all(promiseList);
|
|
944
|
-
}
|
|
938
|
+
const _delFiles = (path, fileList) => {
|
|
939
|
+
let listeners;
|
|
940
|
+
return new Promise((resolve, reject) => {
|
|
941
|
+
listeners = addTempListeners(this, '_delFiles', reject);
|
|
942
|
+
this.debugMsg(`_delFiles: path = ${path} fileList = ${fileList}`);
|
|
943
|
+
let pList = [];
|
|
944
|
+
for (const f of fileList) {
|
|
945
|
+
pList.push(this.delete(`${path}/${f.name}`, true, false));
|
|
945
946
|
}
|
|
946
|
-
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
|
|
947
|
+
resolve(pList);
|
|
948
|
+
})
|
|
949
|
+
.then((p) => {
|
|
950
|
+
return Promise.all(p);
|
|
951
|
+
})
|
|
952
|
+
.finally(() => {
|
|
953
|
+
removeTempListeners(this, listeners, '_delFiles');
|
|
954
|
+
});
|
|
950
955
|
};
|
|
951
956
|
|
|
952
|
-
let listeners;
|
|
953
957
|
try {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (
|
|
958
|
+
this.debugMsg(`rmdir: dir = ${remoteDir} recursive = ${recursive}`);
|
|
959
|
+
let absPath = await normalizeRemotePath(this, remoteDir);
|
|
960
|
+
let existStatus = await this.exists(absPath);
|
|
961
|
+
this.debugMsg(`rmdir: ${absPath} existStatus = ${existStatus}`);
|
|
962
|
+
if (!existStatus) {
|
|
959
963
|
throw this.fmtError(
|
|
960
|
-
`Bad
|
|
964
|
+
`Bad Path: ${remoteDir}: No such directory`,
|
|
961
965
|
'rmdir',
|
|
962
966
|
errorCode.badPath
|
|
963
967
|
);
|
|
964
|
-
}
|
|
968
|
+
}
|
|
969
|
+
if (existStatus !== 'd') {
|
|
965
970
|
throw this.fmtError(
|
|
966
|
-
`Bad
|
|
971
|
+
`Bad Path: ${remoteDir}: Not a directory`,
|
|
967
972
|
'rmdir',
|
|
968
973
|
errorCode.badPath
|
|
969
974
|
);
|
|
970
|
-
} else {
|
|
971
|
-
return await _dormdir(absPath, recursive);
|
|
972
975
|
}
|
|
976
|
+
if (!recursive) {
|
|
977
|
+
this.debugMsg('rmdir: non-recursive - just try to remove it');
|
|
978
|
+
return await _rmdir(absPath);
|
|
979
|
+
}
|
|
980
|
+
let listing = await this.list(absPath);
|
|
981
|
+
this.debugMsg(`rmdir: listing count = ${listing.length}`);
|
|
982
|
+
if (!listing.length) {
|
|
983
|
+
this.debugMsg('rmdir: No sub dir or files, just rmdir');
|
|
984
|
+
return await _rmdir(absPath);
|
|
985
|
+
}
|
|
986
|
+
let fileList = listing.filter((i) => i.type !== 'd');
|
|
987
|
+
this.debugMsg(`rmdir: dir content files to remove = ${fileList.length}`);
|
|
988
|
+
let dirList = listing.filter((i) => i.type === 'd');
|
|
989
|
+
this.debugMsg(`rmdir: sub-directories to remove = ${dirList.length}`);
|
|
990
|
+
await _delFiles(absPath, fileList);
|
|
991
|
+
for (const d of dirList) {
|
|
992
|
+
await this.rmdir(`${absPath}/${d.name}`, true);
|
|
993
|
+
}
|
|
994
|
+
await _rmdir(absPath);
|
|
995
|
+
return 'Successfully removed directory';
|
|
973
996
|
} catch (err) {
|
|
974
|
-
throw err.custom
|
|
975
|
-
|
|
976
|
-
|
|
997
|
+
throw err.custom
|
|
998
|
+
? err
|
|
999
|
+
: this.fmtError(`${err.message} ${remoteDir}`, 'rmdir', err.code);
|
|
977
1000
|
}
|
|
978
1001
|
}
|
|
979
1002
|
|
|
@@ -988,34 +1011,29 @@ class SftpClient {
|
|
|
988
1011
|
* @return {Promise<String>} with string 'Successfully deleted file' once resolved
|
|
989
1012
|
*
|
|
990
1013
|
*/
|
|
991
|
-
|
|
1014
|
+
delete(remotePath, notFoundOK = false, addListeners = true) {
|
|
1015
|
+
let listeners;
|
|
992
1016
|
return new Promise((resolve, reject) => {
|
|
993
|
-
|
|
1017
|
+
if (addListeners) {
|
|
1018
|
+
listeners = addTempListeners(this, 'delete', reject);
|
|
1019
|
+
}
|
|
1020
|
+
this.sftp.unlink(remotePath, (err) => {
|
|
994
1021
|
if (err) {
|
|
995
1022
|
if (notFoundOK && err.code === 2) {
|
|
996
|
-
resolve(`Successfully deleted ${
|
|
1023
|
+
resolve(`Successfully deleted ${remotePath}`);
|
|
997
1024
|
} else {
|
|
998
|
-
reject(this.fmtError(`${err.message} ${
|
|
1025
|
+
reject(this.fmtError(`${err.message} ${remotePath}`, 'delete', err.code));
|
|
999
1026
|
}
|
|
1000
1027
|
}
|
|
1001
|
-
resolve(`Successfully deleted ${
|
|
1028
|
+
resolve(`Successfully deleted ${remotePath}`);
|
|
1002
1029
|
});
|
|
1030
|
+
}).finally(() => {
|
|
1031
|
+
if (addListeners) {
|
|
1032
|
+
removeTempListeners(this, listeners, 'delete');
|
|
1033
|
+
}
|
|
1003
1034
|
});
|
|
1004
1035
|
}
|
|
1005
1036
|
|
|
1006
|
-
async delete(remotePath, notFoundOK = false) {
|
|
1007
|
-
let listeners;
|
|
1008
|
-
try {
|
|
1009
|
-
listeners = addTempListeners(this, 'delete');
|
|
1010
|
-
haveConnection(this, 'delete');
|
|
1011
|
-
return await this._delete(remotePath, notFoundOK);
|
|
1012
|
-
} catch (err) {
|
|
1013
|
-
throw err.custom ? err : this.fmtError(err.message, 'delete', err.code);
|
|
1014
|
-
} finally {
|
|
1015
|
-
removeTempListeners(this, listeners, 'delete');
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
1037
|
/**
|
|
1020
1038
|
* @async
|
|
1021
1039
|
*
|
|
@@ -1023,42 +1041,38 @@ class SftpClient {
|
|
|
1023
1041
|
*
|
|
1024
1042
|
* @param {string} fromPath - path to the file to be renamed.
|
|
1025
1043
|
* @param {string} toPath - path to the new name.
|
|
1044
|
+
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
|
|
1026
1045
|
*
|
|
1027
1046
|
* @return {Promise<String>}
|
|
1028
1047
|
*
|
|
1029
1048
|
*/
|
|
1030
|
-
|
|
1049
|
+
rename(fPath, tPath, addListeners = true) {
|
|
1050
|
+
let listeners;
|
|
1031
1051
|
return new Promise((resolve, reject) => {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1052
|
+
if (addListeners) {
|
|
1053
|
+
listeners = addTempListeners(this, 'rename', reject);
|
|
1054
|
+
}
|
|
1055
|
+
if (haveConnection(this, 'rename', reject)) {
|
|
1056
|
+
this.sftp.rename(fPath, tPath, (err) => {
|
|
1057
|
+
if (err) {
|
|
1058
|
+
reject(
|
|
1059
|
+
this.fmtError(
|
|
1060
|
+
`${err.message} From: ${fPath} To: ${tPath}`,
|
|
1061
|
+
'_rename',
|
|
1062
|
+
err.code
|
|
1063
|
+
)
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
resolve(`Successfully renamed ${fPath} to ${tPath}`);
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}).finally(() => {
|
|
1070
|
+
if (addListeners) {
|
|
1071
|
+
removeTempListeners(this, listeners, 'rename');
|
|
1072
|
+
}
|
|
1044
1073
|
});
|
|
1045
1074
|
}
|
|
1046
1075
|
|
|
1047
|
-
async rename(fromPath, toPath) {
|
|
1048
|
-
let listeners;
|
|
1049
|
-
try {
|
|
1050
|
-
listeners = addTempListeners(this, 'rename');
|
|
1051
|
-
haveConnection(this, 'rename');
|
|
1052
|
-
return await this._rename(fromPath, toPath);
|
|
1053
|
-
} catch (err) {
|
|
1054
|
-
throw err.custom
|
|
1055
|
-
? err
|
|
1056
|
-
: this.fmtError(`${err.message} ${fromPath} ${toPath}`, 'rename', err.code);
|
|
1057
|
-
} finally {
|
|
1058
|
-
removeTempListeners(this, listeners, 'rename');
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
1076
|
/**
|
|
1063
1077
|
* @async
|
|
1064
1078
|
*
|
|
@@ -1067,40 +1081,34 @@ class SftpClient {
|
|
|
1067
1081
|
*
|
|
1068
1082
|
* @param {string} fromPath - path to the file to be renamed.
|
|
1069
1083
|
* @param {string} toPath - path the new name.
|
|
1084
|
+
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true
|
|
1070
1085
|
*
|
|
1071
1086
|
* @return {Promise<String>}
|
|
1072
1087
|
*
|
|
1073
1088
|
*/
|
|
1074
|
-
|
|
1075
|
-
return new Promise((resolve, reject) => {
|
|
1076
|
-
this.sftp.ext_openssh_rename(fPath, tPath, (err) => {
|
|
1077
|
-
if (err) {
|
|
1078
|
-
reject(
|
|
1079
|
-
this.fmtError(
|
|
1080
|
-
`${err.message} From: ${fPath} To: ${tPath}`,
|
|
1081
|
-
'_posixRename',
|
|
1082
|
-
err.code
|
|
1083
|
-
)
|
|
1084
|
-
);
|
|
1085
|
-
}
|
|
1086
|
-
resolve(`Successful POSIX rename ${fPath} to ${tPath}`);
|
|
1087
|
-
});
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
async posixRename(fromPath, toPath) {
|
|
1089
|
+
posixRename(fPath, tPath, addListeners = true) {
|
|
1092
1090
|
let listeners;
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1091
|
+
return new Promise((resolve, reject) => {
|
|
1092
|
+
if (addListeners) {
|
|
1093
|
+
listeners = addTempListeners(this, 'posixRename', reject);
|
|
1094
|
+
}
|
|
1095
|
+
if (haveConnection(this, 'posixRename', reject)) {
|
|
1096
|
+
this.sftp.ext_openssh_rename(fPath, tPath, (err) => {
|
|
1097
|
+
if (err) {
|
|
1098
|
+
reject(
|
|
1099
|
+
this.fmtError(
|
|
1100
|
+
`${err.message} From: ${fPath} To: ${tPath}`,
|
|
1101
|
+
'_posixRename',
|
|
1102
|
+
err.code
|
|
1103
|
+
)
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
resolve(`Successful POSIX rename ${fPath} to ${tPath}`);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}).finally(() => {
|
|
1102
1110
|
removeTempListeners(this, listeners, 'posixRename');
|
|
1103
|
-
}
|
|
1111
|
+
});
|
|
1104
1112
|
}
|
|
1105
1113
|
|
|
1106
1114
|
/**
|
|
@@ -1110,35 +1118,31 @@ class SftpClient {
|
|
|
1110
1118
|
*
|
|
1111
1119
|
* @param {string} remotePath - path to the remote target object.
|
|
1112
1120
|
* @param {number | string} mode - the new octal mode to set
|
|
1121
|
+
* @param {boolean} addListeners - (Optional) if true, add listeners. Default true.
|
|
1113
1122
|
*
|
|
1114
1123
|
* @return {Promise<String>}
|
|
1115
1124
|
*/
|
|
1116
|
-
|
|
1125
|
+
chmod(rPath, mode, addListeners = true) {
|
|
1126
|
+
let listeners;
|
|
1117
1127
|
return new Promise((resolve, reject) => {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1128
|
+
if (addListeners) {
|
|
1129
|
+
listeners = addTempListeners(this, 'chmod', reject);
|
|
1130
|
+
}
|
|
1131
|
+
if (haveConnection(this, 'chmod', reject)) {
|
|
1132
|
+
this.sftp.chmod(rPath, mode, (err) => {
|
|
1133
|
+
if (err) {
|
|
1134
|
+
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code));
|
|
1135
|
+
}
|
|
1136
|
+
resolve('Successfully change file mode');
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}).finally(() => {
|
|
1140
|
+
if (addListeners) {
|
|
1141
|
+
removeTempListeners(this, listeners, 'chmod');
|
|
1142
|
+
}
|
|
1124
1143
|
});
|
|
1125
1144
|
}
|
|
1126
1145
|
|
|
1127
|
-
async chmod(remotePath, mode) {
|
|
1128
|
-
let listeners;
|
|
1129
|
-
try {
|
|
1130
|
-
listeners = addTempListeners(this, 'chmod');
|
|
1131
|
-
haveConnection(this, 'chmod');
|
|
1132
|
-
return await this._chmod(remotePath, mode);
|
|
1133
|
-
} catch (err) {
|
|
1134
|
-
throw err.custom
|
|
1135
|
-
? err
|
|
1136
|
-
: this.fmtError(`${err.message} ${remotePath}`, 'chmod', err.code);
|
|
1137
|
-
} finally {
|
|
1138
|
-
removeTempListeners(this, listeners, 'chmod');
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
1146
|
/**
|
|
1143
1147
|
* @async
|
|
1144
1148
|
*
|
|
@@ -1148,91 +1152,114 @@ class SftpClient {
|
|
|
1148
1152
|
* @param {String} srcDir - local source directory
|
|
1149
1153
|
* @param {String} dstDir - remote destination directory
|
|
1150
1154
|
* @param {Object} options - (Optional) An object with 2 supported properties,
|
|
1151
|
-
* 'filter' and 'useFastput'.
|
|
1155
|
+
* 'filter' and 'useFastput'. Filter is a function of two arguments.
|
|
1156
|
+
* The first argument is the full path of a directory entry from the directory
|
|
1152
1157
|
* to be uploaded and the second argument is a boolean, which will be true if
|
|
1153
|
-
* the target path is for a directory. If the function returns true,
|
|
1158
|
+
* the target path is for a directory. If the function returns true, this item
|
|
1154
1159
|
* will be uploaded and excluded when it returns false. The 'useFastput' property is a
|
|
1155
1160
|
* boolean value. When true, the 'fastPut()' method will be used to upload files. Default
|
|
1156
1161
|
* is to use the slower, but more supported 'put()' method.
|
|
1157
1162
|
*
|
|
1158
|
-
* @returns {Promise<
|
|
1163
|
+
* @returns {Promise<Array>}
|
|
1159
1164
|
*/
|
|
1160
|
-
async
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1165
|
+
async uploadDir(srcDir, dstDir, options) {
|
|
1166
|
+
const getRemoteStatus = async (dstDir) => {
|
|
1167
|
+
let absDstDir = await normalizeRemotePath(this, dstDir);
|
|
1168
|
+
let status = await this.exists(absDstDir);
|
|
1169
|
+
if (status && status !== 'd') {
|
|
1170
|
+
throw this.fmtError(
|
|
1171
|
+
`Bad path ${absDstDir} Not a directory`,
|
|
1172
|
+
'getRemoteStatus',
|
|
1173
|
+
errorCode.badPath
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
return { remoteDir: absDstDir, remoteStatus: status };
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
const checkLocalStatus = (srcDir) => {
|
|
1164
1180
|
const srcType = localExists(srcDir);
|
|
1165
1181
|
if (!srcType) {
|
|
1166
1182
|
throw this.fmtError(
|
|
1167
1183
|
`Bad path: ${srcDir} not exist`,
|
|
1168
|
-
'
|
|
1184
|
+
'getLocalStatus',
|
|
1169
1185
|
errorCode.badPath
|
|
1170
1186
|
);
|
|
1171
1187
|
}
|
|
1172
1188
|
if (srcType !== 'd') {
|
|
1173
1189
|
throw this.fmtError(
|
|
1174
1190
|
`Bad path: ${srcDir}: not a directory`,
|
|
1175
|
-
'
|
|
1191
|
+
'getLocalStatus',
|
|
1176
1192
|
errorCode.badPath
|
|
1177
1193
|
);
|
|
1178
1194
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
);
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1195
|
+
return srcType;
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
const uploadFiles = (srcDir, dstDir, fileList, useFastput) => {
|
|
1199
|
+
let listeners;
|
|
1200
|
+
return new Promise((resolve, reject) => {
|
|
1201
|
+
listeners = addTempListeners(this, 'uploadFiles', reject);
|
|
1202
|
+
let uploads = [];
|
|
1203
|
+
for (const f of fileList) {
|
|
1204
|
+
const newSrc = join(srcDir, f.name);
|
|
1205
|
+
const newDst = `${dstDir}/${f.name}`;
|
|
1206
|
+
if (f.isFile()) {
|
|
1207
|
+
if (useFastput) {
|
|
1208
|
+
uploads.push(this._fastPut(newSrc, newDst, null, false));
|
|
1209
|
+
} else {
|
|
1210
|
+
uploads.push(this._put(newSrc, newDst, null, false));
|
|
1211
|
+
}
|
|
1212
|
+
this.client.emit('upload', { source: newSrc, destination: newDst });
|
|
1213
|
+
} else {
|
|
1214
|
+
this.debugMsg(`uploadFiles: File ignored: ${f.name} not a regular file`);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
resolve(Promise.all(uploads));
|
|
1218
|
+
})
|
|
1219
|
+
.then((pList) => {
|
|
1220
|
+
return Promise.all(pList);
|
|
1221
|
+
})
|
|
1222
|
+
.finally(() => {
|
|
1223
|
+
removeTempListeners(this, listeners, uploadFiles);
|
|
1224
|
+
});
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
try {
|
|
1228
|
+
haveConnection(this, 'uploadDir');
|
|
1229
|
+
this.debugMsg(
|
|
1230
|
+
`uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}`
|
|
1231
|
+
);
|
|
1232
|
+
let { remoteDir, remoteStatus } = await getRemoteStatus(dstDir);
|
|
1233
|
+
this.debugMsg(`uploadDir: remoteDir = ${remoteDir} remoteStatus = ${remoteStatus}`);
|
|
1234
|
+
checkLocalStatus(srcDir);
|
|
1235
|
+
if (!remoteStatus) {
|
|
1236
|
+
await this._mkdir(remoteDir, true);
|
|
1189
1237
|
}
|
|
1190
1238
|
let dirEntries = fs.readdirSync(srcDir, {
|
|
1191
1239
|
encoding: 'utf8',
|
|
1192
1240
|
withFileTypes: true,
|
|
1193
1241
|
});
|
|
1242
|
+
this.debugMsg(`uploadDir: dirEntries = ${dirEntries}`);
|
|
1194
1243
|
if (options?.filter) {
|
|
1195
1244
|
dirEntries = dirEntries.filter((item) =>
|
|
1196
1245
|
options.filter(join(srcDir, item.name), item.isDirectory())
|
|
1197
1246
|
);
|
|
1198
1247
|
}
|
|
1199
|
-
let
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
} else {
|
|
1209
|
-
fileUploads.push(this._put(newSrc, newDst));
|
|
1210
|
-
}
|
|
1211
|
-
this.client.emit('upload', { source: newSrc, destination: newDst });
|
|
1212
|
-
} else {
|
|
1213
|
-
this.debugMsg(`uploadDir: File ignored: ${e.name} not a regular file`);
|
|
1214
|
-
}
|
|
1215
|
-
await Promise.all(fileUploads);
|
|
1248
|
+
let dirUploads = dirEntries.filter((item) => item.isDirectory());
|
|
1249
|
+
let fileUploads = dirEntries.filter((item) => !item.isDirectory());
|
|
1250
|
+
this.debugMsg(`uploadDir: dirUploads = ${dirUploads}`);
|
|
1251
|
+
this.debugMsg(`uploadDir: fileUploads = ${fileUploads}`);
|
|
1252
|
+
await uploadFiles(srcDir, remoteDir, fileUploads, options?.useFastput);
|
|
1253
|
+
for (const d of dirUploads) {
|
|
1254
|
+
let src = join(srcDir, d.name);
|
|
1255
|
+
let dst = `${remoteDir}/${d.name}`;
|
|
1256
|
+
await this.uploadDir(src, dst, options);
|
|
1216
1257
|
}
|
|
1217
|
-
return `${srcDir} uploaded to ${
|
|
1258
|
+
return `${srcDir} uploaded to ${dstDir}`;
|
|
1218
1259
|
} catch (err) {
|
|
1219
1260
|
throw err.custom
|
|
1220
1261
|
? err
|
|
1221
|
-
: this.fmtError(`${err.message} ${srcDir}`, '
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
async uploadDir(srcDir, dstDir, options) {
|
|
1226
|
-
let listeners;
|
|
1227
|
-
try {
|
|
1228
|
-
listeners = addTempListeners(this, 'uploadDir');
|
|
1229
|
-
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`);
|
|
1230
|
-
haveConnection(this, 'uploadDir');
|
|
1231
|
-
return await this._uploadDir(srcDir, dstDir, options);
|
|
1232
|
-
} catch (err) {
|
|
1233
|
-
throw err.custom ? err : this.fmtError(err, 'uploadDir');
|
|
1234
|
-
} finally {
|
|
1235
|
-
removeTempListeners(this, listeners, 'chmod');
|
|
1262
|
+
: this.fmtError(`${err.message} ${srcDir}`, 'uploadDir', err.code);
|
|
1236
1263
|
}
|
|
1237
1264
|
}
|
|
1238
1265
|
|
|
@@ -1251,71 +1278,88 @@ class SftpClient {
|
|
|
1251
1278
|
* is for a directory. If the function returns true, the item will be
|
|
1252
1279
|
* downloaded and excluded if teh function returns false.
|
|
1253
1280
|
*
|
|
1254
|
-
* @returns {Promise<
|
|
1281
|
+
* @returns {Promise<Array>}
|
|
1255
1282
|
*/
|
|
1256
|
-
async
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
`${srcDir}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1283
|
+
async downloadDir(srcDir, dstDir, options = { filter: null, useFastget: false }) {
|
|
1284
|
+
const _getDownloadList = async (srcDir, filter) => {
|
|
1285
|
+
try {
|
|
1286
|
+
let listing = await this.list(srcDir);
|
|
1287
|
+
if (filter) {
|
|
1288
|
+
return listing.filter((item) =>
|
|
1289
|
+
filter(`${srcDir}/${item.name}`, item.type === 'd')
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
return listing;
|
|
1293
|
+
} catch (err) {
|
|
1294
|
+
throw err.custom ? err : this.fmtError(err.message, '_getDownloadList', err.code);
|
|
1266
1295
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
const _prepareDestination = (dst) => {
|
|
1299
|
+
try {
|
|
1300
|
+
const localCheck = haveLocalCreate(dst);
|
|
1301
|
+
if (!localCheck.status && localCheck.details === 'permission denied') {
|
|
1302
|
+
throw this.fmtError(
|
|
1303
|
+
`Bad path: ${dst}: ${localCheck.details}`,
|
|
1304
|
+
'prepareDestination',
|
|
1305
|
+
localCheck.code
|
|
1306
|
+
);
|
|
1307
|
+
} else if (localCheck.status && !localCheck.type) {
|
|
1308
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
1309
|
+
} else if (localCheck.status && localCheck.type !== 'd') {
|
|
1310
|
+
throw this.fmtError(
|
|
1311
|
+
`Bad path: ${dstDir}: not a directory`,
|
|
1312
|
+
'_prepareDestination',
|
|
1313
|
+
errorCode.badPath
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
} catch (err) {
|
|
1317
|
+
throw err.custom
|
|
1318
|
+
? err
|
|
1319
|
+
: this.fmtError(err.message, '_prepareDestination', err.code);
|
|
1282
1320
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
const _downloadFiles = (remotePath, localPath, fileList, useFastget) => {
|
|
1324
|
+
let listeners;
|
|
1325
|
+
return new Promise((resolve, reject) => {
|
|
1326
|
+
listeners = addTempListeners(this, '_downloadFIles', reject);
|
|
1327
|
+
let pList = [];
|
|
1328
|
+
for (const f of fileList) {
|
|
1329
|
+
let src = `${remotePath}/${f.name}`;
|
|
1330
|
+
let dst = join(localPath, f.name);
|
|
1331
|
+
if (useFastget) {
|
|
1332
|
+
pList.push(this.fastGet(src, dst, false));
|
|
1292
1333
|
} else {
|
|
1293
|
-
|
|
1334
|
+
pList.push(this.get(src, dst, false));
|
|
1294
1335
|
}
|
|
1295
|
-
this.client.emit('download', { source:
|
|
1296
|
-
} else {
|
|
1297
|
-
this.debugMsg(`downloadDir: File ignored: ${f.name} not regular file`);
|
|
1336
|
+
this.client.emit('download', { source: src, destination: dst });
|
|
1298
1337
|
}
|
|
1338
|
+
return resolve(Promise.all(pList));
|
|
1339
|
+
}).finally(() => {
|
|
1340
|
+
removeTempListeners(this, listeners, '_downloadFiles');
|
|
1341
|
+
});
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
try {
|
|
1345
|
+
haveConnection(this, 'downloadDir');
|
|
1346
|
+
let downloadList = await _getDownloadList(srcDir, options.filter);
|
|
1347
|
+
_prepareDestination(dstDir);
|
|
1348
|
+
let fileDownloads = downloadList.filter((i) => i.type !== 'd');
|
|
1349
|
+
if (fileDownloads.length) {
|
|
1350
|
+
await _downloadFiles(srcDir, dstDir, fileDownloads, options.useFastget);
|
|
1351
|
+
}
|
|
1352
|
+
let dirDownloads = downloadList.filter((i) => i.type === 'd');
|
|
1353
|
+
for (const d of dirDownloads) {
|
|
1354
|
+
let src = `${srcDir}/${d.name}`;
|
|
1355
|
+
let dst = join(dstDir, d.name);
|
|
1356
|
+
await this.downloadDir(src, dst);
|
|
1299
1357
|
}
|
|
1300
|
-
await Promise.all(downloadFiles);
|
|
1301
1358
|
return `${srcDir} downloaded to ${dstDir}`;
|
|
1302
1359
|
} catch (err) {
|
|
1303
1360
|
throw err.custom
|
|
1304
1361
|
? err
|
|
1305
|
-
: this.fmtError(`${err.message} ${srcDir}`, '
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
async downloadDir(srcDir, dstDir, options) {
|
|
1310
|
-
let listeners;
|
|
1311
|
-
try {
|
|
1312
|
-
listeners = addTempListeners(this, 'downloadDir');
|
|
1313
|
-
haveConnection(this, 'downloadDir');
|
|
1314
|
-
return await this._downloadDir(srcDir, dstDir, options);
|
|
1315
|
-
} catch (err) {
|
|
1316
|
-
throw err.custom ? err : this.fmtError(err, 'downloadDir', err.code);
|
|
1317
|
-
} finally {
|
|
1318
|
-
removeTempListeners(this, listeners, 'downloadDir');
|
|
1362
|
+
: this.fmtError(`${err.message}: ${srcDir}`, 'downloadDir', err.code);
|
|
1319
1363
|
}
|
|
1320
1364
|
}
|
|
1321
1365
|
|
|
@@ -1454,7 +1498,7 @@ class SftpClient {
|
|
|
1454
1498
|
resolve(true);
|
|
1455
1499
|
};
|
|
1456
1500
|
this.on('close', endCloseHandler);
|
|
1457
|
-
if (this.
|
|
1501
|
+
if (this.sftp) {
|
|
1458
1502
|
this.client.end();
|
|
1459
1503
|
} else {
|
|
1460
1504
|
// no actual connection exists - just resolve
|