ssh2-sftp-client 7.2.2 → 8.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 +334 -166
- package/README.org +107 -81
- package/package.json +19 -15
- package/src/index.js +232 -139
- package/src/utils.js +28 -37
package/src/index.js
CHANGED
|
@@ -34,7 +34,6 @@ class SftpClient {
|
|
|
34
34
|
this.remotePathSep = '/';
|
|
35
35
|
this.remotePlatform = 'unix';
|
|
36
36
|
this.debug = undefined;
|
|
37
|
-
this.tempListeners = {};
|
|
38
37
|
|
|
39
38
|
this.client.on('close', () => {
|
|
40
39
|
if (this.endCalled || this.closeHandled) {
|
|
@@ -59,14 +58,13 @@ class SftpClient {
|
|
|
59
58
|
this.client.on('error', (err) => {
|
|
60
59
|
if (this.endCalled || this.errorHandled) {
|
|
61
60
|
// error event expected or handled elsewhere
|
|
62
|
-
this.debugMsg(
|
|
61
|
+
this.debugMsg(`Global: Ignoring handled error: ${err.message}`);
|
|
63
62
|
} else {
|
|
64
63
|
this.debugMsg(`Global; Handling unexpected error; ${err.message}`);
|
|
65
64
|
this.sftp = undefined;
|
|
66
65
|
console.log(
|
|
67
66
|
`ssh2-sftp-client: Unexpected error: ${err.message}. Error code: ${err.code}`
|
|
68
67
|
);
|
|
69
|
-
//throw fmtError(err, 'Global');
|
|
70
68
|
}
|
|
71
69
|
});
|
|
72
70
|
}
|
|
@@ -119,34 +117,31 @@ class SftpClient {
|
|
|
119
117
|
*
|
|
120
118
|
*/
|
|
121
119
|
getConnection(config) {
|
|
122
|
-
let doReady;
|
|
123
|
-
return (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
removeTempListeners(this, 'getConnection');
|
|
142
|
-
this._resetEventFlags();
|
|
143
|
-
})
|
|
144
|
-
);
|
|
120
|
+
let doReady, listeners;
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
listeners = addTempListeners(this, 'getConnection', reject);
|
|
123
|
+
this.debugMsg('getConnection: created promise');
|
|
124
|
+
doReady = () => {
|
|
125
|
+
this.debugMsg(
|
|
126
|
+
'getConnection ready listener: got connection - promise resolved'
|
|
127
|
+
);
|
|
128
|
+
resolve(true);
|
|
129
|
+
};
|
|
130
|
+
this.on('ready', doReady);
|
|
131
|
+
this.client.connect(config);
|
|
132
|
+
}).finally(async () => {
|
|
133
|
+
this.debugMsg('getConnection: finally clause fired');
|
|
134
|
+
await sleep(500);
|
|
135
|
+
this.removeListener('ready', doReady);
|
|
136
|
+
removeTempListeners(this, listeners, 'getConnection');
|
|
137
|
+
this._resetEventFlags();
|
|
138
|
+
});
|
|
145
139
|
}
|
|
146
140
|
|
|
147
141
|
getSftpChannel() {
|
|
142
|
+
let listeners;
|
|
148
143
|
return new Promise((resolve, reject) => {
|
|
149
|
-
addTempListeners(this, 'getSftpChannel', reject);
|
|
144
|
+
listeners = addTempListeners(this, 'getSftpChannel', reject);
|
|
150
145
|
this.debugMsg('getSftpChannel: created promise');
|
|
151
146
|
this.client.sftp((err, sftp) => {
|
|
152
147
|
if (err) {
|
|
@@ -161,7 +156,7 @@ class SftpClient {
|
|
|
161
156
|
});
|
|
162
157
|
}).finally(() => {
|
|
163
158
|
this.debugMsg('getSftpChannel: finally clause fired');
|
|
164
|
-
removeTempListeners(this, 'getSftpChannel');
|
|
159
|
+
removeTempListeners(this, listeners, 'getSftpChannel');
|
|
165
160
|
this._resetEventFlags();
|
|
166
161
|
});
|
|
167
162
|
}
|
|
@@ -196,8 +191,17 @@ class SftpClient {
|
|
|
196
191
|
(retry, attempt) => {
|
|
197
192
|
this.debugMsg(`connect: Connect attempt ${attempt}`);
|
|
198
193
|
return this.getConnection(config).catch((err) => {
|
|
199
|
-
this.debugMsg(
|
|
200
|
-
|
|
194
|
+
this.debugMsg(
|
|
195
|
+
`getConnection retry catch: ${err.message} Code: ${err.code}`
|
|
196
|
+
);
|
|
197
|
+
switch (err.code) {
|
|
198
|
+
case 'ENOTFOUND':
|
|
199
|
+
case 'ECONNREFUSED':
|
|
200
|
+
case 'ERR_SOCKET_BAD_PORT':
|
|
201
|
+
throw err;
|
|
202
|
+
default:
|
|
203
|
+
retry(err);
|
|
204
|
+
}
|
|
201
205
|
});
|
|
202
206
|
},
|
|
203
207
|
{
|
|
@@ -226,9 +230,10 @@ class SftpClient {
|
|
|
226
230
|
* @returns {Promise<String>} - remote absolute path or ''
|
|
227
231
|
*/
|
|
228
232
|
realPath(remotePath) {
|
|
233
|
+
let listeners;
|
|
229
234
|
return new Promise((resolve, reject) => {
|
|
235
|
+
listeners = addTempListeners(this, 'realPath', reject);
|
|
230
236
|
this.debugMsg(`realPath -> ${remotePath}`);
|
|
231
|
-
addTempListeners(this, 'realPath', reject);
|
|
232
237
|
if (haveConnection(this, 'realPath', reject)) {
|
|
233
238
|
this.sftp.realpath(remotePath, (err, absPath) => {
|
|
234
239
|
if (err) {
|
|
@@ -246,7 +251,7 @@ class SftpClient {
|
|
|
246
251
|
});
|
|
247
252
|
}
|
|
248
253
|
}).finally(() => {
|
|
249
|
-
removeTempListeners(this, 'realPath');
|
|
254
|
+
removeTempListeners(this, listeners, 'realPath');
|
|
250
255
|
this._resetEventFlags();
|
|
251
256
|
});
|
|
252
257
|
}
|
|
@@ -270,9 +275,10 @@ class SftpClient {
|
|
|
270
275
|
*/
|
|
271
276
|
async stat(remotePath) {
|
|
272
277
|
const _stat = (aPath) => {
|
|
278
|
+
let listeners;
|
|
273
279
|
return new Promise((resolve, reject) => {
|
|
280
|
+
listeners = addTempListeners(this, '_stat', reject);
|
|
274
281
|
this.debugMsg(`_stat: ${aPath}`);
|
|
275
|
-
addTempListeners(this, '_stat', reject);
|
|
276
282
|
this.sftp.stat(aPath, (err, stats) => {
|
|
277
283
|
if (err) {
|
|
278
284
|
this.debugMsg(`_stat: Error ${err.message} code: ${err.code}`);
|
|
@@ -310,7 +316,7 @@ class SftpClient {
|
|
|
310
316
|
}
|
|
311
317
|
});
|
|
312
318
|
}).finally(() => {
|
|
313
|
-
removeTempListeners(this, '_stat');
|
|
319
|
+
removeTempListeners(this, listeners, '_stat');
|
|
314
320
|
});
|
|
315
321
|
};
|
|
316
322
|
|
|
@@ -394,11 +400,12 @@ class SftpClient {
|
|
|
394
400
|
* @returns {Promise<Array>} array of file description objects
|
|
395
401
|
*/
|
|
396
402
|
list(remotePath, pattern = /.*/) {
|
|
403
|
+
let listeners;
|
|
397
404
|
return new Promise((resolve, reject) => {
|
|
405
|
+
listeners = addTempListeners(this, 'list', reject);
|
|
398
406
|
if (haveConnection(this, 'list', reject)) {
|
|
399
407
|
const reg = /-/gi;
|
|
400
408
|
this.debugMsg(`list: ${remotePath} filter: ${pattern}`);
|
|
401
|
-
addTempListeners(this, 'list', reject);
|
|
402
409
|
this.sftp.readdir(remotePath, (err, fileList) => {
|
|
403
410
|
if (err) {
|
|
404
411
|
this.debugMsg(`list: Error ${err.message} code: ${err.code}`);
|
|
@@ -421,6 +428,7 @@ class SftpClient {
|
|
|
421
428
|
},
|
|
422
429
|
owner: item.attrs.uid,
|
|
423
430
|
group: item.attrs.gid,
|
|
431
|
+
longname: item.longname,
|
|
424
432
|
};
|
|
425
433
|
});
|
|
426
434
|
}
|
|
@@ -439,7 +447,7 @@ class SftpClient {
|
|
|
439
447
|
});
|
|
440
448
|
}
|
|
441
449
|
}).finally(() => {
|
|
442
|
-
removeTempListeners(this, 'list');
|
|
450
|
+
removeTempListeners(this, listeners, 'list');
|
|
443
451
|
this._resetEventFlags();
|
|
444
452
|
});
|
|
445
453
|
}
|
|
@@ -464,12 +472,11 @@ class SftpClient {
|
|
|
464
472
|
dst,
|
|
465
473
|
options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
|
|
466
474
|
) {
|
|
467
|
-
let rdr, wtr;
|
|
468
|
-
|
|
475
|
+
let rdr, wtr, listeners;
|
|
469
476
|
return new Promise((resolve, reject) => {
|
|
477
|
+
listeners = addTempListeners(this, 'get', reject);
|
|
470
478
|
if (haveConnection(this, 'get', reject)) {
|
|
471
479
|
this.debugMsg(`get -> ${remotePath} `, options);
|
|
472
|
-
addTempListeners(this, 'get', reject);
|
|
473
480
|
rdr = this.sftp.createReadStream(
|
|
474
481
|
remotePath,
|
|
475
482
|
options.readStreamOptions ? options.readStreamOptions : {}
|
|
@@ -542,7 +549,7 @@ class SftpClient {
|
|
|
542
549
|
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
|
|
543
550
|
}
|
|
544
551
|
}).finally(() => {
|
|
545
|
-
removeTempListeners(this, 'get');
|
|
552
|
+
removeTempListeners(this, listeners, 'get');
|
|
546
553
|
this._resetEventFlags();
|
|
547
554
|
if (
|
|
548
555
|
rdr &&
|
|
@@ -592,13 +599,14 @@ class SftpClient {
|
|
|
592
599
|
err.code = errorCode.badPath;
|
|
593
600
|
throw err;
|
|
594
601
|
}
|
|
602
|
+
let listeners;
|
|
595
603
|
let rslt = await new Promise((resolve, reject) => {
|
|
604
|
+
listeners = addTempListeners(this, 'fastGet', reject);
|
|
596
605
|
if (haveConnection(this, 'fastGet', reject)) {
|
|
597
606
|
this.debugMsg(
|
|
598
607
|
`fastGet -> remote: ${remotePath} local: ${localPath} `,
|
|
599
608
|
options
|
|
600
609
|
);
|
|
601
|
-
addTempListeners(this, 'fastGet', reject);
|
|
602
610
|
this.sftp.fastGet(remotePath, localPath, options, (err) => {
|
|
603
611
|
if (err) {
|
|
604
612
|
this.debugMsg(`fastGet error ${err.message} code: ${err.code}`);
|
|
@@ -608,7 +616,7 @@ class SftpClient {
|
|
|
608
616
|
});
|
|
609
617
|
}
|
|
610
618
|
}).finally(() => {
|
|
611
|
-
removeTempListeners(this, 'fastGet');
|
|
619
|
+
removeTempListeners(this, listeners, 'fastGet');
|
|
612
620
|
});
|
|
613
621
|
return rslt;
|
|
614
622
|
} catch (err) {
|
|
@@ -631,8 +639,10 @@ class SftpClient {
|
|
|
631
639
|
* @return {Promise<String>} the result of downloading the file
|
|
632
640
|
*/
|
|
633
641
|
fastPut(localPath, remotePath, options) {
|
|
642
|
+
let listeners;
|
|
634
643
|
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
635
644
|
return new Promise((resolve, reject) => {
|
|
645
|
+
listeners = addTempListeners(this, 'fastPut', reject);
|
|
636
646
|
const localCheck = haveLocalAccess(localPath);
|
|
637
647
|
if (!localCheck.status) {
|
|
638
648
|
reject(
|
|
@@ -656,7 +666,6 @@ class SftpClient {
|
|
|
656
666
|
options
|
|
657
667
|
)}`
|
|
658
668
|
);
|
|
659
|
-
addTempListeners(this, 'fastPut', reject);
|
|
660
669
|
this.sftp.fastPut(localPath, remotePath, options, (err) => {
|
|
661
670
|
if (err) {
|
|
662
671
|
this.debugMsg(`fastPut error ${err.message} ${err.code}`);
|
|
@@ -673,7 +682,7 @@ class SftpClient {
|
|
|
673
682
|
});
|
|
674
683
|
}
|
|
675
684
|
}).finally(() => {
|
|
676
|
-
removeTempListeners(this, 'fastPut');
|
|
685
|
+
removeTempListeners(this, listeners, 'fastPut');
|
|
677
686
|
this._resetEventFlags();
|
|
678
687
|
});
|
|
679
688
|
}
|
|
@@ -699,9 +708,9 @@ class SftpClient {
|
|
|
699
708
|
pipeOptions: {},
|
|
700
709
|
}
|
|
701
710
|
) {
|
|
702
|
-
let wtr, rdr;
|
|
703
|
-
|
|
711
|
+
let wtr, rdr, listeners;
|
|
704
712
|
return new Promise((resolve, reject) => {
|
|
713
|
+
listeners = addTempListeners(this, 'put', reject);
|
|
705
714
|
if (typeof localSrc === 'string') {
|
|
706
715
|
const localCheck = haveLocalAccess(localSrc);
|
|
707
716
|
if (!localCheck.status) {
|
|
@@ -716,7 +725,6 @@ class SftpClient {
|
|
|
716
725
|
}
|
|
717
726
|
}
|
|
718
727
|
if (haveConnection(this, 'put')) {
|
|
719
|
-
addTempListeners(this, 'put', reject);
|
|
720
728
|
wtr = this.sftp.createWriteStream(
|
|
721
729
|
remotePath,
|
|
722
730
|
options.writeStreamOptions
|
|
@@ -761,7 +769,7 @@ class SftpClient {
|
|
|
761
769
|
}
|
|
762
770
|
}
|
|
763
771
|
}).finally(() => {
|
|
764
|
-
removeTempListeners(this, 'put');
|
|
772
|
+
removeTempListeners(this, listeners, 'put');
|
|
765
773
|
this._resetEventFlags();
|
|
766
774
|
if (
|
|
767
775
|
rdr &&
|
|
@@ -783,44 +791,63 @@ class SftpClient {
|
|
|
783
791
|
* @param {Object} options
|
|
784
792
|
* @return {Promise<String>}
|
|
785
793
|
*/
|
|
794
|
+
|
|
786
795
|
async append(input, remotePath, options = {}) {
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
796
|
+
const _append = (input, remotePath, options) => {
|
|
797
|
+
return new Promise((resolve, reject) => {
|
|
798
|
+
this.debugMsg(`append -> remote: ${remotePath} `, options);
|
|
799
|
+
options.flags = 'a';
|
|
800
|
+
let stream = this.sftp.createWriteStream(remotePath, options);
|
|
801
|
+
stream.on('error', (err) => {
|
|
802
|
+
this.debugMsg(
|
|
803
|
+
`append: Error ${err.message} appending to ${remotePath}`
|
|
804
|
+
);
|
|
805
|
+
reject(fmtError(`${err.message} ${remotePath}`, 'append', err.code));
|
|
806
|
+
});
|
|
807
|
+
stream.on('close', () => {
|
|
808
|
+
this.debugMsg(`append: data appended to ${remotePath}`);
|
|
809
|
+
resolve(`Appended data to ${remotePath}`);
|
|
810
|
+
});
|
|
811
|
+
if (input instanceof Buffer) {
|
|
812
|
+
this.debugMsg('append: writing data buffer to remote file');
|
|
813
|
+
stream.write(input);
|
|
814
|
+
stream.end();
|
|
799
815
|
} else {
|
|
800
|
-
this.debugMsg(
|
|
801
|
-
|
|
802
|
-
options.flags = 'a';
|
|
803
|
-
let stream = this.sftp.createWriteStream(remotePath, options);
|
|
804
|
-
stream.on('error', (err_1) => {
|
|
805
|
-
reject(
|
|
806
|
-
fmtError(`${err_1.message} ${remotePath}`, 'append', err_1.code)
|
|
807
|
-
);
|
|
808
|
-
});
|
|
809
|
-
stream.on('finish', () => {
|
|
810
|
-
resolve(`Appended data to ${remotePath}`);
|
|
811
|
-
});
|
|
812
|
-
if (input instanceof Buffer) {
|
|
813
|
-
stream.write(input);
|
|
814
|
-
stream.end();
|
|
815
|
-
} else {
|
|
816
|
-
input.pipe(stream);
|
|
817
|
-
}
|
|
816
|
+
this.debugMsg('append: writing stream to remote file');
|
|
817
|
+
input.pipe(stream);
|
|
818
818
|
}
|
|
819
|
+
});
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
let listeners;
|
|
823
|
+
try {
|
|
824
|
+
listeners = addTempListeners(this, 'append');
|
|
825
|
+
if (typeof input === 'string') {
|
|
826
|
+
this.debugMsg('append: attempt to append two files - throw');
|
|
827
|
+
throw fmtError(
|
|
828
|
+
'Cannot append one file to another',
|
|
829
|
+
'append',
|
|
830
|
+
errorCode.badPath
|
|
831
|
+
);
|
|
819
832
|
}
|
|
820
|
-
|
|
821
|
-
|
|
833
|
+
if (haveConnection(this, 'append')) {
|
|
834
|
+
const fileType = await this.exists(remotePath);
|
|
835
|
+
if (fileType && fileType === 'd') {
|
|
836
|
+
this.debugMsg(`append: Error ${remotePath} not a file`);
|
|
837
|
+
throw fmtError(
|
|
838
|
+
`Bad path: ${remotePath}: cannot append to a directory`,
|
|
839
|
+
'append',
|
|
840
|
+
errorCode.badPath
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
await _append(input, remotePath, options);
|
|
844
|
+
}
|
|
845
|
+
} catch (e) {
|
|
846
|
+
throw e.custom ? e : fmtError(e.message, 'append', e.code);
|
|
847
|
+
} finally {
|
|
848
|
+
removeTempListeners(this, listeners, 'append');
|
|
822
849
|
this._resetEventFlags();
|
|
823
|
-
}
|
|
850
|
+
}
|
|
824
851
|
}
|
|
825
852
|
|
|
826
853
|
/**
|
|
@@ -834,9 +861,10 @@ class SftpClient {
|
|
|
834
861
|
*/
|
|
835
862
|
async mkdir(remotePath, recursive = false) {
|
|
836
863
|
const _mkdir = (p) => {
|
|
864
|
+
let listeners;
|
|
837
865
|
return new Promise((resolve, reject) => {
|
|
866
|
+
listeners = addTempListeners(this, '_mkdir', reject);
|
|
838
867
|
this.debugMsg(`_mkdir: create ${p}`);
|
|
839
|
-
addTempListeners(this, '_mkdir', reject);
|
|
840
868
|
this.sftp.mkdir(p, (err) => {
|
|
841
869
|
if (err) {
|
|
842
870
|
this.debugMsg(`_mkdir: Error ${err.message} code: ${err.code}`);
|
|
@@ -860,7 +888,7 @@ class SftpClient {
|
|
|
860
888
|
}
|
|
861
889
|
});
|
|
862
890
|
}).finally(() => {
|
|
863
|
-
removeTempListeners(this, '_mkdir');
|
|
891
|
+
removeTempListeners(this, listeners, '_mkdir');
|
|
864
892
|
this._resetEventFlags();
|
|
865
893
|
});
|
|
866
894
|
};
|
|
@@ -907,45 +935,83 @@ class SftpClient {
|
|
|
907
935
|
* @return {Promise<String>}
|
|
908
936
|
*/
|
|
909
937
|
async rmdir(remotePath, recursive = false) {
|
|
938
|
+
const _delete = (remotePath) => {
|
|
939
|
+
return new Promise((resolve, reject) => {
|
|
940
|
+
this.sftp.unlink(remotePath, (err) => {
|
|
941
|
+
if (err && err.code !== 2) {
|
|
942
|
+
reject(fmtError(`${err.message} ${remotePath}`, 'rmdir', err.code));
|
|
943
|
+
}
|
|
944
|
+
resolve(true);
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
};
|
|
948
|
+
|
|
910
949
|
const _rmdir = (p) => {
|
|
911
950
|
return new Promise((resolve, reject) => {
|
|
912
951
|
this.debugMsg(`rmdir -> ${p}`);
|
|
913
|
-
addTempListeners(this, 'rmdir', reject);
|
|
914
952
|
this.sftp.rmdir(p, (err) => {
|
|
915
953
|
if (err) {
|
|
916
954
|
this.debugMsg(`rmdir error ${err.message} code: ${err.code}`);
|
|
917
|
-
reject(fmtError(`${err.message} ${p}`, '
|
|
955
|
+
reject(fmtError(`${err.message} ${p}`, 'rmdir', err.code));
|
|
918
956
|
}
|
|
919
957
|
resolve('Successfully removed directory');
|
|
920
958
|
});
|
|
921
959
|
}).finally(() => {
|
|
922
|
-
removeTempListeners(this, '
|
|
960
|
+
removeTempListeners(this, listeners, '_rmdir');
|
|
923
961
|
});
|
|
924
962
|
};
|
|
925
963
|
|
|
964
|
+
const _dormdir = async (p, recur) => {
|
|
965
|
+
try {
|
|
966
|
+
if (recur) {
|
|
967
|
+
let list = await this.list(p);
|
|
968
|
+
if (list.length) {
|
|
969
|
+
let files = list.filter((item) => item.type !== 'd');
|
|
970
|
+
let dirs = list.filter((item) => item.type === 'd');
|
|
971
|
+
this.debugMsg('rmdir contents (files): ', files);
|
|
972
|
+
this.debugMsg('rmdir contents (dirs): ', dirs);
|
|
973
|
+
for (let d of dirs) {
|
|
974
|
+
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true);
|
|
975
|
+
}
|
|
976
|
+
let promiseList = [];
|
|
977
|
+
for (let f of files) {
|
|
978
|
+
promiseList.push(_delete(`${p}${this.remotePathSep}${f.name}`));
|
|
979
|
+
}
|
|
980
|
+
await Promise.all(promiseList);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return await _rmdir(p);
|
|
984
|
+
} catch (err) {
|
|
985
|
+
throw err.custom ? err : fmtError(err, '_dormdir', err.code);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
let listeners;
|
|
926
990
|
try {
|
|
991
|
+
listeners = addTempListeners(this, 'rmdir');
|
|
927
992
|
haveConnection(this, 'rmdir');
|
|
928
993
|
let absPath = await normalizeRemotePath(this, remotePath);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
994
|
+
let dirStatus = await this.exists(absPath);
|
|
995
|
+
if (dirStatus && dirStatus !== 'd') {
|
|
996
|
+
throw fmtError(
|
|
997
|
+
`Bad path: ${absPath} not a directory`,
|
|
998
|
+
'rmdir',
|
|
999
|
+
errorCode.badPath
|
|
1000
|
+
);
|
|
1001
|
+
} else if (!dirStatus) {
|
|
1002
|
+
throw fmtError(
|
|
1003
|
+
`Bad path: ${absPath} No such file`,
|
|
1004
|
+
'rmdir',
|
|
1005
|
+
errorCode.badPath
|
|
1006
|
+
);
|
|
1007
|
+
} else {
|
|
1008
|
+
return await _dormdir(absPath, recursive);
|
|
944
1009
|
}
|
|
945
|
-
return _rmdir(absPath);
|
|
946
1010
|
} catch (err) {
|
|
947
1011
|
this._resetEventFlags();
|
|
948
1012
|
throw err.custom ? err : fmtError(err, 'rmdir', err.code);
|
|
1013
|
+
} finally {
|
|
1014
|
+
removeTempListeners(this, listeners, 'rmdir');
|
|
949
1015
|
}
|
|
950
1016
|
}
|
|
951
1017
|
|
|
@@ -961,10 +1027,11 @@ class SftpClient {
|
|
|
961
1027
|
*
|
|
962
1028
|
*/
|
|
963
1029
|
delete(remotePath, notFoundOK = false) {
|
|
1030
|
+
let listeners;
|
|
964
1031
|
return new Promise((resolve, reject) => {
|
|
1032
|
+
listeners = addTempListeners(this, 'delete', reject);
|
|
965
1033
|
if (haveConnection(this, 'delete', reject)) {
|
|
966
1034
|
this.debugMsg(`delete -> ${remotePath}`);
|
|
967
|
-
addTempListeners(this, 'delete', reject);
|
|
968
1035
|
this.sftp.unlink(remotePath, (err) => {
|
|
969
1036
|
if (err) {
|
|
970
1037
|
this.debugMsg(`delete error ${err.message} code: ${err.code}`);
|
|
@@ -981,7 +1048,7 @@ class SftpClient {
|
|
|
981
1048
|
});
|
|
982
1049
|
}
|
|
983
1050
|
}).finally(() => {
|
|
984
|
-
removeTempListeners(this, 'delete');
|
|
1051
|
+
removeTempListeners(this, listeners, 'delete');
|
|
985
1052
|
this._resetEventFlags();
|
|
986
1053
|
});
|
|
987
1054
|
}
|
|
@@ -998,10 +1065,11 @@ class SftpClient {
|
|
|
998
1065
|
*
|
|
999
1066
|
*/
|
|
1000
1067
|
rename(fromPath, toPath) {
|
|
1068
|
+
let listeners;
|
|
1001
1069
|
return new Promise((resolve, reject) => {
|
|
1070
|
+
listeners = addTempListeners(this, 'rename', reject);
|
|
1002
1071
|
if (haveConnection(this, 'rename', reject)) {
|
|
1003
1072
|
this.debugMsg(`rename -> ${fromPath} ${toPath}`);
|
|
1004
|
-
addTempListeners(this, 'rename', reject);
|
|
1005
1073
|
this.sftp.rename(fromPath, toPath, (err) => {
|
|
1006
1074
|
if (err) {
|
|
1007
1075
|
this.debugMsg(`rename error ${err.message} code: ${err.code}`);
|
|
@@ -1017,7 +1085,7 @@ class SftpClient {
|
|
|
1017
1085
|
});
|
|
1018
1086
|
}
|
|
1019
1087
|
}).finally(() => {
|
|
1020
|
-
removeTempListeners(this, 'rename');
|
|
1088
|
+
removeTempListeners(this, listeners, 'rename');
|
|
1021
1089
|
this._resetEventFlags();
|
|
1022
1090
|
});
|
|
1023
1091
|
}
|
|
@@ -1035,10 +1103,11 @@ class SftpClient {
|
|
|
1035
1103
|
*
|
|
1036
1104
|
*/
|
|
1037
1105
|
posixRename(fromPath, toPath) {
|
|
1106
|
+
let listeners;
|
|
1038
1107
|
return new Promise((resolve, reject) => {
|
|
1108
|
+
listeners = addTempListeners(this, 'posixRename', reject);
|
|
1039
1109
|
if (haveConnection(this, 'posixRename', reject)) {
|
|
1040
1110
|
this.debugMsg(`posixRename -> ${fromPath} ${toPath}`);
|
|
1041
|
-
addTempListeners(this, 'posixRename', reject);
|
|
1042
1111
|
this.sftp.ext_openssh_rename(fromPath, toPath, (err) => {
|
|
1043
1112
|
if (err) {
|
|
1044
1113
|
this.debugMsg(`posixRename error ${err.message} code: ${err.code}`);
|
|
@@ -1054,7 +1123,7 @@ class SftpClient {
|
|
|
1054
1123
|
});
|
|
1055
1124
|
}
|
|
1056
1125
|
}).finally(() => {
|
|
1057
|
-
removeTempListeners(this, 'posixRename');
|
|
1126
|
+
removeTempListeners(this, listeners, 'posixRename');
|
|
1058
1127
|
this._resetEventFlags();
|
|
1059
1128
|
});
|
|
1060
1129
|
}
|
|
@@ -1070,9 +1139,10 @@ class SftpClient {
|
|
|
1070
1139
|
* @return {Promise<String>}
|
|
1071
1140
|
*/
|
|
1072
1141
|
chmod(remotePath, mode) {
|
|
1142
|
+
let listeners;
|
|
1073
1143
|
return new Promise((resolve, reject) => {
|
|
1144
|
+
listeners = addTempListeners(this, 'chmod', reject);
|
|
1074
1145
|
this.debugMsg(`chmod -> ${remotePath} ${mode}`);
|
|
1075
|
-
addTempListeners(this, 'chmod', reject);
|
|
1076
1146
|
this.sftp.chmod(remotePath, mode, (err) => {
|
|
1077
1147
|
if (err) {
|
|
1078
1148
|
reject(fmtError(`${err.message} ${remotePath}`, 'chmod', err.code));
|
|
@@ -1080,7 +1150,7 @@ class SftpClient {
|
|
|
1080
1150
|
resolve('Successfully change file mode');
|
|
1081
1151
|
});
|
|
1082
1152
|
}).finally(() => {
|
|
1083
|
-
removeTempListeners(this, 'chmod');
|
|
1153
|
+
removeTempListeners(this, listeners, 'chmod');
|
|
1084
1154
|
this._resetEventFlags();
|
|
1085
1155
|
});
|
|
1086
1156
|
}
|
|
@@ -1093,14 +1163,25 @@ class SftpClient {
|
|
|
1093
1163
|
* server.
|
|
1094
1164
|
* @param {String} srcDir - local source directory
|
|
1095
1165
|
* @param {String} dstDir - remote destination directory
|
|
1096
|
-
* @param {
|
|
1097
|
-
* files and directories to upload
|
|
1166
|
+
* @param {function(String,Boolean):Boolean} filter - (Optional) The first argument is the full path of the item to be uploaded and the second argument is a boolean, which will be true if the target path is for a directory. If the function returns true, the item will be uploaded
|
|
1098
1167
|
* @returns {Promise<String>}
|
|
1099
1168
|
*/
|
|
1100
|
-
async uploadDir(srcDir, dstDir, filter
|
|
1169
|
+
async uploadDir(srcDir, dstDir, filter) {
|
|
1101
1170
|
try {
|
|
1102
|
-
this.debugMsg(`uploadDir -> ${srcDir} ${dstDir}`);
|
|
1171
|
+
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`);
|
|
1172
|
+
haveConnection(this, 'uploadDir');
|
|
1173
|
+
//let absSrcDir = fs.realpathSync(srcDir);
|
|
1174
|
+
let absDstDir = await normalizeRemotePath(this, dstDir);
|
|
1175
|
+
|
|
1176
|
+
this.debugMsg(`uploadDir <- SRC = ${srcDir} DST = ${absDstDir}`);
|
|
1103
1177
|
const srcType = localExists(srcDir);
|
|
1178
|
+
if (!srcType) {
|
|
1179
|
+
throw fmtError(
|
|
1180
|
+
`Bad path: ${srcDir} not exist`,
|
|
1181
|
+
'uploadDir',
|
|
1182
|
+
errorCode.badPath
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1104
1185
|
if (srcType !== 'd') {
|
|
1105
1186
|
throw fmtError(
|
|
1106
1187
|
`Bad path: ${srcDir}: not a directory`,
|
|
@@ -1108,36 +1189,43 @@ class SftpClient {
|
|
|
1108
1189
|
errorCode.badPath
|
|
1109
1190
|
);
|
|
1110
1191
|
}
|
|
1111
|
-
|
|
1112
|
-
let dstStatus = await this.exists(dstDir);
|
|
1192
|
+
let dstStatus = await this.exists(absDstDir);
|
|
1113
1193
|
if (dstStatus && dstStatus !== 'd') {
|
|
1114
|
-
|
|
1194
|
+
this.debugMsg(`UploadDir: DST ${absDstDir} exists but not a directory`);
|
|
1195
|
+
throw fmtError(
|
|
1196
|
+
`Bad path ${absDstDir} Not a directory`,
|
|
1197
|
+
'uploadDir',
|
|
1198
|
+
errorCode.badPath
|
|
1199
|
+
);
|
|
1115
1200
|
}
|
|
1116
1201
|
if (!dstStatus) {
|
|
1117
|
-
|
|
1202
|
+
this.debugMsg(`UploadDir: Creating directory ${absDstDir}`);
|
|
1203
|
+
await this.mkdir(absDstDir, true);
|
|
1118
1204
|
}
|
|
1119
1205
|
let dirEntries = fs.readdirSync(srcDir, {
|
|
1120
1206
|
encoding: 'utf8',
|
|
1121
1207
|
withFileTypes: true,
|
|
1122
1208
|
});
|
|
1123
|
-
|
|
1209
|
+
if (filter) {
|
|
1210
|
+
dirEntries = dirEntries.filter((item) =>
|
|
1211
|
+
filter(join(srcDir, item.name), item.isDirectory())
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1124
1214
|
for (let e of dirEntries) {
|
|
1215
|
+
let newSrc = join(srcDir, e.name);
|
|
1216
|
+
let newDst = `${absDstDir}${this.remotePathSep}${e.name}`;
|
|
1125
1217
|
if (e.isDirectory()) {
|
|
1126
|
-
let newSrc = join(srcDir, e.name);
|
|
1127
|
-
let newDst = dstDir + this.remotePathSep + e.name;
|
|
1128
1218
|
await this.uploadDir(newSrc, newDst, filter);
|
|
1129
1219
|
} else if (e.isFile()) {
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
await this.fastPut(src, dst);
|
|
1133
|
-
this.client.emit('upload', { source: src, destination: dst });
|
|
1220
|
+
await this.put(newSrc, newDst);
|
|
1221
|
+
this.client.emit('upload', { source: newSrc, destination: newDst });
|
|
1134
1222
|
} else {
|
|
1135
1223
|
this.debugMsg(
|
|
1136
1224
|
`uploadDir: File ignored: ${e.name} not a regular file`
|
|
1137
1225
|
);
|
|
1138
1226
|
}
|
|
1139
1227
|
}
|
|
1140
|
-
return `${srcDir} uploaded to ${
|
|
1228
|
+
return `${srcDir} uploaded to ${absDstDir}`;
|
|
1141
1229
|
} catch (err) {
|
|
1142
1230
|
this._resetEventFlags();
|
|
1143
1231
|
throw err.custom ? err : fmtError(err, 'uploadDir');
|
|
@@ -1152,15 +1240,22 @@ class SftpClient {
|
|
|
1152
1240
|
* file system.
|
|
1153
1241
|
* @param {String} srcDir - remote source directory
|
|
1154
1242
|
* @param {String} dstDir - local destination directory
|
|
1155
|
-
* @param {
|
|
1156
|
-
* files and directories to upload
|
|
1243
|
+
* @param {function(String,Boolean):Boolean} filter - (Optional) The first argument is the full path of the item to be downloaded and the second argument is a boolean, which will be true if the target path is for a directory. If the function returns true, the item will be downloaded
|
|
1157
1244
|
* @returns {Promise<String>}
|
|
1158
1245
|
*/
|
|
1159
|
-
async downloadDir(srcDir, dstDir, filter
|
|
1246
|
+
async downloadDir(srcDir, dstDir, filter) {
|
|
1160
1247
|
try {
|
|
1161
1248
|
this.debugMsg(`downloadDir -> ${srcDir} ${dstDir}`);
|
|
1162
1249
|
haveConnection(this, 'downloadDir');
|
|
1163
|
-
let fileList = await this.list(srcDir
|
|
1250
|
+
let fileList = await this.list(srcDir);
|
|
1251
|
+
if (filter) {
|
|
1252
|
+
fileList = fileList.filter((item) =>
|
|
1253
|
+
filter(
|
|
1254
|
+
`${srcDir}${this.remotePathSep}${item.name}`,
|
|
1255
|
+
item.type === 'd' ? true : false
|
|
1256
|
+
)
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1164
1259
|
const localCheck = haveLocalCreate(dstDir);
|
|
1165
1260
|
if (!localCheck.status && localCheck.details === 'permission denied') {
|
|
1166
1261
|
throw fmtError(
|
|
@@ -1178,15 +1273,13 @@ class SftpClient {
|
|
|
1178
1273
|
);
|
|
1179
1274
|
}
|
|
1180
1275
|
for (let f of fileList) {
|
|
1276
|
+
let newSrc = `${srcDir}${this.remotePathSep}${f.name}`;
|
|
1277
|
+
let newDst = join(dstDir, f.name);
|
|
1181
1278
|
if (f.type === 'd') {
|
|
1182
|
-
let newSrc = srcDir + this.remotePathSep + f.name;
|
|
1183
|
-
let newDst = join(dstDir, f.name);
|
|
1184
1279
|
await this.downloadDir(newSrc, newDst, filter);
|
|
1185
1280
|
} else if (f.type === '-') {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
await this.fastGet(src, dst);
|
|
1189
|
-
this.client.emit('download', { source: src, destination: dst });
|
|
1281
|
+
await this.get(newSrc, newDst);
|
|
1282
|
+
this.client.emit('download', { source: newSrc, destination: newDst });
|
|
1190
1283
|
} else {
|
|
1191
1284
|
this.debugMsg(
|
|
1192
1285
|
`downloadDir: File ignored: ${f.name} not regular file`
|
|
@@ -1208,10 +1301,10 @@ class SftpClient {
|
|
|
1208
1301
|
* @returns {Promise<Boolean>}
|
|
1209
1302
|
*/
|
|
1210
1303
|
end() {
|
|
1211
|
-
let endCloseHandler;
|
|
1304
|
+
let endCloseHandler, listeners;
|
|
1212
1305
|
return new Promise((resolve, reject) => {
|
|
1306
|
+
listeners = addTempListeners(this, 'end', reject);
|
|
1213
1307
|
this.endCalled = true;
|
|
1214
|
-
addTempListeners(this, 'end', reject);
|
|
1215
1308
|
endCloseHandler = () => {
|
|
1216
1309
|
this.sftp = undefined;
|
|
1217
1310
|
this.debugMsg('end: Connection closed');
|
|
@@ -1224,7 +1317,7 @@ class SftpClient {
|
|
|
1224
1317
|
}
|
|
1225
1318
|
}).finally(() => {
|
|
1226
1319
|
this.debugMsg('end: finally clause fired');
|
|
1227
|
-
removeTempListeners(this, 'end');
|
|
1320
|
+
removeTempListeners(this, listeners, 'end');
|
|
1228
1321
|
this.removeListener('close', endCloseHandler);
|
|
1229
1322
|
this.endCalled = false;
|
|
1230
1323
|
this._resetEventFlags();
|