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.
Files changed (5) hide show
  1. package/README.md +334 -166
  2. package/README.org +107 -81
  3. package/package.json +19 -15
  4. package/src/index.js +232 -139
  5. 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('Global: Ignoring handled error');
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
- new Promise((resolve, reject) => {
125
- addTempListeners(this, 'getConnection', reject);
126
- this.debugMsg('getConnection: created promise');
127
- doReady = () => {
128
- this.debugMsg('getConnection: got connection - promise resolved');
129
- resolve(true);
130
- };
131
- this.on('ready', doReady);
132
- this.client.connect(config);
133
- })
134
- // .catch((err) => {
135
- // return Promise.reject(err);
136
- // })
137
- .finally(async () => {
138
- this.debugMsg('getConnection: finally clause fired');
139
- await sleep(500);
140
- this.removeListener('ready', doReady);
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('getConnection retry catch');
200
- retry(err);
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 fileType = await this.exists(remotePath);
788
- if (fileType && fileType === 'd') {
789
- throw fmtError(
790
- `Bad path: ${remotePath}: cannot append to a directory`,
791
- 'append',
792
- errorCode.badPath
793
- );
794
- }
795
- return await new Promise((resolve, reject) => {
796
- if (haveConnection(this, 'append', reject)) {
797
- if (typeof input === 'string') {
798
- reject(fmtError('Cannot append one file to another', 'append'));
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(`append -> remote: ${remotePath} `, options);
801
- addTempListeners(this, 'append', reject);
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
- }).finally(() => {
821
- removeTempListeners(this, 'append');
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}`, '_rmdir', err.code));
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, 'rmdir');
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
- if (!recursive) {
930
- return _rmdir(absPath);
931
- }
932
- let list = await this.list(absPath);
933
- if (list.length) {
934
- let files = list.filter((item) => item.type !== 'd');
935
- let dirs = list.filter((item) => item.type === 'd');
936
- this.debugMsg('rmdir contents (files): ', files);
937
- this.debugMsg('rmdir contents (dirs): ', dirs);
938
- for (let f of files) {
939
- await this.delete(`${absPath}${this.remotePathSep}${f.name}`);
940
- }
941
- for (let d of dirs) {
942
- await this.rmdir(`${absPath}${this.remotePathSep}${d.name}`, true);
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 {RegExp} filter - (Optional) a regular expression used to select
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
- haveConnection(this, 'uploadDir');
1112
- let dstStatus = await this.exists(dstDir);
1192
+ let dstStatus = await this.exists(absDstDir);
1113
1193
  if (dstStatus && dstStatus !== 'd') {
1114
- throw fmtError(`Bad path ${dstDir}`, 'uploadDir', errorCode.badPath);
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
- await this.mkdir(dstDir, true);
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
- dirEntries = dirEntries.filter((item) => filter.test(item.name));
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
- let src = join(srcDir, e.name);
1131
- let dst = dstDir + this.remotePathSep + e.name;
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 ${dstDir}`;
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 {RegExp} filter - (Optional) a regular expression used to select
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, filter);
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
- let src = srcDir + this.remotePathSep + f.name;
1187
- let dst = join(dstDir, f.name);
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();