Source: index.js

var Client = require('ssh2').Client

var statToAttrs = function (stats) {
  var attrs = {}
  for (var attr in stats) {
    if (stats.hasOwnProperty(attr)) {
      attrs[attr] = stats[attr]
    }
  }
  return attrs
}

function SFTPClient (config) {
  if (!(this instanceof SFTPClient)) {
    return new SFTPClient(config)
  }

  this.config = config || {}
}

SFTPClient.prototype.MODES = require('ssh2').SFTP_OPEN_MODE
SFTPClient.prototype.CODES = require('ssh2').SFTP_STATUS_CODE

/**
* Creates connection and promise wrapper for sftp commands
*
* @param {callback} cmdCB - callback for sftp, takes connection, reject and resolve cmb_cb(con, reject,resolve)
* @param {ssh2.Client} [session] - existing ssh2 connection, optional
*/
SFTPClient.prototype.sftpCmd = function sftpCmd (cmdCB, session, persist) {
  var self = this
  var conn = session || new Client()
  session = session || false
  persist = persist || false

  // handle persisten connection
  var handleConn = function (failed) {
    if (!session && (!persist || failed)) {
      conn.end()
      conn.destroy()
    }
  }

  // reject promise handler
  var rejected = function (err) {
    handleConn(true)
    return Promise.reject(err)
  }

  // resolve promise handler
  var resolved = function (val) {
    handleConn(false)
    return Promise.resolve(val)
  }

  return new Promise(function (resolve, reject) {
    var compiledCallBack = cmdCB(resolve, reject, conn)
    if (session) {
      conn.sftp(compiledCallBack)
    } else {
      conn.on('ready', function () {
        conn.sftp(compiledCallBack)
      })
      conn.on('end', function () {
        reject(new Error('Connection closed'))
      })
      conn.on('error', function (err) {
        reject(err)
      })
      conn.connect(self.config)
    }
  // handle the persistent connection regardless of how promise fairs
  }).then(resolved, rejected)
}

/**
 * creates a new ssh2 session, short cut for
 * sshClient = require('ssh2')sshClient
 * session = new SFTPClient(config)
 *
 * @params {Object} config - valid ssh2 config
 * @return {Promise} returns a Promse with an ssh2 connection object if resovled
 */
SFTPClient.prototype.session = function session (conf) {
  return new Promise(function (resolve, reject) {
    var conn = new Client()
    conn.on('ready', function () {
      conn.removeAllListeners()
      resolve(conn)
    })
    .on('end', function () {
      reject(new Error('Connection closed'))
    })
    .on('error', function (err) {
      reject(err)
    })
    try {
      conn.connect(conf)
    } catch (err) {
      reject(err)
    }
  })
}

/**
 * unix ls -l style return
 *
 * @param {string} path - on filesystem to stat
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 * @return {Promise} Promise with object describing path
 */
SFTPClient.prototype.ls = function ls (location, session) {
  // create the lsCmd callback for this.sftpCmd
  var lsCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.stat(location, function (err, stat) {
        if (err) { return reject(err) }
        var attrs = statToAttrs(stat)
        if (stat.isDirectory()) {
          sftp.readdir(location, function (err, list) {
            if (err) { return reject(err) }
            resolve({ path: location, type: 'directory', attrs: attrs, entries: list })
          })
        } else if (stat.isFile()) {
          resolve({ path: location, type: 'file', attrs: attrs })
        } else {
          resolve({ path: location, type: 'other', attrs: attrs })
        }
      })
    }
  }
  // return the value of the command
  return this.sftpCmd(lsCmd, session)
}

/**
 * stat a file or directory
 *
 * @param {string} path - on filesystem to stat
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 * @return {Promise} Promise with object describing path
 */
SFTPClient.prototype.stat = function stat (location, session) {
  // create the lsCmd callback for this.sftpCmd
  var statCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.stat(location, function (err, stat) {
        if (err) { return reject(err) }
        var attrs = statToAttrs(stat)
        attrs.path = location
        if (stat.isDirectory()) {
          attrs.type = 'directory'
        } else if (stat.isFile()) {
          attrs.type = 'file'
        } else {
          attrs.type = 'other'
        }
        resolve(attrs)
      })
    }
  }
  // return the value of the command
  return this.sftpCmd(statCmd, session)
}

