ssh2-sftp-client 7.2.3 → 9.0.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/.dir-local.el +1 -0
- package/README.md +384 -166
- package/README.org +181 -90
- package/package.json +10 -6
- package/src/constants.js +1 -0
- package/src/index.js +955 -653
- package/src/utils.js +53 -125
package/src/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ssh2 sftp client for node
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
'use strict';
|
|
6
2
|
|
|
7
3
|
const { Client } = require('ssh2');
|
|
@@ -10,7 +6,6 @@ const concat = require('concat-stream');
|
|
|
10
6
|
const promiseRetry = require('promise-retry');
|
|
11
7
|
const { join, parse } = require('path');
|
|
12
8
|
const {
|
|
13
|
-
fmtError,
|
|
14
9
|
addTempListeners,
|
|
15
10
|
removeTempListeners,
|
|
16
11
|
haveConnection,
|
|
@@ -18,12 +13,12 @@ const {
|
|
|
18
13
|
localExists,
|
|
19
14
|
haveLocalAccess,
|
|
20
15
|
haveLocalCreate,
|
|
21
|
-
sleep,
|
|
22
16
|
} = require('./utils');
|
|
23
17
|
const { errorCode } = require('./constants');
|
|
24
18
|
|
|
25
19
|
class SftpClient {
|
|
26
20
|
constructor(clientName) {
|
|
21
|
+
this.version = '9.0.0';
|
|
27
22
|
this.client = new Client();
|
|
28
23
|
this.sftp = undefined;
|
|
29
24
|
this.clientName = clientName ? clientName : 'sftp';
|
|
@@ -34,7 +29,6 @@ class SftpClient {
|
|
|
34
29
|
this.remotePathSep = '/';
|
|
35
30
|
this.remotePlatform = 'unix';
|
|
36
31
|
this.debug = undefined;
|
|
37
|
-
this.tempListeners = {};
|
|
38
32
|
|
|
39
33
|
this.client.on('close', () => {
|
|
40
34
|
if (this.endCalled || this.closeHandled) {
|
|
@@ -66,7 +60,6 @@ class SftpClient {
|
|
|
66
60
|
console.log(
|
|
67
61
|
`ssh2-sftp-client: Unexpected error: ${err.message}. Error code: ${err.code}`
|
|
68
62
|
);
|
|
69
|
-
//throw fmtError(err, 'Global');
|
|
70
63
|
}
|
|
71
64
|
});
|
|
72
65
|
}
|
|
@@ -83,6 +76,51 @@ class SftpClient {
|
|
|
83
76
|
}
|
|
84
77
|
}
|
|
85
78
|
|
|
79
|
+
fmtError(err, name = 'sftp', eCode, retryCount) {
|
|
80
|
+
let msg = '';
|
|
81
|
+
let code = '';
|
|
82
|
+
const retry = retryCount
|
|
83
|
+
? ` after ${retryCount} ${retryCount > 1 ? 'attempts' : 'attempt'}`
|
|
84
|
+
: '';
|
|
85
|
+
|
|
86
|
+
if (err === undefined) {
|
|
87
|
+
msg = `${name}: Undefined error - probably a bug!`;
|
|
88
|
+
code = errorCode.generic;
|
|
89
|
+
} else if (typeof err === 'string') {
|
|
90
|
+
msg = `${name}: ${err}${retry}`;
|
|
91
|
+
code = eCode ? eCode : errorCode.generic;
|
|
92
|
+
} else if (err.custom) {
|
|
93
|
+
msg = `${name}->${err.message}${retry}`;
|
|
94
|
+
code = err.code;
|
|
95
|
+
} else {
|
|
96
|
+
switch (err.code) {
|
|
97
|
+
case 'ENOTFOUND':
|
|
98
|
+
msg =
|
|
99
|
+
`${name}: ${err.level} error. ` +
|
|
100
|
+
`Address lookup failed for host ${err.hostname}${retry}`;
|
|
101
|
+
break;
|
|
102
|
+
case 'ECONNREFUSED':
|
|
103
|
+
msg =
|
|
104
|
+
`${name}: ${err.level} error. Remote host at ` +
|
|
105
|
+
`${err.address} refused connection${retry}`;
|
|
106
|
+
break;
|
|
107
|
+
case 'ECONNRESET':
|
|
108
|
+
msg =
|
|
109
|
+
`${name}: Remote host has reset the connection: ` +
|
|
110
|
+
`${err.message}${retry}`;
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
msg = `${name}: ${err.message}${retry}`;
|
|
114
|
+
}
|
|
115
|
+
code = err.code ? err.code : errorCode.generic;
|
|
116
|
+
}
|
|
117
|
+
let newError = new Error(msg);
|
|
118
|
+
newError.code = code;
|
|
119
|
+
newError.custom = true;
|
|
120
|
+
this.debugMsg(`${newError.message} (${newError.code})`);
|
|
121
|
+
return newError;
|
|
122
|
+
}
|
|
123
|
+
|
|
86
124
|
/**
|
|
87
125
|
* Add a listner to the client object. This is rarely necessary and can be
|
|
88
126
|
* the source of errors. It is the client's responsibility to remove the
|
|
@@ -93,12 +131,10 @@ class SftpClient {
|
|
|
93
131
|
* @param {function} callback - function called when event triggers
|
|
94
132
|
*/
|
|
95
133
|
on(eventType, callback) {
|
|
96
|
-
this.debugMsg(`Adding listener to ${eventType} event`);
|
|
97
134
|
this.client.prependListener(eventType, callback);
|
|
98
135
|
}
|
|
99
136
|
|
|
100
137
|
removeListener(eventType, callback) {
|
|
101
|
-
this.debugMsg(`Removing listener from ${eventType} event`);
|
|
102
138
|
this.client.removeListener(eventType, callback);
|
|
103
139
|
}
|
|
104
140
|
|
|
@@ -119,10 +155,9 @@ class SftpClient {
|
|
|
119
155
|
*
|
|
120
156
|
*/
|
|
121
157
|
getConnection(config) {
|
|
122
|
-
let doReady;
|
|
158
|
+
let doReady, listeners;
|
|
123
159
|
return new Promise((resolve, reject) => {
|
|
124
|
-
addTempListeners(this, 'getConnection', reject);
|
|
125
|
-
this.debugMsg('getConnection: created promise');
|
|
160
|
+
listeners = addTempListeners(this, 'getConnection', reject);
|
|
126
161
|
doReady = () => {
|
|
127
162
|
this.debugMsg(
|
|
128
163
|
'getConnection ready listener: got connection - promise resolved'
|
|
@@ -131,34 +166,25 @@ class SftpClient {
|
|
|
131
166
|
};
|
|
132
167
|
this.on('ready', doReady);
|
|
133
168
|
this.client.connect(config);
|
|
134
|
-
}).finally(
|
|
135
|
-
this.debugMsg('getConnection: finally clause fired');
|
|
136
|
-
await sleep(500);
|
|
169
|
+
}).finally(() => {
|
|
137
170
|
this.removeListener('ready', doReady);
|
|
138
|
-
removeTempListeners(this, 'getConnection');
|
|
171
|
+
removeTempListeners(this, listeners, 'getConnection');
|
|
139
172
|
this._resetEventFlags();
|
|
140
173
|
});
|
|
141
174
|
}
|
|
142
175
|
|
|
143
176
|
getSftpChannel() {
|
|
144
177
|
return new Promise((resolve, reject) => {
|
|
145
|
-
addTempListeners(this, 'getSftpChannel', reject);
|
|
146
|
-
this.debugMsg('getSftpChannel: created promise');
|
|
147
178
|
this.client.sftp((err, sftp) => {
|
|
148
179
|
if (err) {
|
|
149
|
-
this.debugMsg(`getSftpChannel: SFTP Channel Error: ${err.message}`);
|
|
150
180
|
this.client.end();
|
|
151
|
-
reject(fmtError(err, 'getSftpChannel', err.code));
|
|
181
|
+
reject(this.fmtError(err, 'getSftpChannel', err.code));
|
|
152
182
|
} else {
|
|
153
183
|
this.debugMsg('getSftpChannel: SFTP channel established');
|
|
154
184
|
this.sftp = sftp;
|
|
155
185
|
resolve(sftp);
|
|
156
186
|
}
|
|
157
187
|
});
|
|
158
|
-
}).finally(() => {
|
|
159
|
-
this.debugMsg('getSftpChannel: finally clause fired');
|
|
160
|
-
removeTempListeners(this, 'getSftpChannel');
|
|
161
|
-
this._resetEventFlags();
|
|
162
188
|
});
|
|
163
189
|
}
|
|
164
190
|
|
|
@@ -175,47 +201,68 @@ class SftpClient {
|
|
|
175
201
|
*
|
|
176
202
|
*/
|
|
177
203
|
async connect(config) {
|
|
204
|
+
let listeners;
|
|
205
|
+
|
|
178
206
|
try {
|
|
207
|
+
listeners = addTempListeners(this, 'connect');
|
|
179
208
|
if (config.debug) {
|
|
180
209
|
this.debug = config.debug;
|
|
181
210
|
this.debugMsg('connect: Debugging turned on');
|
|
211
|
+
this.debugMsg(
|
|
212
|
+
`ssh2-sftp-client Version: ${this.version} `,
|
|
213
|
+
process.versions
|
|
214
|
+
);
|
|
182
215
|
}
|
|
183
216
|
if (this.sftp) {
|
|
184
|
-
this.
|
|
185
|
-
throw fmtError(
|
|
217
|
+
throw this.fmtError(
|
|
186
218
|
'An existing SFTP connection is already defined',
|
|
187
219
|
'connect',
|
|
188
220
|
errorCode.connect
|
|
189
221
|
);
|
|
190
222
|
}
|
|
191
|
-
|
|
192
|
-
|
|
223
|
+
let retryOpts = {
|
|
224
|
+
retries: config.retries || 1,
|
|
225
|
+
factor: config.factor || 2,
|
|
226
|
+
minTimeout: config.retry_minTimeout || 25000,
|
|
227
|
+
};
|
|
228
|
+
await promiseRetry(retryOpts, async (retry, attempt) => {
|
|
229
|
+
try {
|
|
193
230
|
this.debugMsg(`connect: Connect attempt ${attempt}`);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
231
|
+
await this.getConnection(config);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
switch (err.code) {
|
|
234
|
+
case 'ENOTFOUND':
|
|
235
|
+
case 'ECONNREFUSED':
|
|
236
|
+
case 'ERR_SOCKET_BAD_PORT':
|
|
237
|
+
throw err;
|
|
238
|
+
case undefined: {
|
|
239
|
+
if (
|
|
240
|
+
err.message.endsWith(
|
|
241
|
+
'All configured authentication methods failed'
|
|
242
|
+
)
|
|
243
|
+
) {
|
|
244
|
+
throw this.fmtError(
|
|
245
|
+
err.message,
|
|
246
|
+
'getConnection',
|
|
247
|
+
errorCode.badAuth
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
retry(err);
|
|
251
|
+
break;
|
|
205
252
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
retries: config.retries || 1,
|
|
210
|
-
factor: config.retry_factor || 2,
|
|
211
|
-
minTimeout: config.retry_minTimeout || 1000,
|
|
253
|
+
default:
|
|
254
|
+
retry(err);
|
|
255
|
+
}
|
|
212
256
|
}
|
|
213
|
-
);
|
|
214
|
-
|
|
257
|
+
});
|
|
258
|
+
let sftp = await this.getSftpChannel();
|
|
259
|
+
return sftp;
|
|
215
260
|
} catch (err) {
|
|
216
|
-
this.
|
|
261
|
+
this.end();
|
|
262
|
+
throw err.custom ? err : this.fmtError(err, 'connect');
|
|
263
|
+
} finally {
|
|
264
|
+
removeTempListeners(this, listeners, 'connect');
|
|
217
265
|
this._resetEventFlags();
|
|
218
|
-
throw fmtError(err, 'connect');
|
|
219
266
|
}
|
|
220
267
|
}
|
|
221
268
|
|
|
@@ -230,32 +277,42 @@ class SftpClient {
|
|
|
230
277
|
* @param {String} remotePath - remote path, may be relative
|
|
231
278
|
* @returns {Promise<String>} - remote absolute path or ''
|
|
232
279
|
*/
|
|
233
|
-
|
|
280
|
+
_realPath(rPath) {
|
|
234
281
|
return new Promise((resolve, reject) => {
|
|
235
|
-
this.debugMsg(`
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
fmtError(`${err.message} ${remotePath}`, 'realPath', err.code)
|
|
246
|
-
);
|
|
247
|
-
}
|
|
282
|
+
this.debugMsg(`_realPath -> ${rPath}`);
|
|
283
|
+
this.sftp.realpath(rPath, (err, absPath) => {
|
|
284
|
+
if (err) {
|
|
285
|
+
if (err.code === 2) {
|
|
286
|
+
this.debugMsg('_realPath <- ""');
|
|
287
|
+
resolve('');
|
|
288
|
+
} else {
|
|
289
|
+
reject(
|
|
290
|
+
this.fmtError(`${err.message} ${rPath}`, 'realPath', err.code)
|
|
291
|
+
);
|
|
248
292
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
}).finally(() => {
|
|
254
|
-
removeTempListeners(this, 'realPath');
|
|
255
|
-
this._resetEventFlags();
|
|
293
|
+
}
|
|
294
|
+
this.debugMsg(`_realPath <- ${absPath}`);
|
|
295
|
+
resolve(absPath);
|
|
296
|
+
});
|
|
256
297
|
});
|
|
257
298
|
}
|
|
258
299
|
|
|
300
|
+
async realPath(remotePath) {
|
|
301
|
+
let listeners;
|
|
302
|
+
try {
|
|
303
|
+
listeners = addTempListeners(this, 'realPath');
|
|
304
|
+
haveConnection(this, 'realPath');
|
|
305
|
+
return await this._realPath(remotePath);
|
|
306
|
+
} catch (e) {
|
|
307
|
+
throw e.custom
|
|
308
|
+
? e
|
|
309
|
+
: this.fmtError(`${e.message} ${remotePath}`, 'realPath', e.code);
|
|
310
|
+
} finally {
|
|
311
|
+
removeTempListeners(this, listeners, 'realPath');
|
|
312
|
+
this._resetEventFlags();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
259
316
|
/**
|
|
260
317
|
* @async
|
|
261
318
|
*
|
|
@@ -273,59 +330,57 @@ class SftpClient {
|
|
|
273
330
|
* @param {String} remotePath - a string containing the path to a file
|
|
274
331
|
* @return {Promise<Object>} stats - attributes info
|
|
275
332
|
*/
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
errorCode.notexist
|
|
290
|
-
)
|
|
291
|
-
);
|
|
292
|
-
} else {
|
|
293
|
-
reject(
|
|
294
|
-
fmtError(`${err.message} ${remotePath}`, '_stat', err.code)
|
|
295
|
-
);
|
|
296
|
-
}
|
|
333
|
+
_stat(aPath) {
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
this.debugMsg(`_stat: ${aPath}`);
|
|
336
|
+
this.sftp.stat(aPath, (err, stats) => {
|
|
337
|
+
if (err) {
|
|
338
|
+
if (err.code === 2 || err.code === 4) {
|
|
339
|
+
reject(
|
|
340
|
+
this.fmtError(
|
|
341
|
+
`No such file: ${aPath}`,
|
|
342
|
+
'_stat',
|
|
343
|
+
errorCode.notexist
|
|
344
|
+
)
|
|
345
|
+
);
|
|
297
346
|
} else {
|
|
298
|
-
|
|
299
|
-
mode: stats.mode,
|
|
300
|
-
uid: stats.uid,
|
|
301
|
-
gid: stats.gid,
|
|
302
|
-
size: stats.size,
|
|
303
|
-
accessTime: stats.atime * 1000,
|
|
304
|
-
modifyTime: stats.mtime * 1000,
|
|
305
|
-
isDirectory: stats.isDirectory(),
|
|
306
|
-
isFile: stats.isFile(),
|
|
307
|
-
isBlockDevice: stats.isBlockDevice(),
|
|
308
|
-
isCharacterDevice: stats.isCharacterDevice(),
|
|
309
|
-
isSymbolicLink: stats.isSymbolicLink(),
|
|
310
|
-
isFIFO: stats.isFIFO(),
|
|
311
|
-
isSocket: stats.isSocket(),
|
|
312
|
-
};
|
|
313
|
-
this.debugMsg('_stat: stats <- ', result);
|
|
314
|
-
resolve(result);
|
|
347
|
+
reject(this.fmtError(`${err.message} ${aPath}`, '_stat', err.code));
|
|
315
348
|
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
349
|
+
} else {
|
|
350
|
+
const result = {
|
|
351
|
+
mode: stats.mode,
|
|
352
|
+
uid: stats.uid,
|
|
353
|
+
gid: stats.gid,
|
|
354
|
+
size: stats.size,
|
|
355
|
+
accessTime: stats.atime * 1000,
|
|
356
|
+
modifyTime: stats.mtime * 1000,
|
|
357
|
+
isDirectory: stats.isDirectory(),
|
|
358
|
+
isFile: stats.isFile(),
|
|
359
|
+
isBlockDevice: stats.isBlockDevice(),
|
|
360
|
+
isCharacterDevice: stats.isCharacterDevice(),
|
|
361
|
+
isSymbolicLink: stats.isSymbolicLink(),
|
|
362
|
+
isFIFO: stats.isFIFO(),
|
|
363
|
+
isSocket: stats.isSocket(),
|
|
364
|
+
};
|
|
365
|
+
this.debugMsg('_stat: stats <- ', result);
|
|
366
|
+
resolve(result);
|
|
367
|
+
}
|
|
319
368
|
});
|
|
320
|
-
};
|
|
369
|
+
});
|
|
370
|
+
}
|
|
321
371
|
|
|
372
|
+
async stat(remotePath) {
|
|
373
|
+
let listeners;
|
|
322
374
|
try {
|
|
375
|
+
listeners = addTempListeners(this, 'stat');
|
|
323
376
|
haveConnection(this, 'stat');
|
|
324
|
-
|
|
325
|
-
return _stat(absPath);
|
|
377
|
+
const absPath = await normalizeRemotePath(this, remotePath);
|
|
378
|
+
return await this._stat(absPath);
|
|
326
379
|
} catch (err) {
|
|
380
|
+
throw err.custom ? err : this.fmtError(err, 'stat', err.code);
|
|
381
|
+
} finally {
|
|
382
|
+
removeTempListeners(this, listeners, 'stat');
|
|
327
383
|
this._resetEventFlags();
|
|
328
|
-
throw err.custom ? err : fmtError(err, 'stat', err.code);
|
|
329
384
|
}
|
|
330
385
|
}
|
|
331
386
|
|
|
@@ -340,48 +395,49 @@ class SftpClient {
|
|
|
340
395
|
* @return {Promise<Boolean|String>} returns false if object does not exist. Returns type of
|
|
341
396
|
* object if it does
|
|
342
397
|
*/
|
|
343
|
-
async
|
|
398
|
+
async _exists(rPath) {
|
|
344
399
|
try {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.debugMsg(`exists: ${remotePath} = d`);
|
|
357
|
-
return 'd';
|
|
358
|
-
}
|
|
359
|
-
if (info.isSymbolicLink) {
|
|
360
|
-
this.debugMsg(`exists: ${remotePath} = l`);
|
|
361
|
-
return 'l';
|
|
362
|
-
}
|
|
363
|
-
if (info.isFile) {
|
|
364
|
-
this.debugMsg(`exists: ${remotePath} = -`);
|
|
365
|
-
return '-';
|
|
366
|
-
}
|
|
367
|
-
this.debugMsg(`exists: ${remotePath} = false`);
|
|
368
|
-
return false;
|
|
369
|
-
} catch (err) {
|
|
370
|
-
if (err.code === errorCode.notexist) {
|
|
371
|
-
this.debugMsg(
|
|
372
|
-
`exists: ${remotePath} = false errorCode = ${err.code}`
|
|
373
|
-
);
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
this.debugMsg(`exists: throw error ${err.message} ${err.code}`);
|
|
377
|
-
throw err;
|
|
378
|
-
}
|
|
400
|
+
const absPath = await normalizeRemotePath(this, rPath);
|
|
401
|
+
this.debugMsg(`exists: ${rPath} -> ${absPath}`);
|
|
402
|
+
const info = await this._stat(absPath);
|
|
403
|
+
this.debugMsg('exists: <- ', info);
|
|
404
|
+
if (info.isDirectory) {
|
|
405
|
+
this.debugMsg(`exists: ${rPath} = d`);
|
|
406
|
+
return 'd';
|
|
407
|
+
}
|
|
408
|
+
if (info.isSymbolicLink) {
|
|
409
|
+
this.debugMsg(`exists: ${rPath} = l`);
|
|
410
|
+
return 'l';
|
|
379
411
|
}
|
|
380
|
-
|
|
412
|
+
if (info.isFile) {
|
|
413
|
+
this.debugMsg(`exists: ${rPath} = -`);
|
|
414
|
+
return '-';
|
|
415
|
+
}
|
|
416
|
+
this.debugMsg(`exists: ${rPath} = false`);
|
|
381
417
|
return false;
|
|
382
418
|
} catch (err) {
|
|
419
|
+
if (err.code === errorCode.notexist) {
|
|
420
|
+
this.debugMsg(`exists: ${rPath} = false errorCode = ${err.code}`);
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
throw err.custom ? err : this.fmtError(err.message, 'exists', err.code);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async exists(remotePath) {
|
|
428
|
+
let listeners;
|
|
429
|
+
try {
|
|
430
|
+
listeners = addTempListeners(this, 'exists');
|
|
431
|
+
haveConnection(this, 'exists');
|
|
432
|
+
if (remotePath === '.') {
|
|
433
|
+
return 'd';
|
|
434
|
+
}
|
|
435
|
+
return await this._exists(remotePath);
|
|
436
|
+
} catch (err) {
|
|
437
|
+
throw err.custom ? err : this.fmtError(err, 'exists', err.code);
|
|
438
|
+
} finally {
|
|
439
|
+
removeTempListeners(this, listeners, 'exists');
|
|
383
440
|
this._resetEventFlags();
|
|
384
|
-
throw err.custom ? err : fmtError(err, 'exists', err.code);
|
|
385
441
|
}
|
|
386
442
|
}
|
|
387
443
|
|
|
@@ -395,60 +451,61 @@ class SftpClient {
|
|
|
395
451
|
* accessTime, rights {user, group other}, owner and group.
|
|
396
452
|
*
|
|
397
453
|
* @param {String} remotePath - path to remote directory
|
|
398
|
-
* @param {
|
|
454
|
+
* @param {function} filter - a filter function used to select return entries
|
|
399
455
|
* @returns {Promise<Array>} array of file description objects
|
|
400
456
|
*/
|
|
401
|
-
|
|
457
|
+
_list(remotePath, filter) {
|
|
402
458
|
return new Promise((resolve, reject) => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
459
|
+
this.sftp.readdir(remotePath, (err, fileList) => {
|
|
460
|
+
if (err) {
|
|
461
|
+
reject(
|
|
462
|
+
this.fmtError(`${err.message} ${remotePath}`, 'list', err.code)
|
|
463
|
+
);
|
|
464
|
+
} else {
|
|
465
|
+
const reg = /-/gi;
|
|
466
|
+
const newList = fileList.map((item) => {
|
|
467
|
+
return {
|
|
468
|
+
type: item.longname.slice(0, 1),
|
|
469
|
+
name: item.filename,
|
|
470
|
+
size: item.attrs.size,
|
|
471
|
+
modifyTime: item.attrs.mtime * 1000,
|
|
472
|
+
accessTime: item.attrs.atime * 1000,
|
|
473
|
+
rights: {
|
|
474
|
+
user: item.longname.slice(1, 4).replace(reg, ''),
|
|
475
|
+
group: item.longname.slice(4, 7).replace(reg, ''),
|
|
476
|
+
other: item.longname.slice(7, 10).replace(reg, ''),
|
|
477
|
+
},
|
|
478
|
+
owner: item.attrs.uid,
|
|
479
|
+
group: item.attrs.gid,
|
|
480
|
+
longname: item.longname,
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
if (filter) {
|
|
484
|
+
resolve(newList.filter((item) => filter(item)));
|
|
411
485
|
} else {
|
|
412
|
-
|
|
413
|
-
// reset file info
|
|
414
|
-
if (fileList) {
|
|
415
|
-
newList = fileList.map((item) => {
|
|
416
|
-
return {
|
|
417
|
-
type: item.longname.slice(0, 1),
|
|
418
|
-
name: item.filename,
|
|
419
|
-
size: item.attrs.size,
|
|
420
|
-
modifyTime: item.attrs.mtime * 1000,
|
|
421
|
-
accessTime: item.attrs.atime * 1000,
|
|
422
|
-
rights: {
|
|
423
|
-
user: item.longname.slice(1, 4).replace(reg, ''),
|
|
424
|
-
group: item.longname.slice(4, 7).replace(reg, ''),
|
|
425
|
-
other: item.longname.slice(7, 10).replace(reg, ''),
|
|
426
|
-
},
|
|
427
|
-
owner: item.attrs.uid,
|
|
428
|
-
group: item.attrs.gid,
|
|
429
|
-
};
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
// provide some compatibility for auxList
|
|
433
|
-
let regex;
|
|
434
|
-
if (pattern instanceof RegExp) {
|
|
435
|
-
regex = pattern;
|
|
436
|
-
} else {
|
|
437
|
-
let newPattern = pattern.replace(/\*([^*])*?/gi, '.*');
|
|
438
|
-
regex = new RegExp(newPattern);
|
|
439
|
-
}
|
|
440
|
-
let filteredList = newList.filter((item) => regex.test(item.name));
|
|
441
|
-
this.debugMsg('list: result: ', filteredList);
|
|
442
|
-
resolve(filteredList);
|
|
486
|
+
resolve(newList);
|
|
443
487
|
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}).finally(() => {
|
|
447
|
-
removeTempListeners(this, 'list');
|
|
448
|
-
this._resetEventFlags();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
449
490
|
});
|
|
450
491
|
}
|
|
451
492
|
|
|
493
|
+
async list(remotePath, filter) {
|
|
494
|
+
let listeners;
|
|
495
|
+
try {
|
|
496
|
+
listeners = addTempListeners(this, 'list');
|
|
497
|
+
haveConnection(this, 'list');
|
|
498
|
+
return await this._list(remotePath, filter);
|
|
499
|
+
} catch (e) {
|
|
500
|
+
throw e.custom
|
|
501
|
+
? e
|
|
502
|
+
: this.fmtError(`${e.message} ${remotePath}`, 'list', e.code);
|
|
503
|
+
} finally {
|
|
504
|
+
removeTempListeners(this, listeners, 'list');
|
|
505
|
+
this._resetEventFlags();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
452
509
|
/**
|
|
453
510
|
* get file
|
|
454
511
|
*
|
|
@@ -462,113 +519,87 @@ class SftpClient {
|
|
|
462
519
|
* @param {Object} options - options object with supported properties of readStreamOptions,
|
|
463
520
|
* writeStreamOptions and pipeOptions.
|
|
464
521
|
*
|
|
522
|
+
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls
|
|
523
|
+
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been
|
|
524
|
+
* added to support low-level access to stream objects.
|
|
525
|
+
*
|
|
465
526
|
* @return {Promise<String|Stream|Buffer>}
|
|
466
527
|
*/
|
|
467
|
-
|
|
468
|
-
remotePath,
|
|
469
|
-
dst,
|
|
470
|
-
options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
|
|
471
|
-
) {
|
|
528
|
+
_get(rPath, dst, opts) {
|
|
472
529
|
let rdr, wtr;
|
|
473
|
-
|
|
474
530
|
return new Promise((resolve, reject) => {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
531
|
+
opts = {
|
|
532
|
+
...opts,
|
|
533
|
+
readStreamOptions: { autoClose: true },
|
|
534
|
+
writeStreamOptions: { autoClose: true },
|
|
535
|
+
pipeOptions: { end: true },
|
|
536
|
+
};
|
|
537
|
+
rdr = this.sftp.createReadStream(rPath, opts.readStreamOptions);
|
|
538
|
+
rdr.once('error', (err) => {
|
|
539
|
+
reject(this.fmtError(`${err.message} ${rPath}`, '_get', err.code));
|
|
540
|
+
});
|
|
541
|
+
if (dst === undefined) {
|
|
542
|
+
// no dst specified, return buffer of data
|
|
543
|
+
this.debugMsg('get returning buffer of data');
|
|
544
|
+
wtr = concat((buff) => {
|
|
545
|
+
resolve(buff);
|
|
484
546
|
});
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
547
|
+
} else if (typeof dst === 'string') {
|
|
548
|
+
// dst local file path
|
|
549
|
+
this.debugMsg('get returning local file');
|
|
550
|
+
const localCheck = haveLocalCreate(dst);
|
|
551
|
+
if (!localCheck.status) {
|
|
552
|
+
reject(
|
|
553
|
+
this.fmtError(
|
|
554
|
+
`Bad path: ${dst}: ${localCheck.details}`,
|
|
555
|
+
'get',
|
|
556
|
+
localCheck.code
|
|
557
|
+
)
|
|
558
|
+
);
|
|
559
|
+
return;
|
|
492
560
|
} else {
|
|
493
|
-
|
|
494
|
-
// dst local file path
|
|
495
|
-
this.debugMsg('get returning local file');
|
|
496
|
-
const localCheck = haveLocalCreate(dst);
|
|
497
|
-
if (!localCheck.status) {
|
|
498
|
-
return reject(
|
|
499
|
-
fmtError(
|
|
500
|
-
`Bad path: ${dst}: ${localCheck.details}`,
|
|
501
|
-
'get',
|
|
502
|
-
localCheck.code
|
|
503
|
-
)
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
wtr = fs.createWriteStream(
|
|
507
|
-
dst,
|
|
508
|
-
options.writeStreamOptions ? options.writeStreamOptions : {}
|
|
509
|
-
);
|
|
510
|
-
} else {
|
|
511
|
-
this.debugMsg('get returning data into supplied stream');
|
|
512
|
-
wtr = dst;
|
|
513
|
-
}
|
|
514
|
-
wtr.once('error', (err) => {
|
|
515
|
-
reject(
|
|
516
|
-
fmtError(
|
|
517
|
-
`${err.message} ${typeof dst === 'string' ? dst : ''}`,
|
|
518
|
-
'get',
|
|
519
|
-
err.code
|
|
520
|
-
)
|
|
521
|
-
);
|
|
522
|
-
});
|
|
523
|
-
if (
|
|
524
|
-
Object.hasOwnProperty.call(options, 'pipeOptions') &&
|
|
525
|
-
Object.hasOwnProperty.call(options.pipeOptions, 'end') &&
|
|
526
|
-
!options.pipeOptions.end
|
|
527
|
-
) {
|
|
528
|
-
rdr.once('end', () => {
|
|
529
|
-
this.debugMsg('get resolved on reader end event');
|
|
530
|
-
if (typeof dst === 'string') {
|
|
531
|
-
resolve(dst);
|
|
532
|
-
} else {
|
|
533
|
-
resolve(wtr);
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
} else {
|
|
537
|
-
wtr.once('finish', () => {
|
|
538
|
-
this.debugMsg('get resolved on writer finish event');
|
|
539
|
-
if (typeof dst === 'string') {
|
|
540
|
-
resolve(dst);
|
|
541
|
-
} else {
|
|
542
|
-
resolve(wtr);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
}
|
|
561
|
+
wtr = fs.createWriteStream(dst, opts.writeStreamOptions);
|
|
546
562
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
removeTempListeners(this, 'get');
|
|
551
|
-
this._resetEventFlags();
|
|
552
|
-
if (
|
|
553
|
-
rdr &&
|
|
554
|
-
Object.hasOwnProperty.call(options, 'readStreamOptions') &&
|
|
555
|
-
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
|
|
556
|
-
options.readStreamOptions.autoClose === false
|
|
557
|
-
) {
|
|
558
|
-
rdr.destroy();
|
|
559
|
-
}
|
|
560
|
-
if (
|
|
561
|
-
wtr &&
|
|
562
|
-
Object.hasOwnProperty.call(options, 'writeStreamOptions') &&
|
|
563
|
-
Object.hasOwnProperty.call(options.writeStreamOptions, 'autoClose') &&
|
|
564
|
-
options.writeStreamOptions.autoClose === false &&
|
|
565
|
-
typeof dst === 'string'
|
|
566
|
-
) {
|
|
567
|
-
wtr.destroy();
|
|
563
|
+
} else {
|
|
564
|
+
this.debugMsg('get: returning data into supplied stream');
|
|
565
|
+
wtr = dst;
|
|
568
566
|
}
|
|
567
|
+
wtr.once('error', (err) => {
|
|
568
|
+
reject(
|
|
569
|
+
this.fmtError(
|
|
570
|
+
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`,
|
|
571
|
+
'get',
|
|
572
|
+
err.code
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
rdr.once('end', () => {
|
|
577
|
+
if (typeof dst === 'string') {
|
|
578
|
+
resolve(dst);
|
|
579
|
+
} else {
|
|
580
|
+
resolve(wtr);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
rdr.pipe(wtr, opts.pipeOptions);
|
|
569
584
|
});
|
|
570
585
|
}
|
|
571
586
|
|
|
587
|
+
async get(remotePath, dst, options) {
|
|
588
|
+
let listeners;
|
|
589
|
+
try {
|
|
590
|
+
listeners = addTempListeners(this, 'get');
|
|
591
|
+
haveConnection(this, 'get');
|
|
592
|
+
return await this._get(remotePath, dst, options);
|
|
593
|
+
} catch (e) {
|
|
594
|
+
throw e.custom
|
|
595
|
+
? e
|
|
596
|
+
: this.fmtError(`${e.message} ${remotePath}`, 'get', e.code);
|
|
597
|
+
} finally {
|
|
598
|
+
removeTempListeners(this, listeners, 'get');
|
|
599
|
+
this._resetEventFlags();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
572
603
|
/**
|
|
573
604
|
* Use SSH2 fastGet for downloading the file.
|
|
574
605
|
* Downloads a file at remotePath to localPath using parallel reads
|
|
@@ -579,46 +610,45 @@ class SftpClient {
|
|
|
579
610
|
* @param {Object} options
|
|
580
611
|
* @return {Promise<String>} the result of downloading the file
|
|
581
612
|
*/
|
|
613
|
+
_fastGet(rPath, lPath, opts) {
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
this.sftp.fastGet(rPath, lPath, opts, (err) => {
|
|
616
|
+
if (err) {
|
|
617
|
+
reject(
|
|
618
|
+
this.fmtError(`${err.message} Remote: ${rPath} Local: ${lPath}`)
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
resolve(`${rPath} was successfully download to ${lPath}!`);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
582
626
|
async fastGet(remotePath, localPath, options) {
|
|
627
|
+
let listeners;
|
|
583
628
|
try {
|
|
629
|
+
listeners = addTempListeners(this, 'fastGet');
|
|
630
|
+
haveConnection(this, 'fastGet');
|
|
584
631
|
const ftype = await this.exists(remotePath);
|
|
585
632
|
if (ftype !== '-') {
|
|
586
|
-
const msg =
|
|
587
|
-
ftype
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
let err = new Error(msg);
|
|
591
|
-
err.code = errorCode.badPath;
|
|
592
|
-
throw err;
|
|
633
|
+
const msg = `${
|
|
634
|
+
!ftype ? 'No such file ' : 'Not a regular file'
|
|
635
|
+
} ${remotePath}`;
|
|
636
|
+
throw this.fmtError(msg, 'fastGet', errorCode.badPath);
|
|
593
637
|
}
|
|
594
638
|
const localCheck = haveLocalCreate(localPath);
|
|
595
639
|
if (!localCheck.status) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
640
|
+
throw this.fmtError(
|
|
641
|
+
`Bad path: ${localPath}: ${localCheck.details}`,
|
|
642
|
+
'fastGet',
|
|
643
|
+
errorCode.badPath
|
|
644
|
+
);
|
|
599
645
|
}
|
|
600
|
-
|
|
601
|
-
if (haveConnection(this, 'fastGet', reject)) {
|
|
602
|
-
this.debugMsg(
|
|
603
|
-
`fastGet -> remote: ${remotePath} local: ${localPath} `,
|
|
604
|
-
options
|
|
605
|
-
);
|
|
606
|
-
addTempListeners(this, 'fastGet', reject);
|
|
607
|
-
this.sftp.fastGet(remotePath, localPath, options, (err) => {
|
|
608
|
-
if (err) {
|
|
609
|
-
this.debugMsg(`fastGet error ${err.message} code: ${err.code}`);
|
|
610
|
-
reject(err);
|
|
611
|
-
}
|
|
612
|
-
resolve(`${remotePath} was successfully download to ${localPath}!`);
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
}).finally(() => {
|
|
616
|
-
removeTempListeners(this, 'fastGet');
|
|
617
|
-
});
|
|
618
|
-
return rslt;
|
|
646
|
+
return await this._fastGet(remotePath, localPath, options);
|
|
619
647
|
} catch (err) {
|
|
648
|
+
throw this.fmtError(err, 'fastGet');
|
|
649
|
+
} finally {
|
|
650
|
+
removeTempListeners(this, listeners, 'fastGet');
|
|
620
651
|
this._resetEventFlags();
|
|
621
|
-
throw fmtError(err, 'fastGet');
|
|
622
652
|
}
|
|
623
653
|
}
|
|
624
654
|
|
|
@@ -635,52 +665,50 @@ class SftpClient {
|
|
|
635
665
|
* @param {Object} options
|
|
636
666
|
* @return {Promise<String>} the result of downloading the file
|
|
637
667
|
*/
|
|
638
|
-
|
|
639
|
-
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
668
|
+
_fastPut(lPath, rPath, opts) {
|
|
640
669
|
return new Promise((resolve, reject) => {
|
|
670
|
+
this.sftp.fastPut(lPath, rPath, opts, (err) => {
|
|
671
|
+
if (err) {
|
|
672
|
+
reject(
|
|
673
|
+
this.fmtError(
|
|
674
|
+
`${err.message} Local: ${lPath} Remote: ${rPath}`,
|
|
675
|
+
'fastPut',
|
|
676
|
+
err.code
|
|
677
|
+
)
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
resolve(`${lPath} was successfully uploaded to ${rPath}!`);
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async fastPut(localPath, remotePath, options) {
|
|
686
|
+
let listeners;
|
|
687
|
+
try {
|
|
688
|
+
listeners = addTempListeners(this, 'fastPut');
|
|
689
|
+
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
690
|
+
haveConnection(this, 'fastPut');
|
|
641
691
|
const localCheck = haveLocalAccess(localPath);
|
|
642
692
|
if (!localCheck.status) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
localCheck.code
|
|
648
|
-
)
|
|
693
|
+
throw this.fmtError(
|
|
694
|
+
`Bad path: ${localPath}: ${localCheck.details}`,
|
|
695
|
+
'fastPut',
|
|
696
|
+
localCheck.code
|
|
649
697
|
);
|
|
650
698
|
} else if (localCheck.status && localExists(localPath) === 'd') {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
errorCode.badPath
|
|
656
|
-
)
|
|
657
|
-
);
|
|
658
|
-
} else if (haveConnection(this, 'fastPut', reject)) {
|
|
659
|
-
this.debugMsg(
|
|
660
|
-
`fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify(
|
|
661
|
-
options
|
|
662
|
-
)}`
|
|
699
|
+
throw this.fmtError(
|
|
700
|
+
`Bad path: ${localPath} not a regular file`,
|
|
701
|
+
'fastgPut',
|
|
702
|
+
errorCode.badPath
|
|
663
703
|
);
|
|
664
|
-
addTempListeners(this, 'fastPut', reject);
|
|
665
|
-
this.sftp.fastPut(localPath, remotePath, options, (err) => {
|
|
666
|
-
if (err) {
|
|
667
|
-
this.debugMsg(`fastPut error ${err.message} ${err.code}`);
|
|
668
|
-
reject(
|
|
669
|
-
fmtError(
|
|
670
|
-
`${err.message} Local: ${localPath} Remote: ${remotePath}`,
|
|
671
|
-
'fastPut',
|
|
672
|
-
err.code
|
|
673
|
-
)
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
this.debugMsg('fastPut file transferred');
|
|
677
|
-
resolve(`${localPath} was successfully uploaded to ${remotePath}!`);
|
|
678
|
-
});
|
|
679
704
|
}
|
|
680
|
-
|
|
681
|
-
|
|
705
|
+
return await this._fastPut(localPath, remotePath, options);
|
|
706
|
+
} catch (e) {
|
|
707
|
+
throw e.custom ? e : this.fmtError(e.message, 'fastPut', e.code);
|
|
708
|
+
} finally {
|
|
709
|
+
removeTempListeners(this, listeners, 'fastPut');
|
|
682
710
|
this._resetEventFlags();
|
|
683
|
-
}
|
|
711
|
+
}
|
|
684
712
|
}
|
|
685
713
|
|
|
686
714
|
/**
|
|
@@ -693,91 +721,75 @@ class SftpClient {
|
|
|
693
721
|
* @param {Object} options - options used for read, write stream and pipe configuration
|
|
694
722
|
* value supported by node. Allowed properties are readStreamOptions,
|
|
695
723
|
* writeStreamOptions and pipeOptions.
|
|
724
|
+
*
|
|
725
|
+
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls
|
|
726
|
+
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been
|
|
727
|
+
* added to support low-level access to stream objects.
|
|
728
|
+
*
|
|
696
729
|
* @return {Promise<String>}
|
|
697
730
|
*/
|
|
698
|
-
|
|
699
|
-
localSrc,
|
|
700
|
-
remotePath,
|
|
701
|
-
options = {
|
|
702
|
-
readStreamOptions: {},
|
|
703
|
-
writeStreamOptions: { autoClose: true },
|
|
704
|
-
pipeOptions: {},
|
|
705
|
-
}
|
|
706
|
-
) {
|
|
731
|
+
_put(lPath, rPath, opts) {
|
|
707
732
|
let wtr, rdr;
|
|
708
|
-
|
|
709
733
|
return new Promise((resolve, reject) => {
|
|
734
|
+
opts = {
|
|
735
|
+
...opts,
|
|
736
|
+
readStreamOptions: { autoClose: true },
|
|
737
|
+
writeStreamOptions: { autoClose: true },
|
|
738
|
+
pipeOptions: { end: true },
|
|
739
|
+
};
|
|
740
|
+
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
|
|
741
|
+
wtr.once('error', (err) => {
|
|
742
|
+
reject(this.fmtError(`${err.message} ${rPath}`, 'put', err.code));
|
|
743
|
+
});
|
|
744
|
+
wtr.once('close', () => {
|
|
745
|
+
resolve(`Uploaded data stream to ${rPath}`);
|
|
746
|
+
});
|
|
747
|
+
if (lPath instanceof Buffer) {
|
|
748
|
+
this.debugMsg('put source is a buffer');
|
|
749
|
+
wtr.end(lPath);
|
|
750
|
+
} else {
|
|
751
|
+
rdr =
|
|
752
|
+
typeof lPath === 'string'
|
|
753
|
+
? fs.createReadStream(lPath, opts.readStreamOptions)
|
|
754
|
+
: lPath;
|
|
755
|
+
rdr.once('error', (err) => {
|
|
756
|
+
reject(
|
|
757
|
+
this.fmtError(
|
|
758
|
+
`${err.message} ${
|
|
759
|
+
typeof lPath === 'string' ? lPath : '<stream>'
|
|
760
|
+
}`,
|
|
761
|
+
'_put',
|
|
762
|
+
err.code
|
|
763
|
+
)
|
|
764
|
+
);
|
|
765
|
+
});
|
|
766
|
+
rdr.pipe(wtr, opts.pipeOptions);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async put(localSrc, remotePath, options) {
|
|
772
|
+
let listeners;
|
|
773
|
+
try {
|
|
774
|
+
listeners = addTempListeners(this, 'put');
|
|
775
|
+
haveConnection(this, 'put');
|
|
710
776
|
if (typeof localSrc === 'string') {
|
|
711
777
|
const localCheck = haveLocalAccess(localSrc);
|
|
712
778
|
if (!localCheck.status) {
|
|
713
|
-
this.
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
'put',
|
|
718
|
-
localCheck.code
|
|
719
|
-
)
|
|
779
|
+
throw this.fmtError(
|
|
780
|
+
`Bad path: ${localSrc} ${localCheck.details}`,
|
|
781
|
+
'put',
|
|
782
|
+
localCheck.code
|
|
720
783
|
);
|
|
721
784
|
}
|
|
722
785
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
? { ...options.writeStreamOptions, autoClose: true }
|
|
729
|
-
: {}
|
|
730
|
-
);
|
|
731
|
-
wtr.once('error', (err) => {
|
|
732
|
-
this.debugMsg(`put: write stream error ${err.message}`);
|
|
733
|
-
reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
|
|
734
|
-
});
|
|
735
|
-
wtr.once('close', () => {
|
|
736
|
-
this.debugMsg('put: promise resolved');
|
|
737
|
-
resolve(`Uploaded data stream to ${remotePath}`);
|
|
738
|
-
});
|
|
739
|
-
if (localSrc instanceof Buffer) {
|
|
740
|
-
this.debugMsg('put source is a buffer');
|
|
741
|
-
wtr.end(localSrc);
|
|
742
|
-
} else {
|
|
743
|
-
if (typeof localSrc === 'string') {
|
|
744
|
-
this.debugMsg(`put source is a file path: ${localSrc}`);
|
|
745
|
-
rdr = fs.createReadStream(
|
|
746
|
-
localSrc,
|
|
747
|
-
options.readStreamOptions ? options.readStreamOptions : {}
|
|
748
|
-
);
|
|
749
|
-
} else {
|
|
750
|
-
this.debugMsg('put source is a stream');
|
|
751
|
-
rdr = localSrc;
|
|
752
|
-
}
|
|
753
|
-
rdr.once('error', (err) => {
|
|
754
|
-
this.debugMsg(`put: read stream error ${err.message}`);
|
|
755
|
-
reject(
|
|
756
|
-
fmtError(
|
|
757
|
-
`${err.message} ${
|
|
758
|
-
typeof localSrc === 'string' ? localSrc : ''
|
|
759
|
-
}`,
|
|
760
|
-
'put',
|
|
761
|
-
err.code
|
|
762
|
-
)
|
|
763
|
-
);
|
|
764
|
-
});
|
|
765
|
-
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}).finally(() => {
|
|
769
|
-
removeTempListeners(this, 'put');
|
|
786
|
+
return await this._put(localSrc, remotePath, options);
|
|
787
|
+
} catch (e) {
|
|
788
|
+
throw e.custom ? e : this.fmtError(e.message, 'put', e.code);
|
|
789
|
+
} finally {
|
|
790
|
+
removeTempListeners(this, listeners, 'put');
|
|
770
791
|
this._resetEventFlags();
|
|
771
|
-
|
|
772
|
-
rdr &&
|
|
773
|
-
Object.hasOwnProperty.call(options, 'readStreamOptions') &&
|
|
774
|
-
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
|
|
775
|
-
options.readStreamOptions.autoClose === false &&
|
|
776
|
-
typeof localSrc === 'string'
|
|
777
|
-
) {
|
|
778
|
-
rdr.destroy();
|
|
779
|
-
}
|
|
780
|
-
});
|
|
792
|
+
}
|
|
781
793
|
}
|
|
782
794
|
|
|
783
795
|
/**
|
|
@@ -788,44 +800,53 @@ class SftpClient {
|
|
|
788
800
|
* @param {Object} options
|
|
789
801
|
* @return {Promise<String>}
|
|
790
802
|
*/
|
|
803
|
+
_append(input, rPath, opts) {
|
|
804
|
+
return new Promise((resolve, reject) => {
|
|
805
|
+
this.debugMsg(`append -> remote: ${rPath} `, opts);
|
|
806
|
+
opts.flags = 'a';
|
|
807
|
+
const stream = this.sftp.createWriteStream(rPath, opts);
|
|
808
|
+
stream.on('error', (err) => {
|
|
809
|
+
reject(this.fmtError(`${err.message} ${rPath}`, 'append', err.code));
|
|
810
|
+
});
|
|
811
|
+
stream.on('close', () => {
|
|
812
|
+
resolve(`Appended data to ${rPath}`);
|
|
813
|
+
});
|
|
814
|
+
if (input instanceof Buffer) {
|
|
815
|
+
stream.write(input);
|
|
816
|
+
stream.end();
|
|
817
|
+
} else {
|
|
818
|
+
input.pipe(stream);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
791
823
|
async append(input, remotePath, options = {}) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
if (haveConnection(this, 'append', reject)) {
|
|
802
|
-
if (typeof input === 'string') {
|
|
803
|
-
reject(fmtError('Cannot append one file to another', 'append'));
|
|
804
|
-
} else {
|
|
805
|
-
this.debugMsg(`append -> remote: ${remotePath} `, options);
|
|
806
|
-
addTempListeners(this, 'append', reject);
|
|
807
|
-
options.flags = 'a';
|
|
808
|
-
let stream = this.sftp.createWriteStream(remotePath, options);
|
|
809
|
-
stream.on('error', (err_1) => {
|
|
810
|
-
reject(
|
|
811
|
-
fmtError(`${err_1.message} ${remotePath}`, 'append', err_1.code)
|
|
812
|
-
);
|
|
813
|
-
});
|
|
814
|
-
stream.on('finish', () => {
|
|
815
|
-
resolve(`Appended data to ${remotePath}`);
|
|
816
|
-
});
|
|
817
|
-
if (input instanceof Buffer) {
|
|
818
|
-
stream.write(input);
|
|
819
|
-
stream.end();
|
|
820
|
-
} else {
|
|
821
|
-
input.pipe(stream);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
+
let listeners;
|
|
825
|
+
try {
|
|
826
|
+
listeners = addTempListeners(this, 'append');
|
|
827
|
+
if (typeof input === 'string') {
|
|
828
|
+
throw this.fmtError(
|
|
829
|
+
'Cannot append one file to another',
|
|
830
|
+
'append',
|
|
831
|
+
errorCode.badPath
|
|
832
|
+
);
|
|
824
833
|
}
|
|
825
|
-
|
|
826
|
-
|
|
834
|
+
haveConnection(this, 'append');
|
|
835
|
+
const fileType = await this.exists(remotePath);
|
|
836
|
+
if (fileType && fileType === 'd') {
|
|
837
|
+
throw this.fmtError(
|
|
838
|
+
`Bad path: ${remotePath}: cannot append to a directory`,
|
|
839
|
+
'append',
|
|
840
|
+
errorCode.badPath
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
await this._append(input, remotePath, options);
|
|
844
|
+
} catch (e) {
|
|
845
|
+
throw e.custom ? e : this.fmtError(e.message, 'append', e.code);
|
|
846
|
+
} finally {
|
|
847
|
+
removeTempListeners(this, listeners, 'append');
|
|
827
848
|
this._resetEventFlags();
|
|
828
|
-
}
|
|
849
|
+
}
|
|
829
850
|
}
|
|
830
851
|
|
|
831
852
|
/**
|
|
@@ -837,67 +858,85 @@ class SftpClient {
|
|
|
837
858
|
* @param {boolean} recursive - if true, recursively create directories
|
|
838
859
|
* @return {Promise<String>}
|
|
839
860
|
*/
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
861
|
+
_doMkdir(p) {
|
|
862
|
+
return new Promise((resolve, reject) => {
|
|
863
|
+
this.sftp.mkdir(p, (err) => {
|
|
864
|
+
if (err) {
|
|
865
|
+
if (err.code === 4) {
|
|
866
|
+
//fix for windows dodgy error messages
|
|
867
|
+
reject(
|
|
868
|
+
this.fmtError(
|
|
869
|
+
`Bad path: ${p} permission denied`,
|
|
870
|
+
'_doMkdir',
|
|
871
|
+
errorCode.badPath
|
|
872
|
+
)
|
|
873
|
+
);
|
|
874
|
+
} else if (err.code === 2) {
|
|
875
|
+
reject(
|
|
876
|
+
this.fmtError(
|
|
877
|
+
`Bad path: ${p} parent not a directory or not exist`,
|
|
878
|
+
'_doMkdir',
|
|
879
|
+
errorCode.badPath
|
|
880
|
+
)
|
|
881
|
+
);
|
|
862
882
|
} else {
|
|
863
|
-
this.
|
|
864
|
-
resolve(`${p} directory created`);
|
|
883
|
+
reject(this.fmtError(`${err.message} ${p}`, '_doMkdir', err.code));
|
|
865
884
|
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
this._resetEventFlags();
|
|
885
|
+
} else {
|
|
886
|
+
resolve(`${p} directory created`);
|
|
887
|
+
}
|
|
870
888
|
});
|
|
871
|
-
};
|
|
889
|
+
});
|
|
890
|
+
}
|
|
872
891
|
|
|
892
|
+
async _mkdir(remotePath, recursive) {
|
|
873
893
|
try {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
let targetExists = await this.exists(rPath);
|
|
894
|
+
const rPath = await normalizeRemotePath(this, remotePath);
|
|
895
|
+
const targetExists = await this.exists(rPath);
|
|
877
896
|
if (targetExists && targetExists !== 'd') {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
897
|
+
throw this.fmtError(
|
|
898
|
+
`Bad path: ${rPath} already exists as a file`,
|
|
899
|
+
'_mkdir',
|
|
900
|
+
errorCode.badPath
|
|
901
|
+
);
|
|
881
902
|
} else if (targetExists) {
|
|
882
903
|
return `${rPath} already exists`;
|
|
883
904
|
}
|
|
884
905
|
if (!recursive) {
|
|
885
|
-
return await
|
|
906
|
+
return await this._doMkdir(rPath);
|
|
886
907
|
}
|
|
887
|
-
|
|
908
|
+
const dir = parse(rPath).dir;
|
|
888
909
|
if (dir) {
|
|
889
|
-
|
|
910
|
+
const dirExists = await this.exists(dir);
|
|
890
911
|
if (!dirExists) {
|
|
891
|
-
await this.
|
|
912
|
+
await this._mkdir(dir, true);
|
|
892
913
|
} else if (dirExists !== 'd') {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
914
|
+
throw this.fmtError(
|
|
915
|
+
`Bad path: ${dir} not a directory`,
|
|
916
|
+
'_mkdir',
|
|
917
|
+
errorCode.badPath
|
|
918
|
+
);
|
|
896
919
|
}
|
|
897
920
|
}
|
|
898
|
-
return await
|
|
921
|
+
return await this._doMkdir(rPath);
|
|
922
|
+
} catch (err) {
|
|
923
|
+
throw err.custom
|
|
924
|
+
? err
|
|
925
|
+
: this.fmtError(`${err.message} ${remotePath}`, '_mkdir', err.code);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async mkdir(remotePath, recursive = false) {
|
|
930
|
+
let listeners;
|
|
931
|
+
try {
|
|
932
|
+
listeners = addTempListeners(this, '_mkdir');
|
|
933
|
+
haveConnection(this, 'mkdir');
|
|
934
|
+
return await this._mkdir(remotePath, recursive);
|
|
899
935
|
} catch (err) {
|
|
900
|
-
throw fmtError(`${err.message}`, 'mkdir', err.code);
|
|
936
|
+
throw this.fmtError(`${err.message}`, 'mkdir', err.code);
|
|
937
|
+
} finally {
|
|
938
|
+
removeTempListeners(this, listeners, 'append');
|
|
939
|
+
this._resetEventFlags();
|
|
901
940
|
}
|
|
902
941
|
}
|
|
903
942
|
|
|
@@ -915,42 +954,68 @@ class SftpClient {
|
|
|
915
954
|
const _rmdir = (p) => {
|
|
916
955
|
return new Promise((resolve, reject) => {
|
|
917
956
|
this.debugMsg(`rmdir -> ${p}`);
|
|
918
|
-
addTempListeners(this, 'rmdir', reject);
|
|
919
957
|
this.sftp.rmdir(p, (err) => {
|
|
920
958
|
if (err) {
|
|
921
|
-
this.
|
|
922
|
-
reject(fmtError(`${err.message} ${p}`, '_rmdir', err.code));
|
|
959
|
+
reject(this.fmtError(`${err.message} ${p}`, 'rmdir', err.code));
|
|
923
960
|
}
|
|
924
961
|
resolve('Successfully removed directory');
|
|
925
962
|
});
|
|
926
|
-
}).finally(() => {
|
|
927
|
-
removeTempListeners(this, 'rmdir');
|
|
928
963
|
});
|
|
929
964
|
};
|
|
930
965
|
|
|
966
|
+
const _dormdir = async (p, recur) => {
|
|
967
|
+
try {
|
|
968
|
+
if (recur) {
|
|
969
|
+
const list = await this.list(p);
|
|
970
|
+
if (list.length) {
|
|
971
|
+
const files = list.filter((item) => item.type !== 'd');
|
|
972
|
+
const dirs = list.filter((item) => item.type === 'd');
|
|
973
|
+
this.debugMsg('rmdir contents (files): ', files);
|
|
974
|
+
this.debugMsg('rmdir contents (dirs): ', dirs);
|
|
975
|
+
for (const d of dirs) {
|
|
976
|
+
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true);
|
|
977
|
+
}
|
|
978
|
+
const promiseList = [];
|
|
979
|
+
for (const f of files) {
|
|
980
|
+
promiseList.push(
|
|
981
|
+
this._delete(`${p}${this.remotePathSep}${f.name}`)
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
await Promise.all(promiseList);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return await _rmdir(p);
|
|
988
|
+
} catch (err) {
|
|
989
|
+
throw err.custom ? err : this.fmtError(err, '_dormdir', err.code);
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
let listeners;
|
|
931
994
|
try {
|
|
995
|
+
listeners = addTempListeners(this, 'rmdir');
|
|
932
996
|
haveConnection(this, 'rmdir');
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
this.
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
997
|
+
const absPath = await normalizeRemotePath(this, remotePath);
|
|
998
|
+
const dirStatus = await this.exists(absPath);
|
|
999
|
+
if (dirStatus && dirStatus !== 'd') {
|
|
1000
|
+
throw this.fmtError(
|
|
1001
|
+
`Bad path: ${absPath} not a directory`,
|
|
1002
|
+
'rmdir',
|
|
1003
|
+
errorCode.badPath
|
|
1004
|
+
);
|
|
1005
|
+
} else if (!dirStatus) {
|
|
1006
|
+
throw this.fmtError(
|
|
1007
|
+
`Bad path: ${absPath} No such file`,
|
|
1008
|
+
'rmdir',
|
|
1009
|
+
errorCode.badPath
|
|
1010
|
+
);
|
|
1011
|
+
} else {
|
|
1012
|
+
return await _dormdir(absPath, recursive);
|
|
949
1013
|
}
|
|
950
|
-
return _rmdir(absPath);
|
|
951
1014
|
} catch (err) {
|
|
1015
|
+
throw err.custom ? err : this.fmtError(err.message, 'rmdir', err.code);
|
|
1016
|
+
} finally {
|
|
1017
|
+
removeTempListeners(this, listeners, 'rmdir');
|
|
952
1018
|
this._resetEventFlags();
|
|
953
|
-
throw err.custom ? err : fmtError(err, 'rmdir', err.code);
|
|
954
1019
|
}
|
|
955
1020
|
}
|
|
956
1021
|
|
|
@@ -965,32 +1030,37 @@ class SftpClient {
|
|
|
965
1030
|
* @return {Promise<String>} with string 'Successfully deleted file' once resolved
|
|
966
1031
|
*
|
|
967
1032
|
*/
|
|
968
|
-
|
|
1033
|
+
_delete(rPath, notFoundOK) {
|
|
969
1034
|
return new Promise((resolve, reject) => {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
resolve(`Successfully deleted ${remotePath}`);
|
|
979
|
-
} else {
|
|
980
|
-
reject(
|
|
981
|
-
fmtError(`${err.message} ${remotePath}`, 'delete', err.code)
|
|
982
|
-
);
|
|
983
|
-
}
|
|
1035
|
+
this.sftp.unlink(rPath, (err) => {
|
|
1036
|
+
if (err) {
|
|
1037
|
+
if (notFoundOK && err.code === 2) {
|
|
1038
|
+
resolve(`Successfully deleted ${rPath}`);
|
|
1039
|
+
} else {
|
|
1040
|
+
reject(
|
|
1041
|
+
this.fmtError(`${err.message} ${rPath}`, 'delete', err.code)
|
|
1042
|
+
);
|
|
984
1043
|
}
|
|
985
|
-
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
}).finally(() => {
|
|
989
|
-
removeTempListeners(this, 'delete');
|
|
990
|
-
this._resetEventFlags();
|
|
1044
|
+
}
|
|
1045
|
+
resolve(`Successfully deleted ${rPath}`);
|
|
1046
|
+
});
|
|
991
1047
|
});
|
|
992
1048
|
}
|
|
993
1049
|
|
|
1050
|
+
async delete(remotePath, notFoundOK = false) {
|
|
1051
|
+
let listeners;
|
|
1052
|
+
try {
|
|
1053
|
+
listeners = addTempListeners(this, 'delete');
|
|
1054
|
+
haveConnection(this, 'delete');
|
|
1055
|
+
return await this._delete(remotePath, notFoundOK);
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
throw err.custom ? err : this.fmtError(err.message, 'delete', err.code);
|
|
1058
|
+
} finally {
|
|
1059
|
+
removeTempListeners(this, listeners, 'delete');
|
|
1060
|
+
this._resetEventFlags();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
994
1064
|
/**
|
|
995
1065
|
* @async
|
|
996
1066
|
*
|
|
@@ -1002,31 +1072,43 @@ class SftpClient {
|
|
|
1002
1072
|
* @return {Promise<String>}
|
|
1003
1073
|
*
|
|
1004
1074
|
*/
|
|
1005
|
-
|
|
1075
|
+
_rename(fPath, tPath) {
|
|
1006
1076
|
return new Promise((resolve, reject) => {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
resolve(`Successfully renamed ${fromPath} to ${toPath}`);
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
}).finally(() => {
|
|
1025
|
-
removeTempListeners(this, 'rename');
|
|
1026
|
-
this._resetEventFlags();
|
|
1077
|
+
this.sftp.rename(fPath, tPath, (err) => {
|
|
1078
|
+
if (err) {
|
|
1079
|
+
reject(
|
|
1080
|
+
this.fmtError(
|
|
1081
|
+
`${err.message} From: ${fPath} To: ${tPath}`,
|
|
1082
|
+
'_rename',
|
|
1083
|
+
err.code
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
resolve(`Successfully renamed ${fPath} to ${tPath}`);
|
|
1088
|
+
});
|
|
1027
1089
|
});
|
|
1028
1090
|
}
|
|
1029
1091
|
|
|
1092
|
+
async rename(fromPath, toPath) {
|
|
1093
|
+
let listeners;
|
|
1094
|
+
try {
|
|
1095
|
+
listeners = addTempListeners(this, 'rename');
|
|
1096
|
+
haveConnection(this, 'rename');
|
|
1097
|
+
return await this._rename(fromPath, toPath);
|
|
1098
|
+
} catch (err) {
|
|
1099
|
+
throw err.custom
|
|
1100
|
+
? err
|
|
1101
|
+
: this.fmtError(
|
|
1102
|
+
`${err.message} ${fromPath} ${toPath}`,
|
|
1103
|
+
'rename',
|
|
1104
|
+
err.code
|
|
1105
|
+
);
|
|
1106
|
+
} finally {
|
|
1107
|
+
removeTempListeners(this, listeners, 'rename');
|
|
1108
|
+
this._resetEventFlags();
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1030
1112
|
/**
|
|
1031
1113
|
* @async
|
|
1032
1114
|
*
|
|
@@ -1039,31 +1121,43 @@ class SftpClient {
|
|
|
1039
1121
|
* @return {Promise<String>}
|
|
1040
1122
|
*
|
|
1041
1123
|
*/
|
|
1042
|
-
|
|
1124
|
+
_posixRename(fPath, tPath) {
|
|
1043
1125
|
return new Promise((resolve, reject) => {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
resolve(`Successful POSIX rename ${fromPath} to ${toPath}`);
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
}).finally(() => {
|
|
1062
|
-
removeTempListeners(this, 'posixRename');
|
|
1063
|
-
this._resetEventFlags();
|
|
1126
|
+
this.sftp.ext_openssh_rename(fPath, tPath, (err) => {
|
|
1127
|
+
if (err) {
|
|
1128
|
+
reject(
|
|
1129
|
+
this.fmtError(
|
|
1130
|
+
`${err.message} From: ${fPath} To: ${tPath}`,
|
|
1131
|
+
'_posixRename',
|
|
1132
|
+
err.code
|
|
1133
|
+
)
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
resolve(`Successful POSIX rename ${fPath} to ${tPath}`);
|
|
1137
|
+
});
|
|
1064
1138
|
});
|
|
1065
1139
|
}
|
|
1066
1140
|
|
|
1141
|
+
async posixRename(fromPath, toPath) {
|
|
1142
|
+
let listeners;
|
|
1143
|
+
try {
|
|
1144
|
+
listeners = addTempListeners(this, 'posixRename');
|
|
1145
|
+
haveConnection(this, 'posixRename');
|
|
1146
|
+
return await this._posixRename(fromPath, toPath);
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
throw err.custom
|
|
1149
|
+
? err
|
|
1150
|
+
: this.fmtError(
|
|
1151
|
+
`${err.message} ${fromPath} ${toPath}`,
|
|
1152
|
+
'posixRename',
|
|
1153
|
+
err.code
|
|
1154
|
+
);
|
|
1155
|
+
} finally {
|
|
1156
|
+
removeTempListeners(this, listeners, 'posixRename');
|
|
1157
|
+
this._resetEventFlags();
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1067
1161
|
/**
|
|
1068
1162
|
* @async
|
|
1069
1163
|
*
|
|
@@ -1074,22 +1168,33 @@ class SftpClient {
|
|
|
1074
1168
|
*
|
|
1075
1169
|
* @return {Promise<String>}
|
|
1076
1170
|
*/
|
|
1077
|
-
|
|
1171
|
+
_chmod(rPath, mode) {
|
|
1078
1172
|
return new Promise((resolve, reject) => {
|
|
1079
|
-
this.
|
|
1080
|
-
addTempListeners(this, 'chmod', reject);
|
|
1081
|
-
this.sftp.chmod(remotePath, mode, (err) => {
|
|
1173
|
+
this.sftp.chmod(rPath, mode, (err) => {
|
|
1082
1174
|
if (err) {
|
|
1083
|
-
reject(fmtError(`${err.message} ${
|
|
1175
|
+
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code));
|
|
1084
1176
|
}
|
|
1085
1177
|
resolve('Successfully change file mode');
|
|
1086
1178
|
});
|
|
1087
|
-
}).finally(() => {
|
|
1088
|
-
removeTempListeners(this, 'chmod');
|
|
1089
|
-
this._resetEventFlags();
|
|
1090
1179
|
});
|
|
1091
1180
|
}
|
|
1092
1181
|
|
|
1182
|
+
async chmod(remotePath, mode) {
|
|
1183
|
+
let listeners;
|
|
1184
|
+
try {
|
|
1185
|
+
listeners = addTempListeners(this, 'chmod');
|
|
1186
|
+
haveConnection(this, 'chmod');
|
|
1187
|
+
return await this._chmod(remotePath, mode);
|
|
1188
|
+
} catch (err) {
|
|
1189
|
+
throw err.custom
|
|
1190
|
+
? err
|
|
1191
|
+
: this.fmtError(`${err.message} ${remotePath}`, 'chmod', err.code);
|
|
1192
|
+
} finally {
|
|
1193
|
+
removeTempListeners(this, listeners, 'chmod');
|
|
1194
|
+
this._resetEventFlags();
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1093
1198
|
/**
|
|
1094
1199
|
* @async
|
|
1095
1200
|
*
|
|
@@ -1098,54 +1203,95 @@ class SftpClient {
|
|
|
1098
1203
|
* server.
|
|
1099
1204
|
* @param {String} srcDir - local source directory
|
|
1100
1205
|
* @param {String} dstDir - remote destination directory
|
|
1101
|
-
* @param {
|
|
1102
|
-
*
|
|
1206
|
+
* @param {Object} options - (Optional) An object with 2 supported properties,
|
|
1207
|
+
* 'filter' and 'useFastput'. The first argument is the full path of the item
|
|
1208
|
+
* to be uploaded and the second argument is a boolean, which will be true if
|
|
1209
|
+
* the target path is for a directory. If the function returns true, the item
|
|
1210
|
+
* will be uploaded and excluded when it returns false. The 'useFastput' property is a
|
|
1211
|
+
* boolean value. When true, the 'fastPut()' method will be used to upload files. Default
|
|
1212
|
+
* is to use the slower, but more supported 'put()' method.
|
|
1213
|
+
*
|
|
1103
1214
|
* @returns {Promise<String>}
|
|
1104
1215
|
*/
|
|
1105
|
-
async
|
|
1216
|
+
async _uploadDir(srcDir, dstDir, options) {
|
|
1106
1217
|
try {
|
|
1107
|
-
|
|
1218
|
+
const absDstDir = await normalizeRemotePath(this, dstDir);
|
|
1219
|
+
this.debugMsg(`uploadDir <- SRC = ${srcDir} DST = ${absDstDir}`);
|
|
1108
1220
|
const srcType = localExists(srcDir);
|
|
1221
|
+
if (!srcType) {
|
|
1222
|
+
throw this.fmtError(
|
|
1223
|
+
`Bad path: ${srcDir} not exist`,
|
|
1224
|
+
'_uploadDir',
|
|
1225
|
+
errorCode.badPath
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1109
1228
|
if (srcType !== 'd') {
|
|
1110
|
-
throw fmtError(
|
|
1229
|
+
throw this.fmtError(
|
|
1111
1230
|
`Bad path: ${srcDir}: not a directory`,
|
|
1112
|
-
'
|
|
1231
|
+
'_uploadDir',
|
|
1113
1232
|
errorCode.badPath
|
|
1114
1233
|
);
|
|
1115
1234
|
}
|
|
1116
|
-
|
|
1117
|
-
let dstStatus = await this.exists(dstDir);
|
|
1235
|
+
const dstStatus = await this.exists(absDstDir);
|
|
1118
1236
|
if (dstStatus && dstStatus !== 'd') {
|
|
1119
|
-
throw fmtError(
|
|
1237
|
+
throw this.fmtError(
|
|
1238
|
+
`Bad path ${absDstDir} Not a directory`,
|
|
1239
|
+
'_uploadDir',
|
|
1240
|
+
errorCode.badPath
|
|
1241
|
+
);
|
|
1120
1242
|
}
|
|
1121
1243
|
if (!dstStatus) {
|
|
1122
|
-
await this.
|
|
1244
|
+
await this._mkdir(absDstDir, true);
|
|
1123
1245
|
}
|
|
1124
1246
|
let dirEntries = fs.readdirSync(srcDir, {
|
|
1125
1247
|
encoding: 'utf8',
|
|
1126
1248
|
withFileTypes: true,
|
|
1127
1249
|
});
|
|
1128
|
-
|
|
1129
|
-
|
|
1250
|
+
if (options?.filter) {
|
|
1251
|
+
dirEntries = dirEntries.filter((item) =>
|
|
1252
|
+
options.filter(join(srcDir, item.name), item.isDirectory())
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
let fileUploads = [];
|
|
1256
|
+
for (const e of dirEntries) {
|
|
1257
|
+
const newSrc = join(srcDir, e.name);
|
|
1258
|
+
const newDst = `${absDstDir}${this.remotePathSep}${e.name}`;
|
|
1130
1259
|
if (e.isDirectory()) {
|
|
1131
|
-
|
|
1132
|
-
let newDst = dstDir + this.remotePathSep + e.name;
|
|
1133
|
-
await this.uploadDir(newSrc, newDst, filter);
|
|
1260
|
+
await this.uploadDir(newSrc, newDst, options);
|
|
1134
1261
|
} else if (e.isFile()) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1262
|
+
if (options?.useFastput) {
|
|
1263
|
+
fileUploads.push(this._fastPut(newSrc, newDst));
|
|
1264
|
+
} else {
|
|
1265
|
+
fileUploads.push(this._put(newSrc, newDst));
|
|
1266
|
+
}
|
|
1267
|
+
this.client.emit('upload', { source: newSrc, destination: newDst });
|
|
1139
1268
|
} else {
|
|
1140
1269
|
this.debugMsg(
|
|
1141
1270
|
`uploadDir: File ignored: ${e.name} not a regular file`
|
|
1142
1271
|
);
|
|
1143
1272
|
}
|
|
1273
|
+
await Promise.all(fileUploads);
|
|
1144
1274
|
}
|
|
1145
|
-
return `${srcDir} uploaded to ${
|
|
1275
|
+
return `${srcDir} uploaded to ${absDstDir}`;
|
|
1276
|
+
} catch (err) {
|
|
1277
|
+
throw err.custom
|
|
1278
|
+
? err
|
|
1279
|
+
: this.fmtError(`${err.message} ${srcDir}`, '_uploadDir', err.code);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async uploadDir(srcDir, dstDir, options) {
|
|
1284
|
+
let listeners;
|
|
1285
|
+
try {
|
|
1286
|
+
listeners = addTempListeners(this, 'uploadDir');
|
|
1287
|
+
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`);
|
|
1288
|
+
haveConnection(this, 'uploadDir');
|
|
1289
|
+
return await this._uploadDir(srcDir, dstDir, options);
|
|
1146
1290
|
} catch (err) {
|
|
1291
|
+
throw err.custom ? err : this.fmtError(err, 'uploadDir');
|
|
1292
|
+
} finally {
|
|
1293
|
+
removeTempListeners(this, listeners, 'chmod');
|
|
1147
1294
|
this._resetEventFlags();
|
|
1148
|
-
throw err.custom ? err : fmtError(err, 'uploadDir');
|
|
1149
1295
|
}
|
|
1150
1296
|
}
|
|
1151
1297
|
|
|
@@ -1157,18 +1303,29 @@ class SftpClient {
|
|
|
1157
1303
|
* file system.
|
|
1158
1304
|
* @param {String} srcDir - remote source directory
|
|
1159
1305
|
* @param {String} dstDir - local destination directory
|
|
1160
|
-
* @param {
|
|
1161
|
-
*
|
|
1306
|
+
* @param {Object} options - (Optional) Object with 2 supported properties,
|
|
1307
|
+
* 'filter' and 'useFastget'. The filter property is a function of two
|
|
1308
|
+
* arguments. The first argument is the full path of the item to be downloaded
|
|
1309
|
+
* and the second argument is a boolean, which will be true if the target path
|
|
1310
|
+
* is for a directory. If the function returns true, the item will be
|
|
1311
|
+
* downloaded and excluded if teh function returns false.
|
|
1312
|
+
*
|
|
1162
1313
|
* @returns {Promise<String>}
|
|
1163
1314
|
*/
|
|
1164
|
-
async
|
|
1315
|
+
async _downloadDir(srcDir, dstDir, options) {
|
|
1165
1316
|
try {
|
|
1166
|
-
this.
|
|
1167
|
-
|
|
1168
|
-
|
|
1317
|
+
let fileList = await this._list(srcDir);
|
|
1318
|
+
if (options?.filter) {
|
|
1319
|
+
fileList = fileList.filter((item) =>
|
|
1320
|
+
options.filter(
|
|
1321
|
+
`${srcDir}${this.remotePathSep}${item.name}`,
|
|
1322
|
+
item.type === 'd' ? true : false
|
|
1323
|
+
)
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1169
1326
|
const localCheck = haveLocalCreate(dstDir);
|
|
1170
1327
|
if (!localCheck.status && localCheck.details === 'permission denied') {
|
|
1171
|
-
throw fmtError(
|
|
1328
|
+
throw this.fmtError(
|
|
1172
1329
|
`Bad path: ${dstDir}: ${localCheck.details}`,
|
|
1173
1330
|
'downloadDir',
|
|
1174
1331
|
localCheck.code
|
|
@@ -1176,35 +1333,182 @@ class SftpClient {
|
|
|
1176
1333
|
} else if (localCheck.status && !localCheck.type) {
|
|
1177
1334
|
fs.mkdirSync(dstDir, { recursive: true });
|
|
1178
1335
|
} else if (localCheck.status && localCheck.type !== 'd') {
|
|
1179
|
-
throw fmtError(
|
|
1336
|
+
throw this.fmtError(
|
|
1180
1337
|
`Bad path: ${dstDir}: not a directory`,
|
|
1181
1338
|
'downloadDir',
|
|
1182
1339
|
errorCode.badPath
|
|
1183
1340
|
);
|
|
1184
1341
|
}
|
|
1185
|
-
|
|
1342
|
+
let downloadFiles = [];
|
|
1343
|
+
for (const f of fileList) {
|
|
1344
|
+
const newSrc = `${srcDir}${this.remotePathSep}${f.name}`;
|
|
1345
|
+
const newDst = join(dstDir, f.name);
|
|
1186
1346
|
if (f.type === 'd') {
|
|
1187
|
-
|
|
1188
|
-
let newDst = join(dstDir, f.name);
|
|
1189
|
-
await this.downloadDir(newSrc, newDst, filter);
|
|
1347
|
+
await this._downloadDir(newSrc, newDst, options);
|
|
1190
1348
|
} else if (f.type === '-') {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1349
|
+
if (options?.useFasget) {
|
|
1350
|
+
downloadFiles.push(this._fastGet(newSrc, newDst));
|
|
1351
|
+
} else {
|
|
1352
|
+
downloadFiles.push(this._get(newSrc, newDst));
|
|
1353
|
+
}
|
|
1354
|
+
this.client.emit('download', { source: newSrc, destination: newDst });
|
|
1195
1355
|
} else {
|
|
1196
1356
|
this.debugMsg(
|
|
1197
1357
|
`downloadDir: File ignored: ${f.name} not regular file`
|
|
1198
1358
|
);
|
|
1199
1359
|
}
|
|
1200
1360
|
}
|
|
1361
|
+
await Promise.all(downloadFiles);
|
|
1201
1362
|
return `${srcDir} downloaded to ${dstDir}`;
|
|
1202
1363
|
} catch (err) {
|
|
1364
|
+
throw err.custom
|
|
1365
|
+
? err
|
|
1366
|
+
: this.fmtError(`${err.message} ${srcDir}`, '_downloadDir', err.code);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
async downloadDir(srcDir, dstDir, options) {
|
|
1371
|
+
let listeners;
|
|
1372
|
+
try {
|
|
1373
|
+
listeners = addTempListeners(this, 'downloadDir');
|
|
1374
|
+
haveConnection(this, 'downloadDir');
|
|
1375
|
+
return await this._downloadDir(srcDir, dstDir, options);
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
throw err.custom ? err : this.fmtError(err, 'downloadDir', err.code);
|
|
1378
|
+
} finally {
|
|
1379
|
+
removeTempListeners(this, listeners, 'downloadDir');
|
|
1380
|
+
this._resetEventFlags();
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
*
|
|
1386
|
+
* Returns a read stream object. This is a low level method which will return a read stream
|
|
1387
|
+
* connected to the remote file object specified as an argument. Client code is fully responsible
|
|
1388
|
+
* for managing this stream object i.e. adding any necessary listeners and disposing of the object etc.
|
|
1389
|
+
* See the SSH2 sftp documentation for details on possible options which can be used.
|
|
1390
|
+
*
|
|
1391
|
+
* @param {String} remotePath - path to remote file to attach stream to
|
|
1392
|
+
* @param {Object} options - options to pass to the create stream process
|
|
1393
|
+
*
|
|
1394
|
+
* @returns {Object} a read stream object
|
|
1395
|
+
*
|
|
1396
|
+
*/
|
|
1397
|
+
createReadStream(remotePath, options) {
|
|
1398
|
+
let listeners;
|
|
1399
|
+
try {
|
|
1400
|
+
listeners = addTempListeners(this, 'createReadStream');
|
|
1401
|
+
haveConnection(this, 'createReadStream');
|
|
1402
|
+
const stream = this.sftp.createReadStream(remotePath, options);
|
|
1403
|
+
return stream;
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
throw err.custom
|
|
1406
|
+
? err
|
|
1407
|
+
: this.fmtError(err.message, 'createReadStream', err.code);
|
|
1408
|
+
} finally {
|
|
1409
|
+
removeTempListeners(this, listeners, 'createReadStreame');
|
|
1410
|
+
this._resetEventFlags();
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
*
|
|
1416
|
+
* Create a write stream object connected to a file on the remote sftp server.
|
|
1417
|
+
* This is a low level method which will return a write stream for the remote file specified
|
|
1418
|
+
* in the 'remotePath' argument. Client code to responsible for managing this object once created.
|
|
1419
|
+
* This includes disposing of file handles, setting up any necessary event listeners etc.
|
|
1420
|
+
*
|
|
1421
|
+
* @param {String} remotePath - path to the remote file on the sftp server
|
|
1422
|
+
* @param (Object} options - options to pass to the create write stream process)
|
|
1423
|
+
*
|
|
1424
|
+
* @returns {Object} a stream object
|
|
1425
|
+
*
|
|
1426
|
+
*/
|
|
1427
|
+
createWriteStream(remotePath, options) {
|
|
1428
|
+
let listeners;
|
|
1429
|
+
try {
|
|
1430
|
+
listeners = addTempListeners(this, 'createWriteStream');
|
|
1431
|
+
haveConnection(this, 'createWriteStream');
|
|
1432
|
+
const stream = this.sftp.createWriteStream(remotePath, options);
|
|
1433
|
+
return stream;
|
|
1434
|
+
} catch (err) {
|
|
1435
|
+
throw err.custom
|
|
1436
|
+
? err
|
|
1437
|
+
: this.fmtError(err.message, 'createWriteStream', err.code);
|
|
1438
|
+
} finally {
|
|
1439
|
+
removeTempListeners(this, listeners, 'createWriteStream');
|
|
1203
1440
|
this._resetEventFlags();
|
|
1204
|
-
throw err.custom ? err : fmtError(err, 'downloadDir', err.code);
|
|
1205
1441
|
}
|
|
1206
1442
|
}
|
|
1207
1443
|
|
|
1444
|
+
/**
|
|
1445
|
+
* @async
|
|
1446
|
+
*
|
|
1447
|
+
* Make a remote copy of a remote file. Create a copy of a remote file on the remote
|
|
1448
|
+
* server. It is assumed the directory where the copy will be placed already exists.
|
|
1449
|
+
* The destination file must not already exist.
|
|
1450
|
+
*
|
|
1451
|
+
* @param {String} srcPath - path to the remote file to be copied
|
|
1452
|
+
* @param {String} dstPath - destination path for the copy.
|
|
1453
|
+
*
|
|
1454
|
+
* @returns {String}.
|
|
1455
|
+
*
|
|
1456
|
+
*/
|
|
1457
|
+
_rcopy(srcPath, dstPath) {
|
|
1458
|
+
return new Promise((resolve, reject) => {
|
|
1459
|
+
const ws = this.sftp.createWriteStream(dstPath);
|
|
1460
|
+
const rs = this.sftp.createReadStream(srcPath);
|
|
1461
|
+
ws.on('error', (err) => {
|
|
1462
|
+
reject(this.fmtError(`${err.message} ${dstPath}`, '_rcopy'));
|
|
1463
|
+
});
|
|
1464
|
+
rs.on('error', (err) => {
|
|
1465
|
+
reject(this.fmtError(`${err.message} ${srcPath}`, '_rcopy'));
|
|
1466
|
+
});
|
|
1467
|
+
ws.on('close', () => {
|
|
1468
|
+
resolve(`${srcPath} copied to ${dstPath}`);
|
|
1469
|
+
});
|
|
1470
|
+
rs.pipe(ws);
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
async rcopy(src, dst) {
|
|
1475
|
+
let listeners;
|
|
1476
|
+
try {
|
|
1477
|
+
listeners = addTempListeners(this, 'rcopy');
|
|
1478
|
+
haveConnection(this, 'rcopy');
|
|
1479
|
+
const srcPath = await normalizeRemotePath(this, src);
|
|
1480
|
+
const srcExists = await this.exists(srcPath);
|
|
1481
|
+
if (!srcExists) {
|
|
1482
|
+
throw this.fmtError(
|
|
1483
|
+
`Source does not exist ${srcPath}`,
|
|
1484
|
+
'rcopy',
|
|
1485
|
+
errorCode.badPath
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
if (srcExists !== '-') {
|
|
1489
|
+
throw this.fmtError(
|
|
1490
|
+
`Source not a file ${srcPath}`,
|
|
1491
|
+
'rcopy',
|
|
1492
|
+
errorCode.badPath
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
const dstPath = await normalizeRemotePath(this, dst);
|
|
1496
|
+
const dstExists = await this.exists(dstPath);
|
|
1497
|
+
if (dstExists) {
|
|
1498
|
+
throw this.fmtError(
|
|
1499
|
+
`Destination already exists ${dstPath}`,
|
|
1500
|
+
'rcopy',
|
|
1501
|
+
errorCode.badPath
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
return await this._rcopy(srcPath, dstPath);
|
|
1505
|
+
} catch (err) {
|
|
1506
|
+
throw err.custom ? err : this.fmtError(err, 'rcopy');
|
|
1507
|
+
} finally {
|
|
1508
|
+
removeTempListeners(this, listeners, 'rcopy');
|
|
1509
|
+
this._resetEventFlags();
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1208
1512
|
/**
|
|
1209
1513
|
* @async
|
|
1210
1514
|
*
|
|
@@ -1213,10 +1517,10 @@ class SftpClient {
|
|
|
1213
1517
|
* @returns {Promise<Boolean>}
|
|
1214
1518
|
*/
|
|
1215
1519
|
end() {
|
|
1216
|
-
let endCloseHandler;
|
|
1520
|
+
let endCloseHandler, listeners;
|
|
1217
1521
|
return new Promise((resolve, reject) => {
|
|
1522
|
+
listeners = addTempListeners(this, 'end', reject);
|
|
1218
1523
|
this.endCalled = true;
|
|
1219
|
-
addTempListeners(this, 'end', reject);
|
|
1220
1524
|
endCloseHandler = () => {
|
|
1221
1525
|
this.sftp = undefined;
|
|
1222
1526
|
this.debugMsg('end: Connection closed');
|
|
@@ -1224,12 +1528,10 @@ class SftpClient {
|
|
|
1224
1528
|
};
|
|
1225
1529
|
this.on('close', endCloseHandler);
|
|
1226
1530
|
if (haveConnection(this, 'end', reject)) {
|
|
1227
|
-
this.debugMsg('end: Have connection - calling end()');
|
|
1228
1531
|
this.client.end();
|
|
1229
1532
|
}
|
|
1230
1533
|
}).finally(() => {
|
|
1231
|
-
this
|
|
1232
|
-
removeTempListeners(this, 'end');
|
|
1534
|
+
removeTempListeners(this, listeners, 'end');
|
|
1233
1535
|
this.removeListener('close', endCloseHandler);
|
|
1234
1536
|
this.endCalled = false;
|
|
1235
1537
|
this._resetEventFlags();
|