diff --git a/bin/scp2 b/bin/scp2 new file mode 100755 index 0000000..eb62865 --- /dev/null +++ b/bin/scp2 @@ -0,0 +1,165 @@ +#!/usr/bin/env node + +var fs = require('fs'); +var path = require('path'); +var util = require('util'); +var client = require('../'); + +main(process.argv.slice()); + +function main(argv) { + var identify, src, dest; + + var quiet = false; + var port = 22; + var defaults = {}; + + var getArg = function() { + var args = argv.shift(); + args = args.split('='); + if (args.length > 1) { + argv.unshit(args.slice(1).join('=')); + } + return args[0]; + } + + var arg, remains = []; + + while (argv.length) { + arg = getArg(); + switch(arg) { + case '-p': + case '--port': + port = argv.shift(); + break; + case '-q': + case '--quiet': + quiet = true; + break; + case '-i': + case '--identify': + identify = argv.shift(); + break; + case '-h': + case '--help': + helpMessage(); + break; + default: + remains.push(arg); + break; + } + } + + if (remains.length !== 4) { + helpMessage(); + } + printLog(quiet); + + src = remains[2]; + dest = remains[3]; + + defaults = { + port: parseInt(port, 10) + } + + var password; + var parsed = client.parse(src); + + if (parsed.username && parsed.host && parsed.path) { + password = parsed.password; + } else { + password = client.parse(dest).password; + } + + client.on('error', function(err) { + if (err.code === 'ECONNREFUSED') { + prompt(' password: ', function(val) { + client.close(); + delete client.__ssh; + defaults.password = val; + scp(src, dest, defaults); + }); + } else { + console.error(err); + process.exit(1); + } + }); + + if (!password && identify && fs.existsSync(identify)) { + defaults.privateKey = fs.readFileSync(identify); + scp(src, dest, defaults); + } else if (!password) { + prompt(' password: ', function(val) { + defaults.password = val; + scp(src, dest, defaults); + }); + } else { + defaults.password = password; + scp(src, dest, defaults); + } +} + +function helpMessage() { + console.log(); + var lines = [ + ' Usage:', + ' scp2 [-p 22] localfile server', + '', + ' Options:', + ' -p, --port= ssh port, default: 22', + ' -i, --identify= identify file', + ' -q, --quiet do not show log', + ' -h, --help display this message', + '', + ' Examples:', + ' $ scp2 data.txt admin:password@example.com:/home/admin/', + ' $ scp2 data.txt admin@example.com:/home/admin/rename.txt', + '', + ]; + console.log(lines.join('\n')); + process.exit(); +} + +function printLog(quiet) { + if (!quiet) { + client.on('connect', function() { + util.log('connected'); + }); + client.on('ready', function() { + util.log('ready'); + }); + client.on('mkdir', function(p) { + util.log('mkdir ' + p); + }); + client.on('write', function(o) { + util.log('write ' + o.destination); + }); + client.on('close', function() { + util.log('close'); + }); + client.on('end', function() { + util.log('end'); + }); + } +} + +function prompt(str, fn) { + process.stdout.write(str); + process.stdin.setEncoding('utf8'); + process.stdin.once('data', function(val) { + fn(val.trim()); + }).resume(); +} + +function scp(src, dest, defaults) { + client.defaults(defaults); + client.scp(src, dest, function(err) { + if (err) { + console.error(err); + process.exit(1); + } else { + util.log('scp success.'); + } + process.exit(); + }); +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..ca43c09 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/scp'); diff --git a/lib/client.js b/lib/client.js index 210ece8..1d13066 100644 --- a/lib/client.js +++ b/lib/client.js @@ -6,7 +6,6 @@ var Buffer = require('buffer').Buffer; var EventEmitter = require('events').EventEmitter; var Connection = require('ssh2'); var _ = require('lodash'); -var file = require('./file'); function Client(options) { @@ -29,6 +28,7 @@ Client.prototype.parse = function(remote) { // username:password@host:/path/to var regex = /^([a-zA-Z0-9\-\.]+)(\:.*)?@([^:]+)(\:.*)?$/; var m = remote.match(regex); + if (!m) return {}; var ret = { username: m[1], host: m[3] @@ -39,8 +39,10 @@ Client.prototype.parse = function(remote) { if (m[4]) { ret.path = m[4].slice(1); } + this.remote = ret; return ret; } + this.remote = remote; return remote; }; @@ -49,9 +51,14 @@ Client.prototype.sftp = function(callback) { callback(null, this.__sftp); return; } + var remote = _.defaults(this.remote, this._options); - var self = this; + if (this.__ssh) { + this.__ssh.connect(remote); + return; + } + var self = this; var ssh = new Connection(); ssh.on('connect', function() { self.emit('connect'); @@ -137,8 +144,10 @@ Client.prototype.write = function(options, callback) { content = new Buffer(content, options.encoding); } var self = this; + this.sftp(function(err, sftp) { - sftp.open(destination, 'w', attrs, function(err, handle) { + + var _write = function(handle) { self.emit('write', options); sftp.write(handle, content, 0, content.length, 0, function(err) { var writeErr = err; @@ -146,11 +155,28 @@ Client.prototype.write = function(options, callback) { callback(err || writeErr); }); }); + }; + + sftp.open(destination, 'w', attrs, function(err, handle) { + if (err) { + // destination is directory + destination = path.join( + destination, path.basename(options.source) + ); + sftp.open(destination, 'w', attrs, function(err, handle) { + _write(handle); + }); + } else { + _write(handle); + } }); }); }; Client.prototype.upload = function(src, dest, callback) { + if (process.platform === 'win32') { + dest = dest.replace(/\\/g, '/'); + } var self = this; fs.stat(src, function(err, stats) { if (err) { @@ -158,12 +184,7 @@ Client.prototype.upload = function(src, dest, callback) { return; } - var attrs = { - ctime: stats.ctime, - atime: stats.atime, - mtime: stats.mtime - }; - + var attrs = util.inspect(stats); fs.readFile(src, function(err, content) { if (err) { callback(err); @@ -172,6 +193,7 @@ Client.prototype.upload = function(src, dest, callback) { // mkdir for safety self.mkdir(path.dirname(dest), attrs, function(err) { self.write({ + source: src, destination: dest, content: content, attrs: attrs diff --git a/lib/file.js b/lib/file.js deleted file mode 100644 index cb02674..0000000 --- a/lib/file.js +++ /dev/null @@ -1,43 +0,0 @@ -var _ = require('lodash'); - -var file = module.exports = {}; - -file.ignorecvs = false; -file.glob = require('glob'); - -file.list = function(src, filter) { - var ret = []; - file.recurse(src, function(filepath) { - ret.push(filepath); - }, filter); - return ret; -} - -file.recurse = function(rootdir, callback, subdir, filter) { - if (_.isFunction(subdir)) { - filter = subdir; - subdir = null; - } - var abspath = subdir ? path.join(rootdir, subdir) : rootdir; - fs.readdirSync(abspath).forEach(function(filename) { - var filepath = path.join(abspath, filename); - if (filter && !filter(filepath, subdir, filename)) { - return; - } - if (file.ignorecvs && /^\.(git|hg|svn)$/.test(subdir)) { - return; - } - if (fs.statSync(filepath).isDirectory()) { - recurse(rootdir, callback, unixifyPath(path.join(subdir, filename)), filter); - } else { - callback(unixifyPath(filepath), rootdir, subdir, filename); - } - }); -} - -function unixifyPath(filepath) { - if (process.platform === 'win32') { - return filepath.replace(/\\/g, '/'); - } - return filepath; -} diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 7e60e3a..0000000 --- a/lib/index.js +++ /dev/null @@ -1,29 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var async = require('async'); -var Buffer = require('buffer').Buffer; -var Connection = require('ssh2'); -var _ = require('lodash'); -var file = require('./file'); - - -var client = new Client(); -exports = module.exports = function(local, remote, callback) { - var parsed = client.parse(local); - - if (parsed.username && parsed.host) { - local = remote; - client.remote = parsed; - client.download(local, callback); - } else { - client.remote = client.parse(remote); - client.upload(local, callback); - } -} -exports.defaults = client.defaults; - -exports.upload = client.upload; -//exports.download = client.download; - -exports.Client = Client; diff --git a/lib/scp.js b/lib/scp.js new file mode 100644 index 0000000..b15bbe9 --- /dev/null +++ b/lib/scp.js @@ -0,0 +1,87 @@ +var fs = require('fs'); +var path = require('path'); +var glob = require('glob'); +var async = require('async'); +var client = require('./client'); + +function cp2remote(src, dest, callback) { + client.remote = client.parse(dest); + + var _upload = function(files, callback) { + var rootdir = files[0]; + + async.eachSeries(files, function(fpath, done) { + fs.stat(fpath, function(err, stats) { + if (err) { + done(err); + return; + } + if (stats.isFile()) { + var fname = path.relative(rootdir, fpath); + client.upload( + fpath, path.join(client.remote.path, fname), done + ) + } else { + done(); + } + }); + }, function(err) { + // never forget to close the session + client.on('close', function() { + callback(err); + }); + client.close(); + }); + }; + + if (src.indexOf('*') === -1) { + fs.stat(src, function(err, stats) { + if (err) { + callback(err); + return; + } + if (stats.isFile()) { + client.upload(src, client.remote.path, function(err) { + client.on('close', function() { + callback(err); + }); + client.close(); + }); + } else if (stats.isDirectory()) { + glob(src.replace(/\/$/, '') + '/**/**', function(err, files) { + if (err) { + callback(err); + } else { + _upload(files, callback); + } + }); + } else { + callback('unsupported'); + } + }); + } else { + glob(src, function(err, files) { + if (err) { + callback(err) + return; + } + _upload(files, callback); + }); + } +} + + +function cp2local(src, dest, callback) { + client.remote = client.parse(src); +} + +exports = module.exports = client; + +exports.scp = function(src, dest, callback) { + var parsed = client.parse(src); + if (parsed.password && parsed.path) { + cp2local(parsed, dest, callback); + } else { + cp2remote(src, dest, callback); + } +};