/**
 * get remote file contents into a Buffer
 *
 * @param {string} path - on filesystem to stat
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 * @return {Promise} Promise with Buffer on resolve
 */
SFTPClient.prototype.getBuffer = function getBuffer (location, session) {
  var getBufferCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.open(location, 'r', function (err, handle) {
        if (err) { return reject(err) }
        sftp.fstat(handle, function (err, stat) {
          if (err) { return reject(err) }
          var bytes = stat.size
          var buffer = Buffer.alloc(bytes)
          if (bytes === 0) {
            return resolve(buffer)
          }
          buffer.fill(0)
          var cb = function (err, readBytes, offsetBuffer, position) {
            if (err) { return reject(err) }
            position = position + readBytes
            bytes = bytes - readBytes
            if (bytes < 1) {
              sftp.close(handle, function (err) {
                if (err) { return reject(err) }
                resolve(buffer)
              })
            } else {
              sftp.read(handle, buffer, position, bytes, position, cb)
            }
          }
          sftp.read(handle, buffer, 0, bytes, 0, cb)
        })
      })
    }
  }
  return this.sftpCmd(getBufferCmd, session)
}

/**
 * put buffer to remote file
 *
 * @param {Buffer} - Buffer containing file contents
 * @param {string} path - on filesystem to stat
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 * @return {Promise} Promise with boolean true if tranfer was successful
 */
SFTPClient.prototype.putBuffer = function putBuffer (buffer, location, session) {
  var putBufferCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.open(location, 'w', function (err, handle) {
        if (err) { return reject(err) }
        sftp.write(handle, buffer, 0, buffer.length, 0, function (err) {
          if (err) { return reject(err) }
          sftp.close(handle, function (err) {
            if (err) { return reject(err) }
            resolve(true)
          })
        })
      })
    }
  }
  return this.sftpCmd(putBufferCmd, session)
}

/**
 * get remote file and save it locally
 *
 * @param {string} remotepath - path to remote file
 * @param {string} localpath - destination path on local filesystem
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 */
SFTPClient.prototype.get = function get (remote, local, session) {
  var getCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.fastGet(remote, local, function (err) {
        if (err) { return reject(err) }
        resolve(true)
      })
    }
  }
  return this.sftpCmd(getCmd, session)
}

/**
 * put local file in remote path
 *
 * @param {string} localpath - path to local file
 * @param {string} remotepath - destination path on remote filesystem
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 */
SFTPClient.prototype.put = function put (local, remote, session) {
  var putCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.fastPut(local, remote, function (err) {
        if (err) { return reject(err) }
        resolve(true)
      })
    }
  }
  return this.sftpCmd(putCmd, session)
}

/**
 * remove remote file
 *
 * @param {string} path - remote file to remove
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 */
SFTPClient.prototype.rm = function rm (location, session) {
  var rmCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.unlink(location, function (err) {
        if (err) { return reject(err) }
        resolve(true)
      })
    }
  }
  return this.sftpCmd(rmCmd, session)
}

/**
 * move remote file from one spot to another
 *
 * @param {string} source - remote filesystem source path
 * @param {string} destination - remote filesystem desitnation path
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 */
SFTPClient.prototype.mv = function rm (src, dest, session) {
  var mvCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.rename(src, dest, function (err) {
        if (err) { return reject(err) }
        resolve(true)
      })
    }
  }
  return this.sftpCmd(mvCmd, session)
}

/**
 * removes and empty directory
 *
 * @param {string} path - remote directroy to remove
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 */
SFTPClient.prototype.rmdir = function rmdir (path, session) {
  var rmdirCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.rmdir(path, function (err) {
        if (err) { return reject(err) }
        return resolve(true)
      })
    }
  }
  return this.sftpCmd(rmdirCmd, session)
}

/**
 *  makes a directory
 *
 * @param {string} path - remote directory to be created
 * @param {ssh2.Client} [session] - existing ssh2 connection, optional
 */
