ssh2-sftp-client
Advanced tools
Comparing version 8.1.0 to 9.0.0
{ | ||
"name": "ssh2-sftp-client", | ||
"version": "8.1.0", | ||
"version": "9.0.0", | ||
"description": "ssh2 sftp client for node", | ||
@@ -38,3 +38,3 @@ "main": "src/index.js", | ||
"dotenv": "^16.0.0", | ||
"eslint": "^8.12.0", | ||
"eslint": "^8.17.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
@@ -45,3 +45,3 @@ "eslint-plugin-mocha": "^10.0.3", | ||
"eslint-plugin-unicorn": "^42.0.0", | ||
"mocha": "^9.2.2", | ||
"mocha": "^10.0.0", | ||
"moment": "^2.29.1", | ||
@@ -56,4 +56,4 @@ "nyc": "^15.1.0", | ||
"promise-retry": "^2.0.1", | ||
"ssh2": "^1.10.0" | ||
"ssh2": "^1.11.0" | ||
} | ||
} |
@@ -8,2 +8,3 @@ const errorCode = { | ||
notdir: 'ENOTDIR', | ||
badAuth: 'ERR_BAD_AUTH', | ||
}; | ||
@@ -10,0 +11,0 @@ |
1536
src/index.js
@@ -1,5 +0,1 @@ | ||
/** | ||
* ssh2 sftp client for node | ||
*/ | ||
'use strict'; | ||
@@ -13,3 +9,2 @@ | ||
const { | ||
fmtError, | ||
addTempListeners, | ||
@@ -22,3 +17,2 @@ removeTempListeners, | ||
haveLocalCreate, | ||
sleep, | ||
} = require('./utils'); | ||
@@ -29,2 +23,3 @@ const { errorCode } = require('./constants'); | ||
constructor(clientName) { | ||
this.version = '9.0.0'; | ||
this.client = new Client(); | ||
@@ -87,2 +82,47 @@ this.sftp = undefined; | ||
fmtError(err, name = 'sftp', eCode, retryCount) { | ||
let msg = ''; | ||
let code = ''; | ||
const retry = retryCount | ||
? ` after ${retryCount} ${retryCount > 1 ? 'attempts' : 'attempt'}` | ||
: ''; | ||
if (err === undefined) { | ||
msg = `${name}: Undefined error - probably a bug!`; | ||
code = errorCode.generic; | ||
} else if (typeof err === 'string') { | ||
msg = `${name}: ${err}${retry}`; | ||
code = eCode ? eCode : errorCode.generic; | ||
} else if (err.custom) { | ||
msg = `${name}->${err.message}${retry}`; | ||
code = err.code; | ||
} else { | ||
switch (err.code) { | ||
case 'ENOTFOUND': | ||
msg = | ||
`${name}: ${err.level} error. ` + | ||
`Address lookup failed for host ${err.hostname}${retry}`; | ||
break; | ||
case 'ECONNREFUSED': | ||
msg = | ||
`${name}: ${err.level} error. Remote host at ` + | ||
`${err.address} refused connection${retry}`; | ||
break; | ||
case 'ECONNRESET': | ||
msg = | ||
`${name}: Remote host has reset the connection: ` + | ||
`${err.message}${retry}`; | ||
break; | ||
default: | ||
msg = `${name}: ${err.message}${retry}`; | ||
} | ||
code = err.code ? err.code : errorCode.generic; | ||
} | ||
let newError = new Error(msg); | ||
newError.code = code; | ||
newError.custom = true; | ||
this.debugMsg(`${newError.message} (${newError.code})`); | ||
return newError; | ||
} | ||
/** | ||
@@ -98,3 +138,2 @@ * Add a listner to the client object. This is rarely necessary and can be | ||
on(eventType, callback) { | ||
this.debugMsg(`Adding listener to ${eventType} event`); | ||
this.client.prependListener(eventType, callback); | ||
@@ -104,3 +143,2 @@ } | ||
removeListener(eventType, callback) { | ||
this.debugMsg(`Removing listener from ${eventType} event`); | ||
this.client.removeListener(eventType, callback); | ||
@@ -129,3 +167,2 @@ } | ||
listeners = addTempListeners(this, 'getConnection', reject); | ||
this.debugMsg('getConnection: created promise'); | ||
doReady = () => { | ||
@@ -139,5 +176,3 @@ this.debugMsg( | ||
this.client.connect(config); | ||
}).finally(async () => { | ||
this.debugMsg('getConnection: finally clause fired'); | ||
await sleep(500); | ||
}).finally(() => { | ||
this.removeListener('ready', doReady); | ||
@@ -150,11 +185,7 @@ removeTempListeners(this, listeners, 'getConnection'); | ||
getSftpChannel() { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'getSftpChannel', reject); | ||
this.debugMsg('getSftpChannel: created promise'); | ||
this.client.sftp((err, sftp) => { | ||
if (err) { | ||
this.debugMsg(`getSftpChannel: SFTP Channel Error: ${err.message}`); | ||
this.client.end(); | ||
reject(fmtError(err, 'getSftpChannel', err.code)); | ||
reject(this.fmtError(err, 'getSftpChannel', err.code)); | ||
} else { | ||
@@ -166,6 +197,2 @@ this.debugMsg('getSftpChannel: SFTP channel established'); | ||
}); | ||
}).finally(() => { | ||
this.debugMsg('getSftpChannel: finally clause fired'); | ||
removeTempListeners(this, listeners, 'getSftpChannel'); | ||
this._resetEventFlags(); | ||
}); | ||
@@ -187,10 +214,16 @@ } | ||
async connect(config) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'connect'); | ||
if (config.debug) { | ||
this.debug = config.debug; | ||
this.debugMsg('connect: Debugging turned on'); | ||
this.debugMsg( | ||
`ssh2-sftp-client Version: ${this.version} `, | ||
process.versions | ||
); | ||
} | ||
if (this.sftp) { | ||
this.debugMsg('connect: Already connected - reject'); | ||
throw fmtError( | ||
throw this.fmtError( | ||
'An existing SFTP connection is already defined', | ||
@@ -201,30 +234,45 @@ 'connect', | ||
} | ||
await promiseRetry( | ||
(retry, attempt) => { | ||
let retryOpts = { | ||
retries: config.retries || 1, | ||
factor: config.factor || 2, | ||
minTimeout: config.retry_minTimeout || 25000, | ||
}; | ||
await promiseRetry(retryOpts, async (retry, attempt) => { | ||
try { | ||
this.debugMsg(`connect: Connect attempt ${attempt}`); | ||
return this.getConnection(config).catch((err) => { | ||
this.debugMsg( | ||
`getConnection retry catch: ${err.message} Code: ${err.code}` | ||
); | ||
switch (err.code) { | ||
case 'ENOTFOUND': | ||
case 'ECONNREFUSED': | ||
case 'ERR_SOCKET_BAD_PORT': | ||
throw err; | ||
default: | ||
retry(err); | ||
await this.getConnection(config); | ||
} catch (err) { | ||
switch (err.code) { | ||
case 'ENOTFOUND': | ||
case 'ECONNREFUSED': | ||
case 'ERR_SOCKET_BAD_PORT': | ||
throw err; | ||
case undefined: { | ||
if ( | ||
err.message.endsWith( | ||
'All configured authentication methods failed' | ||
) | ||
) { | ||
throw this.fmtError( | ||
err.message, | ||
'getConnection', | ||
errorCode.badAuth | ||
); | ||
} | ||
retry(err); | ||
break; | ||
} | ||
}); | ||
}, | ||
{ | ||
retries: config.retries || 1, | ||
factor: config.retry_factor || 2, | ||
minTimeout: config.retry_minTimeout || 1000, | ||
default: | ||
retry(err); | ||
} | ||
} | ||
); | ||
return this.getSftpChannel(); | ||
}); | ||
let sftp = await this.getSftpChannel(); | ||
return sftp; | ||
} catch (err) { | ||
this.debugMsg(`connect: Error ${err.message}`); | ||
this.end(); | ||
throw err.custom ? err : this.fmtError(err, 'connect'); | ||
} finally { | ||
removeTempListeners(this, listeners, 'connect'); | ||
this._resetEventFlags(); | ||
throw fmtError(err, 'connect'); | ||
} | ||
@@ -244,27 +292,36 @@ } | ||
*/ | ||
realPath(remotePath) { | ||
let listeners; | ||
_realPath(rPath) { | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'realPath', reject); | ||
this.debugMsg(`realPath -> ${remotePath}`); | ||
if (haveConnection(this, 'realPath', reject)) { | ||
this.sftp.realpath(remotePath, (err, absPath) => { | ||
if (err) { | ||
this.debugMsg(`realPath Error: ${err.message} Code: ${err.code}`); | ||
if (err.code === 2) { | ||
resolve(''); | ||
} else { | ||
reject( | ||
fmtError(`${err.message} ${remotePath}`, 'realPath', err.code) | ||
); | ||
} | ||
this.debugMsg(`_realPath -> ${rPath}`); | ||
this.sftp.realpath(rPath, (err, absPath) => { | ||
if (err) { | ||
if (err.code === 2) { | ||
this.debugMsg('_realPath <- ""'); | ||
resolve(''); | ||
} else { | ||
reject( | ||
this.fmtError(`${err.message} ${rPath}`, 'realPath', err.code) | ||
); | ||
} | ||
this.debugMsg(`realPath <- ${absPath}`); | ||
resolve(absPath); | ||
}); | ||
} | ||
}).finally(() => { | ||
} | ||
this.debugMsg(`_realPath <- ${absPath}`); | ||
resolve(absPath); | ||
}); | ||
}); | ||
} | ||
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'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -289,56 +346,53 @@ | ||
*/ | ||
async stat(remotePath) { | ||
const _stat = (aPath) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, '_stat', reject); | ||
this.debugMsg(`_stat: ${aPath}`); | ||
this.sftp.stat(aPath, (err, stats) => { | ||
if (err) { | ||
this.debugMsg(`_stat: Error ${err.message} code: ${err.code}`); | ||
if (err.code === 2 || err.code === 4) { | ||
reject( | ||
fmtError( | ||
`No such file: ${remotePath}`, | ||
'_stat', | ||
errorCode.notexist | ||
) | ||
); | ||
} else { | ||
reject( | ||
fmtError(`${err.message} ${remotePath}`, '_stat', err.code) | ||
); | ||
} | ||
_stat(aPath) { | ||
return new Promise((resolve, reject) => { | ||
this.debugMsg(`_stat: ${aPath}`); | ||
this.sftp.stat(aPath, (err, stats) => { | ||
if (err) { | ||
if (err.code === 2 || err.code === 4) { | ||
reject( | ||
this.fmtError( | ||
`No such file: ${aPath}`, | ||
'_stat', | ||
errorCode.notexist | ||
) | ||
); | ||
} else { | ||
let result = { | ||
mode: stats.mode, | ||
uid: stats.uid, | ||
gid: stats.gid, | ||
size: stats.size, | ||
accessTime: stats.atime * 1000, | ||
modifyTime: stats.mtime * 1000, | ||
isDirectory: stats.isDirectory(), | ||
isFile: stats.isFile(), | ||
isBlockDevice: stats.isBlockDevice(), | ||
isCharacterDevice: stats.isCharacterDevice(), | ||
isSymbolicLink: stats.isSymbolicLink(), | ||
isFIFO: stats.isFIFO(), | ||
isSocket: stats.isSocket(), | ||
}; | ||
this.debugMsg('_stat: stats <- ', result); | ||
resolve(result); | ||
reject(this.fmtError(`${err.message} ${aPath}`, '_stat', err.code)); | ||
} | ||
}); | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, '_stat'); | ||
} else { | ||
const result = { | ||
mode: stats.mode, | ||
uid: stats.uid, | ||
gid: stats.gid, | ||
size: stats.size, | ||
accessTime: stats.atime * 1000, | ||
modifyTime: stats.mtime * 1000, | ||
isDirectory: stats.isDirectory(), | ||
isFile: stats.isFile(), | ||
isBlockDevice: stats.isBlockDevice(), | ||
isCharacterDevice: stats.isCharacterDevice(), | ||
isSymbolicLink: stats.isSymbolicLink(), | ||
isFIFO: stats.isFIFO(), | ||
isSocket: stats.isSocket(), | ||
}; | ||
this.debugMsg('_stat: stats <- ', result); | ||
resolve(result); | ||
} | ||
}); | ||
}; | ||
}); | ||
} | ||
async stat(remotePath) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'stat'); | ||
haveConnection(this, 'stat'); | ||
let absPath = await normalizeRemotePath(this, remotePath); | ||
return _stat(absPath); | ||
const absPath = await normalizeRemotePath(this, remotePath); | ||
return await this._stat(absPath); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'stat', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'stat'); | ||
this._resetEventFlags(); | ||
throw err.custom ? err : fmtError(err, 'stat', err.code); | ||
} | ||
@@ -358,44 +412,45 @@ } | ||
*/ | ||
async exists(remotePath) { | ||
async _exists(rPath) { | ||
try { | ||
if (haveConnection(this, 'exists')) { | ||
if (remotePath === '.') { | ||
this.debugMsg('exists: . = d'); | ||
return 'd'; | ||
} | ||
let absPath = await normalizeRemotePath(this, remotePath); | ||
try { | ||
this.debugMsg(`exists: ${remotePath} -> ${absPath}`); | ||
let info = await this.stat(absPath); | ||
this.debugMsg('exists: <- ', info); | ||
if (info.isDirectory) { | ||
this.debugMsg(`exists: ${remotePath} = d`); | ||
return 'd'; | ||
} | ||
if (info.isSymbolicLink) { | ||
this.debugMsg(`exists: ${remotePath} = l`); | ||
return 'l'; | ||
} | ||
if (info.isFile) { | ||
this.debugMsg(`exists: ${remotePath} = -`); | ||
return '-'; | ||
} | ||
this.debugMsg(`exists: ${remotePath} = false`); | ||
return false; | ||
} catch (err) { | ||
if (err.code === errorCode.notexist) { | ||
this.debugMsg( | ||
`exists: ${remotePath} = false errorCode = ${err.code}` | ||
); | ||
return false; | ||
} | ||
this.debugMsg(`exists: throw error ${err.message} ${err.code}`); | ||
throw err; | ||
} | ||
const absPath = await normalizeRemotePath(this, rPath); | ||
this.debugMsg(`exists: ${rPath} -> ${absPath}`); | ||
const info = await this._stat(absPath); | ||
this.debugMsg('exists: <- ', info); | ||
if (info.isDirectory) { | ||
this.debugMsg(`exists: ${rPath} = d`); | ||
return 'd'; | ||
} | ||
this.debugMsg(`exists: default ${remotePath} = false`); | ||
if (info.isSymbolicLink) { | ||
this.debugMsg(`exists: ${rPath} = l`); | ||
return 'l'; | ||
} | ||
if (info.isFile) { | ||
this.debugMsg(`exists: ${rPath} = -`); | ||
return '-'; | ||
} | ||
this.debugMsg(`exists: ${rPath} = false`); | ||
return false; | ||
} catch (err) { | ||
if (err.code === errorCode.notexist) { | ||
this.debugMsg(`exists: ${rPath} = false errorCode = ${err.code}`); | ||
return false; | ||
} | ||
throw err.custom ? err : this.fmtError(err.message, 'exists', err.code); | ||
} | ||
} | ||
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'); | ||
this._resetEventFlags(); | ||
throw err.custom ? err : fmtError(err, 'exists', err.code); | ||
} | ||
@@ -414,56 +469,55 @@ } | ||
* @param {String} remotePath - path to remote directory | ||
* @param {RegExp} pattern - regular expression to match filenames | ||
* @param {function} filter - a filter function used to select return entries | ||
* @returns {Promise<Array>} array of file description objects | ||
*/ | ||
list(remotePath, pattern = /.*/) { | ||
let listeners; | ||
_list(remotePath, filter) { | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'list', reject); | ||
if (haveConnection(this, 'list', reject)) { | ||
const reg = /-/gi; | ||
this.debugMsg(`list: ${remotePath} filter: ${pattern}`); | ||
this.sftp.readdir(remotePath, (err, fileList) => { | ||
if (err) { | ||
this.debugMsg(`list: Error ${err.message} code: ${err.code}`); | ||
reject(fmtError(`${err.message} ${remotePath}`, 'list', err.code)); | ||
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))); | ||
} else { | ||
let newList = []; | ||
// reset file info | ||
if (fileList) { | ||
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, | ||
}; | ||
}); | ||
} | ||
// provide some compatibility for auxList | ||
let regex; | ||
if (pattern instanceof RegExp) { | ||
regex = pattern; | ||
} else { | ||
let newPattern = pattern.replace(/\*([^*])*?/gi, '.*'); | ||
regex = new RegExp(newPattern); | ||
} | ||
let filteredList = newList.filter((item) => regex.test(item.name)); | ||
this.debugMsg('list: result: ', filteredList); | ||
resolve(filteredList); | ||
resolve(newList); | ||
} | ||
}); | ||
} | ||
}).finally(() => { | ||
} | ||
}); | ||
}); | ||
} | ||
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'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -484,106 +538,81 @@ | ||
* | ||
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls | ||
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been | ||
* added to support low-level access to stream objects. | ||
* | ||
* @return {Promise<String|Stream|Buffer>} | ||
*/ | ||
get( | ||
remotePath, | ||
dst, | ||
options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} } | ||
) { | ||
let rdr, wtr, listeners; | ||
_get(rPath, dst, opts) { | ||
let rdr, wtr; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'get', reject); | ||
if (haveConnection(this, 'get', reject)) { | ||
this.debugMsg(`get -> ${remotePath} `, options); | ||
rdr = this.sftp.createReadStream( | ||
remotePath, | ||
options.readStreamOptions ? options.readStreamOptions : {} | ||
); | ||
rdr.once('error', (err) => { | ||
reject(fmtError(`${err.message} ${remotePath}`, 'get', err.code)); | ||
opts = { | ||
...opts, | ||
readStreamOptions: { autoClose: true }, | ||
writeStreamOptions: { autoClose: true }, | ||
pipeOptions: { end: true }, | ||
}; | ||
rdr = this.sftp.createReadStream(rPath, opts.readStreamOptions); | ||
rdr.once('error', (err) => { | ||
reject(this.fmtError(`${err.message} ${rPath}`, '_get', err.code)); | ||
}); | ||
if (dst === undefined) { | ||
// no dst specified, return buffer of data | ||
this.debugMsg('get returning buffer of data'); | ||
wtr = concat((buff) => { | ||
resolve(buff); | ||
}); | ||
if (dst === undefined) { | ||
// no dst specified, return buffer of data | ||
this.debugMsg('get returning buffer of data'); | ||
wtr = concat((buff) => { | ||
//rdr.removeAllListeners('error'); | ||
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 { | ||
if (typeof dst === 'string') { | ||
// dst local file path | ||
this.debugMsg('get returning local file'); | ||
const localCheck = haveLocalCreate(dst); | ||
if (!localCheck.status) { | ||
return reject( | ||
fmtError( | ||
`Bad path: ${dst}: ${localCheck.details}`, | ||
'get', | ||
localCheck.code | ||
) | ||
); | ||
} | ||
wtr = fs.createWriteStream( | ||
dst, | ||
options.writeStreamOptions ? options.writeStreamOptions : {} | ||
); | ||
} else { | ||
this.debugMsg('get returning data into supplied stream'); | ||
wtr = dst; | ||
} | ||
wtr.once('error', (err) => { | ||
reject( | ||
fmtError( | ||
`${err.message} ${typeof dst === 'string' ? dst : ''}`, | ||
'get', | ||
err.code | ||
) | ||
); | ||
}); | ||
if ( | ||
Object.hasOwnProperty.call(options, 'pipeOptions') && | ||
Object.hasOwnProperty.call(options.pipeOptions, 'end') && | ||
!options.pipeOptions.end | ||
) { | ||
rdr.once('end', () => { | ||
this.debugMsg('get resolved on reader end event'); | ||
if (typeof dst === 'string') { | ||
resolve(dst); | ||
} else { | ||
resolve(wtr); | ||
} | ||
}); | ||
} else { | ||
wtr.once('finish', () => { | ||
this.debugMsg('get resolved on writer finish event'); | ||
if (typeof dst === 'string') { | ||
resolve(dst); | ||
} else { | ||
resolve(wtr); | ||
} | ||
}); | ||
} | ||
wtr = fs.createWriteStream(dst, opts.writeStreamOptions); | ||
} | ||
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {}); | ||
} else { | ||
this.debugMsg('get: returning data into supplied stream'); | ||
wtr = dst; | ||
} | ||
}).finally(() => { | ||
wtr.once('error', (err) => { | ||
reject( | ||
this.fmtError( | ||
`${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`, | ||
'get', | ||
err.code | ||
) | ||
); | ||
}); | ||
rdr.once('end', () => { | ||
if (typeof dst === 'string') { | ||
resolve(dst); | ||
} else { | ||
resolve(wtr); | ||
} | ||
}); | ||
rdr.pipe(wtr, opts.pipeOptions); | ||
}); | ||
} | ||
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'); | ||
this._resetEventFlags(); | ||
if ( | ||
rdr && | ||
Object.hasOwnProperty.call(options, 'readStreamOptions') && | ||
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') && | ||
options.readStreamOptions.autoClose === false | ||
) { | ||
rdr.destroy(); | ||
} | ||
if ( | ||
wtr && | ||
Object.hasOwnProperty.call(options, 'writeStreamOptions') && | ||
Object.hasOwnProperty.call(options.writeStreamOptions, 'autoClose') && | ||
options.writeStreamOptions.autoClose === false && | ||
typeof dst === 'string' | ||
) { | ||
wtr.destroy(); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -601,43 +630,41 @@ | ||
*/ | ||
_fastGet(rPath, lPath, opts) { | ||
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}!`); | ||
}); | ||
}); | ||
} | ||
async fastGet(remotePath, localPath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'fastGet'); | ||
haveConnection(this, 'fastGet'); | ||
const ftype = await this.exists(remotePath); | ||
if (ftype !== '-') { | ||
const msg = | ||
ftype === false | ||
? `No such file ${remotePath}` | ||
: `Not a regular file ${remotePath}`; | ||
let err = new Error(msg); | ||
err.code = errorCode.badPath; | ||
throw err; | ||
const msg = `${ | ||
!ftype ? 'No such file ' : 'Not a regular file' | ||
} ${remotePath}`; | ||
throw this.fmtError(msg, 'fastGet', errorCode.badPath); | ||
} | ||
const localCheck = haveLocalCreate(localPath); | ||
if (!localCheck.status) { | ||
let err = new Error(`Bad path: ${localPath}: ${localCheck.details}`); | ||
err.code = errorCode.badPath; | ||
throw err; | ||
throw this.fmtError( | ||
`Bad path: ${localPath}: ${localCheck.details}`, | ||
'fastGet', | ||
errorCode.badPath | ||
); | ||
} | ||
let listeners; | ||
let rslt = await new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'fastGet', reject); | ||
if (haveConnection(this, 'fastGet', reject)) { | ||
this.debugMsg( | ||
`fastGet -> remote: ${remotePath} local: ${localPath} `, | ||
options | ||
); | ||
this.sftp.fastGet(remotePath, localPath, options, (err) => { | ||
if (err) { | ||
this.debugMsg(`fastGet error ${err.message} code: ${err.code}`); | ||
reject(err); | ||
} | ||
resolve(`${remotePath} was successfully download to ${localPath}!`); | ||
}); | ||
} | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, 'fastGet'); | ||
}); | ||
return rslt; | ||
return await this._fastGet(remotePath, localPath, options); | ||
} catch (err) { | ||
throw this.fmtError(err, 'fastGet'); | ||
} finally { | ||
removeTempListeners(this, listeners, 'fastGet'); | ||
this._resetEventFlags(); | ||
throw fmtError(err, 'fastGet'); | ||
} | ||
@@ -659,49 +686,46 @@ } | ||
*/ | ||
fastPut(localPath, remotePath, options) { | ||
_fastPut(lPath, rPath, opts) { | ||
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}!`); | ||
}); | ||
}); | ||
} | ||
async fastPut(localPath, remotePath, options) { | ||
let listeners; | ||
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`); | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'fastPut', reject); | ||
try { | ||
listeners = addTempListeners(this, 'fastPut'); | ||
this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`); | ||
haveConnection(this, 'fastPut'); | ||
const localCheck = haveLocalAccess(localPath); | ||
if (!localCheck.status) { | ||
reject( | ||
fmtError( | ||
`Bad path: ${localPath}: ${localCheck.details}`, | ||
'fastPut', | ||
localCheck.code | ||
) | ||
throw this.fmtError( | ||
`Bad path: ${localPath}: ${localCheck.details}`, | ||
'fastPut', | ||
localCheck.code | ||
); | ||
} else if (localCheck.status && localExists(localPath) === 'd') { | ||
reject( | ||
fmtError( | ||
`Bad path: ${localPath} not a regular file`, | ||
'fastPut', | ||
errorCode.badPath | ||
) | ||
throw this.fmtError( | ||
`Bad path: ${localPath} not a regular file`, | ||
'fastgPut', | ||
errorCode.badPath | ||
); | ||
} else if (haveConnection(this, 'fastPut', reject)) { | ||
this.debugMsg( | ||
`fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify( | ||
options | ||
)}` | ||
); | ||
this.sftp.fastPut(localPath, remotePath, options, (err) => { | ||
if (err) { | ||
this.debugMsg(`fastPut error ${err.message} ${err.code}`); | ||
reject( | ||
fmtError( | ||
`${err.message} Local: ${localPath} Remote: ${remotePath}`, | ||
'fastPut', | ||
err.code | ||
) | ||
); | ||
} | ||
this.debugMsg('fastPut file transferred'); | ||
resolve(`${localPath} was successfully uploaded to ${remotePath}!`); | ||
}); | ||
} | ||
}).finally(() => { | ||
return await this._fastPut(localPath, remotePath, options); | ||
} catch (e) { | ||
throw e.custom ? e : this.fmtError(e.message, 'fastPut', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'fastPut'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -719,86 +743,71 @@ | ||
* writeStreamOptions and pipeOptions. | ||
* | ||
* *Important Note*: The ability to set ''autoClose' on read/write streams and 'end' on pipe() calls | ||
* is no longer supported. New methods 'createReadStream()' and 'createWriteStream()' have been | ||
* added to support low-level access to stream objects. | ||
* | ||
* @return {Promise<String>} | ||
*/ | ||
put( | ||
localSrc, | ||
remotePath, | ||
options = { | ||
readStreamOptions: {}, | ||
writeStreamOptions: { autoClose: true }, | ||
pipeOptions: {}, | ||
} | ||
) { | ||
let wtr, rdr, listeners; | ||
_put(lPath, rPath, opts) { | ||
let wtr, rdr; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'put', reject); | ||
opts = { | ||
...opts, | ||
readStreamOptions: { autoClose: true }, | ||
writeStreamOptions: { autoClose: true }, | ||
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) => { | ||
reject( | ||
this.fmtError( | ||
`${err.message} ${ | ||
typeof lPath === 'string' ? lPath : '<stream>' | ||
}`, | ||
'_put', | ||
err.code | ||
) | ||
); | ||
}); | ||
rdr.pipe(wtr, opts.pipeOptions); | ||
} | ||
}); | ||
} | ||
async put(localSrc, remotePath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'put'); | ||
haveConnection(this, 'put'); | ||
if (typeof localSrc === 'string') { | ||
const localCheck = haveLocalAccess(localSrc); | ||
if (!localCheck.status) { | ||
this.debugMsg(`put: local source check error ${localCheck.details}`); | ||
return reject( | ||
fmtError( | ||
`Bad path: ${localSrc}: ${localCheck.details}`, | ||
'put', | ||
localCheck.code | ||
) | ||
throw this.fmtError( | ||
`Bad path: ${localSrc} ${localCheck.details}`, | ||
'put', | ||
localCheck.code | ||
); | ||
} | ||
} | ||
if (haveConnection(this, 'put')) { | ||
wtr = this.sftp.createWriteStream( | ||
remotePath, | ||
options.writeStreamOptions | ||
? { ...options.writeStreamOptions, autoClose: true } | ||
: {} | ||
); | ||
wtr.once('error', (err) => { | ||
this.debugMsg(`put: write stream error ${err.message}`); | ||
reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code)); | ||
}); | ||
wtr.once('close', () => { | ||
this.debugMsg('put: promise resolved'); | ||
resolve(`Uploaded data stream to ${remotePath}`); | ||
}); | ||
if (localSrc instanceof Buffer) { | ||
this.debugMsg('put source is a buffer'); | ||
wtr.end(localSrc); | ||
} else { | ||
if (typeof localSrc === 'string') { | ||
this.debugMsg(`put source is a file path: ${localSrc}`); | ||
rdr = fs.createReadStream( | ||
localSrc, | ||
options.readStreamOptions ? options.readStreamOptions : {} | ||
); | ||
} else { | ||
this.debugMsg('put source is a stream'); | ||
rdr = localSrc; | ||
} | ||
rdr.once('error', (err) => { | ||
this.debugMsg(`put: read stream error ${err.message}`); | ||
reject( | ||
fmtError( | ||
`${err.message} ${ | ||
typeof localSrc === 'string' ? localSrc : '' | ||
}`, | ||
'put', | ||
err.code | ||
) | ||
); | ||
}); | ||
rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {}); | ||
} | ||
} | ||
}).finally(() => { | ||
return await this._put(localSrc, remotePath, options); | ||
} catch (e) { | ||
throw e.custom ? e : this.fmtError(e.message, 'put', e.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'put'); | ||
this._resetEventFlags(); | ||
if ( | ||
rdr && | ||
Object.hasOwnProperty.call(options, 'readStreamOptions') && | ||
Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') && | ||
options.readStreamOptions.autoClose === false && | ||
typeof localSrc === 'string' | ||
) { | ||
rdr.destroy(); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -814,30 +823,23 @@ | ||
*/ | ||
_append(input, rPath, opts) { | ||
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); | ||
} | ||
}); | ||
} | ||
async append(input, remotePath, options = {}) { | ||
const _append = (input, remotePath, options) => { | ||
return new Promise((resolve, reject) => { | ||
this.debugMsg(`append -> remote: ${remotePath} `, options); | ||
options.flags = 'a'; | ||
let stream = this.sftp.createWriteStream(remotePath, options); | ||
stream.on('error', (err) => { | ||
this.debugMsg( | ||
`append: Error ${err.message} appending to ${remotePath}` | ||
); | ||
reject(fmtError(`${err.message} ${remotePath}`, 'append', err.code)); | ||
}); | ||
stream.on('close', () => { | ||
this.debugMsg(`append: data appended to ${remotePath}`); | ||
resolve(`Appended data to ${remotePath}`); | ||
}); | ||
if (input instanceof Buffer) { | ||
this.debugMsg('append: writing data buffer to remote file'); | ||
stream.write(input); | ||
stream.end(); | ||
} else { | ||
this.debugMsg('append: writing stream to remote file'); | ||
input.pipe(stream); | ||
} | ||
}); | ||
}; | ||
let listeners; | ||
@@ -847,4 +849,3 @@ try { | ||
if (typeof input === 'string') { | ||
this.debugMsg('append: attempt to append two files - throw'); | ||
throw fmtError( | ||
throw this.fmtError( | ||
'Cannot append one file to another', | ||
@@ -855,16 +856,14 @@ 'append', | ||
} | ||
if (haveConnection(this, 'append')) { | ||
const fileType = await this.exists(remotePath); | ||
if (fileType && fileType === 'd') { | ||
this.debugMsg(`append: Error ${remotePath} not a file`); | ||
throw fmtError( | ||
`Bad path: ${remotePath}: cannot append to a directory`, | ||
'append', | ||
errorCode.badPath | ||
); | ||
} | ||
await _append(input, remotePath, options); | ||
haveConnection(this, 'append'); | ||
const fileType = await this.exists(remotePath); | ||
if (fileType && fileType === 'd') { | ||
throw this.fmtError( | ||
`Bad path: ${remotePath}: cannot append to a directory`, | ||
'append', | ||
errorCode.badPath | ||
); | ||
} | ||
await this._append(input, remotePath, options); | ||
} catch (e) { | ||
throw e.custom ? e : fmtError(e.message, 'append', e.code); | ||
throw e.custom ? e : this.fmtError(e.message, 'append', e.code); | ||
} finally { | ||
@@ -885,44 +884,43 @@ removeTempListeners(this, listeners, 'append'); | ||
*/ | ||
async mkdir(remotePath, recursive = false) { | ||
const _mkdir = (p) => { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, '_mkdir', reject); | ||
this.debugMsg(`_mkdir: create ${p}`); | ||
this.sftp.mkdir(p, (err) => { | ||
if (err) { | ||
this.debugMsg(`_mkdir: Error ${err.message} code: ${err.code}`); | ||
if (err.code === 4) { | ||
//fix for windows dodgy error messages | ||
let error = new Error(`Bad path: ${p} permission denied`); | ||
error.code = errorCode.badPath; | ||
reject(error); | ||
} else if (err.code === 2) { | ||
let error = new Error( | ||
`Bad path: ${p} parent not a directory or not exist` | ||
); | ||
error.code = errorCode.badPath; | ||
reject(error); | ||
} else { | ||
reject(err); | ||
} | ||
_doMkdir(p) { | ||
return new Promise((resolve, reject) => { | ||
this.sftp.mkdir(p, (err) => { | ||
if (err) { | ||
if (err.code === 4) { | ||
//fix for windows dodgy error messages | ||
reject( | ||
this.fmtError( | ||
`Bad path: ${p} permission denied`, | ||
'_doMkdir', | ||
errorCode.badPath | ||
) | ||
); | ||
} else if (err.code === 2) { | ||
reject( | ||
this.fmtError( | ||
`Bad path: ${p} parent not a directory or not exist`, | ||
'_doMkdir', | ||
errorCode.badPath | ||
) | ||
); | ||
} else { | ||
this.debugMsg('_mkdir: directory created'); | ||
resolve(`${p} directory created`); | ||
reject(this.fmtError(`${err.message} ${p}`, '_doMkdir', err.code)); | ||
} | ||
}); | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, '_mkdir'); | ||
this._resetEventFlags(); | ||
} else { | ||
resolve(`${p} directory created`); | ||
} | ||
}); | ||
}; | ||
}); | ||
} | ||
async _mkdir(remotePath, recursive) { | ||
try { | ||
haveConnection(this, 'mkdir'); | ||
let rPath = await normalizeRemotePath(this, remotePath); | ||
let targetExists = await this.exists(rPath); | ||
const rPath = await normalizeRemotePath(this, remotePath); | ||
const targetExists = await this.exists(rPath); | ||
if (targetExists && targetExists !== 'd') { | ||
let error = new Error(`Bad path: ${rPath} already exists as a file`); | ||
error.code = errorCode.badPath; | ||
throw error; | ||
throw this.fmtError( | ||
`Bad path: ${rPath} already exists as a file`, | ||
'_mkdir', | ||
errorCode.badPath | ||
); | ||
} else if (targetExists) { | ||
@@ -932,21 +930,39 @@ return `${rPath} already exists`; | ||
if (!recursive) { | ||
return await _mkdir(rPath); | ||
return await this._doMkdir(rPath); | ||
} | ||
let dir = parse(rPath).dir; | ||
const dir = parse(rPath).dir; | ||
if (dir) { | ||
let dirExists = await this.exists(dir); | ||
const dirExists = await this.exists(dir); | ||
if (!dirExists) { | ||
await this.mkdir(dir, true); | ||
await this._mkdir(dir, true); | ||
} else if (dirExists !== 'd') { | ||
let error = new Error(`Bad path: ${dir} not a directory`); | ||
error.code = errorCode.badPath; | ||
throw error; | ||
throw this.fmtError( | ||
`Bad path: ${dir} not a directory`, | ||
'_mkdir', | ||
errorCode.badPath | ||
); | ||
} | ||
} | ||
return await _mkdir(rPath); | ||
return await this._doMkdir(rPath); | ||
} catch (err) { | ||
throw fmtError(`${err.message}`, 'mkdir', err.code); | ||
throw err.custom | ||
? err | ||
: this.fmtError(`${err.message} ${remotePath}`, '_mkdir', err.code); | ||
} | ||
} | ||
async mkdir(remotePath, recursive = false) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, '_mkdir'); | ||
haveConnection(this, 'mkdir'); | ||
return await this._mkdir(remotePath, recursive); | ||
} catch (err) { | ||
throw this.fmtError(`${err.message}`, 'mkdir', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'append'); | ||
this._resetEventFlags(); | ||
} | ||
} | ||
/** | ||
@@ -963,13 +979,2 @@ * @async | ||
async rmdir(remotePath, recursive = false) { | ||
const _delete = (remotePath) => { | ||
return new Promise((resolve, reject) => { | ||
this.sftp.unlink(remotePath, (err) => { | ||
if (err && err.code !== 2) { | ||
reject(fmtError(`${err.message} ${remotePath}`, 'rmdir', err.code)); | ||
} | ||
resolve(true); | ||
}); | ||
}); | ||
}; | ||
const _rmdir = (p) => { | ||
@@ -980,9 +985,6 @@ return new Promise((resolve, reject) => { | ||
if (err) { | ||
this.debugMsg(`rmdir error ${err.message} code: ${err.code}`); | ||
reject(fmtError(`${err.message} ${p}`, 'rmdir', err.code)); | ||
reject(this.fmtError(`${err.message} ${p}`, 'rmdir', err.code)); | ||
} | ||
resolve('Successfully removed directory'); | ||
}); | ||
}).finally(() => { | ||
removeTempListeners(this, listeners, '_rmdir'); | ||
}); | ||
@@ -994,14 +996,16 @@ }; | ||
if (recur) { | ||
let list = await this.list(p); | ||
const list = await this.list(p); | ||
if (list.length) { | ||
let files = list.filter((item) => item.type !== 'd'); | ||
let dirs = list.filter((item) => item.type === 'd'); | ||
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 (let d of dirs) { | ||
for (const d of dirs) { | ||
await _dormdir(`${p}${this.remotePathSep}${d.name}`, true); | ||
} | ||
let promiseList = []; | ||
for (let f of files) { | ||
promiseList.push(_delete(`${p}${this.remotePathSep}${f.name}`)); | ||
const promiseList = []; | ||
for (const f of files) { | ||
promiseList.push( | ||
this._delete(`${p}${this.remotePathSep}${f.name}`) | ||
); | ||
} | ||
@@ -1013,3 +1017,3 @@ await Promise.all(promiseList); | ||
} catch (err) { | ||
throw err.custom ? err : fmtError(err, '_dormdir', err.code); | ||
throw err.custom ? err : this.fmtError(err, '_dormdir', err.code); | ||
} | ||
@@ -1022,6 +1026,6 @@ }; | ||
haveConnection(this, 'rmdir'); | ||
let absPath = await normalizeRemotePath(this, remotePath); | ||
let dirStatus = await this.exists(absPath); | ||
const absPath = await normalizeRemotePath(this, remotePath); | ||
const dirStatus = await this.exists(absPath); | ||
if (dirStatus && dirStatus !== 'd') { | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path: ${absPath} not a directory`, | ||
@@ -1032,3 +1036,3 @@ 'rmdir', | ||
} else if (!dirStatus) { | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path: ${absPath} No such file`, | ||
@@ -1042,6 +1046,6 @@ 'rmdir', | ||
} catch (err) { | ||
this._resetEventFlags(); | ||
throw err.custom ? err : fmtError(err, 'rmdir', err.code); | ||
throw err.custom ? err : this.fmtError(err.message, 'rmdir', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'rmdir'); | ||
this._resetEventFlags(); | ||
} | ||
@@ -1061,27 +1065,31 @@ } | ||
*/ | ||
delete(remotePath, notFoundOK = false) { | ||
let listeners; | ||
_delete(rPath, notFoundOK) { | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'delete', reject); | ||
if (haveConnection(this, 'delete', reject)) { | ||
this.debugMsg(`delete -> ${remotePath}`); | ||
this.sftp.unlink(remotePath, (err) => { | ||
if (err) { | ||
this.debugMsg(`delete error ${err.message} code: ${err.code}`); | ||
if (notFoundOK && err.code === 2) { | ||
this.debugMsg('delete ignore missing target error'); | ||
resolve(`Successfully deleted ${remotePath}`); | ||
} else { | ||
reject( | ||
fmtError(`${err.message} ${remotePath}`, 'delete', err.code) | ||
); | ||
} | ||
this.sftp.unlink(rPath, (err) => { | ||
if (err) { | ||
if (notFoundOK && err.code === 2) { | ||
resolve(`Successfully deleted ${rPath}`); | ||
} else { | ||
reject( | ||
this.fmtError(`${err.message} ${rPath}`, 'delete', err.code) | ||
); | ||
} | ||
resolve(`Successfully deleted ${remotePath}`); | ||
}); | ||
} | ||
}).finally(() => { | ||
} | ||
resolve(`Successfully deleted ${rPath}`); | ||
}); | ||
}); | ||
} | ||
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'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -1100,26 +1108,37 @@ | ||
*/ | ||
rename(fromPath, toPath) { | ||
_rename(fPath, tPath) { | ||
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}`); | ||
}); | ||
}); | ||
} | ||
async rename(fromPath, toPath) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'rename', reject); | ||
if (haveConnection(this, 'rename', reject)) { | ||
this.debugMsg(`rename -> ${fromPath} ${toPath}`); | ||
this.sftp.rename(fromPath, toPath, (err) => { | ||
if (err) { | ||
this.debugMsg(`rename error ${err.message} code: ${err.code}`); | ||
reject( | ||
fmtError( | ||
`${err.message} From: ${fromPath} To: ${toPath}`, | ||
'rename', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`Successfully renamed ${fromPath} to ${toPath}`); | ||
}); | ||
} | ||
}).finally(() => { | ||
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'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -1139,26 +1158,37 @@ | ||
*/ | ||
posixRename(fromPath, toPath) { | ||
_posixRename(fPath, tPath) { | ||
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}`); | ||
}); | ||
}); | ||
} | ||
async posixRename(fromPath, toPath) { | ||
let listeners; | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'posixRename', reject); | ||
if (haveConnection(this, 'posixRename', reject)) { | ||
this.debugMsg(`posixRename -> ${fromPath} ${toPath}`); | ||
this.sftp.ext_openssh_rename(fromPath, toPath, (err) => { | ||
if (err) { | ||
this.debugMsg(`posixRename error ${err.message} code: ${err.code}`); | ||
reject( | ||
fmtError( | ||
`${err.message} From: ${fromPath} To: ${toPath}`, | ||
'posixRename', | ||
err.code | ||
) | ||
); | ||
} | ||
resolve(`Successful POSIX rename ${fromPath} to ${toPath}`); | ||
}); | ||
} | ||
}).finally(() => { | ||
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'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -1176,17 +1206,27 @@ | ||
*/ | ||
chmod(remotePath, mode) { | ||
let listeners; | ||
_chmod(rPath, mode) { | ||
return new Promise((resolve, reject) => { | ||
listeners = addTempListeners(this, 'chmod', reject); | ||
this.debugMsg(`chmod -> ${remotePath} ${mode}`); | ||
this.sftp.chmod(remotePath, mode, (err) => { | ||
this.sftp.chmod(rPath, mode, (err) => { | ||
if (err) { | ||
reject(fmtError(`${err.message} ${remotePath}`, 'chmod', err.code)); | ||
reject(this.fmtError(`${err.message} ${rPath}`, '_chmod', err.code)); | ||
} | ||
resolve('Successfully change file mode'); | ||
}); | ||
}).finally(() => { | ||
}); | ||
} | ||
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'); | ||
this._resetEventFlags(); | ||
}); | ||
} | ||
} | ||
@@ -1202,18 +1242,21 @@ | ||
* @param {String} dstDir - remote destination directory | ||
* @param {function(String,Boolean):Boolean} filter - (Optional) The first argument is the full path of the item to be uploaded and the second argument is a boolean, which will be true if the target path is for a directory. If the function returns true, the item will be uploaded | ||
* @param {Object} options - (Optional) An object with 2 supported properties, | ||
* 'filter' and 'useFastput'. The first argument is the full path of the item | ||
* to be uploaded and the second argument is a boolean, which will be true if | ||
* the target path is for a directory. If the function returns true, the item | ||
* will be uploaded and excluded when it returns false. The 'useFastput' property is a | ||
* boolean value. When true, the 'fastPut()' method will be used to upload files. Default | ||
* is to use the slower, but more supported 'put()' method. | ||
* | ||
* @returns {Promise<String>} | ||
*/ | ||
async uploadDir(srcDir, dstDir, filter) { | ||
async _uploadDir(srcDir, dstDir, options) { | ||
try { | ||
this.debugMsg(`uploadDir -> SRC = ${srcDir} DST = ${dstDir}`); | ||
haveConnection(this, 'uploadDir'); | ||
//let absSrcDir = fs.realpathSync(srcDir); | ||
let absDstDir = await normalizeRemotePath(this, dstDir); | ||
const absDstDir = await normalizeRemotePath(this, dstDir); | ||
this.debugMsg(`uploadDir <- SRC = ${srcDir} DST = ${absDstDir}`); | ||
const srcType = localExists(srcDir); | ||
if (!srcType) { | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path: ${srcDir} not exist`, | ||
'uploadDir', | ||
'_uploadDir', | ||
errorCode.badPath | ||
@@ -1223,14 +1266,13 @@ ); | ||
if (srcType !== 'd') { | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path: ${srcDir}: not a directory`, | ||
'uploadDir', | ||
'_uploadDir', | ||
errorCode.badPath | ||
); | ||
} | ||
let dstStatus = await this.exists(absDstDir); | ||
const dstStatus = await this.exists(absDstDir); | ||
if (dstStatus && dstStatus !== 'd') { | ||
this.debugMsg(`UploadDir: DST ${absDstDir} exists but not a directory`); | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path ${absDstDir} Not a directory`, | ||
'uploadDir', | ||
'_uploadDir', | ||
errorCode.badPath | ||
@@ -1240,4 +1282,3 @@ ); | ||
if (!dstStatus) { | ||
this.debugMsg(`UploadDir: Creating directory ${absDstDir}`); | ||
await this.mkdir(absDstDir, true); | ||
await this._mkdir(absDstDir, true); | ||
} | ||
@@ -1248,14 +1289,19 @@ let dirEntries = fs.readdirSync(srcDir, { | ||
}); | ||
if (filter) { | ||
if (options?.filter) { | ||
dirEntries = dirEntries.filter((item) => | ||
filter(join(srcDir, item.name), item.isDirectory()) | ||
options.filter(join(srcDir, item.name), item.isDirectory()) | ||
); | ||
} | ||
for (let e of dirEntries) { | ||
let newSrc = join(srcDir, e.name); | ||
let newDst = `${absDstDir}${this.remotePathSep}${e.name}`; | ||
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, filter); | ||
await this.uploadDir(newSrc, newDst, options); | ||
} else if (e.isFile()) { | ||
await this.put(newSrc, newDst); | ||
if (options?.useFastput) { | ||
fileUploads.push(this._fastPut(newSrc, newDst)); | ||
} else { | ||
fileUploads.push(this._put(newSrc, newDst)); | ||
} | ||
this.client.emit('upload', { source: newSrc, destination: newDst }); | ||
@@ -1267,7 +1313,24 @@ } else { | ||
} | ||
await Promise.all(fileUploads); | ||
} | ||
return `${srcDir} uploaded to ${absDstDir}`; | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: 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'); | ||
this._resetEventFlags(); | ||
throw err.custom ? err : fmtError(err, 'uploadDir'); | ||
} | ||
@@ -1284,13 +1347,17 @@ } | ||
* @param {String} dstDir - local destination directory | ||
* @param {function(String,Boolean):Boolean} filter - (Optional) The first argument is the full path of the item to be downloaded and the second argument is a boolean, which will be true if the target path is for a directory. If the function returns true, the item will be downloaded | ||
* @param {Object} options - (Optional) Object with 2 supported properties, | ||
* 'filter' and 'useFastget'. The filter property is a function of two | ||
* arguments. The first argument is the full path of the item to be downloaded | ||
* and the second argument is a boolean, which will be true if the target path | ||
* is for a directory. If the function returns true, the item will be | ||
* downloaded and excluded if teh function returns false. | ||
* | ||
* @returns {Promise<String>} | ||
*/ | ||
async downloadDir(srcDir, dstDir, filter) { | ||
async _downloadDir(srcDir, dstDir, options) { | ||
try { | ||
this.debugMsg(`downloadDir -> ${srcDir} ${dstDir}`); | ||
haveConnection(this, 'downloadDir'); | ||
let fileList = await this.list(srcDir); | ||
if (filter) { | ||
let fileList = await this._list(srcDir); | ||
if (options?.filter) { | ||
fileList = fileList.filter((item) => | ||
filter( | ||
options.filter( | ||
`${srcDir}${this.remotePathSep}${item.name}`, | ||
@@ -1303,3 +1370,3 @@ item.type === 'd' ? true : false | ||
if (!localCheck.status && localCheck.details === 'permission denied') { | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path: ${dstDir}: ${localCheck.details}`, | ||
@@ -1312,3 +1379,3 @@ 'downloadDir', | ||
} else if (localCheck.status && localCheck.type !== 'd') { | ||
throw fmtError( | ||
throw this.fmtError( | ||
`Bad path: ${dstDir}: not a directory`, | ||
@@ -1319,9 +1386,14 @@ 'downloadDir', | ||
} | ||
for (let f of fileList) { | ||
let newSrc = `${srcDir}${this.remotePathSep}${f.name}`; | ||
let newDst = join(dstDir, f.name); | ||
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, filter); | ||
await this._downloadDir(newSrc, newDst, options); | ||
} else if (f.type === '-') { | ||
await this.get(newSrc, newDst); | ||
if (options?.useFasget) { | ||
downloadFiles.push(this._fastGet(newSrc, newDst)); | ||
} else { | ||
downloadFiles.push(this._get(newSrc, newDst)); | ||
} | ||
this.client.emit('download', { source: newSrc, destination: newDst }); | ||
@@ -1334,6 +1406,22 @@ } else { | ||
} | ||
await Promise.all(downloadFiles); | ||
return `${srcDir} downloaded to ${dstDir}`; | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: 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'); | ||
this._resetEventFlags(); | ||
throw err.custom ? err : fmtError(err, 'downloadDir', err.code); | ||
} | ||
@@ -1343,4 +1431,132 @@ } | ||
/** | ||
* | ||
* Returns a read stream object. This is a low level method which will return a read stream | ||
* connected to the remote file object specified as an argument. Client code is fully responsible | ||
* for managing this stream object i.e. adding any necessary listeners and disposing of the object etc. | ||
* See the SSH2 sftp documentation for details on possible options which can be used. | ||
* | ||
* @param {String} remotePath - path to remote file to attach stream to | ||
* @param {Object} options - options to pass to the create stream process | ||
* | ||
* @returns {Object} a read stream object | ||
* | ||
*/ | ||
createReadStream(remotePath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'createReadStream'); | ||
haveConnection(this, 'createReadStream'); | ||
const stream = this.sftp.createReadStream(remotePath, options); | ||
return stream; | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(err.message, 'createReadStream', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'createReadStreame'); | ||
this._resetEventFlags(); | ||
} | ||
} | ||
/** | ||
* | ||
* Create a write stream object connected to a file on the remote sftp server. | ||
* This is a low level method which will return a write stream for the remote file specified | ||
* in the 'remotePath' argument. Client code to responsible for managing this object once created. | ||
* This includes disposing of file handles, setting up any necessary event listeners etc. | ||
* | ||
* @param {String} remotePath - path to the remote file on the sftp server | ||
* @param (Object} options - options to pass to the create write stream process) | ||
* | ||
* @returns {Object} a stream object | ||
* | ||
*/ | ||
createWriteStream(remotePath, options) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'createWriteStream'); | ||
haveConnection(this, 'createWriteStream'); | ||
const stream = this.sftp.createWriteStream(remotePath, options); | ||
return stream; | ||
} catch (err) { | ||
throw err.custom | ||
? err | ||
: this.fmtError(err.message, 'createWriteStream', err.code); | ||
} finally { | ||
removeTempListeners(this, listeners, 'createWriteStream'); | ||
this._resetEventFlags(); | ||
} | ||
} | ||
/** | ||
* @async | ||
* | ||
* Make a remote copy of a remote file. Create a copy of a remote file on the remote | ||
* server. It is assumed the directory where the copy will be placed already exists. | ||
* The destination file must not already exist. | ||
* | ||
* @param {String} srcPath - path to the remote file to be copied | ||
* @param {String} dstPath - destination path for the copy. | ||
* | ||
* @returns {String}. | ||
* | ||
*/ | ||
_rcopy(srcPath, dstPath) { | ||
return new Promise((resolve, reject) => { | ||
const ws = this.sftp.createWriteStream(dstPath); | ||
const rs = this.sftp.createReadStream(srcPath); | ||
ws.on('error', (err) => { | ||
reject(this.fmtError(`${err.message} ${dstPath}`, '_rcopy')); | ||
}); | ||
rs.on('error', (err) => { | ||
reject(this.fmtError(`${err.message} ${srcPath}`, '_rcopy')); | ||
}); | ||
ws.on('close', () => { | ||
resolve(`${srcPath} copied to ${dstPath}`); | ||
}); | ||
rs.pipe(ws); | ||
}); | ||
} | ||
async rcopy(src, dst) { | ||
let listeners; | ||
try { | ||
listeners = addTempListeners(this, 'rcopy'); | ||
haveConnection(this, 'rcopy'); | ||
const srcPath = await normalizeRemotePath(this, src); | ||
const srcExists = await this.exists(srcPath); | ||
if (!srcExists) { | ||
throw this.fmtError( | ||
`Source does not exist ${srcPath}`, | ||
'rcopy', | ||
errorCode.badPath | ||
); | ||
} | ||
if (srcExists !== '-') { | ||
throw this.fmtError( | ||
`Source not a file ${srcPath}`, | ||
'rcopy', | ||
errorCode.badPath | ||
); | ||
} | ||
const dstPath = await normalizeRemotePath(this, dst); | ||
const dstExists = await this.exists(dstPath); | ||
if (dstExists) { | ||
throw this.fmtError( | ||
`Destination already exists ${dstPath}`, | ||
'rcopy', | ||
errorCode.badPath | ||
); | ||
} | ||
return await this._rcopy(srcPath, dstPath); | ||
} catch (err) { | ||
throw err.custom ? err : this.fmtError(err, 'rcopy'); | ||
} finally { | ||
removeTempListeners(this, listeners, 'rcopy'); | ||
this._resetEventFlags(); | ||
} | ||
} | ||
/** | ||
* @async | ||
* | ||
* End the SFTP connection | ||
@@ -1362,7 +1578,5 @@ * | ||
if (haveConnection(this, 'end', reject)) { | ||
this.debugMsg('end: Have connection - calling end()'); | ||
this.client.end(); | ||
} | ||
}).finally(() => { | ||
this.debugMsg('end: finally clause fired'); | ||
removeTempListeners(this, listeners, 'end'); | ||
@@ -1369,0 +1583,0 @@ this.removeListener('close', endCloseHandler); |
149
src/utils.js
@@ -1,62 +0,6 @@ | ||
'use strict'; | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const { errorCode } = require('./constants'); | ||
/** | ||
* Generate a new Error object with a reformatted error message which | ||
* is a little more informative and useful to users. | ||
* | ||
* @param {Error|string} err - The Error object the new error will be based on | ||
* @param {number} retryCount - For those functions which use retry. Number of | ||
* attempts to complete before giving up | ||
* @returns {Error} New error with custom error message | ||
*/ | ||
function fmtError(err, name = 'sftp', eCode, retryCount) { | ||
let msg = ''; | ||
let code = ''; | ||
let retry = retryCount | ||
? ` after ${retryCount} ${retryCount > 1 ? 'attempts' : 'attempt'}` | ||
: ''; | ||
if (err === undefined) { | ||
msg = `${name}: Undefined error - probably a bug!`; | ||
code = errorCode.generic; | ||
} else if (typeof err === 'string') { | ||
msg = `${name}: ${err}${retry}`; | ||
code = eCode ? eCode : errorCode.generic; | ||
} else if (err.custom) { | ||
msg = `${name}->${err.message}${retry}`; | ||
code = err.code; | ||
} else { | ||
switch (err.code) { | ||
case 'ENOTFOUND': | ||
msg = | ||
`${name}: ${err.level} error. ` + | ||
`Address lookup failed for host ${err.hostname}${retry}`; | ||
break; | ||
case 'ECONNREFUSED': | ||
msg = | ||
`${name}: ${err.level} error. Remote host at ` + | ||
`${err.address} refused connection${retry}`; | ||
break; | ||
case 'ECONNRESET': | ||
msg = | ||
`${name}: Remote host has reset the connection: ` + | ||
`${err.message}${retry}`; | ||
break; | ||
default: | ||
msg = `${name}: ${err.message}${retry}`; | ||
} | ||
code = err.code ? err.code : errorCode.generic; | ||
} | ||
let newError = new Error(msg); | ||
newError.code = code; | ||
newError.custom = true; | ||
return newError; | ||
} | ||
/** | ||
* Simple default error listener. Will reformat the error message and | ||
@@ -71,13 +15,12 @@ * throw a new error. | ||
if (client.endCalled || client.errorHandled) { | ||
client.debugMsg(`${name} Error: Ignoring handled error: ${err.message}`); | ||
// error already handled or expected - ignore | ||
return; | ||
} | ||
client.errorHandled = true; | ||
let newError = new Error(`${name}: ${err.message}`); | ||
newError.code = err.code; | ||
if (reject) { | ||
reject(newError); | ||
} else { | ||
client.debugMsg(`${name} Error: Handling error: ${err.message}`); | ||
client.errorHandled = true; | ||
if (reject) { | ||
client.debugMsg(`${name} Error: handled error with reject`); | ||
reject(fmtError(err, name, err.code)); | ||
} else { | ||
client.debugMsg(`${name} Error: handling error with throw`); | ||
throw fmtError(err, name, err.code); | ||
} | ||
throw newError; | ||
} | ||
@@ -91,14 +34,12 @@ }; | ||
if (client.endCalled || client.endHandled) { | ||
client.debugMsg(`${name} End: Ignoring expected end event`); | ||
// end event already handled - ignore | ||
return; | ||
} | ||
client.sftp = undefined; | ||
client.endHandled = true; | ||
let err = new Error(`${name} Unexpected end event raised`); | ||
if (reject) { | ||
reject(err); | ||
} else { | ||
client.debugMsg(`${name} End: Handling end event`); | ||
client.sftp = undefined; | ||
client.endHandled = true; | ||
if (reject) { | ||
client.debugMsg(`${name} End: handling end event with reject'`); | ||
reject(fmtError('Unexpected end event raised', name)); | ||
} else { | ||
client.debugMsg(`${name} End: handling end event with throw`); | ||
throw fmtError('Unexpected end event raised', name); | ||
} | ||
throw err; | ||
} | ||
@@ -112,14 +53,12 @@ }; | ||
if (client.endCalled || client.closeHandled) { | ||
client.debugMsg(`${name} Close: ignoring expected close event`); | ||
// handled or expected close event - ignore | ||
return; | ||
} | ||
client.sftp = undefined; | ||
client.closeHandled = true; | ||
let err = new Error(`${name}: Unexpected close event raised`); | ||
if (reject) { | ||
reject(err); | ||
} else { | ||
client.debugMsg(`${name} Close: handling unexpected close event`); | ||
client.sftp = undefined; | ||
client.closeHandled = true; | ||
if (reject) { | ||
client.debugMsg(`${name} Close: handling close event with reject`); | ||
reject(fmtError('Unexpected close event raised', name)); | ||
} else { | ||
client.debugMsg(`${name} Close: handling close event with throw`); | ||
throw fmtError('Unexpected close event raised', name); | ||
} | ||
throw err; | ||
} | ||
@@ -136,3 +75,2 @@ }; | ||
}; | ||
client.debugMsg(`${name}: Adding temp event listeners`); | ||
client.on('end', listeners.end); | ||
@@ -145,6 +83,9 @@ client.on('close', listeners.close); | ||
function removeTempListeners(client, listeners, name) { | ||
client.debugMsg(`${name}: Removing temp event listeners`); | ||
client.removeListener('end', listeners.end); | ||
client.removeListener('close', listeners.close); | ||
client.removeListener('error', listeners.error); | ||
try { | ||
client.removeListener('end', listeners.end); | ||
client.removeListener('close', listeners.close); | ||
client.removeListener('error', listeners.error); | ||
} catch (err) { | ||
throw new Error(`${name}: Error removing temp listeners: ${err.message}`); | ||
} | ||
} | ||
@@ -173,7 +114,7 @@ | ||
} else { | ||
throw fmtError( | ||
`Bad path: ${filePath}: target must be a file or directory`, | ||
'localExists', | ||
errorCode.badPath | ||
let err = new Error( | ||
`Bad path: ${filePath}: target must be a file or directory` | ||
); | ||
err.code = errorCode.badPath; | ||
throw err; | ||
} | ||
@@ -293,6 +234,6 @@ } | ||
if (aPath.startsWith('..')) { | ||
let root = await client.realPath('..'); | ||
const root = await client.realPath('..'); | ||
return root + client.remotePathSep + aPath.slice(3); | ||
} else if (aPath.startsWith('.')) { | ||
let root = await client.realPath('.'); | ||
const root = await client.realPath('.'); | ||
return root + client.remotePathSep + aPath.slice(2); | ||
@@ -302,3 +243,3 @@ } | ||
} catch (err) { | ||
throw fmtError(err, 'normalizeRemotePath'); | ||
throw new Error(`normalizeRemotePath: ${err.message}`); | ||
} | ||
@@ -319,7 +260,4 @@ } | ||
if (!client.sftp) { | ||
let newError = fmtError( | ||
'No SFTP connection available', | ||
name, | ||
errorCode.connect | ||
); | ||
let newError = new Error(`${name}: No SFTP connection available`); | ||
newError.code = errorCode.connect; | ||
if (reject) { | ||
@@ -348,3 +286,2 @@ reject(newError); | ||
module.exports = { | ||
fmtError, | ||
errorListener, | ||
@@ -351,0 +288,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
219550
8
1781
1584
Updatedssh2@^1.11.0