ssh2-sftp-client 10.0.2 → 11.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/README.md +1640 -1
- package/README.org +141 -28
- package/package.json +7 -7
- package/src/index.js +38 -47
- package/src/utils.js +83 -38
package/README.org
CHANGED
|
@@ -3,41 +3,148 @@
|
|
|
3
3
|
|
|
4
4
|
* Overview
|
|
5
5
|
|
|
6
|
-
an SFTP client for node.js
|
|
7
|
-
|
|
6
|
+
This packan provides the class SftpClient, an SFTP client for node.js. It is a promise
|
|
7
|
+
based decorator class around the excdellent [[https://github.com/mscdex/ssh2][SSH2]] package, which provides a pure node
|
|
8
|
+
Javascript event based ssh2 implementation.
|
|
8
9
|
|
|
9
10
|
Documentation on the methods and available options in the underlying modules can
|
|
10
|
-
be found on the [[https://github.com/mscdex/ssh2][SSH2]] project pages. As
|
|
11
|
+
be found on the [[https://github.com/mscdex/ssh2][SSH2]] project pages. As the ssh2-sftp-client package just a wrapper around the
|
|
11
12
|
~ssh2~ module, you will find lots of useful information, tips and examples in the ~ssh2~
|
|
12
13
|
repository.
|
|
13
14
|
|
|
14
|
-
Current stable release is *
|
|
15
|
+
Current stable release is *v11.0.0.
|
|
15
16
|
|
|
16
|
-
Code has been tested against Node versions
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
significantly slower.
|
|
20
|
-
|
|
21
|
-
Node versions < 16.x are not supported.
|
|
17
|
+
Code has been tested against Node versions 18.20.4, 20.16.0 and 22.5.1. Node versions
|
|
18
|
+
prior to v18.x are not supported. Also note there is currently a deprecation warning when
|
|
19
|
+
using node v22+. This is from the ~ssh2~ package and outside control of this package.
|
|
22
20
|
|
|
23
21
|
If you find this module useful and you would like to support the on-going maintenance and
|
|
24
22
|
support of users, please consider making a small [[https://square.link/u/gB2kSdkY?src=embed][donation]].
|
|
25
23
|
|
|
26
|
-
** Version
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
** Version 11.0.0 Changes
|
|
25
|
+
|
|
26
|
+
The main change in v11 concerns how the package manages events raised by the =ssh2= packagwe
|
|
27
|
+
it depends on. Managing events within the context of asynchronous code as ocurs when
|
|
28
|
+
using promises is challenging. To understand some of the more subtle issues involved, it
|
|
29
|
+
is recommended you read the [[https://nodejs.org/docs/latest/api/events.html#asynchronous-vs-synchronous][Asynchronous vs. synchronous]] and [[https://nodejs.org/docs/latest/api/events.html#error-events][Error events]] sections of the
|
|
30
|
+
Events chapter from the node documentation.
|
|
31
|
+
|
|
32
|
+
The previous versions of this package use a global event handler approach to manage events
|
|
33
|
+
raised outside the execution of any promise. However, this approach is problematic as all
|
|
34
|
+
the global listeners can really do is raise an error. Attempting to catch errors raised
|
|
35
|
+
inside event handlers is extremely difficult to manage within client code because those
|
|
36
|
+
errors are raised inside a separate asynchronous execution context. In version 11,
|
|
37
|
+
this approach has been replaced by a mechanism whereby the client code can pass in
|
|
38
|
+
application specific global handlers if desired. If no handlers are defined, default
|
|
39
|
+
handlers will log the event and when necessary, invalidate any existing connection
|
|
40
|
+
objects. See the [[Background]] section for details.
|
|
41
|
+
|
|
42
|
+
** Background
|
|
43
|
+
|
|
44
|
+
In basic terms =ssh2-sftp-client= is a simple wrapper around the =ssh2= package which provides
|
|
45
|
+
a promise base API for interacting with a remote SFTP server . The =ssh2= package provides
|
|
46
|
+
an event based API for interacting with the ~ssh~ protcolo. The ~ssh2-sftp-client~ package
|
|
47
|
+
uses the ~sftp~ subsystem of this protocol to implement the basic operations typically
|
|
48
|
+
associated with an ~sftp~ client.
|
|
49
|
+
|
|
50
|
+
Wrapping an event based API with a promised based API comes with a number of
|
|
51
|
+
challenges. In particular, efficiently and reliably managing events within the context of
|
|
52
|
+
asynchrounous code execution. This package uses the following strategies;
|
|
53
|
+
|
|
54
|
+
- All direct interactions with the ~ssh2~ API are wrapped in promise objects. When the
|
|
55
|
+
API call succeeds, the assoiated promise will be sucessfully resolved. When an error occurs,
|
|
56
|
+
the promise is rejected.
|
|
57
|
+
|
|
58
|
+
- An error can either be due to a low level network error, such as a lost connection
|
|
59
|
+
to the remote server or due to an operational error, such as a file not
|
|
60
|
+
existing or not having the appropriate permissions for access.
|
|
61
|
+
|
|
62
|
+
- Each of the available ~SftpClient~ methods wrap the method call inside a promise. In
|
|
63
|
+
crwating eacvh promise, the class adds temporary event listenrs for the error, end
|
|
64
|
+
and close events and links those liseners to the method's promise via it's ~reject()~
|
|
65
|
+
method.
|
|
66
|
+
|
|
67
|
+
- If a promise is waiting to be fulfilled when either of the two types of errors
|
|
68
|
+
occurs, the error will be communicated back to client code via a rejected promise.
|
|
69
|
+
|
|
70
|
+
- When the ~ssh2~ emitter raises an event outside the context of any promise, that event
|
|
71
|
+
will be handled by global event handlers. By default, these event handlers will log
|
|
72
|
+
the event and will invalidate any existing socket connection objects, preventing any
|
|
73
|
+
further API calls until a new connection is extablished.
|
|
74
|
+
|
|
75
|
+
- The ~SftpClient~ class constructor supports an optoinal second argument which is an
|
|
76
|
+
objedct whi8ch can have any of three properties representing event callbaqck
|
|
77
|
+
funct5ions which will be executed for each of the possible events error, end and
|
|
78
|
+
close.
|
|
79
|
+
|
|
80
|
+
The need for both global listeners and temporary promise listeners is because network end,
|
|
81
|
+
close or error events can occur at any time, including in-betwseen API calls. During an
|
|
82
|
+
API call, a promise is active and can be used to communicate event information back to the calling
|
|
83
|
+
code via normal promise communication means i.e. async/await with try/catch or promise chain's then/catch
|
|
84
|
+
mechanism. However, outside API calls, no promise exists and there is no reliable
|
|
85
|
+
mechanism to return error and other event information back to calling code. You cannot
|
|
86
|
+
reliably use try/cdatch to catch errorsx thrown inside event listenrs as you lack control
|
|
87
|
+
over when the listener code runs. Your try/catch block can easily complete before the
|
|
88
|
+
error is raised as there is no equivalent /await/ type functionality in this situation.
|
|
89
|
+
|
|
90
|
+
As there is no simple default way to return error and other event information back to the
|
|
91
|
+
calling code, ~ssh2-sftp-client~ doesn't try to. Instead, the default action is
|
|
92
|
+
to just log the event information and invalidate any existing sftp connections. This
|
|
93
|
+
strategy is often sufficient for many use cases. For those cases where it isn't, client
|
|
94
|
+
code can pass in end, close and/or error listener functions when instantiating the
|
|
95
|
+
~SftpClient~ object. If provided, these listners will be executed whenever the default
|
|
96
|
+
global listeners are executed, which is whenever the ~ssh2~ event emitter raises an end,
|
|
97
|
+
close or error event which is not handled by one of the temporary promise linked event
|
|
98
|
+
listeners.
|
|
99
|
+
|
|
100
|
+
Version 11 of ~ssh2-sftp-client~ also changes the behaviour of the temporary promise linked
|
|
101
|
+
/end/ and /close/ listeners. Prior to versioln 11, these listeners did not reject
|
|
102
|
+
promises. They would only invalidate the underlying ~ssh~ connection object. Only the /error/
|
|
103
|
+
listener would actually reject the associated promise. This was done because you cannot
|
|
104
|
+
guarantee the order in which events are responded to.
|
|
105
|
+
|
|
106
|
+
In most cases, events will occur in the order /error/, /end/ and then /close/. The error event
|
|
107
|
+
would contain details about the cause of the error while the end and close events just
|
|
108
|
+
communicvate that these events have been raised. The normal flow would be
|
|
109
|
+
|
|
110
|
+
- Error event occurs including error cause description. Listener catches event,
|
|
111
|
+
creates an error object and calls the associated promises reject function to reject
|
|
112
|
+
the promise. The calling process receives a rejected promise object.
|
|
113
|
+
|
|
114
|
+
- End event occurs. The end listener catches the event and marks the connection object
|
|
115
|
+
as invalid as the socket connection has been ended. There is no need to call the
|
|
116
|
+
reject method of the associated promise as it has already been called by the error
|
|
117
|
+
listener and you can only call one promise resolution function.
|
|
118
|
+
|
|
119
|
+
- Close event occurs. This event means the socket connection has been closed. The
|
|
120
|
+
listener will ensure any connection information has been invalidated. Aga8in, ther
|
|
121
|
+
is no need to call the reject method of the associated promise as it has already
|
|
122
|
+
been called by the error listener.
|
|
123
|
+
|
|
124
|
+
In some cases, no ende event is raised and you only get an error event followed by a close
|
|
125
|
+
event. In versions of ~ssh2-sftp-client~ prior to version 11, neither the end or the close
|
|
126
|
+
listeners attempted to call the reject method of the associated promise. It was assumed
|
|
127
|
+
that all sftp servers would raise an error event whenever a connection was unexpectedly
|
|
128
|
+
ended or closed. Unfortunately, it turns out some sftp servers are not well behaved and
|
|
129
|
+
will terminate the connection without providing any error information or raising an error
|
|
130
|
+
event. When this occurred in versions prior to version 11, it could result in either an
|
|
131
|
+
API call hanging because its associated promise never gets rejected or resolved or the
|
|
132
|
+
call gets rejected with a timeout error aftrer a significant delay.
|
|
133
|
+
|
|
134
|
+
In order to handle the possible hanging issue in version 11, the temporary promise linked
|
|
135
|
+
end and close listeners have been updated to always call the promise's reject function if
|
|
136
|
+
they fire. While this works, it can cause a minor issue. As wse cannot gurantee the order
|
|
137
|
+
in which events are resonded to by listeners, it is possible that either the end or close
|
|
138
|
+
listener may be executed before the error listener. When this occurs, the promise is
|
|
139
|
+
rejected, but the only information wse have at that point is that the promise wsas reject
|
|
140
|
+
due to either an end or close event. We don't yet have any details regarding what error
|
|
141
|
+
has caused the unexpected end or close event. Furthermore, because only the first promise
|
|
142
|
+
resolution function call has any effect, calling reject within the error listener
|
|
143
|
+
(assuming an error event does eventually arrive) has no effect and does not communicate
|
|
144
|
+
error information back to the caller. This means that in some circumstances, especially
|
|
145
|
+
when working with some poorly behaved sftp servers, an sftp connection will be lost/closed
|
|
146
|
+
with no indication as to reason. This can make diagnosis and bug tracking frustrating.
|
|
37
147
|
|
|
38
|
-
- Various minor documentation fixes and some minor fixes for typos in option property
|
|
39
|
-
names.
|
|
40
|
-
|
|
41
148
|
* Installation
|
|
42
149
|
|
|
43
150
|
#+begin_src shell
|
|
@@ -144,7 +251,7 @@ refer your issues to the maintainers of those modules.
|
|
|
144
251
|
|
|
145
252
|
** Methods
|
|
146
253
|
|
|
147
|
-
*** new SftpClient(name) ===> SFTP client object
|
|
254
|
+
*** new SftpClient(name, callbacks) ===> SFTP client object
|
|
148
255
|
|
|
149
256
|
Constructor to create a new ~ssh2-sftp-client~ object. An optional ~name~ string
|
|
150
257
|
can be provided, which will be used in error messages to help identify which
|
|
@@ -152,8 +259,14 @@ client has thrown the error.
|
|
|
152
259
|
|
|
153
260
|
**** Constructor Arguments
|
|
154
261
|
|
|
155
|
-
- name :: string. An optional name string used in error messages
|
|
156
|
-
|
|
262
|
+
- name :: string. An optional name string used in error messages. Defaults to the string
|
|
263
|
+
'sftp'
|
|
264
|
+
- callbacks :: object. An object with the properties error, end and close. Associted with
|
|
265
|
+
each property is a function that will be executed whenever the associated global event
|
|
266
|
+
listener for error, end or close is executed. The error function should accept one
|
|
267
|
+
argument. The end and close functions have no arguments. Default functions just print a
|
|
268
|
+
message to ~console.log()~.
|
|
269
|
+
|
|
157
270
|
**** Example Use
|
|
158
271
|
|
|
159
272
|
#+begin_src javascript
|
|
@@ -989,7 +1102,7 @@ remote SFTP server will result in failures.
|
|
|
989
1102
|
|
|
990
1103
|
*** downloadDir(srcDir, dstDir, options) ==> string
|
|
991
1104
|
|
|
992
|
-
Download the remote directory specified by ~srcDir~ to the local file system
|
|
1105
|
+
Download the content of the remote directory specified by ~srcDir~ to the local file system
|
|
993
1106
|
directory specified by ~dstDir~. The ~dstDir~ directory will be created if
|
|
994
1107
|
required. All sub directories within ~srcDir~ will also be copied. Any existing
|
|
995
1108
|
files in the local path will be overwritten. No files in the local path will be
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh2-sftp-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "ssh2 sftp client for node",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"lint": "eslint \"src/**/*.js\" \"test/**/*.js\""
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": ">=18.20.4"
|
|
22
22
|
},
|
|
23
23
|
"author": "Tim Cross",
|
|
24
24
|
"email": "theophilusx@gmail.com",
|
|
@@ -34,20 +34,20 @@
|
|
|
34
34
|
],
|
|
35
35
|
"license": "Apache-2.0",
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"chai": "^4.
|
|
38
|
-
"chai-as-promised": "^7.1.
|
|
37
|
+
"chai": "^4.5.0",
|
|
38
|
+
"chai-as-promised": "^7.1.2",
|
|
39
39
|
"chai-subset": "^1.6.0",
|
|
40
40
|
"checksum": "^1.0.0",
|
|
41
41
|
"dotenv": "^16.0.0",
|
|
42
|
-
"eslint": "^
|
|
42
|
+
"eslint": "^9.6.0",
|
|
43
43
|
"eslint-config-prettier": "^9.0.0",
|
|
44
44
|
"eslint-plugin-mocha": "^10.2.0",
|
|
45
45
|
"eslint-plugin-node": "^11.1.0",
|
|
46
46
|
"eslint-plugin-promise": "^6.0.0",
|
|
47
|
-
"eslint-plugin-unicorn": "^
|
|
47
|
+
"eslint-plugin-unicorn": "^54.0.0",
|
|
48
48
|
"mocha": "^10.0.0",
|
|
49
49
|
"moment": "^2.29.1",
|
|
50
|
-
"nyc": "^
|
|
50
|
+
"nyc": "^17.0.0",
|
|
51
51
|
"prettier": "^3.0.3",
|
|
52
52
|
"through2": "^4.0.2",
|
|
53
53
|
"winston": "^3.11.0"
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
2
|
const { Client } = require('ssh2');
|
|
4
3
|
const fs = require('node:fs');
|
|
5
4
|
const concat = require('concat-stream');
|
|
@@ -19,11 +18,18 @@ const {
|
|
|
19
18
|
const { errorCode } = require('./constants');
|
|
20
19
|
|
|
21
20
|
class SftpClient {
|
|
22
|
-
constructor(
|
|
23
|
-
|
|
21
|
+
constructor(
|
|
22
|
+
clientName = 'sftp',
|
|
23
|
+
callbacks = {
|
|
24
|
+
error: (err) => console.error(`Global error listener: ${err.message}`),
|
|
25
|
+
end: () => console.log('Global end listener: end event raised'),
|
|
26
|
+
close: () => console.log('Global close listener: close event raised'),
|
|
27
|
+
},
|
|
28
|
+
) {
|
|
29
|
+
this.version = '11.0.0';
|
|
24
30
|
this.client = new Client();
|
|
25
31
|
this.sftp = undefined;
|
|
26
|
-
this.clientName = clientName
|
|
32
|
+
this.clientName = clientName;
|
|
27
33
|
this.endCalled = false;
|
|
28
34
|
this.errorHandled = false;
|
|
29
35
|
this.closeHandled = false;
|
|
@@ -31,9 +37,10 @@ class SftpClient {
|
|
|
31
37
|
this.remotePlatform = 'unix';
|
|
32
38
|
this.debug = undefined;
|
|
33
39
|
this.promiseLimit = 10;
|
|
34
|
-
this.
|
|
35
|
-
this.client.on('
|
|
36
|
-
this.client.on('
|
|
40
|
+
this.eventCallbacks = callbacks;
|
|
41
|
+
this.client.on('close', globalListener(this, 'close', this.eventCallbacks));
|
|
42
|
+
this.client.on('end', globalListener(this, 'end', this.eventCallbacks));
|
|
43
|
+
this.client.on('error', globalListener(this, 'error', this.eventCallbacks));
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
debugMsg(msg, obj) {
|
|
@@ -128,14 +135,12 @@ class SftpClient {
|
|
|
128
135
|
return new Promise((resolve, reject) => {
|
|
129
136
|
listeners = addTempListeners(this, 'getConnection', reject);
|
|
130
137
|
doReady = () => {
|
|
131
|
-
this.debugMsg('getConnection ready listener: got connection - promise resolved');
|
|
132
138
|
resolve(true);
|
|
133
139
|
};
|
|
134
140
|
this.on('ready', doReady);
|
|
135
141
|
try {
|
|
136
142
|
this.client.connect(config);
|
|
137
143
|
} catch (err) {
|
|
138
|
-
this.debugMsg(`getConnection: ${err.message}`);
|
|
139
144
|
reject(err);
|
|
140
145
|
}
|
|
141
146
|
}).finally(() => {
|
|
@@ -219,6 +224,7 @@ class SftpClient {
|
|
|
219
224
|
}
|
|
220
225
|
});
|
|
221
226
|
const sftp = await this.getSftpChannel();
|
|
227
|
+
this.endCalled = false;
|
|
222
228
|
return sftp;
|
|
223
229
|
} catch (err) {
|
|
224
230
|
this.end();
|
|
@@ -246,18 +252,14 @@ class SftpClient {
|
|
|
246
252
|
if (addListeners) {
|
|
247
253
|
listeners = addTempListeners(this, 'realPath', reject);
|
|
248
254
|
}
|
|
249
|
-
this.debugMsg(`realPath -> ${remotePath}`);
|
|
250
255
|
this.sftp.realpath(remotePath, (err, absPath) => {
|
|
251
256
|
if (err) {
|
|
252
257
|
if (err.code === 2) {
|
|
253
|
-
this.debugMsg('realPath <- ""');
|
|
254
258
|
resolve('');
|
|
255
259
|
} else {
|
|
256
|
-
this.debugMsg(`${err.message} ${remotePath}`, 'realPath');
|
|
257
260
|
reject(this.fmtError(`${err.message} ${remotePath}`, 'realPath', err.code));
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
|
-
this.debugMsg(`realPath <- ${absPath}`);
|
|
261
263
|
resolve(absPath);
|
|
262
264
|
});
|
|
263
265
|
}).finally(() => {
|
|
@@ -313,7 +315,6 @@ class SftpClient {
|
|
|
313
315
|
isFIFO: stats.isFIFO(),
|
|
314
316
|
isSocket: stats.isSocket(),
|
|
315
317
|
};
|
|
316
|
-
this.debugMsg('_xstat: result = ', result);
|
|
317
318
|
resolve(result);
|
|
318
319
|
}
|
|
319
320
|
};
|
|
@@ -382,13 +383,11 @@ class SftpClient {
|
|
|
382
383
|
* object if it does
|
|
383
384
|
*/
|
|
384
385
|
async exists(remotePath) {
|
|
385
|
-
this.debugMsg(`exists: remotePath = ${remotePath}`);
|
|
386
386
|
try {
|
|
387
387
|
if (remotePath === '.') {
|
|
388
388
|
return 'd';
|
|
389
389
|
}
|
|
390
390
|
const info = await this.lstat(remotePath);
|
|
391
|
-
this.debugMsg('exists: <- ', info);
|
|
392
391
|
if (info.isDirectory) {
|
|
393
392
|
return 'd';
|
|
394
393
|
} else if (info.isSymbolicLink) {
|
|
@@ -501,20 +500,20 @@ class SftpClient {
|
|
|
501
500
|
};
|
|
502
501
|
rdr = this.sftp.createReadStream(remotePath, options.readStreamOptions);
|
|
503
502
|
rdr.once('error', (err) => {
|
|
504
|
-
if (dst && typeof dst
|
|
505
|
-
|
|
503
|
+
if (dst && typeof dst === 'string' && wtr && !wtr.destroyed) {
|
|
504
|
+
wtr.destroy();
|
|
506
505
|
}
|
|
507
506
|
reject(this.fmtError(`${err.message} ${remotePath}`, 'get', err.code));
|
|
508
507
|
});
|
|
509
508
|
if (dst === undefined) {
|
|
510
509
|
// no dst specified, return buffer of data
|
|
511
|
-
this.debugMsg('get resolving buffer of data');
|
|
510
|
+
this.debugMsg('get resolving with buffer of data');
|
|
512
511
|
wtr = concat((buff) => {
|
|
513
512
|
resolve(buff);
|
|
514
513
|
});
|
|
515
514
|
} else if (typeof dst === 'string') {
|
|
516
515
|
// dst local file path
|
|
517
|
-
this.debugMsg(
|
|
516
|
+
this.debugMsg(`get called with file path destination ${dst}`);
|
|
518
517
|
const localCheck = haveLocalCreate(dst);
|
|
519
518
|
if (localCheck.status) {
|
|
520
519
|
wtr = fs.createWriteStream(dst, options.writeStreamOptions);
|
|
@@ -528,7 +527,7 @@ class SftpClient {
|
|
|
528
527
|
);
|
|
529
528
|
}
|
|
530
529
|
} else {
|
|
531
|
-
this.debugMsg('get
|
|
530
|
+
this.debugMsg('get called with stream destination');
|
|
532
531
|
wtr = dst;
|
|
533
532
|
}
|
|
534
533
|
wtr.once('error', (err) => {
|
|
@@ -542,16 +541,17 @@ class SftpClient {
|
|
|
542
541
|
});
|
|
543
542
|
rdr.once('end', () => {
|
|
544
543
|
if (typeof dst === 'string') {
|
|
545
|
-
this.debugMsg('get: resolving with dst filename');
|
|
546
544
|
resolve(dst);
|
|
547
545
|
} else if (dst !== undefined) {
|
|
548
|
-
this.debugMsg('get: resolving with writer stream object');
|
|
549
546
|
resolve(wtr);
|
|
550
547
|
}
|
|
551
548
|
});
|
|
552
549
|
rdr.pipe(wtr, options.pipeOptions);
|
|
553
550
|
}
|
|
554
551
|
}).finally(() => {
|
|
552
|
+
if (rdr && !rdr.destroyed) {
|
|
553
|
+
rdr.destroy();
|
|
554
|
+
}
|
|
555
555
|
if (addListeners) {
|
|
556
556
|
removeTempListeners(this, listeners, 'get');
|
|
557
557
|
}
|
|
@@ -661,7 +661,6 @@ class SftpClient {
|
|
|
661
661
|
|
|
662
662
|
async fastPut(localPath, remotePath, options) {
|
|
663
663
|
try {
|
|
664
|
-
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
|
|
665
664
|
const localCheck = haveLocalAccess(localPath);
|
|
666
665
|
if (!localCheck.status) {
|
|
667
666
|
throw this.fmtError(
|
|
@@ -713,6 +712,9 @@ class SftpClient {
|
|
|
713
712
|
if (haveConnection(this, '_put', reject)) {
|
|
714
713
|
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
|
|
715
714
|
wtr.once('error', (err) => {
|
|
715
|
+
if (typeof lPath === 'string' && rdr && !rdr.destroyed) {
|
|
716
|
+
rdr.destroy();
|
|
717
|
+
}
|
|
716
718
|
reject(
|
|
717
719
|
this.fmtError(
|
|
718
720
|
`Write stream error: ${err.message} ${rPath}`,
|
|
@@ -728,10 +730,13 @@ class SftpClient {
|
|
|
728
730
|
this.debugMsg('put source is a buffer');
|
|
729
731
|
wtr.end(lPath);
|
|
730
732
|
} else {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
733
|
+
if (typeof lPath === 'string') {
|
|
734
|
+
this.debugMsg('put source is string path');
|
|
735
|
+
rdr = fs.createReadStream(lPath, opts.readStreamOptions);
|
|
736
|
+
} else {
|
|
737
|
+
this.debugMsg('put source is a stream');
|
|
738
|
+
rdr = lPath;
|
|
739
|
+
}
|
|
735
740
|
rdr.once('error', (err) => {
|
|
736
741
|
reject(
|
|
737
742
|
this.fmtError(
|
|
@@ -747,6 +752,9 @@ class SftpClient {
|
|
|
747
752
|
}
|
|
748
753
|
}
|
|
749
754
|
}).finally(() => {
|
|
755
|
+
if (wtr && !wtr.destroyed) {
|
|
756
|
+
wtr.destroy();
|
|
757
|
+
}
|
|
750
758
|
if (addListeners) {
|
|
751
759
|
removeTempListeners(this, listeners, '_put');
|
|
752
760
|
}
|
|
@@ -786,7 +794,6 @@ class SftpClient {
|
|
|
786
794
|
listeners = addTempListeners(this, '_append', reject);
|
|
787
795
|
}
|
|
788
796
|
if (haveConnection(this, '_append', reject)) {
|
|
789
|
-
this.debugMsg(`append -> remote: ${rPath} `, opts);
|
|
790
797
|
opts.flags = 'a';
|
|
791
798
|
const stream = this.sftp.createWriteStream(rPath, opts);
|
|
792
799
|
stream.on('error', (err) => {
|
|
@@ -826,7 +833,7 @@ class SftpClient {
|
|
|
826
833
|
errorCode.badPath,
|
|
827
834
|
);
|
|
828
835
|
}
|
|
829
|
-
await this._append(input, remotePath, options);
|
|
836
|
+
return await this._append(input, remotePath, options);
|
|
830
837
|
} catch (e) {
|
|
831
838
|
throw e.custom ? e : this.fmtError(e.message, 'append', e.code);
|
|
832
839
|
}
|
|
@@ -941,7 +948,6 @@ class SftpClient {
|
|
|
941
948
|
let listeners;
|
|
942
949
|
return new Promise((resolve, reject) => {
|
|
943
950
|
listeners = addTempListeners(this, '_rmdir', reject);
|
|
944
|
-
this.debugMsg(`_rmdir: dir = ${dir}`);
|
|
945
951
|
this.sftp.rmdir(dir, (err) => {
|
|
946
952
|
if (err) {
|
|
947
953
|
reject(this.fmtError(`${err.message} ${dir}`, 'rmdir', err.code));
|
|
@@ -957,7 +963,6 @@ class SftpClient {
|
|
|
957
963
|
let listeners;
|
|
958
964
|
return new Promise((resolve, reject) => {
|
|
959
965
|
listeners = addTempListeners(this, '_delFiles', reject);
|
|
960
|
-
this.debugMsg(`_delFiles: path = ${path} fileList = ${fileList}`);
|
|
961
966
|
const pList = [];
|
|
962
967
|
for (const f of fileList) {
|
|
963
968
|
pList.push(this.delete(`${path}/${f.name}`, true, false));
|
|
@@ -973,10 +978,8 @@ class SftpClient {
|
|
|
973
978
|
};
|
|
974
979
|
|
|
975
980
|
try {
|
|
976
|
-
this.debugMsg(`rmdir: dir = ${remoteDir} recursive = ${recursive}`);
|
|
977
981
|
const absPath = await normalizeRemotePath(this, remoteDir);
|
|
978
982
|
const existStatus = await this.exists(absPath);
|
|
979
|
-
this.debugMsg(`rmdir: ${absPath} existStatus = ${existStatus}`);
|
|
980
983
|
if (!existStatus) {
|
|
981
984
|
throw this.fmtError(
|
|
982
985
|
`Bad Path: ${remoteDir}: No such directory`,
|
|
@@ -992,19 +995,14 @@ class SftpClient {
|
|
|
992
995
|
);
|
|
993
996
|
}
|
|
994
997
|
if (!recursive) {
|
|
995
|
-
this.debugMsg('rmdir: non-recursive - just try to remove it');
|
|
996
998
|
return await _rmdir(absPath);
|
|
997
999
|
}
|
|
998
1000
|
const listing = await this.list(absPath);
|
|
999
|
-
this.debugMsg(`rmdir: listing count = ${listing.length}`);
|
|
1000
1001
|
if (!listing.length) {
|
|
1001
|
-
this.debugMsg('rmdir: No sub dir or files, just rmdir');
|
|
1002
1002
|
return await _rmdir(absPath);
|
|
1003
1003
|
}
|
|
1004
1004
|
const fileList = listing.filter((i) => i.type !== 'd');
|
|
1005
|
-
this.debugMsg(`rmdir: dir content files to remove = ${fileList.length}`);
|
|
1006
1005
|
const dirList = listing.filter((i) => i.type === 'd');
|
|
1007
|
-
this.debugMsg(`rmdir: sub-directories to remove = ${dirList.length}`);
|
|
1008
1006
|
await _delFiles(absPath, fileList);
|
|
1009
1007
|
for (const d of dirList) {
|
|
1010
1008
|
await this.rmdir(`${absPath}/${d.name}`, true);
|
|
@@ -1244,11 +1242,7 @@ class SftpClient {
|
|
|
1244
1242
|
|
|
1245
1243
|
try {
|
|
1246
1244
|
haveConnection(this, 'uploadDir');
|
|
1247
|
-
this.debugMsg(
|
|
1248
|
-
`uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}`,
|
|
1249
|
-
);
|
|
1250
1245
|
const { remoteDir, remoteStatus } = await getRemoteStatus(dstDir);
|
|
1251
|
-
this.debugMsg(`uploadDir: remoteDir = ${remoteDir} remoteStatus = ${remoteStatus}`);
|
|
1252
1246
|
checkLocalStatus(srcDir);
|
|
1253
1247
|
if (!remoteStatus) {
|
|
1254
1248
|
await this._mkdir(remoteDir, true);
|
|
@@ -1257,7 +1251,6 @@ class SftpClient {
|
|
|
1257
1251
|
encoding: 'utf8',
|
|
1258
1252
|
withFileTypes: true,
|
|
1259
1253
|
});
|
|
1260
|
-
this.debugMsg(`uploadDir: dirEntries = ${dirEntries}`);
|
|
1261
1254
|
if (options?.filter) {
|
|
1262
1255
|
dirEntries = dirEntries.filter((item) =>
|
|
1263
1256
|
options.filter(join(srcDir, item.name), item.isDirectory()),
|
|
@@ -1265,8 +1258,6 @@ class SftpClient {
|
|
|
1265
1258
|
}
|
|
1266
1259
|
const dirUploads = dirEntries.filter((item) => item.isDirectory());
|
|
1267
1260
|
const fileUploads = dirEntries.filter((item) => !item.isDirectory());
|
|
1268
|
-
this.debugMsg(`uploadDir: dirUploads = ${dirUploads}`);
|
|
1269
|
-
this.debugMsg(`uploadDir: fileUploads = ${fileUploads}`);
|
|
1270
1261
|
await uploadFiles(srcDir, remoteDir, fileUploads, options?.useFastput);
|
|
1271
1262
|
for (const d of dirUploads) {
|
|
1272
1263
|
const src = join(srcDir, d.name);
|
|
@@ -1527,6 +1518,7 @@ class SftpClient {
|
|
|
1527
1518
|
};
|
|
1528
1519
|
this.on('close', endCloseHandler);
|
|
1529
1520
|
if (this.sftp) {
|
|
1521
|
+
this.debugMsg('end: Ending SFTP connection');
|
|
1530
1522
|
this.client.end();
|
|
1531
1523
|
} else {
|
|
1532
1524
|
// no actual connection exists - just resolve
|
|
@@ -1536,7 +1528,6 @@ class SftpClient {
|
|
|
1536
1528
|
}).finally(() => {
|
|
1537
1529
|
removeTempListeners(this, listeners, 'end');
|
|
1538
1530
|
this.removeListener('close', endCloseHandler);
|
|
1539
|
-
this.endCalled = false;
|
|
1540
1531
|
});
|
|
1541
1532
|
}
|
|
1542
1533
|
}
|