ssh2-sftp-client
Advanced tools
Comparing version 9.0.4 to 9.1.0
{ | ||
"name": "ssh2-sftp-client", | ||
"version": "9.0.4", | ||
"version": "9.1.0", | ||
"description": "ssh2 sftp client for node", | ||
@@ -43,3 +43,3 @@ "main": "src/index.js", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"eslint-plugin-unicorn": "^43.0.2", | ||
"eslint-plugin-unicorn": "^46.0.0", | ||
"mocha": "^10.0.0", | ||
@@ -55,4 +55,4 @@ "moment": "^2.29.1", | ||
"promise-retry": "^2.0.1", | ||
"ssh2": "^1.11.0" | ||
"ssh2": "^1.12.0" | ||
} | ||
} |
1128
src/index.js
@@ -9,2 +9,3 @@ 'use strict'; | ||
const { | ||
globalListener, | ||
addTempListeners, | ||
@@ -30,38 +31,8 @@ removeTempListeners, | ||
this.endHandled = false; | ||
this.remotePathSep = '/'; | ||
this.remotePlatform = 'unix'; | ||
this.debug = undefined; | ||
this.client.on('close', () => { | ||
if (this.endCalled || this.errorHandled || this.closeHandled) { | ||
// we are processing an expected end event or close event handled elsewhere | ||
this.debugMsg('Global: Ignoring handled close event'); | ||
} else { | ||
this.debugMsg('Global: Handling unexpected close event'); | ||
this.sftp = undefined; | ||
} | ||
}); | ||
this.client.on('end', () => { | ||
if (this.endCalled || this.errorHandled || this.endHandled) { | ||
// end event expected or handled elsewhere | ||
this.debugMsg('Global: Ignoring hanlded end event'); | ||
} else { | ||
this.debugMsg('Global: Handling unexpected end event'); | ||
this.sftp = undefined; | ||
} | ||
}); | ||
this.client.on('error', (err) => { | ||
if (this.endCalled || this.errorHandled) { | ||
// error event expected or handled elsewhere | ||
this.debugMsg(`Global: Ignoring handled error: ${err.message}`); | ||
} else { | ||
this.debugMsg(`Global; Handling unexpected error; ${err.message}`); | ||
this.sftp = undefined; | ||
console.log( | ||
`ssh2-sftp-client: Unexpected error: ${err.message}. Error code: ${err.code}` | ||
); | ||
} | ||
}); | ||
this.client.on('close', globalListener(this, 'close')); | ||
this.client.on('end', globalListener(this, 'end')); | ||
this.client.on('error', globalListener(this, 'error')); | ||
} | ||
@@ -180,3 +151,2 @@ | ||
} else { | ||
this.debugMsg('getSftpChannel: SFTP channel established'); | ||
this.sftp = sftp; | ||
@@ -267,37 +237,32 @@ resolve(sftp); | ||
* @param {String} remotePath - remote path, may be relative | ||
* @param {Boolean} addListeners - (Optional) add event listeners. Default = true | ||
* @returns {Promise<String>} - remote absolute path or '' | ||
*/ | ||
_realPath(rPath) { | ||
realPath(remotePath, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.debugMsg(`_realPath -> ${rPath}`); | ||
this.sftp.realpath(rPath, (err, absPath) => { | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'realPath', reject); | ||
} | ||
this.debugMsg(`realPath -> ${remotePath}`); | ||
this.sftp.realpath(remotePath, (err, absPath) => { | ||
if (err) { | ||
if (err.code === 2) { | ||
this.debugMsg('_realPath <- ""'); | ||
this.debugMsg('realPath <- ""'); | ||
resolve(''); | ||
} else { | ||
reject(this.fmtError(`${err.message} ${rPath}`, 'realPath', err.code)); | ||
this.debugMsg(`${err.message} ${remotePath}`, 'realPath'); | ||
reject(this.fmtError(`${err.message} ${remotePath}`, 'realPath', err.code)); | ||
} | ||
} | ||
this.debugMsg(`_realPath <- ${absPath}`); | ||
this.debugMsg(`realPath <- ${absPath}`); | ||
resolve(absPath); | ||
}); | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, 'realPath'); | ||
} | ||
}); | ||
} | ||
async realPath(remotePath) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'realPath'); | ||
haveConnection(this, 'realPath'); | ||
return await this._realPath(remotePath); | ||
} catch (e) { | ||
throw e.custom | ||
? e | ||
: this.fmtError(`${e.message} ${remotePath}`, 'realPath', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'realPath'); | ||
} | ||
} | ||
/** | ||
@@ -315,16 +280,19 @@ * @async | ||
/** | ||
* Retrieves attributes for path | ||
* Retrieves attributes for path using cmd, which is either | ||
* this.sftp.stat or this.sftp.lstat | ||
* | ||
* @param {Function} cmd - either this.sftp.stat or this.sftp.lstat | ||
* @param {String} remotePath - a string containing the path to a file | ||
* @param {Boolean} addListeners - (Optional) if true add event listeners. Default true. | ||
* @return {Promise<Object>} stats - attributes info | ||
*/ | ||
_stat(aPath) { | ||
_xstat(cmd, aPath, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.debugMsg(`_stat: ${aPath}`); | ||
this.sftp.stat(aPath, (err, stats) => { | ||
let cb = (err, stats) => { | ||
if (err) { | ||
if (err.code === 2 || err.code === 4) { | ||
reject(this.fmtError(`No such file: ${aPath}`, '_stat', errorCode.notexist)); | ||
reject(this.fmtError(`No such file: ${aPath}`, '_xstat', errorCode.notexist)); | ||
} else { | ||
reject(this.fmtError(`${err.message} ${aPath}`, '_stat', err.code)); | ||
reject(this.fmtError(`${err.message} ${aPath}`, '_xstat', err.code)); | ||
} | ||
@@ -347,23 +315,61 @@ } else { | ||
}; | ||
this.debugMsg('_stat: stats <- ', result); | ||
this.debugMsg('_xstat: result = ', result); | ||
resolve(result); | ||
} | ||
}); | ||
}; | ||
if (addListeners) { | ||
listeners = addTempListeners(this, '_xstat', reject); | ||
} | ||
if (cmd === 'stat') { | ||
this.sftp.stat(aPath, cb); | ||
} else { | ||
this.sftp.lstat(aPath, cb); | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, '_xstat'); | ||
} | ||
}); | ||
} | ||
/* | ||
* Use the stat command to obtain attributes associated with a remote path. | ||
* THe difference between stat and lstat is that stat, in the case of symbolic | ||
* links, will return the attributes associated with the target of the link. With | ||
* lstat, attributes associated with the symbolic link rather than the target are | ||
* returned. | ||
* | ||
* @param {String} remotePath - path to an object on the remote server | ||
* @return {Promise<Object>} stats - attributes info | ||
* | ||
*/ | ||
async stat(remotePath) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'stat'); | ||
haveConnection(this, 'stat'); | ||
const absPath = await normalizeRemotePath(this, remotePath); | ||
return await this._stat(absPath); | ||
return await this._xstat('stat', remotePath); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'stat', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'stat'); | ||
} | ||
} | ||
/* | ||
* Use the lstat command to obtain attributes associated with a remote path. | ||
* THe difference between stat and lstat is that stat, in the case of symbolic | ||
* links, will return the attributes associated with the target of the link. With | ||
* lstat, attributes associated with the symbolic link rather than the target are | ||
* returned. | ||
* | ||
* @param {String} remotePath - path to an object on the remote server | ||
* @return {Promise<Object>} stats - attributes info | ||
* | ||
*/ | ||
async lstat(remotePath) { | ||
try { | ||
haveConnection(this, 'lstat'); | ||
return await this._xstat('lstat', remotePath); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'lstat', err.code); | ||
} | ||
} | ||
/** | ||
@@ -380,25 +386,21 @@ * @async | ||
*/ | ||
async _exists(rPath) { | ||
async exists(remotePath) { | ||
this.debugMsg(`exists: remotePath = ${remotePath}`); | ||
try { | ||
const absPath = await normalizeRemotePath(this, rPath); | ||
this.debugMsg(`exists: ${rPath} -> ${absPath}`); | ||
const info = await this._stat(absPath); | ||
if (remotePath === '.') { | ||
return 'd'; | ||
} | ||
const info = await this.lstat(remotePath); | ||
this.debugMsg('exists: <- ', info); | ||
if (info.isDirectory) { | ||
this.debugMsg(`exists: ${rPath} = d`); | ||
return 'd'; | ||
} | ||
if (info.isSymbolicLink) { | ||
this.debugMsg(`exists: ${rPath} = l`); | ||
} else if (info.isSymbolicLink) { | ||
return 'l'; | ||
} | ||
if (info.isFile) { | ||
this.debugMsg(`exists: ${rPath} = -`); | ||
} else if (info.isFile) { | ||
return '-'; | ||
} else { | ||
return false; | ||
} | ||
this.debugMsg(`exists: ${rPath} = false`); | ||
return false; | ||
} catch (err) { | ||
if (err.code === errorCode.notexist) { | ||
this.debugMsg(`exists: ${rPath} = false errorCode = ${err.code}`); | ||
return false; | ||
@@ -410,18 +412,2 @@ } | ||
async exists(remotePath) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'exists'); | ||
haveConnection(this, 'exists'); | ||
if (remotePath === '.') { | ||
return 'd'; | ||
} | ||
return await this._exists(remotePath); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'exists', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'exists'); | ||
} | ||
} | ||
/** | ||
@@ -438,51 +424,49 @@ * @async | ||
* @param {function} filter - a filter function used to select return entries | ||
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true | ||
* @returns {Promise<Array>} array of file description objects | ||
*/ | ||
_list(remotePath, filter) { | ||
list(remotePath, filter, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.readdir(remotePath, (err, fileList) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} ${remotePath}`, 'list', err.code)); | ||
} else { | ||
const reg = /-/gi; | ||
const newList = fileList.map((item) => { | ||
return { | ||
type: item.longname.slice(0, 1), | ||
name: item.filename, | ||
size: item.attrs.size, | ||
modifyTime: item.attrs.mtime * 1000, | ||
accessTime: item.attrs.atime * 1000, | ||
rights: { | ||
user: item.longname.slice(1, 4).replace(reg, ''), | ||
group: item.longname.slice(4, 7).replace(reg, ''), | ||
other: item.longname.slice(7, 10).replace(reg, ''), | ||
}, | ||
owner: item.attrs.uid, | ||
group: item.attrs.gid, | ||
longname: item.longname, | ||
}; | ||
}); | ||
if (filter) { | ||
resolve(newList.filter((item) => filter(item))); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'list', reject); | ||
} | ||
if (haveConnection(this, 'list', reject)) { | ||
this.sftp.readdir(remotePath, (err, fileList) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} ${remotePath}`, 'list', err.code)); | ||
} else { | ||
resolve(newList); | ||
const reg = /-/gi; | ||
const newList = fileList.map((item) => { | ||
return { | ||
type: item.longname.slice(0, 1), | ||
name: item.filename, | ||
size: item.attrs.size, | ||
modifyTime: item.attrs.mtime * 1000, | ||
accessTime: item.attrs.atime * 1000, | ||
rights: { | ||
user: item.longname.slice(1, 4).replace(reg, ''), | ||
group: item.longname.slice(4, 7).replace(reg, ''), | ||
other: item.longname.slice(7, 10).replace(reg, ''), | ||
}, | ||
owner: item.attrs.uid, | ||
group: item.attrs.gid, | ||
longname: item.longname, | ||
}; | ||
}); | ||
if (filter) { | ||
resolve(newList.filter((item) => filter(item))); | ||
} else { | ||
resolve(newList); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, 'list'); | ||
} | ||
}); | ||
} | ||
async list(remotePath, filter) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'list'); | ||
haveConnection(this, 'list'); | ||
return await this._list(remotePath, filter); | ||
} catch (e) { | ||
throw e.custom ? e : this.fmtError(`${e.message} ${remotePath}`, 'list', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'list'); | ||
} | ||
} | ||
/** | ||
@@ -500,2 +484,3 @@ * get file | ||
* writeStreamOptions and pipeOptions. | ||
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true | ||
* | ||
@@ -508,79 +493,74 @@ * *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls | ||
*/ | ||
_get(rPath, dst, opts) { | ||
let rdr, wtr; | ||
get(remotePath, dst, options, addListeners = true) { | ||
let listeners, rdr, wtr; | ||
return new Promise((resolve, reject) => { | ||
opts = { | ||
...opts, | ||
readStreamOptions: { autoClose: true }, | ||
writeStreamOptions: { autoClose: true }, | ||
pipeOptions: { end: true }, | ||
}; | ||
rdr = this.sftp.createReadStream(rPath, opts.readStreamOptions); | ||
rdr.once('error', (err) => { | ||
if (dst && typeof dst !== 'string' && !dst.destroyed) { | ||
dst.destroy(); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'get', reject); | ||
} | ||
if (haveConnection(this, 'get', reject)) { | ||
options = { | ||
readStreamOptions: { ...options?.readStreamOptions, autoClose: true }, | ||
writeStreamOptions: { ...options?.writeStreamOptions, autoClose: true }, | ||
pipeOptions: { ...options?.pipeOptions, end: true }, | ||
}; | ||
rdr = this.sftp.createReadStream(remotePath, options.readStreamOptions); | ||
rdr.once('error', (err) => { | ||
if (dst && typeof dst !== 'string' && !dst.destroyed) { | ||
dst.destroy(); | ||
} | ||
reject(this.fmtError(`${err.message} ${remotePath}`, 'get', err.code)); | ||
}); | ||
if (dst === undefined) { | ||
// no dst specified, return buffer of data | ||
this.debugMsg('get resolving buffer of data'); | ||
wtr = concat((buff) => { | ||
resolve(buff); | ||
}); | ||
} else if (typeof dst === 'string') { | ||
// dst local file path | ||
this.debugMsg('get returning local file'); | ||
const localCheck = haveLocalCreate(dst); | ||
if (!localCheck.status) { | ||
reject( | ||
this.fmtError( | ||
`Bad path: ${dst}: ${localCheck.details}`, | ||
'get', | ||
localCheck.code | ||
) | ||
); | ||
return; | ||
} else { | ||
wtr = fs.createWriteStream(dst, options.writeStreamOptions); | ||
} | ||
} else { | ||
this.debugMsg('get: returning data into supplied stream'); | ||
wtr = dst; | ||
} | ||
reject(this.fmtError(`${err.message} ${rPath}`, '_get', err.code)); | ||
}); | ||
if (dst === undefined) { | ||
// no dst specified, return buffer of data | ||
this.debugMsg('get resolving buffer of data'); | ||
wtr = concat((buff) => { | ||
resolve(buff); | ||
}); | ||
} else if (typeof dst === 'string') { | ||
// dst local file path | ||
this.debugMsg('get returning local file'); | ||
const localCheck = haveLocalCreate(dst); | ||
if (!localCheck.status) { | ||
wtr.once('error', (err) => { | ||
reject( | ||
this.fmtError( | ||
`Bad path: ${dst}: ${localCheck.details}`, | ||
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`, | ||
'get', | ||
localCheck.code | ||
err.code | ||
) | ||
); | ||
return; | ||
} else { | ||
wtr = fs.createWriteStream(dst, opts.writeStreamOptions); | ||
} | ||
} else { | ||
this.debugMsg('get: returning data into supplied stream'); | ||
wtr = dst; | ||
}); | ||
rdr.once('end', () => { | ||
if (typeof dst === 'string') { | ||
this.debugMsg('get: resolving with dst filename'); | ||
resolve(dst); | ||
} else if (dst !== undefined) { | ||
this.debugMsg('get: resolving with writer stream object'); | ||
resolve(wtr); | ||
} | ||
}); | ||
rdr.pipe(wtr, options.pipeOptions); | ||
} | ||
wtr.once('error', (err) => { | ||
reject( | ||
this.fmtError( | ||
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`, | ||
'get', | ||
err.code | ||
) | ||
); | ||
}); | ||
rdr.once('end', () => { | ||
if (typeof dst === 'string') { | ||
this.debugMsg('get: resolving with dst filename'); | ||
resolve(dst); | ||
} else if (dst !== undefined) { | ||
this.debugMsg('get: resolving with writer stream object'); | ||
resolve(wtr); | ||
} | ||
}); | ||
rdr.pipe(wtr, opts.pipeOptions); | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, 'get'); | ||
} | ||
}); | ||
} | ||
async get(remotePath, dst, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'get'); | ||
haveConnection(this, 'get'); | ||
return await this._get(remotePath, dst, options); | ||
} catch (e) { | ||
throw e.custom ? e : this.fmtError(`${e.message} ${remotePath}`, 'get', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'get'); | ||
} | ||
} | ||
/** | ||
@@ -596,10 +576,20 @@ * Use SSH2 fastGet for downloading the file. | ||
*/ | ||
_fastGet(rPath, lPath, opts) { | ||
_fastGet(rPath, lPath, opts, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.fastGet(rPath, lPath, opts, (err) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} Remote: ${rPath} Local: ${lPath}`)); | ||
} | ||
resolve(`${rPath} was successfully download to ${lPath}!`); | ||
}); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, '_fastGet', reject); | ||
} | ||
if (haveConnection(this, '_fastGet', reject)) { | ||
this.sftp.fastGet(rPath, lPath, opts, (err) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} Remote: ${rPath} Local: ${lPath}`)); | ||
} | ||
resolve(`${rPath} was successfully download to ${lPath}!`); | ||
}); | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, '_fastGet'); | ||
} | ||
}); | ||
@@ -609,6 +599,3 @@ } | ||
async fastGet(remotePath, localPath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'fastGet'); | ||
haveConnection(this, 'fastGet'); | ||
const ftype = await this.exists(remotePath); | ||
@@ -630,4 +617,2 @@ if (ftype !== '-') { | ||
throw this.fmtError(err, 'fastGet'); | ||
} finally { | ||
removeTempListeners(this, listeners, 'fastGet'); | ||
} | ||
@@ -644,21 +629,32 @@ } | ||
* | ||
* @param {String} localPath | ||
* @param {String} remotePath | ||
* @param {Object} options | ||
* @param {String} localPath - path to local file to put | ||
* @param {String} remotePath - destination path for put file | ||
* @param {Object} options - additonal fastPut options | ||
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true. | ||
* @return {Promise<String>} the result of downloading the file | ||
*/ | ||
_fastPut(lPath, rPath, opts) { | ||
_fastPut(lPath, rPath, opts, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.fastPut(lPath, rPath, opts, (err) => { | ||
if (err) { | ||
reject( | ||
this.fmtError( | ||
`${err.message} Local: ${lPath} Remote: ${rPath}`, | ||
'fastPut', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`${lPath} was successfully uploaded to ${rPath}!`); | ||
}); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, '_fastPut', reject); | ||
} | ||
if (haveConnection(this, '_fastPut', reject)) { | ||
this.sftp.fastPut(lPath, rPath, opts, (err) => { | ||
if (err) { | ||
reject( | ||
this.fmtError( | ||
`${err.message} Local: ${lPath} Remote: ${rPath}`, | ||
'fastPut', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`${lPath} was successfully uploaded to ${rPath}!`); | ||
}); | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, '_fastPut'); | ||
} | ||
}); | ||
@@ -668,7 +664,4 @@ } | ||
async fastPut(localPath, remotePath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'fastPut'); | ||
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`); | ||
haveConnection(this, 'fastPut'); | ||
const localCheck = haveLocalAccess(localPath); | ||
@@ -691,4 +684,2 @@ if (!localCheck.status) { | ||
throw e.custom ? e : this.fmtError(e.message, 'fastPut', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'fastPut'); | ||
} | ||
@@ -714,37 +705,49 @@ } | ||
*/ | ||
_put(lPath, rPath, opts) { | ||
let wtr, rdr; | ||
_put(lPath, rPath, opts, addListeners = true) { | ||
let listeners, wtr, rdr; | ||
return new Promise((resolve, reject) => { | ||
if (addListeners) { | ||
listeners = addTempListeners(this, '_put', reject); | ||
} | ||
opts = { | ||
...opts, | ||
readStreamOptions: { autoClose: true }, | ||
writeStreamOptions: { autoClose: true }, | ||
pipeOptions: { end: true }, | ||
readStreamOptions: { ...opts?.readStreamOptions, autoClose: true }, | ||
writeStreamOptions: { ...opts?.writeStreamOptions, autoClose: true }, | ||
pipeOptions: { ...opts?.pipeOptions, end: true }, | ||
}; | ||
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions); | ||
wtr.once('error', (err) => { | ||
reject(this.fmtError(`${err.message} ${rPath}`, 'put', err.code)); | ||
}); | ||
wtr.once('close', () => { | ||
resolve(`Uploaded data stream to ${rPath}`); | ||
}); | ||
if (lPath instanceof Buffer) { | ||
this.debugMsg('put source is a buffer'); | ||
wtr.end(lPath); | ||
} else { | ||
rdr = | ||
typeof lPath === 'string' | ||
? fs.createReadStream(lPath, opts.readStreamOptions) | ||
: lPath; | ||
rdr.once('error', (err) => { | ||
if (haveConnection(this, '_put', reject)) { | ||
wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions); | ||
wtr.once('error', (err) => { | ||
reject( | ||
this.fmtError( | ||
`${err.message} ${typeof lPath === 'string' ? lPath : '<stream>'}`, | ||
'_put', | ||
err.code | ||
) | ||
this.fmtError(`Write stream error: ${err.message} ${rPath}`, '_put', err.code) | ||
); | ||
}); | ||
rdr.pipe(wtr, opts.pipeOptions); | ||
wtr.once('close', () => { | ||
resolve(`Uploaded data stream to ${rPath}`); | ||
}); | ||
if (lPath instanceof Buffer) { | ||
this.debugMsg('put source is a buffer'); | ||
wtr.end(lPath); | ||
} else { | ||
rdr = | ||
typeof lPath === 'string' | ||
? fs.createReadStream(lPath, opts.readStreamOptions) | ||
: lPath; | ||
rdr.once('error', (err) => { | ||
reject( | ||
this.fmtError( | ||
`Read stream error: ${err.message} ${ | ||
typeof lPath === 'string' ? lPath : '<stream>' | ||
}`, | ||
'_put', | ||
err.code | ||
) | ||
); | ||
}); | ||
rdr.pipe(wtr, opts.pipeOptions); | ||
} | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, '_put'); | ||
} | ||
}); | ||
@@ -754,6 +757,3 @@ } | ||
async put(localSrc, remotePath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'put'); | ||
haveConnection(this, 'put'); | ||
if (typeof localSrc === 'string') { | ||
@@ -771,5 +771,3 @@ const localCheck = haveLocalAccess(localSrc); | ||
} catch (e) { | ||
throw e.custom ? e : this.fmtError(e.message, 'put', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'put'); | ||
throw e.custom ? e : this.fmtError(`Re-thrown: ${e.message}`, 'put', e.code); | ||
} | ||
@@ -786,19 +784,29 @@ } | ||
*/ | ||
_append(input, rPath, opts) { | ||
_append(input, rPath, opts, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.debugMsg(`append -> remote: ${rPath} `, opts); | ||
opts.flags = 'a'; | ||
const stream = this.sftp.createWriteStream(rPath, opts); | ||
stream.on('error', (err) => { | ||
reject(this.fmtError(`${err.message} ${rPath}`, 'append', err.code)); | ||
}); | ||
stream.on('close', () => { | ||
resolve(`Appended data to ${rPath}`); | ||
}); | ||
if (input instanceof Buffer) { | ||
stream.write(input); | ||
stream.end(); | ||
} else { | ||
input.pipe(stream); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, '_append', reject); | ||
} | ||
if (haveConnection(this, '_append', reject)) { | ||
this.debugMsg(`append -> remote: ${rPath} `, opts); | ||
opts.flags = 'a'; | ||
const stream = this.sftp.createWriteStream(rPath, opts); | ||
stream.on('error', (err) => { | ||
reject(this.fmtError(`${err.message} ${rPath}`, 'append', err.code)); | ||
}); | ||
stream.on('close', () => { | ||
resolve(`Appended data to ${rPath}`); | ||
}); | ||
if (input instanceof Buffer) { | ||
stream.write(input); | ||
stream.end(); | ||
} else { | ||
input.pipe(stream); | ||
} | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, '_append'); | ||
} | ||
}); | ||
@@ -808,5 +816,3 @@ } | ||
async append(input, remotePath, options = {}) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'append'); | ||
if (typeof input === 'string') { | ||
@@ -819,3 +825,2 @@ throw this.fmtError( | ||
} | ||
haveConnection(this, 'append'); | ||
const fileType = await this.exists(remotePath); | ||
@@ -832,4 +837,2 @@ if (fileType && fileType === 'd') { | ||
throw e.custom ? e : this.fmtError(e.message, 'append', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'append'); | ||
} | ||
@@ -847,4 +850,8 @@ } | ||
*/ | ||
_doMkdir(p) { | ||
_doMkdir(p, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
if (addListeners) { | ||
listeners = addTempListeners(this, '_doMkdir', reject); | ||
} | ||
this.sftp.mkdir(p, (err) => { | ||
@@ -876,2 +883,6 @@ if (err) { | ||
}); | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, '_doMkdir'); | ||
} | ||
}); | ||
@@ -918,5 +929,3 @@ } | ||
async mkdir(remotePath, recursive = false) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, '_mkdir'); | ||
haveConnection(this, 'mkdir'); | ||
@@ -926,4 +935,2 @@ return await this._mkdir(remotePath, recursive); | ||
throw this.fmtError(`${err.message}`, 'mkdir', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'append'); | ||
} | ||
@@ -942,65 +949,81 @@ } | ||
*/ | ||
async rmdir(remotePath, recursive = false) { | ||
const _rmdir = (p) => { | ||
async rmdir(remoteDir, recursive = false) { | ||
const _rmdir = (dir) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.debugMsg(`rmdir -> ${p}`); | ||
this.sftp.rmdir(p, (err) => { | ||
listeners = addTempListeners(this, '_rmdir', reject); | ||
this.debugMsg(`_rmdir: dir = ${dir}`); | ||
this.sftp.rmdir(dir, (err) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} ${p}`, 'rmdir', err.code)); | ||
reject(this.fmtError(`${err.message} ${dir}`, 'rmdir', err.code)); | ||
} | ||
resolve('Successfully removed directory'); | ||
}); | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, '_rmdir'); | ||
}); | ||
}; | ||
const _dormdir = async (p, recur) => { | ||
try { | ||
if (recur) { | ||
const list = await this.list(p); | ||
if (list.length) { | ||
const files = list.filter((item) => item.type !== 'd'); | ||
const dirs = list.filter((item) => item.type === 'd'); | ||
this.debugMsg('rmdir contents (files): ', files); | ||
this.debugMsg('rmdir contents (dirs): ', dirs); | ||
for (const d of dirs) { | ||
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true); | ||
} | ||
const promiseList = []; | ||
for (const f of files) { | ||
promiseList.push(this._delete(`${p}${this.remotePathSep}${f.name}`)); | ||
} | ||
await Promise.all(promiseList); | ||
} | ||
const _delFiles = (path, fileList) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, '_delFiles', reject); | ||
this.debugMsg(`_delFiles: path = ${path} fileList = ${fileList}`); | ||
let pList = []; | ||
for (const f of fileList) { | ||
pList.push(this.delete(`${path}/${f.name}`, true, false)); | ||
} | ||
return await _rmdir(p); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, '_dormdir', err.code); | ||
} | ||
resolve(pList); | ||
}) | ||
.then((p) => { | ||
return Promise.all(p); | ||
}) | ||
.finally(() => { | ||
removeTempListeners(this, listeners, '_delFiles'); | ||
}); | ||
}; | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'rmdir'); | ||
haveConnection(this, 'rmdir'); | ||
const absPath = await normalizeRemotePath(this, remotePath); | ||
const dirStatus = await this.exists(absPath); | ||
if (dirStatus && dirStatus !== 'd') { | ||
this.debugMsg(`rmdir: dir = ${remoteDir} recursive = ${recursive}`); | ||
let absPath = await normalizeRemotePath(this, remoteDir); | ||
let existStatus = await this.exists(absPath); | ||
this.debugMsg(`rmdir: ${absPath} existStatus = ${existStatus}`); | ||
if (!existStatus) { | ||
throw this.fmtError( | ||
`Bad path: ${absPath} not a directory`, | ||
`Bad Path: ${remoteDir}: No such directory`, | ||
'rmdir', | ||
errorCode.badPath | ||
); | ||
} else if (!dirStatus) { | ||
} | ||
if (existStatus !== 'd') { | ||
throw this.fmtError( | ||
`Bad path: ${absPath} No such file`, | ||
`Bad Path: ${remoteDir}: Not a directory`, | ||
'rmdir', | ||
errorCode.badPath | ||
); | ||
} else { | ||
return await _dormdir(absPath, recursive); | ||
} | ||
if (!recursive) { | ||
this.debugMsg('rmdir: non-recursive - just try to remove it'); | ||
return await _rmdir(absPath); | ||
} | ||
let listing = await this.list(absPath); | ||
this.debugMsg(`rmdir: listing count = ${listing.length}`); | ||
if (!listing.length) { | ||
this.debugMsg('rmdir: No sub dir or files, just rmdir'); | ||
return await _rmdir(absPath); | ||
} | ||
let fileList = listing.filter((i) => i.type !== 'd'); | ||
this.debugMsg(`rmdir: dir content files to remove = ${fileList.length}`); | ||
let dirList = listing.filter((i) => i.type === 'd'); | ||
this.debugMsg(`rmdir: sub-directories to remove = ${dirList.length}`); | ||
await _delFiles(absPath, fileList); | ||
for (const d of dirList) { | ||
await this.rmdir(`${absPath}/${d.name}`, true); | ||
} | ||
await _rmdir(absPath); | ||
return 'Successfully removed directory'; | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err.message, 'rmdir', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'rmdir'); | ||
throw err.custom | ||
? err | ||
: this.fmtError(`${err.message} ${remoteDir}`, 'rmdir', err.code); | ||
} | ||
@@ -1020,30 +1043,25 @@ } | ||
*/ | ||
_delete(rPath, notFoundOK) { | ||
delete(remotePath, notFoundOK = false, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.unlink(rPath, (err) => { | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'delete', reject); | ||
} | ||
this.sftp.unlink(remotePath, (err) => { | ||
if (err) { | ||
if (notFoundOK && err.code === 2) { | ||
resolve(`Successfully deleted ${rPath}`); | ||
resolve(`Successfully deleted ${remotePath}`); | ||
} else { | ||
reject(this.fmtError(`${err.message} ${rPath}`, 'delete', err.code)); | ||
reject(this.fmtError(`${err.message} ${remotePath}`, 'delete', err.code)); | ||
} | ||
} | ||
resolve(`Successfully deleted ${rPath}`); | ||
resolve(`Successfully deleted ${remotePath}`); | ||
}); | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, 'delete'); | ||
} | ||
}); | ||
} | ||
async delete(remotePath, notFoundOK = false) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'delete'); | ||
haveConnection(this, 'delete'); | ||
return await this._delete(remotePath, notFoundOK); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err.message, 'delete', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'delete'); | ||
} | ||
} | ||
/** | ||
@@ -1056,2 +1074,3 @@ * @async | ||
* @param {string} toPath - path to the new name. | ||
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true | ||
* | ||
@@ -1061,34 +1080,29 @@ * @return {Promise<String>} | ||
*/ | ||
_rename(fPath, tPath) { | ||
rename(fPath, tPath, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.rename(fPath, tPath, (err) => { | ||
if (err) { | ||
reject( | ||
this.fmtError( | ||
`${err.message} From: ${fPath} To: ${tPath}`, | ||
'_rename', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`Successfully renamed ${fPath} to ${tPath}`); | ||
}); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'rename', reject); | ||
} | ||
if (haveConnection(this, 'rename', reject)) { | ||
this.sftp.rename(fPath, tPath, (err) => { | ||
if (err) { | ||
reject( | ||
this.fmtError( | ||
`${err.message} From: ${fPath} To: ${tPath}`, | ||
'_rename', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`Successfully renamed ${fPath} to ${tPath}`); | ||
}); | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, 'rename'); | ||
} | ||
}); | ||
} | ||
async rename(fromPath, toPath) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'rename'); | ||
haveConnection(this, 'rename'); | ||
return await this._rename(fromPath, toPath); | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(`${err.message} ${fromPath} ${toPath}`, 'rename', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'rename'); | ||
} | ||
} | ||
/** | ||
@@ -1102,2 +1116,3 @@ * @async | ||
* @param {string} toPath - path the new name. | ||
* @param {Boolean} addListeners - (Optional) if true, add listeners. Default true | ||
* | ||
@@ -1107,34 +1122,27 @@ * @return {Promise<String>} | ||
*/ | ||
_posixRename(fPath, tPath) { | ||
posixRename(fPath, tPath, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.ext_openssh_rename(fPath, tPath, (err) => { | ||
if (err) { | ||
reject( | ||
this.fmtError( | ||
`${err.message} From: ${fPath} To: ${tPath}`, | ||
'_posixRename', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`Successful POSIX rename ${fPath} to ${tPath}`); | ||
}); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'posixRename', reject); | ||
} | ||
if (haveConnection(this, 'posixRename', reject)) { | ||
this.sftp.ext_openssh_rename(fPath, tPath, (err) => { | ||
if (err) { | ||
reject( | ||
this.fmtError( | ||
`${err.message} From: ${fPath} To: ${tPath}`, | ||
'_posixRename', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`Successful POSIX rename ${fPath} to ${tPath}`); | ||
}); | ||
} | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, 'posixRename'); | ||
}); | ||
} | ||
async posixRename(fromPath, toPath) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'posixRename'); | ||
haveConnection(this, 'posixRename'); | ||
return await this._posixRename(fromPath, toPath); | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(`${err.message} ${fromPath} ${toPath}`, 'posixRename', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'posixRename'); | ||
} | ||
} | ||
/** | ||
@@ -1147,31 +1155,27 @@ * @async | ||
* @param {number | string} mode - the new octal mode to set | ||
* @param {boolean} addListeners - (Optional) if true, add listeners. Default true. | ||
* | ||
* @return {Promise<String>} | ||
*/ | ||
_chmod(rPath, mode) { | ||
chmod(rPath, mode, addListeners = true) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
this.sftp.chmod(rPath, mode, (err) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code)); | ||
} | ||
resolve('Successfully change file mode'); | ||
}); | ||
if (addListeners) { | ||
listeners = addTempListeners(this, 'chmod', reject); | ||
} | ||
if (haveConnection(this, 'chmod', reject)) { | ||
this.sftp.chmod(rPath, mode, (err) => { | ||
if (err) { | ||
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code)); | ||
} | ||
resolve('Successfully change file mode'); | ||
}); | ||
} | ||
}).finally(() => { | ||
if (addListeners) { | ||
removeTempListeners(this, listeners, 'chmod'); | ||
} | ||
}); | ||
} | ||
async chmod(remotePath, mode) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'chmod'); | ||
haveConnection(this, 'chmod'); | ||
return await this._chmod(remotePath, mode); | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(`${err.message} ${remotePath}`, 'chmod', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'chmod'); | ||
} | ||
} | ||
/** | ||
@@ -1186,5 +1190,6 @@ * @async | ||
* @param {Object} options - (Optional) An object with 2 supported properties, | ||
* 'filter' and 'useFastput'. The first argument is the full path of the item | ||
* 'filter' and 'useFastput'. Filter is a function of two arguments. | ||
* The first argument is the full path of a directory entry from the directory | ||
* to be uploaded and the second argument is a boolean, which will be true if | ||
* the target path is for a directory. If the function returns true, the item | ||
* the target path is for a directory. If the function returns true, this item | ||
* will be uploaded and excluded when it returns false. The 'useFastput' property is a | ||
@@ -1194,8 +1199,19 @@ * boolean value. When true, the 'fastPut()' method will be used to upload files. Default | ||
* | ||
* @returns {Promise<String>} | ||
* @returns {Promise<Array>} | ||
*/ | ||
async _uploadDir(srcDir, dstDir, options) { | ||
try { | ||
const absDstDir = await normalizeRemotePath(this, dstDir); | ||
this.debugMsg(`uploadDir <- SRC = ${srcDir} DST = ${absDstDir}`); | ||
async uploadDir(srcDir, dstDir, options) { | ||
const getRemoteStatus = async (dstDir) => { | ||
let absDstDir = await normalizeRemotePath(this, dstDir); | ||
let status = await this.exists(absDstDir); | ||
if (status && status !== 'd') { | ||
throw this.fmtError( | ||
`Bad path ${absDstDir} Not a directory`, | ||
'getRemoteStatus', | ||
errorCode.badPath | ||
); | ||
} | ||
return { remoteDir: absDstDir, remoteStatus: status }; | ||
}; | ||
const checkLocalStatus = (srcDir) => { | ||
const srcType = localExists(srcDir); | ||
@@ -1205,3 +1221,3 @@ if (!srcType) { | ||
`Bad path: ${srcDir} not exist`, | ||
'_uploadDir', | ||
'getLocalStatus', | ||
errorCode.badPath | ||
@@ -1213,17 +1229,49 @@ ); | ||
`Bad path: ${srcDir}: not a directory`, | ||
'_uploadDir', | ||
'getLocalStatus', | ||
errorCode.badPath | ||
); | ||
} | ||
const dstStatus = await this.exists(absDstDir); | ||
if (dstStatus && dstStatus !== 'd') { | ||
throw this.fmtError( | ||
`Bad path ${absDstDir} Not a directory`, | ||
'_uploadDir', | ||
errorCode.badPath | ||
); | ||
return srcType; | ||
}; | ||
const uploadFiles = (srcDir, dstDir, fileList, useFastput) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'uploadFiles', reject); | ||
let uploads = []; | ||
for (const f of fileList) { | ||
const newSrc = join(srcDir, f.name); | ||
const newDst = `${dstDir}/${f.name}`; | ||
if (f.isFile()) { | ||
if (useFastput) { | ||
uploads.push(this._fastPut(newSrc, newDst, null, false)); | ||
} else { | ||
uploads.push(this._put(newSrc, newDst, null, false)); | ||
} | ||
this.client.emit('upload', { source: newSrc, destination: newDst }); | ||
} else { | ||
this.debugMsg(`uploadFiles: File ignored: ${f.name} not a regular file`); | ||
} | ||
} | ||
resolve(Promise.all(uploads)); | ||
}) | ||
.then((pList) => { | ||
return Promise.all(pList); | ||
}) | ||
.finally(() => { | ||
removeTempListeners(this, listeners, uploadFiles); | ||
}); | ||
}; | ||
try { | ||
haveConnection(this, 'uploadDir'); | ||
this.debugMsg( | ||
`uploadDir: srcDir = ${srcDir} dstDir = ${dstDir} options = ${options}` | ||
); | ||
let { remoteDir, remoteStatus } = await getRemoteStatus(dstDir); | ||
this.debugMsg(`uploadDir: remoteDir = ${remoteDir} remoteStatus = ${remoteStatus}`); | ||
checkLocalStatus(srcDir); | ||
if (!remoteStatus) { | ||
await this._mkdir(remoteDir, true); | ||
} | ||
if (!dstStatus) { | ||
await this._mkdir(absDstDir, true); | ||
} | ||
let dirEntries = fs.readdirSync(srcDir, { | ||
@@ -1233,2 +1281,3 @@ encoding: 'utf8', | ||
}); | ||
this.debugMsg(`uploadDir: dirEntries = ${dirEntries}`); | ||
if (options?.filter) { | ||
@@ -1239,42 +1288,20 @@ dirEntries = dirEntries.filter((item) => | ||
} | ||
let fileUploads = []; | ||
for (const e of dirEntries) { | ||
const newSrc = join(srcDir, e.name); | ||
const newDst = `${absDstDir}${this.remotePathSep}${e.name}`; | ||
if (e.isDirectory()) { | ||
await this.uploadDir(newSrc, newDst, options); | ||
} else if (e.isFile()) { | ||
if (options?.useFastput) { | ||
fileUploads.push(this._fastPut(newSrc, newDst)); | ||
} else { | ||
fileUploads.push(this._put(newSrc, newDst)); | ||
} | ||
this.client.emit('upload', { source: newSrc, destination: newDst }); | ||
} else { | ||
this.debugMsg(`uploadDir: File ignored: ${e.name} not a regular file`); | ||
} | ||
await Promise.all(fileUploads); | ||
let dirUploads = dirEntries.filter((item) => item.isDirectory()); | ||
let fileUploads = dirEntries.filter((item) => !item.isDirectory()); | ||
this.debugMsg(`uploadDir: dirUploads = ${dirUploads}`); | ||
this.debugMsg(`uploadDir: fileUploads = ${fileUploads}`); | ||
await uploadFiles(srcDir, remoteDir, fileUploads, options?.useFastput); | ||
for (const d of dirUploads) { | ||
let src = join(srcDir, d.name); | ||
let dst = `${remoteDir}/${d.name}`; | ||
await this.uploadDir(src, dst, options); | ||
} | ||
return `${srcDir} uploaded to ${absDstDir}`; | ||
return `${srcDir} uploaded to ${dstDir}`; | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(`${err.message} ${srcDir}`, '_uploadDir', err.code); | ||
: this.fmtError(`${err.message} ${srcDir}`, 'uploadDir', err.code); | ||
} | ||
} | ||
async uploadDir(srcDir, dstDir, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'uploadDir'); | ||
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`); | ||
haveConnection(this, 'uploadDir'); | ||
return await this._uploadDir(srcDir, dstDir, options); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'uploadDir'); | ||
} finally { | ||
removeTempListeners(this, listeners, 'chmod'); | ||
} | ||
} | ||
/** | ||
@@ -1295,49 +1322,79 @@ * @async | ||
* | ||
* @returns {Promise<String>} | ||
* @returns {Promise<Array>} | ||
*/ | ||
async _downloadDir(srcDir, dstDir, options) { | ||
try { | ||
let fileList = await this._list(srcDir); | ||
if (options?.filter) { | ||
fileList = fileList.filter((item) => | ||
options.filter( | ||
`${srcDir}${this.remotePathSep}${item.name}`, | ||
item.type === 'd' ? true : false | ||
) | ||
); | ||
async downloadDir(srcDir, dstDir, options = { filter: null, useFastget: false }) { | ||
const _getDownloadList = async (srcDir, filter) => { | ||
try { | ||
let listing = await this.list(srcDir); | ||
if (filter) { | ||
return listing.filter((item) => | ||
filter(`${srcDir}/${item.name}`, item.type === 'd') | ||
); | ||
} | ||
return listing; | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err.message, '_getDownloadList', err.code); | ||
} | ||
const localCheck = haveLocalCreate(dstDir); | ||
if (!localCheck.status && localCheck.details === 'permission denied') { | ||
throw this.fmtError( | ||
`Bad path: ${dstDir}: ${localCheck.details}`, | ||
'downloadDir', | ||
localCheck.code | ||
); | ||
} else if (localCheck.status && !localCheck.type) { | ||
fs.mkdirSync(dstDir, { recursive: true }); | ||
} else if (localCheck.status && localCheck.type !== 'd') { | ||
throw this.fmtError( | ||
`Bad path: ${dstDir}: not a directory`, | ||
'downloadDir', | ||
errorCode.badPath | ||
); | ||
}; | ||
const _prepareDestination = (dst) => { | ||
try { | ||
const localCheck = haveLocalCreate(dst); | ||
if (!localCheck.status && localCheck.details === 'permission denied') { | ||
throw this.fmtError( | ||
`Bad path: ${dst}: ${localCheck.details}`, | ||
'prepareDestination', | ||
localCheck.code | ||
); | ||
} else if (localCheck.status && !localCheck.type) { | ||
fs.mkdirSync(dst, { recursive: true }); | ||
} else if (localCheck.status && localCheck.type !== 'd') { | ||
throw this.fmtError( | ||
`Bad path: ${dstDir}: not a directory`, | ||
'_prepareDestination', | ||
errorCode.badPath | ||
); | ||
} | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(err.message, '_prepareDestination', err.code); | ||
} | ||
let downloadFiles = []; | ||
for (const f of fileList) { | ||
const newSrc = `${srcDir}${this.remotePathSep}${f.name}`; | ||
const newDst = join(dstDir, f.name); | ||
if (f.type === 'd') { | ||
await this._downloadDir(newSrc, newDst, options); | ||
} else if (f.type === '-') { | ||
if (options?.useFasget) { | ||
downloadFiles.push(this._fastGet(newSrc, newDst)); | ||
}; | ||
const _downloadFiles = (remotePath, localPath, fileList, useFastget) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, '_downloadFIles', reject); | ||
let pList = []; | ||
for (const f of fileList) { | ||
let src = `${remotePath}/${f.name}`; | ||
let dst = join(localPath, f.name); | ||
if (useFastget) { | ||
pList.push(this.fastGet(src, dst, false)); | ||
} else { | ||
downloadFiles.push(this._get(newSrc, newDst)); | ||
pList.push(this.get(src, dst, false)); | ||
} | ||
this.client.emit('download', { source: newSrc, destination: newDst }); | ||
} else { | ||
this.debugMsg(`downloadDir: File ignored: ${f.name} not regular file`); | ||
this.client.emit('download', { source: src, destination: dst }); | ||
} | ||
return resolve(Promise.all(pList)); | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, '_downloadFiles'); | ||
}); | ||
}; | ||
try { | ||
haveConnection(this, 'downloadDir'); | ||
let downloadList = await _getDownloadList(srcDir, options.filter); | ||
_prepareDestination(dstDir); | ||
let fileDownloads = downloadList.filter((i) => i.type !== 'd'); | ||
if (fileDownloads.length) { | ||
await _downloadFiles(srcDir, dstDir, fileDownloads, options.useFastget); | ||
} | ||
await Promise.all(downloadFiles); | ||
let dirDownloads = downloadList.filter((i) => i.type === 'd'); | ||
for (const d of dirDownloads) { | ||
let src = `${srcDir}/${d.name}`; | ||
let dst = join(dstDir, d.name); | ||
await this.downloadDir(src, dst); | ||
} | ||
return `${srcDir} downloaded to ${dstDir}`; | ||
@@ -1347,19 +1404,6 @@ } catch (err) { | ||
? err | ||
: this.fmtError(`${err.message} ${srcDir}`, '_downloadDir', err.code); | ||
: this.fmtError(`${err.message}: ${srcDir}`, 'downloadDir', err.code); | ||
} | ||
} | ||
async downloadDir(srcDir, dstDir, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'downloadDir'); | ||
haveConnection(this, 'downloadDir'); | ||
return await this._downloadDir(srcDir, dstDir, options); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'downloadDir', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'downloadDir'); | ||
} | ||
} | ||
/** | ||
@@ -1500,3 +1544,3 @@ * | ||
this.on('close', endCloseHandler); | ||
if (this.client.sftp) { | ||
if (this.sftp) { | ||
this.client.end(); | ||
@@ -1503,0 +1547,0 @@ } else { |
@@ -31,2 +31,14 @@ const fs = require('fs'); | ||
function globalListener(client, evt) { | ||
return () => { | ||
if (client.endCalled || client.errorHandled || client.closeHandled) { | ||
// we are processing an expected event handled elsewhere | ||
client.debugMsg(`Global ${evt} event: Ignoring expected and handled event`); | ||
} else { | ||
client.debugMsg(`Global ${evt} event: Handling unexpected event`); | ||
client.sftp = undefined; | ||
} | ||
}; | ||
} | ||
function endListener(client, name, reject) { | ||
@@ -243,6 +255,6 @@ const fn = function () { | ||
const root = await client.realPath('..'); | ||
return root + client.remotePathSep + aPath.slice(3); | ||
return `${root}/${aPath.slice(3)}`; | ||
} else if (aPath.startsWith('.')) { | ||
const root = await client.realPath('.'); | ||
return root + client.remotePathSep + aPath.slice(2); | ||
return `${root}/${aPath.slice(2)}`; | ||
} | ||
@@ -282,5 +294,9 @@ return aPath; | ||
try { | ||
setTimeout(() => { | ||
resolve(true); | ||
}, ms); | ||
if (isNaN(ms) || ms < 0) { | ||
reject('Argument must be anumber >= 0'); | ||
} else { | ||
setTimeout(() => { | ||
resolve(true); | ||
}, ms); | ||
} | ||
} catch (err) { | ||
@@ -293,2 +309,3 @@ reject(err); | ||
module.exports = { | ||
globalListener, | ||
errorListener, | ||
@@ -295,0 +312,0 @@ endListener, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1789
1589
219658
Updatedssh2@^1.12.0