ssh2-sftp-client 6.0.1 → 7.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -118
- package/README.org +177 -153
- package/package.json +9 -8
- package/src/index.js +508 -374
- package/src/utils.js +171 -39
- package/CHANGELOG.org +0 -225
package/src/index.js
CHANGED
|
@@ -4,20 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
-
const {Client} = require('ssh2');
|
|
7
|
+
const { Client } = require('ssh2');
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const concat = require('concat-stream');
|
|
10
10
|
const promiseRetry = require('promise-retry');
|
|
11
|
-
const {join, parse} = require('path');
|
|
11
|
+
const { join, parse } = require('path');
|
|
12
12
|
const {
|
|
13
13
|
fmtError,
|
|
14
14
|
addTempListeners,
|
|
15
15
|
removeTempListeners,
|
|
16
16
|
haveConnection,
|
|
17
17
|
normalizeRemotePath,
|
|
18
|
-
localExists
|
|
18
|
+
localExists,
|
|
19
|
+
haveLocalAccess,
|
|
20
|
+
haveLocalCreate,
|
|
21
|
+
sleep,
|
|
19
22
|
} = require('./utils');
|
|
20
|
-
const {errorCode} = require('./constants');
|
|
23
|
+
const { errorCode } = require('./constants');
|
|
21
24
|
|
|
22
25
|
class SftpClient {
|
|
23
26
|
constructor(clientName) {
|
|
@@ -26,31 +29,43 @@ class SftpClient {
|
|
|
26
29
|
this.clientName = clientName ? clientName : 'sftp';
|
|
27
30
|
this.endCalled = false;
|
|
28
31
|
this.errorHandled = false;
|
|
32
|
+
this.closeHandled = false;
|
|
33
|
+
this.endHandled = false;
|
|
29
34
|
this.remotePathSep = '/';
|
|
30
35
|
this.remotePlatform = 'unix';
|
|
31
36
|
this.debug = undefined;
|
|
32
37
|
|
|
33
38
|
this.client.on('close', () => {
|
|
34
|
-
if (
|
|
35
|
-
|
|
39
|
+
if (this.endCalled || this.closeHandled) {
|
|
40
|
+
// we are processing an expected end event or close event handled elsewhere
|
|
41
|
+
this.debugMsg('Global: Ignoring handled close event');
|
|
42
|
+
} else {
|
|
43
|
+
this.debugMsg('Global: Handling unexpected close event');
|
|
36
44
|
this.sftp = undefined;
|
|
37
45
|
}
|
|
38
46
|
});
|
|
47
|
+
|
|
39
48
|
this.client.on('end', () => {
|
|
40
|
-
if (
|
|
41
|
-
|
|
49
|
+
if (this.endCalled || this.endHandled) {
|
|
50
|
+
// end event expected or handled elsewhere
|
|
51
|
+
this.debugMsg('Global: Ignoring hanlded end event');
|
|
52
|
+
} else {
|
|
53
|
+
this.debugMsg('Global: Handling unexpected end event');
|
|
42
54
|
this.sftp = undefined;
|
|
43
55
|
}
|
|
44
56
|
});
|
|
57
|
+
|
|
45
58
|
this.client.on('error', (err) => {
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'global-error-handler',
|
|
50
|
-
err.code
|
|
51
|
-
);
|
|
59
|
+
if (this.endCalled || this.errorHandled) {
|
|
60
|
+
// error event expected or handled elsewhere
|
|
61
|
+
this.debugMsg('Global: Ignoring handled error');
|
|
52
62
|
} else {
|
|
53
|
-
this.
|
|
63
|
+
this.debugMsg(`Global; Handling unexpected error; ${err.message}`);
|
|
64
|
+
this.sftp = undefined;
|
|
65
|
+
console.log(
|
|
66
|
+
`ssh2-sftp-client: Unexpected error: ${err.message}. Error code: ${err.code}`
|
|
67
|
+
);
|
|
68
|
+
//throw fmtError(err, 'Global');
|
|
54
69
|
}
|
|
55
70
|
});
|
|
56
71
|
}
|
|
@@ -77,81 +92,125 @@ class SftpClient {
|
|
|
77
92
|
* @param {function} callback - function called when event triggers
|
|
78
93
|
*/
|
|
79
94
|
on(eventType, callback) {
|
|
80
|
-
this.debugMsg(`Adding listener to ${eventType}`);
|
|
81
|
-
this.client.
|
|
95
|
+
this.debugMsg(`Adding listener to ${eventType} event`);
|
|
96
|
+
this.client.prependListener(eventType, callback);
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
removeListener(eventType, callback) {
|
|
85
|
-
this.debugMsg(`Removing listener from ${eventType}`);
|
|
100
|
+
this.debugMsg(`Removing listener from ${eventType} event`);
|
|
86
101
|
this.client.removeListener(eventType, callback);
|
|
87
102
|
}
|
|
88
103
|
|
|
104
|
+
_resetEventFlags() {
|
|
105
|
+
this.closeHandled = false;
|
|
106
|
+
this.endHandled = false;
|
|
107
|
+
this.errorHandled = false;
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
/**
|
|
90
111
|
* @async
|
|
91
112
|
*
|
|
92
113
|
* Create a new SFTP connection to a remote SFTP server
|
|
93
114
|
*
|
|
94
115
|
* @param {Object} config - an SFTP configuration object
|
|
95
|
-
* @param {string} connectMethod - ???
|
|
96
116
|
*
|
|
97
117
|
* @return {Promise} which will resolve to an sftp client object
|
|
98
118
|
*
|
|
99
119
|
*/
|
|
100
|
-
|
|
101
|
-
let
|
|
120
|
+
getConnection(config) {
|
|
121
|
+
let doReady;
|
|
122
|
+
return (
|
|
123
|
+
new Promise((resolve, reject) => {
|
|
124
|
+
addTempListeners(this, 'getConnection', reject);
|
|
125
|
+
this.debugMsg('getConnection: created promise');
|
|
126
|
+
doReady = () => {
|
|
127
|
+
this.debugMsg('getConnection: got connection - promise resolved');
|
|
128
|
+
resolve(true);
|
|
129
|
+
};
|
|
130
|
+
this.on('ready', doReady);
|
|
131
|
+
this.client.connect(config);
|
|
132
|
+
})
|
|
133
|
+
// .catch((err) => {
|
|
134
|
+
// return Promise.reject(err);
|
|
135
|
+
// })
|
|
136
|
+
.finally(async (resp) => {
|
|
137
|
+
this.debugMsg('getConnection: finally clause fired');
|
|
138
|
+
await sleep(500);
|
|
139
|
+
this.removeListener('ready', doReady);
|
|
140
|
+
removeTempListeners(this, 'getConnection');
|
|
141
|
+
this._resetEventFlags();
|
|
142
|
+
return resp;
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
}
|
|
102
146
|
|
|
147
|
+
getSftpChannel() {
|
|
103
148
|
return new Promise((resolve, reject) => {
|
|
104
|
-
addTempListeners(this, '
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
this.
|
|
120
|
-
removeTempListeners(this.client);
|
|
121
|
-
return rsp;
|
|
149
|
+
addTempListeners(this, 'getSftpChannel', reject);
|
|
150
|
+
this.debugMsg('getSftpChannel: created promise');
|
|
151
|
+
this.client.sftp((err, sftp) => {
|
|
152
|
+
if (err) {
|
|
153
|
+
this.debugMsg(`getSftpChannel: SFTP Channel Error: ${err.message}`);
|
|
154
|
+
reject(fmtError(err, 'getSftpChannel', err.code));
|
|
155
|
+
} else {
|
|
156
|
+
this.debugMsg('getSftpChannel: SFTP channel established');
|
|
157
|
+
this.sftp = sftp;
|
|
158
|
+
resolve(sftp);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}).finally((resp) => {
|
|
162
|
+
this.debugMsg('getSftpChannel: finally clause fired');
|
|
163
|
+
removeTempListeners(this, 'getSftpChannel');
|
|
164
|
+
this._resetEventFlags();
|
|
122
165
|
});
|
|
123
166
|
}
|
|
124
167
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
/**
|
|
169
|
+
* @async
|
|
170
|
+
*
|
|
171
|
+
* Create a new SFTP connection to a remote SFTP server.
|
|
172
|
+
* The connection options are the same as those offered
|
|
173
|
+
* by the underlying SSH2 module.
|
|
174
|
+
*
|
|
175
|
+
* @param {Object} config - an SFTP configuration object
|
|
176
|
+
*
|
|
177
|
+
* @return {Promise} which will resolve to an sftp client object
|
|
178
|
+
*
|
|
179
|
+
*/
|
|
180
|
+
async connect(config) {
|
|
181
|
+
try {
|
|
182
|
+
if (config.debug) {
|
|
183
|
+
this.debug = config.debug;
|
|
184
|
+
this.debugMsg('connect: Debugging turned on');
|
|
185
|
+
}
|
|
186
|
+
if (this.sftp) {
|
|
187
|
+
this.debugMsg('connect: Already connected - reject');
|
|
188
|
+
throw fmtError(
|
|
134
189
|
'An existing SFTP connection is already defined',
|
|
135
190
|
'connect',
|
|
136
191
|
errorCode.connect
|
|
137
|
-
)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
await promiseRetry(
|
|
195
|
+
(retry, attempt) => {
|
|
196
|
+
this.debugMsg(`connect: Connect attempt ${attempt}`);
|
|
197
|
+
return this.getConnection(config).catch((err) => {
|
|
198
|
+
this.debugMsg('getConnection retry catch');
|
|
199
|
+
retry(err);
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
retries: config.retries || 1,
|
|
204
|
+
factor: config.retry_factor || 2,
|
|
205
|
+
minTimeout: config.retry_minTimeout || 1000,
|
|
206
|
+
}
|
|
138
207
|
);
|
|
208
|
+
await this.getSftpChannel();
|
|
209
|
+
} catch (err) {
|
|
210
|
+
this.debugMsg(`connect: Error ${err.message}`);
|
|
211
|
+
this._resetEventFlags();
|
|
212
|
+
throw fmtError(err, 'connect');
|
|
139
213
|
}
|
|
140
|
-
return promiseRetry(
|
|
141
|
-
(retry, attempt) => {
|
|
142
|
-
this.debugMsg(`Connect attempt ${attempt}`);
|
|
143
|
-
return this.sftpConnect(config).catch((err) => {
|
|
144
|
-
retry(err);
|
|
145
|
-
});
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
retries: config.retries || 1,
|
|
149
|
-
factor: config.retry_factor || 2,
|
|
150
|
-
minTimeout: config.retry_minTimeout || 1000
|
|
151
|
-
}
|
|
152
|
-
).then((sftp) => {
|
|
153
|
-
this.sftp = sftp;
|
|
154
|
-
});
|
|
155
214
|
}
|
|
156
215
|
|
|
157
216
|
/**
|
|
@@ -160,11 +219,10 @@ class SftpClient {
|
|
|
160
219
|
* Returns the real absolute path on the remote server. Is able to handle
|
|
161
220
|
* both '.' and '..' in path names, but not '~'. If the path is relative
|
|
162
221
|
* then the current working directory is prepended to create an absolute path.
|
|
163
|
-
* Returns undefined if the
|
|
164
|
-
* path does not exists.
|
|
222
|
+
* Returns undefined if the path does not exists.
|
|
165
223
|
*
|
|
166
224
|
* @param {String} remotePath - remote path, may be relative
|
|
167
|
-
* @returns {}
|
|
225
|
+
* @returns {Promise} - remote absolute path or undefined
|
|
168
226
|
*/
|
|
169
227
|
realPath(remotePath) {
|
|
170
228
|
return new Promise((resolve, reject) => {
|
|
@@ -187,7 +245,8 @@ class SftpClient {
|
|
|
187
245
|
});
|
|
188
246
|
}
|
|
189
247
|
}).finally((rsp) => {
|
|
190
|
-
removeTempListeners(this
|
|
248
|
+
removeTempListeners(this, 'realPath');
|
|
249
|
+
this._resetEventFlags();
|
|
191
250
|
return rsp;
|
|
192
251
|
});
|
|
193
252
|
}
|
|
@@ -199,17 +258,17 @@ class SftpClient {
|
|
|
199
258
|
/**
|
|
200
259
|
* Retrieves attributes for path
|
|
201
260
|
*
|
|
202
|
-
* @param {String}
|
|
203
|
-
* @return {Promise} stats
|
|
261
|
+
* @param {String} remotePath - a string containing the path to a file
|
|
262
|
+
* @return {Promise} stats - attributes info
|
|
204
263
|
*/
|
|
205
264
|
async stat(remotePath) {
|
|
206
265
|
const _stat = (aPath) => {
|
|
207
266
|
return new Promise((resolve, reject) => {
|
|
208
|
-
this.debugMsg(`
|
|
209
|
-
addTempListeners(this, '
|
|
267
|
+
this.debugMsg(`_stat: ${aPath}`);
|
|
268
|
+
addTempListeners(this, '_stat', reject);
|
|
210
269
|
this.sftp.stat(aPath, (err, stats) => {
|
|
211
270
|
if (err) {
|
|
212
|
-
this.debugMsg(`
|
|
271
|
+
this.debugMsg(`_stat: Error ${err.message} code: ${err.code}`);
|
|
213
272
|
if (err.code === 2 || err.code === 4) {
|
|
214
273
|
reject(
|
|
215
274
|
fmtError(
|
|
@@ -224,8 +283,7 @@ class SftpClient {
|
|
|
224
283
|
);
|
|
225
284
|
}
|
|
226
285
|
} else {
|
|
227
|
-
|
|
228
|
-
resolve({
|
|
286
|
+
let result = {
|
|
229
287
|
mode: stats.mode,
|
|
230
288
|
uid: stats.uid,
|
|
231
289
|
gid: stats.gid,
|
|
@@ -238,12 +296,14 @@ class SftpClient {
|
|
|
238
296
|
isCharacterDevice: stats.isCharacterDevice(),
|
|
239
297
|
isSymbolicLink: stats.isSymbolicLink(),
|
|
240
298
|
isFIFO: stats.isFIFO(),
|
|
241
|
-
isSocket: stats.isSocket()
|
|
242
|
-
}
|
|
299
|
+
isSocket: stats.isSocket(),
|
|
300
|
+
};
|
|
301
|
+
this.debugMsg('_stat: stats <- ', result);
|
|
302
|
+
resolve(result);
|
|
243
303
|
}
|
|
244
304
|
});
|
|
245
305
|
}).finally((rsp) => {
|
|
246
|
-
removeTempListeners(this
|
|
306
|
+
removeTempListeners(this, 'stat');
|
|
247
307
|
return rsp;
|
|
248
308
|
});
|
|
249
309
|
};
|
|
@@ -253,11 +313,8 @@ class SftpClient {
|
|
|
253
313
|
let absPath = await normalizeRemotePath(this, remotePath);
|
|
254
314
|
return _stat(absPath);
|
|
255
315
|
} catch (err) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
} else {
|
|
259
|
-
throw fmtError(err, 'stat', err.code);
|
|
260
|
-
}
|
|
316
|
+
this._resetEventFlags();
|
|
317
|
+
throw err.custom ? err : fmtError(err, 'stat', err.code);
|
|
261
318
|
}
|
|
262
319
|
}
|
|
263
320
|
|
|
@@ -267,47 +324,53 @@ class SftpClient {
|
|
|
267
324
|
* Tests to see if an object exists. If it does, return the type of that object
|
|
268
325
|
* (in the format returned by list). If it does not exist, return false.
|
|
269
326
|
*
|
|
270
|
-
* @param {string}
|
|
327
|
+
* @param {string} remotePath - path to the object on the sftp server.
|
|
271
328
|
*
|
|
272
|
-
* @return {
|
|
329
|
+
* @return {Promise} returns false if object does not exist. Returns type of
|
|
273
330
|
* object if it does
|
|
274
331
|
*/
|
|
275
332
|
async exists(remotePath) {
|
|
276
333
|
try {
|
|
277
334
|
if (haveConnection(this, 'exists')) {
|
|
278
335
|
if (remotePath === '.') {
|
|
336
|
+
this.debugMsg('exists: . = d');
|
|
279
337
|
return 'd';
|
|
280
338
|
}
|
|
281
339
|
let absPath = await normalizeRemotePath(this, remotePath);
|
|
282
340
|
try {
|
|
283
|
-
this.debugMsg(`exists -> ${absPath}`);
|
|
341
|
+
this.debugMsg(`exists: ${remotePath} -> ${absPath}`);
|
|
284
342
|
let info = await this.stat(absPath);
|
|
285
|
-
this.debugMsg('exists <- ', info);
|
|
343
|
+
this.debugMsg('exists: <- ', info);
|
|
286
344
|
if (info.isDirectory) {
|
|
345
|
+
this.debugMsg(`exists: ${remotePath} = d`);
|
|
287
346
|
return 'd';
|
|
288
347
|
}
|
|
289
348
|
if (info.isSymbolicLink) {
|
|
349
|
+
this.debugMsg(`exists: ${remotePath} = l`);
|
|
290
350
|
return 'l';
|
|
291
351
|
}
|
|
292
352
|
if (info.isFile) {
|
|
353
|
+
this.debugMsg(`exists: ${remotePath} = -`);
|
|
293
354
|
return '-';
|
|
294
355
|
}
|
|
356
|
+
this.debugMsg(`exists: ${remotePath} = false`);
|
|
295
357
|
return false;
|
|
296
358
|
} catch (err) {
|
|
297
359
|
if (err.code === errorCode.notexist) {
|
|
360
|
+
this.debugMsg(
|
|
361
|
+
`exists: ${remotePath} = false errorCode = ${err.code}`
|
|
362
|
+
);
|
|
298
363
|
return false;
|
|
299
364
|
}
|
|
365
|
+
this.debugMsg(`exists: throw error ${err.message} ${err.code}`);
|
|
300
366
|
throw err;
|
|
301
367
|
}
|
|
302
|
-
} else {
|
|
303
|
-
return false;
|
|
304
368
|
}
|
|
369
|
+
this.debugMsg(`exists: default ${remotePath} = false`);
|
|
370
|
+
return false;
|
|
305
371
|
} catch (err) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
} else {
|
|
309
|
-
throw fmtError(err, 'exists', err.code);
|
|
310
|
-
}
|
|
372
|
+
this._resetEventFlags();
|
|
373
|
+
throw err.custom ? err : fmtError(err, 'exists', err.code);
|
|
311
374
|
}
|
|
312
375
|
}
|
|
313
376
|
|
|
@@ -322,21 +385,20 @@ class SftpClient {
|
|
|
322
385
|
*
|
|
323
386
|
* @param {String} remotePath - path to remote directory
|
|
324
387
|
* @param {RegExp} pattern - regular expression to match filenames
|
|
325
|
-
* @returns {
|
|
388
|
+
* @returns {Promise} array of file description objects
|
|
326
389
|
* @throws {Error}
|
|
327
390
|
*/
|
|
328
391
|
list(remotePath, pattern = /.*/) {
|
|
329
392
|
return new Promise((resolve, reject) => {
|
|
330
393
|
if (haveConnection(this, 'list', reject)) {
|
|
331
394
|
const reg = /-/gi;
|
|
332
|
-
this.debugMsg(`list
|
|
395
|
+
this.debugMsg(`list: ${remotePath} filter: ${pattern}`);
|
|
333
396
|
addTempListeners(this, 'list', reject);
|
|
334
397
|
this.sftp.readdir(remotePath, (err, fileList) => {
|
|
335
398
|
if (err) {
|
|
336
|
-
this.debugMsg(`list
|
|
399
|
+
this.debugMsg(`list: Error ${err.message} code: ${err.code}`);
|
|
337
400
|
reject(fmtError(`${err.message} ${remotePath}`, 'list', err.code));
|
|
338
401
|
} else {
|
|
339
|
-
this.debugMsg('list <- ', fileList);
|
|
340
402
|
let newList = [];
|
|
341
403
|
// reset file info
|
|
342
404
|
if (fileList) {
|
|
@@ -350,10 +412,10 @@ class SftpClient {
|
|
|
350
412
|
rights: {
|
|
351
413
|
user: item.longname.substr(1, 3).replace(reg, ''),
|
|
352
414
|
group: item.longname.substr(4, 3).replace(reg, ''),
|
|
353
|
-
other: item.longname.substr(7, 3).replace(reg, '')
|
|
415
|
+
other: item.longname.substr(7, 3).replace(reg, ''),
|
|
354
416
|
},
|
|
355
417
|
owner: item.attrs.uid,
|
|
356
|
-
group: item.attrs.gid
|
|
418
|
+
group: item.attrs.gid,
|
|
357
419
|
};
|
|
358
420
|
});
|
|
359
421
|
}
|
|
@@ -365,12 +427,15 @@ class SftpClient {
|
|
|
365
427
|
let newPattern = pattern.replace(/\*([^*])*?/gi, '.*');
|
|
366
428
|
regex = new RegExp(newPattern);
|
|
367
429
|
}
|
|
368
|
-
|
|
430
|
+
let filteredList = newList.filter((item) => regex.test(item.name));
|
|
431
|
+
this.debugMsg('list: result: ', filteredList);
|
|
432
|
+
resolve(filteredList);
|
|
369
433
|
}
|
|
370
434
|
});
|
|
371
435
|
}
|
|
372
436
|
}).finally((rsp) => {
|
|
373
|
-
removeTempListeners(this
|
|
437
|
+
removeTempListeners(this, 'list');
|
|
438
|
+
this._resetEventFlags();
|
|
374
439
|
return rsp;
|
|
375
440
|
});
|
|
376
441
|
}
|
|
@@ -383,35 +448,56 @@ class SftpClient {
|
|
|
383
448
|
* piped into the stream or undefined, in which case the data is returned as
|
|
384
449
|
* a Buffer object.
|
|
385
450
|
*
|
|
386
|
-
* @param {String}
|
|
387
|
-
* @param {string|stream|undefined} dst
|
|
388
|
-
* @param {Object}
|
|
451
|
+
* @param {String} remotePath - remote file path
|
|
452
|
+
* @param {string|stream|undefined} dst - data destination
|
|
453
|
+
* @param {Object} options - options object with supported properties of readStreamOptions,
|
|
454
|
+
* writeStreamOptions and pipeOptions.
|
|
389
455
|
*
|
|
390
456
|
* @return {Promise}
|
|
391
457
|
*/
|
|
392
|
-
get(
|
|
458
|
+
get(
|
|
459
|
+
remotePath,
|
|
460
|
+
dst,
|
|
461
|
+
options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
|
|
462
|
+
) {
|
|
463
|
+
let rdr, wtr;
|
|
464
|
+
|
|
393
465
|
return new Promise((resolve, reject) => {
|
|
394
466
|
if (haveConnection(this, 'get', reject)) {
|
|
395
467
|
this.debugMsg(`get -> ${remotePath} `, options);
|
|
396
468
|
addTempListeners(this, 'get', reject);
|
|
397
|
-
|
|
469
|
+
rdr = this.sftp.createReadStream(
|
|
470
|
+
remotePath,
|
|
471
|
+
options.readStreamOptions ? options.readStreamOptions : {}
|
|
472
|
+
);
|
|
398
473
|
rdr.once('error', (err) => {
|
|
399
474
|
reject(fmtError(`${err.message} ${remotePath}`, 'get', err.code));
|
|
400
475
|
});
|
|
401
476
|
if (dst === undefined) {
|
|
402
477
|
// no dst specified, return buffer of data
|
|
403
478
|
this.debugMsg('get returning buffer of data');
|
|
404
|
-
|
|
405
|
-
rdr.removeAllListeners('error');
|
|
479
|
+
wtr = concat((buff) => {
|
|
480
|
+
//rdr.removeAllListeners('error');
|
|
406
481
|
resolve(buff);
|
|
407
482
|
});
|
|
408
|
-
rdr.pipe(concatStream);
|
|
409
483
|
} else {
|
|
410
|
-
let wtr;
|
|
411
484
|
if (typeof dst === 'string') {
|
|
412
485
|
// dst local file path
|
|
413
486
|
this.debugMsg('get returning local file');
|
|
414
|
-
|
|
487
|
+
const localCheck = haveLocalCreate(dst);
|
|
488
|
+
if (!localCheck.status) {
|
|
489
|
+
return reject(
|
|
490
|
+
fmtError(
|
|
491
|
+
`Bad path: ${dst}: ${localCheck.details}`,
|
|
492
|
+
'get',
|
|
493
|
+
localCheck.code
|
|
494
|
+
)
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
wtr = fs.createWriteStream(
|
|
498
|
+
dst,
|
|
499
|
+
options.writeStreamOptions ? options.writeStreamOptions : {}
|
|
500
|
+
);
|
|
415
501
|
} else {
|
|
416
502
|
this.debugMsg('get returning data into supplied stream');
|
|
417
503
|
wtr = dst;
|
|
@@ -424,25 +510,35 @@ class SftpClient {
|
|
|
424
510
|
err.code
|
|
425
511
|
)
|
|
426
512
|
);
|
|
427
|
-
if (options.autoClose === false) {
|
|
428
|
-
rdr.destroy();
|
|
429
|
-
}
|
|
430
513
|
});
|
|
431
|
-
|
|
432
|
-
if (options.autoClose === false) {
|
|
433
|
-
rdr.destroy();
|
|
434
|
-
}
|
|
514
|
+
rdr.once('end', () => {
|
|
435
515
|
if (typeof dst === 'string') {
|
|
436
516
|
resolve(dst);
|
|
437
517
|
} else {
|
|
438
518
|
resolve(wtr);
|
|
439
519
|
}
|
|
440
520
|
});
|
|
441
|
-
rdr.pipe(wtr);
|
|
442
521
|
}
|
|
522
|
+
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
|
|
443
523
|
}
|
|
444
524
|
}).finally((rsp) => {
|
|
445
|
-
removeTempListeners(this
|
|
525
|
+
removeTempListeners(this, 'get');
|
|
526
|
+
this._resetEventFlags();
|
|
527
|
+
if (
|
|
528
|
+
rdr &&
|
|
529
|
+
options.readStreamOptions &&
|
|
530
|
+
options.readStreamOptions.autoClose === false
|
|
531
|
+
) {
|
|
532
|
+
rdr.destroy();
|
|
533
|
+
}
|
|
534
|
+
if (
|
|
535
|
+
wtr &&
|
|
536
|
+
options.writeStreamOptions &&
|
|
537
|
+
options.writeStreamOptions.autoClose === false &&
|
|
538
|
+
typeof dst === 'string'
|
|
539
|
+
) {
|
|
540
|
+
wtr.destroy();
|
|
541
|
+
}
|
|
446
542
|
return rsp;
|
|
447
543
|
});
|
|
448
544
|
}
|
|
@@ -460,40 +556,47 @@ class SftpClient {
|
|
|
460
556
|
* @param {Object} options
|
|
461
557
|
* @return {Promise} the result of downloading the file
|
|
462
558
|
*/
|
|
463
|
-
fastGet(remotePath, localPath, options) {
|
|
464
|
-
|
|
465
|
-
.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
559
|
+
async fastGet(remotePath, localPath, options) {
|
|
560
|
+
try {
|
|
561
|
+
const ftype = await this.exists(remotePath);
|
|
562
|
+
if (ftype !== '-') {
|
|
563
|
+
const msg =
|
|
564
|
+
ftype === false
|
|
565
|
+
? `No such file ${remotePath}`
|
|
566
|
+
: `Not a regular file ${remotePath}`;
|
|
567
|
+
let err = new Error(msg);
|
|
568
|
+
err.code = errorCode.badPath;
|
|
569
|
+
throw err;
|
|
570
|
+
}
|
|
571
|
+
const localCheck = haveLocalCreate(localPath);
|
|
572
|
+
if (!localCheck.status) {
|
|
573
|
+
let err = new Error(`Bad path: ${localPath}: ${localCheck.details}`);
|
|
574
|
+
err.code = errorCode.badPath;
|
|
575
|
+
throw err;
|
|
576
|
+
}
|
|
577
|
+
await new Promise((resolve, reject) => {
|
|
578
|
+
if (haveConnection(this, 'fastGet', reject)) {
|
|
579
|
+
this.debugMsg(
|
|
580
|
+
`fastGet -> remote: ${remotePath} local: ${localPath} `,
|
|
581
|
+
options
|
|
582
|
+
);
|
|
583
|
+
addTempListeners(this, 'fastGet', reject);
|
|
584
|
+
this.sftp.fastGet(remotePath, localPath, options, (err) => {
|
|
585
|
+
if (err) {
|
|
586
|
+
this.debugMsg(`fastGet error ${err.message} code: ${err.code}`);
|
|
587
|
+
reject(err);
|
|
588
|
+
}
|
|
589
|
+
resolve(`${remotePath} was successfully download to ${localPath}!`);
|
|
590
|
+
});
|
|
472
591
|
}
|
|
473
|
-
})
|
|
474
|
-
|
|
475
|
-
return
|
|
476
|
-
if (haveConnection(this, 'fastGet', reject)) {
|
|
477
|
-
this.debugMsg(
|
|
478
|
-
`fastGet -> remote: ${remotePath} local: ${localPath} `,
|
|
479
|
-
options
|
|
480
|
-
);
|
|
481
|
-
addTempListeners(this, 'fastGet', reject);
|
|
482
|
-
this.sftp.fastGet(remotePath, localPath, options, (err) => {
|
|
483
|
-
if (err) {
|
|
484
|
-
this.debugMsg(`fastGet error ${err.message} code: ${err.code}`);
|
|
485
|
-
reject(fmtError(err, 'fastGet'));
|
|
486
|
-
}
|
|
487
|
-
resolve(
|
|
488
|
-
`${remotePath} was successfully download to ${localPath}!`
|
|
489
|
-
);
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
}).finally((rsp) => {
|
|
493
|
-
removeTempListeners(this.client);
|
|
494
|
-
return rsp;
|
|
495
|
-
});
|
|
592
|
+
}).finally((rsp) => {
|
|
593
|
+
removeTempListeners(this, 'fastGet');
|
|
594
|
+
return rsp;
|
|
496
595
|
});
|
|
596
|
+
} catch (err) {
|
|
597
|
+
this._resetEventFlags();
|
|
598
|
+
throw fmtError(err, 'fastGet');
|
|
599
|
+
}
|
|
497
600
|
}
|
|
498
601
|
|
|
499
602
|
/**
|
|
@@ -511,60 +614,51 @@ class SftpClient {
|
|
|
511
614
|
*/
|
|
512
615
|
fastPut(localPath, remotePath, options) {
|
|
513
616
|
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
514
|
-
return
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
617
|
+
return new Promise((resolve, reject) => {
|
|
618
|
+
const localCheck = haveLocalAccess(localPath);
|
|
619
|
+
if (!localCheck.status) {
|
|
620
|
+
reject(
|
|
621
|
+
fmtError(
|
|
622
|
+
`Bad path: ${localPath}: ${localCheck.details}`,
|
|
623
|
+
'fastPut',
|
|
624
|
+
localCheck.code
|
|
625
|
+
)
|
|
626
|
+
);
|
|
627
|
+
} else if (localCheck.status && localExists(localPath) === 'd') {
|
|
628
|
+
reject(
|
|
629
|
+
fmtError(
|
|
630
|
+
`Bad path: ${localPath} not a regular file`,
|
|
631
|
+
'fastPut',
|
|
632
|
+
errorCode.badPath
|
|
633
|
+
)
|
|
634
|
+
);
|
|
635
|
+
} else if (haveConnection(this, 'fastPut', reject)) {
|
|
636
|
+
this.debugMsg(
|
|
637
|
+
`fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify(
|
|
638
|
+
options
|
|
639
|
+
)}`
|
|
640
|
+
);
|
|
641
|
+
addTempListeners(this, 'fastPut', reject);
|
|
642
|
+
this.sftp.fastPut(localPath, remotePath, options, (err) => {
|
|
643
|
+
if (err) {
|
|
644
|
+
this.debugMsg(`fastPut error ${err.message} ${err.code}`);
|
|
645
|
+
reject(
|
|
646
|
+
fmtError(
|
|
647
|
+
`${err.message} Local: ${localPath} Remote: ${remotePath}`,
|
|
648
|
+
'fastPut',
|
|
649
|
+
err.code
|
|
650
|
+
)
|
|
544
651
|
);
|
|
545
|
-
addTempListeners(this, 'fastPut', reject);
|
|
546
|
-
this.sftp.fastPut(localPath, remotePath, options, (err) => {
|
|
547
|
-
if (err) {
|
|
548
|
-
this.debugMsg(`fastPut error ${err.message} ${err.code}`);
|
|
549
|
-
reject(
|
|
550
|
-
fmtError(
|
|
551
|
-
`${err.message} Local: ${localPath} Remote: ${remotePath}`,
|
|
552
|
-
'fastPut',
|
|
553
|
-
err.code
|
|
554
|
-
)
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
this.debugMsg('fastPut file transferred');
|
|
558
|
-
resolve(
|
|
559
|
-
`${localPath} was successfully uploaded to ${remotePath}!`
|
|
560
|
-
);
|
|
561
|
-
});
|
|
562
652
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return rsp;
|
|
653
|
+
this.debugMsg('fastPut file transferred');
|
|
654
|
+
resolve(`${localPath} was successfully uploaded to ${remotePath}!`);
|
|
566
655
|
});
|
|
567
|
-
}
|
|
656
|
+
}
|
|
657
|
+
}).finally((rsp) => {
|
|
658
|
+
removeTempListeners(this, 'fastPut');
|
|
659
|
+
this._resetEventFlags();
|
|
660
|
+
return rsp;
|
|
661
|
+
});
|
|
568
662
|
}
|
|
569
663
|
|
|
570
664
|
/**
|
|
@@ -572,140 +666,148 @@ class SftpClient {
|
|
|
572
666
|
* can be a buffer, string or read stream. If 'src' is a string, it
|
|
573
667
|
* should be the path to a local file.
|
|
574
668
|
*
|
|
575
|
-
* @param {String|Buffer|stream}
|
|
669
|
+
* @param {String|Buffer|stream} localSrc - source data to use
|
|
576
670
|
* @param {String} remotePath - path to remote file
|
|
577
|
-
* @param {Object} options - options used for write stream configuration
|
|
578
|
-
* value supported by node
|
|
671
|
+
* @param {Object} options - options used for read, write stream and pipe configuration
|
|
672
|
+
* value supported by node. Allowed properties are readStreamOptions,
|
|
673
|
+
* writeStreamOptions and pipeOptions.
|
|
579
674
|
* @return {Promise}
|
|
580
675
|
*/
|
|
581
|
-
put(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
676
|
+
put(
|
|
677
|
+
localSrc,
|
|
678
|
+
remotePath,
|
|
679
|
+
options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
|
|
680
|
+
) {
|
|
681
|
+
let wtr, rdr;
|
|
682
|
+
|
|
683
|
+
return new Promise((resolve, reject) => {
|
|
684
|
+
if (typeof localSrc === 'string') {
|
|
685
|
+
const localCheck = haveLocalAccess(localSrc);
|
|
686
|
+
if (!localCheck.status) {
|
|
687
|
+
this.debugMsg(`put: local source check error ${localCheck.details}`);
|
|
688
|
+
return reject(
|
|
689
|
+
fmtError(
|
|
690
|
+
`Bad path: ${localSrc}: ${localCheck.details}`,
|
|
691
|
+
'put',
|
|
692
|
+
localCheck.code
|
|
693
|
+
)
|
|
594
694
|
);
|
|
595
695
|
}
|
|
596
|
-
|
|
696
|
+
}
|
|
697
|
+
if (haveConnection(this, 'put')) {
|
|
698
|
+
addTempListeners(this, 'put', reject);
|
|
699
|
+
wtr = this.sftp.createWriteStream(
|
|
700
|
+
remotePath,
|
|
701
|
+
options.writeStreamOptions ? options.writeStreamOptions : {}
|
|
702
|
+
);
|
|
703
|
+
wtr.once('error', (err) => {
|
|
704
|
+
this.debugMsg(`put: write stream error ${err.message}`);
|
|
705
|
+
reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
|
|
706
|
+
});
|
|
707
|
+
wtr.once('finish', () => {
|
|
708
|
+
this.debugMsg('put: promise resolved');
|
|
709
|
+
resolve(`Uploaded data stream to ${remotePath}`);
|
|
710
|
+
});
|
|
711
|
+
if (localSrc instanceof Buffer) {
|
|
712
|
+
this.debugMsg('put source is a buffer');
|
|
713
|
+
wtr.end(localSrc);
|
|
714
|
+
} else {
|
|
597
715
|
if (typeof localSrc === 'string') {
|
|
598
|
-
|
|
716
|
+
this.debugMsg(`put source is a file path: ${localSrc}`);
|
|
717
|
+
rdr = fs.createReadStream(
|
|
599
718
|
localSrc,
|
|
600
|
-
|
|
601
|
-
(err) => {
|
|
602
|
-
if (err) {
|
|
603
|
-
this.debugMsg(`put: Cannot read ${localSrc} - rejecting`);
|
|
604
|
-
reject(
|
|
605
|
-
fmtError(
|
|
606
|
-
`Permission denied ${localSrc}`,
|
|
607
|
-
'put',
|
|
608
|
-
errorCode.permission
|
|
609
|
-
)
|
|
610
|
-
);
|
|
611
|
-
} else {
|
|
612
|
-
this.debugMsg('put: localSrc file OK');
|
|
613
|
-
resolve(true);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
719
|
+
options.readStreamOptions ? options.readStreamOptions : {}
|
|
616
720
|
);
|
|
617
721
|
} else {
|
|
618
|
-
this.debugMsg('put
|
|
619
|
-
|
|
722
|
+
this.debugMsg('put source is a stream');
|
|
723
|
+
rdr = localSrc;
|
|
620
724
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
err.code
|
|
657
|
-
)
|
|
658
|
-
);
|
|
659
|
-
if (options.autoClose === false) {
|
|
660
|
-
stream.destroy();
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
rdr.pipe(stream);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}).finally((rsp) => {
|
|
667
|
-
removeTempListeners(this.client);
|
|
668
|
-
return rsp;
|
|
669
|
-
});
|
|
670
|
-
});
|
|
725
|
+
rdr.once('error', (err) => {
|
|
726
|
+
this.debugMsg(`put: read stream error ${err.message}`);
|
|
727
|
+
reject(
|
|
728
|
+
fmtError(
|
|
729
|
+
`${err.message} ${
|
|
730
|
+
typeof localSrc === 'string' ? localSrc : ''
|
|
731
|
+
}`,
|
|
732
|
+
'put',
|
|
733
|
+
err.code
|
|
734
|
+
)
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}).finally((resp) => {
|
|
741
|
+
removeTempListeners(this, 'put');
|
|
742
|
+
this._resetEventFlags();
|
|
743
|
+
if (
|
|
744
|
+
rdr &&
|
|
745
|
+
options.readStreamOptions &&
|
|
746
|
+
options.readStreamOptions.autoClose === false &&
|
|
747
|
+
typeof localSrc === 'string'
|
|
748
|
+
) {
|
|
749
|
+
rdr.destroy();
|
|
750
|
+
}
|
|
751
|
+
if (
|
|
752
|
+
wtr &&
|
|
753
|
+
options.writeStreamOptions &&
|
|
754
|
+
options.writeStreamOptions.autoClose === false
|
|
755
|
+
) {
|
|
756
|
+
wtr.destroy();
|
|
757
|
+
}
|
|
758
|
+
return resp;
|
|
759
|
+
});
|
|
671
760
|
}
|
|
672
761
|
|
|
673
762
|
/**
|
|
674
763
|
* Append to an existing remote file
|
|
675
764
|
*
|
|
676
765
|
* @param {Buffer|stream} input
|
|
677
|
-
* @param {String} remotePath
|
|
766
|
+
* @param {String} remotePath
|
|
678
767
|
* @param {Object} options
|
|
679
768
|
* @return {Promise}
|
|
680
769
|
*/
|
|
681
770
|
append(input, remotePath, options = {}) {
|
|
682
|
-
return
|
|
683
|
-
if (
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
stream.once('finish', () => {
|
|
697
|
-
resolve(`Appended data to ${remotePath}`);
|
|
698
|
-
});
|
|
699
|
-
if (input instanceof Buffer) {
|
|
700
|
-
stream.end(input);
|
|
771
|
+
return this.exists(remotePath).then((fileType) => {
|
|
772
|
+
if (fileType && fileType === 'd') {
|
|
773
|
+
return Promise.reject(
|
|
774
|
+
fmtError(
|
|
775
|
+
`Bad path: ${remotePath}: cannot append to a directory`,
|
|
776
|
+
'append',
|
|
777
|
+
errorCode.badPath
|
|
778
|
+
)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return new Promise((resolve, reject) => {
|
|
782
|
+
if (haveConnection(this, 'append', reject)) {
|
|
783
|
+
if (typeof input === 'string') {
|
|
784
|
+
reject(fmtError('Cannot append one file to another', 'append'));
|
|
701
785
|
} else {
|
|
702
|
-
|
|
786
|
+
this.debugMsg(`append -> remote: ${remotePath} `, options);
|
|
787
|
+
addTempListeners(this, 'append', reject);
|
|
788
|
+
options.flags = 'a';
|
|
789
|
+
let stream = this.sftp.createWriteStream(remotePath, options);
|
|
790
|
+
stream.on('error', (err) => {
|
|
791
|
+
reject(
|
|
792
|
+
fmtError(`${err.message} ${remotePath}`, 'append', err.code)
|
|
793
|
+
);
|
|
794
|
+
});
|
|
795
|
+
stream.on('finish', () => {
|
|
796
|
+
resolve(`Appended data to ${remotePath}`);
|
|
797
|
+
});
|
|
798
|
+
if (input instanceof Buffer) {
|
|
799
|
+
stream.write(input);
|
|
800
|
+
stream.end();
|
|
801
|
+
} else {
|
|
802
|
+
input.pipe(stream);
|
|
803
|
+
}
|
|
703
804
|
}
|
|
704
805
|
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
806
|
+
}).finally((rsp) => {
|
|
807
|
+
removeTempListeners(this, 'append');
|
|
808
|
+
this._resetEventFlags();
|
|
809
|
+
return rsp;
|
|
810
|
+
});
|
|
709
811
|
});
|
|
710
812
|
}
|
|
711
813
|
|
|
@@ -714,24 +816,40 @@ class SftpClient {
|
|
|
714
816
|
*
|
|
715
817
|
* Make a directory on remote server
|
|
716
818
|
*
|
|
717
|
-
* @param {string}
|
|
718
|
-
* @param {boolean} recursive
|
|
719
|
-
* @return {Promise}
|
|
819
|
+
* @param {string} remotePath - remote directory path.
|
|
820
|
+
* @param {boolean} recursive - if true, recursively create directories
|
|
821
|
+
* @return {Promise}
|
|
720
822
|
*/
|
|
721
823
|
async mkdir(remotePath, recursive = false) {
|
|
722
824
|
const _mkdir = (p) => {
|
|
723
825
|
return new Promise((resolve, reject) => {
|
|
724
|
-
this.debugMsg(`
|
|
725
|
-
addTempListeners(this, '
|
|
826
|
+
this.debugMsg(`_mkdir: create ${p}`);
|
|
827
|
+
addTempListeners(this, '_mkdir', reject);
|
|
726
828
|
this.sftp.mkdir(p, (err) => {
|
|
727
829
|
if (err) {
|
|
728
|
-
this.debugMsg(`
|
|
729
|
-
|
|
830
|
+
this.debugMsg(`_mkdir: Error ${err.message} code: ${err.code}`);
|
|
831
|
+
if (err.code === 4) {
|
|
832
|
+
//fix for windows dodgy error messages
|
|
833
|
+
let error = new Error(`Bad path: ${p} permission denied`);
|
|
834
|
+
error.code = errorCode.badPath;
|
|
835
|
+
reject(error);
|
|
836
|
+
} else if (err.code === 2) {
|
|
837
|
+
let error = new Error(
|
|
838
|
+
`Bad path: ${p} parent not a directory or not exist`
|
|
839
|
+
);
|
|
840
|
+
error.code = errorCode.badPath;
|
|
841
|
+
reject(error);
|
|
842
|
+
} else {
|
|
843
|
+
reject(err);
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
this.debugMsg('_mkdir: directory created');
|
|
847
|
+
resolve(`${p} directory created`);
|
|
730
848
|
}
|
|
731
|
-
resolve(`${p} directory created`);
|
|
732
849
|
});
|
|
733
850
|
}).finally((rsp) => {
|
|
734
|
-
removeTempListeners(this
|
|
851
|
+
removeTempListeners(this, '_mkdir');
|
|
852
|
+
this._resetEventFlags();
|
|
735
853
|
return rsp;
|
|
736
854
|
});
|
|
737
855
|
};
|
|
@@ -740,22 +858,22 @@ class SftpClient {
|
|
|
740
858
|
haveConnection(this, 'mkdir');
|
|
741
859
|
let rPath = await normalizeRemotePath(this, remotePath);
|
|
742
860
|
if (!recursive) {
|
|
743
|
-
return _mkdir(rPath);
|
|
861
|
+
return await _mkdir(rPath);
|
|
744
862
|
}
|
|
745
863
|
let dir = parse(rPath).dir;
|
|
746
864
|
if (dir) {
|
|
747
865
|
let dirExists = await this.exists(dir);
|
|
748
866
|
if (!dirExists) {
|
|
749
867
|
await this.mkdir(dir, true);
|
|
868
|
+
} else if (dirExists !== 'd') {
|
|
869
|
+
let error = new Error(`Bad path: ${dir} not a directory`);
|
|
870
|
+
error.code = errorCode.badPath;
|
|
871
|
+
throw error;
|
|
750
872
|
}
|
|
751
873
|
}
|
|
752
|
-
return _mkdir(rPath);
|
|
874
|
+
return await _mkdir(rPath);
|
|
753
875
|
} catch (err) {
|
|
754
|
-
|
|
755
|
-
throw err;
|
|
756
|
-
} else {
|
|
757
|
-
throw fmtError(`${err.message} ${remotePath}`, 'mkdir', err.code);
|
|
758
|
-
}
|
|
876
|
+
throw fmtError(`${err.message}`, 'mkdir', err.code);
|
|
759
877
|
}
|
|
760
878
|
}
|
|
761
879
|
|
|
@@ -764,10 +882,10 @@ class SftpClient {
|
|
|
764
882
|
*
|
|
765
883
|
* Remove directory on remote server
|
|
766
884
|
*
|
|
767
|
-
* @param {string}
|
|
768
|
-
* @param {boolean} recursive
|
|
885
|
+
* @param {string} remotePath - path to directory to be removed
|
|
886
|
+
* @param {boolean} recursive - if true, remove directories/files in target
|
|
769
887
|
* directory
|
|
770
|
-
* @return {Promise}
|
|
888
|
+
* @return {Promise}
|
|
771
889
|
*/
|
|
772
890
|
async rmdir(remotePath, recursive = false) {
|
|
773
891
|
const _rmdir = (p) => {
|
|
@@ -782,7 +900,7 @@ class SftpClient {
|
|
|
782
900
|
resolve('Successfully removed directory');
|
|
783
901
|
});
|
|
784
902
|
}).finally((rsp) => {
|
|
785
|
-
removeTempListeners(this
|
|
903
|
+
removeTempListeners(this, 'rmdir');
|
|
786
904
|
return rsp;
|
|
787
905
|
});
|
|
788
906
|
};
|
|
@@ -808,11 +926,8 @@ class SftpClient {
|
|
|
808
926
|
}
|
|
809
927
|
return _rmdir(absPath);
|
|
810
928
|
} catch (err) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
} else {
|
|
814
|
-
throw fmtError(err, 'rmdir', err.code);
|
|
815
|
-
}
|
|
929
|
+
this._resetEventFlags();
|
|
930
|
+
throw err.custom ? err : fmtError(err, 'rmdir', err.code);
|
|
816
931
|
}
|
|
817
932
|
}
|
|
818
933
|
|
|
@@ -821,10 +936,10 @@ class SftpClient {
|
|
|
821
936
|
*
|
|
822
937
|
* Delete a file on the remote SFTP server
|
|
823
938
|
*
|
|
824
|
-
* @param {string}
|
|
939
|
+
* @param {string} remotePath - path to the file to delete
|
|
825
940
|
* @param {boolean} notFoundOK - if true, ignore errors for missing target.
|
|
826
941
|
* Default is false.
|
|
827
|
-
* @return {Promise} with string 'Successfully
|
|
942
|
+
* @return {Promise} with string 'Successfully deleted file' once resolved
|
|
828
943
|
*
|
|
829
944
|
*/
|
|
830
945
|
delete(remotePath, notFoundOK = false) {
|
|
@@ -848,7 +963,8 @@ class SftpClient {
|
|
|
848
963
|
});
|
|
849
964
|
}
|
|
850
965
|
}).finally((rsp) => {
|
|
851
|
-
removeTempListeners(this
|
|
966
|
+
removeTempListeners(this, 'delete');
|
|
967
|
+
this._resetEventFlags();
|
|
852
968
|
return rsp;
|
|
853
969
|
});
|
|
854
970
|
}
|
|
@@ -884,7 +1000,8 @@ class SftpClient {
|
|
|
884
1000
|
});
|
|
885
1001
|
}
|
|
886
1002
|
}).finally((rsp) => {
|
|
887
|
-
removeTempListeners(this
|
|
1003
|
+
removeTempListeners(this, 'rename');
|
|
1004
|
+
this._resetEventFlags();
|
|
888
1005
|
return rsp;
|
|
889
1006
|
});
|
|
890
1007
|
}
|
|
@@ -921,7 +1038,8 @@ class SftpClient {
|
|
|
921
1038
|
});
|
|
922
1039
|
}
|
|
923
1040
|
}).finally((rsp) => {
|
|
924
|
-
removeTempListeners(this
|
|
1041
|
+
removeTempListeners(this, 'posixRename');
|
|
1042
|
+
this._resetEventFlags();
|
|
925
1043
|
return rsp;
|
|
926
1044
|
});
|
|
927
1045
|
}
|
|
@@ -932,9 +1050,9 @@ class SftpClient {
|
|
|
932
1050
|
* Change the mode of a remote file on the SFTP repository
|
|
933
1051
|
*
|
|
934
1052
|
* @param {string} remotePath - path to the remote target object.
|
|
935
|
-
* @param {
|
|
1053
|
+
* @param {number | string} mode - the new octal mode to set
|
|
936
1054
|
*
|
|
937
|
-
* @return {Promise}
|
|
1055
|
+
* @return {Promise}
|
|
938
1056
|
*/
|
|
939
1057
|
chmod(remotePath, mode) {
|
|
940
1058
|
return new Promise((resolve, reject) => {
|
|
@@ -947,7 +1065,8 @@ class SftpClient {
|
|
|
947
1065
|
resolve('Successfully change file mode');
|
|
948
1066
|
});
|
|
949
1067
|
}).finally((rsp) => {
|
|
950
|
-
removeTempListeners(this
|
|
1068
|
+
removeTempListeners(this, 'chmod');
|
|
1069
|
+
this._resetEventFlags();
|
|
951
1070
|
return rsp;
|
|
952
1071
|
});
|
|
953
1072
|
}
|
|
@@ -960,7 +1079,7 @@ class SftpClient {
|
|
|
960
1079
|
* server.
|
|
961
1080
|
* @param {String} srcDir - local source directory
|
|
962
1081
|
* @param {String} dstDir - remote destination directory
|
|
963
|
-
* @param {
|
|
1082
|
+
* @param {RegExp} filter - (Optional) a regular expression used to select
|
|
964
1083
|
* files and directories to upload
|
|
965
1084
|
* @returns {String}
|
|
966
1085
|
* @throws {Error}
|
|
@@ -968,6 +1087,14 @@ class SftpClient {
|
|
|
968
1087
|
async uploadDir(srcDir, dstDir, filter = /.*/) {
|
|
969
1088
|
try {
|
|
970
1089
|
this.debugMsg(`uploadDir -> ${srcDir} ${dstDir}`);
|
|
1090
|
+
const srcType = localExists(srcDir);
|
|
1091
|
+
if (srcType !== 'd') {
|
|
1092
|
+
throw fmtError(
|
|
1093
|
+
`Bad path: ${srcDir}: not a directory`,
|
|
1094
|
+
'uploadDir',
|
|
1095
|
+
errorCode.badPath
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
971
1098
|
haveConnection(this, 'uploadDir');
|
|
972
1099
|
let dstStatus = await this.exists(dstDir);
|
|
973
1100
|
if (dstStatus && dstStatus !== 'd') {
|
|
@@ -978,7 +1105,7 @@ class SftpClient {
|
|
|
978
1105
|
}
|
|
979
1106
|
let dirEntries = fs.readdirSync(srcDir, {
|
|
980
1107
|
encoding: 'utf8',
|
|
981
|
-
withFileTypes: true
|
|
1108
|
+
withFileTypes: true,
|
|
982
1109
|
});
|
|
983
1110
|
dirEntries = dirEntries.filter((item) => filter.test(item.name));
|
|
984
1111
|
for (let e of dirEntries) {
|
|
@@ -990,7 +1117,7 @@ class SftpClient {
|
|
|
990
1117
|
let src = join(srcDir, e.name);
|
|
991
1118
|
let dst = dstDir + this.remotePathSep + e.name;
|
|
992
1119
|
await this.fastPut(src, dst);
|
|
993
|
-
this.client.emit('upload', {source: src, destination: dst});
|
|
1120
|
+
this.client.emit('upload', { source: src, destination: dst });
|
|
994
1121
|
} else {
|
|
995
1122
|
this.debugMsg(
|
|
996
1123
|
`uploadDir: File ignored: ${e.name} not a regular file`
|
|
@@ -999,11 +1126,8 @@ class SftpClient {
|
|
|
999
1126
|
}
|
|
1000
1127
|
return `${srcDir} uploaded to ${dstDir}`;
|
|
1001
1128
|
} catch (err) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
} else {
|
|
1005
|
-
throw fmtError(err, 'uploadDir');
|
|
1006
|
-
}
|
|
1129
|
+
this._resetEventFlags();
|
|
1130
|
+
throw err.custom ? err : fmtError(err, 'uploadDir');
|
|
1007
1131
|
}
|
|
1008
1132
|
}
|
|
1009
1133
|
|
|
@@ -1015,9 +1139,9 @@ class SftpClient {
|
|
|
1015
1139
|
* file system.
|
|
1016
1140
|
* @param {String} srcDir - remote source directory
|
|
1017
1141
|
* @param {String} dstDir - local destination directory
|
|
1018
|
-
* @param {
|
|
1142
|
+
* @param {RegExp} filter - (Optional) a regular expression used to select
|
|
1019
1143
|
* files and directories to upload
|
|
1020
|
-
* @returns {
|
|
1144
|
+
* @returns {Promise}
|
|
1021
1145
|
* @throws {Error}
|
|
1022
1146
|
*/
|
|
1023
1147
|
async downloadDir(srcDir, dstDir, filter = /.*/) {
|
|
@@ -1025,12 +1149,21 @@ class SftpClient {
|
|
|
1025
1149
|
this.debugMsg(`downloadDir -> ${srcDir} ${dstDir}`);
|
|
1026
1150
|
haveConnection(this, 'downloadDir');
|
|
1027
1151
|
let fileList = await this.list(srcDir, filter);
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1030
|
-
throw fmtError(
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1152
|
+
const localCheck = haveLocalCreate(dstDir);
|
|
1153
|
+
if (!localCheck.status && localCheck.details === 'permission denied') {
|
|
1154
|
+
throw fmtError(
|
|
1155
|
+
`Bad path: ${dstDir}: ${localCheck.details}`,
|
|
1156
|
+
'downloadDir',
|
|
1157
|
+
localCheck.code
|
|
1158
|
+
);
|
|
1159
|
+
} else if (localCheck.status && !localCheck.type) {
|
|
1160
|
+
fs.mkdirSync(dstDir, { recursive: true });
|
|
1161
|
+
} else if (localCheck.status && localCheck.type !== 'd') {
|
|
1162
|
+
throw fmtError(
|
|
1163
|
+
`Bad path: ${dstDir}: not a directory`,
|
|
1164
|
+
'downloadDir',
|
|
1165
|
+
errorCode.badPath
|
|
1166
|
+
);
|
|
1034
1167
|
}
|
|
1035
1168
|
for (let f of fileList) {
|
|
1036
1169
|
if (f.type === 'd') {
|
|
@@ -1041,7 +1174,7 @@ class SftpClient {
|
|
|
1041
1174
|
let src = srcDir + this.remotePathSep + f.name;
|
|
1042
1175
|
let dst = join(dstDir, f.name);
|
|
1043
1176
|
await this.fastGet(src, dst);
|
|
1044
|
-
this.client.emit('download', {source: src, destination: dst});
|
|
1177
|
+
this.client.emit('download', { source: src, destination: dst });
|
|
1045
1178
|
} else {
|
|
1046
1179
|
this.debugMsg(
|
|
1047
1180
|
`downloadDir: File ignored: ${f.name} not regular file`
|
|
@@ -1050,11 +1183,8 @@ class SftpClient {
|
|
|
1050
1183
|
}
|
|
1051
1184
|
return `${srcDir} downloaded to ${dstDir}`;
|
|
1052
1185
|
} catch (err) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
} else {
|
|
1056
|
-
throw fmtError(err, 'downloadDir', err.code);
|
|
1057
|
-
}
|
|
1186
|
+
this._resetEventFlags();
|
|
1187
|
+
throw err.custom ? err : fmtError(err, 'downloadDir', err.code);
|
|
1058
1188
|
}
|
|
1059
1189
|
}
|
|
1060
1190
|
|
|
@@ -1071,17 +1201,21 @@ class SftpClient {
|
|
|
1071
1201
|
addTempListeners(this, 'end', reject);
|
|
1072
1202
|
endCloseHandler = () => {
|
|
1073
1203
|
this.sftp = undefined;
|
|
1204
|
+
this.debugMsg('end: Connection closed');
|
|
1074
1205
|
resolve(true);
|
|
1075
1206
|
};
|
|
1076
1207
|
this.on('close', endCloseHandler);
|
|
1077
1208
|
if (haveConnection(this, 'end', reject)) {
|
|
1078
|
-
this.debugMsg('Have connection - calling end()');
|
|
1209
|
+
this.debugMsg('end: Have connection - calling end()');
|
|
1079
1210
|
this.client.end();
|
|
1080
1211
|
}
|
|
1081
|
-
}).finally(() => {
|
|
1082
|
-
|
|
1212
|
+
}).finally((resp) => {
|
|
1213
|
+
this.debugMsg('end: finally clause fired');
|
|
1214
|
+
removeTempListeners(this, 'end');
|
|
1083
1215
|
this.removeListener('close', endCloseHandler);
|
|
1084
|
-
|
|
1216
|
+
this.endCalled = false;
|
|
1217
|
+
this._resetEventFlags();
|
|
1218
|
+
return resp;
|
|
1085
1219
|
});
|
|
1086
1220
|
}
|
|
1087
1221
|
}
|