SFTPClient.prototype.mkdir = function mkdir (path, session) {
  var mkdirCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (err) {
        return reject(err)
      }
      sftp.mkdir(path, function (err) {
        if (err) { return reject(err) }
        return resolve(true)
      })
    }
  }
  return this.sftpCmd(mkdirCmd, session)
}

/**
 * stream file contents from remote file
 *
 * @parm {string} path - remote file path
 * @parm {writableStream} writableStream - writable stream to pipe read data to
 * @parm {ssh2.Client} [session] - existing ssh2 connection
 */
SFTPClient.prototype.getStream = function getStream (path, writableStream, session) {
  var getStreamCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (!writableStream.writable) {
        return reject(new Error('Stream must be a writable stream'))
      }
      if (err) { return reject(err) }
      sftp.stat(path, function (err, stat) {
        if (err) { return reject(err) }
        var bytes = stat.size
        if (bytes > 0) {
          bytes -= 1
        }
        try {
          var stream = sftp.createReadStream(path, {start: 0, end: bytes})
        } catch (err) {
          return reject(err)
        }
        stream.pipe(writableStream)
        stream.on('end', function () {
          resolve(true)
        })
        stream.on('error', function (err) {
          reject(err)
        })
      })
    }
  }
  return this.sftpCmd(getStreamCmd, session)
}

/**
 * stream file contents from local file
 *
 * @parm {string} path - remote file path
 * @parm {readableStream} readableStream - writable stream to pipe read data to
 * @parm {ssh2.Client} [session] - existing ssh2 connection
 */
SFTPClient.prototype.putStream = function putStream (path, readableStream, session) {
  var putStreamCmd = function (resolve, reject) {
    return function (err, sftp) {
      if (!readableStream.readable) {
        return reject(new Error('Stream must be a readable stream'))
      }
      if (err) { return reject(err) }
      try {
        var stream = sftp.createWriteStream(path)
      } catch (err) {
        return reject(err)
      }
      stream.on('open', function () {
        readableStream.pipe(stream)
      })
      stream.on('finish', function () {
        resolve(true)
      })
      stream.on('error', function (err) {
        reject(err)
      })
    }
  }
  return this.sftpCmd(putStreamCmd, session)
}

/**
 * get a readable stream to remote file
 *
 * @parm {string} path - remote file path
 * @parm {ssh2.Client} [session] - existing ssh2 connection
 */
SFTPClient.prototype.createReadStream = function getReadStream (path, session) {
  var createReadStreamCmd = function (resolve, reject, conn) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      sftp.stat(path, function (err, stat) {
        if (err) { return reject(err) }
        var bytes = stat.size
        if (bytes > 0) {
          bytes -= 1
        }
        try {
          var stream = sftp.createReadStream(path, {start: 0, end: bytes})
        } catch (err) {
          return reject(err)
        }
        stream.on('close', function () {
          // if there is no session we need to clean the connection
          if (!session) {
            conn.end()
            conn.destroy()
          }
        })
        stream.on('error', function () {
          if (!session) {
            conn.end()
            conn.destroy()
          }
        })
        stream.on('readable', function () {
          resolve(stream)
        })
      })
    }
  }
  return this.sftpCmd(createReadStreamCmd, session, true)
}

/**
 * get a writable stream to remote file
 *
 * @parm {string} path - remote file path
 * @parm {ssh2.Client} [session] - existing ssh2 connection
 */
SFTPClient.prototype.createWriteStream = function createWriteStream (path, session) {
  var createWriteStreamCmd = function (resolve, reject, conn) {
    return function (err, sftp) {
      if (err) { return reject(err) }
      try {
        var stream = sftp.createWriteStream(path)
      } catch (err) {
        return reject(err)
      }
      stream.on('close', function () {
        // if there is no session we need to clean the connection
        if (!session) {
          conn.end()
          conn.destroy()
        }
      })
      stream.on('error', function (err) {
        if (!session) {
          conn.end()
          conn.destroy()
        }
        reject(err)
      })
      stream.on('open', function () {
        resolve(stream)
      })
    }
  }
  return this.sftpCmd(createWriteStreamCmd, session, true)
}

// export client
module.exports = SFTPClient