diff --git a/sftp-sync.js b/sftp-sync.js index 69771a130059af08bef64a4d61edf0a2d997ba6a..7c12c316c6868125c8ccc21a2d439a04e00b324b 100644 --- a/sftp-sync.js +++ b/sftp-sync.js @@ -6,50 +6,178 @@ var cli = require('cli'); var options = cli.parse({ sourcedir: [null, 'From where to upload (required)', 'string'], - remotedir: [null, 'Target directory for upload (required)', 'string'], + targetdir: [null, 'Target directory for upload (required)', 'string'], server: ['s', 'SFTP server (required)', 'string'], - user: ['u', 'SFTP user (required)', 'string'], + user: ['u', 'SFTP user (required if server != "localdirectory")', 'string'], port: ['p', 'port', 'number', 22] }); -function missingArguments() { console.log('Missing required arguments. Use --help.')} -if(!options.sourcedir) return missingArguments(); -if(!options.server) return missingArguments(); -if(!options.user) return missingArguments(); -if(!options.remotedir) return missingArguments(); -if(!process.env.PASSWORD) return console.log('Missing environment variable PASSWORD'); +function missingArguments(arg) { console.log('Missing required arguments '+arg+'. Use --help.')} +if(!options.sourcedir) return missingArguments('sourcedir'); +if(!options.server) return missingArguments('server'); +if(!options.user && options.server != 'localdirectory') return missingArguments('user'); +if(!options.targetdir) return missingArguments('targetdir'); +if(!process.env.PASSWORD && options.server != 'localdirectory') + return console.log('Missing environment variable PASSWORD'); -var Client = require('ssh2').Client; +var remoteFS; +if(options.server == 'localdirectory') { + remoteFS = { + remotedir: '', + setDir: function(dir) { + remoteFS.remotedir = dir; + }, + connect: function() { + return Promise.resolve(); + }, + close: function() { + }, + uploadFile: function(localfile, remotefile) { + function copyFile(source, target, cb) { + var cbCalled = false; -var conn = new Client(); -function connect(arg) { - return new Promise(function(resolve, reject) { - var connReady = false; + var rd = fs.createReadStream(source); + rd.on('error', function(err) { + done(err); + }); + var wr = fs.createWriteStream(target); + wr.on('error', function(err) { + done(err); + }); + wr.on('close', function(ex) { + done(); + }); + rd.pipe(wr); - var resolved = false; - conn.once('ready', function() { - console.log('Client :: ready'); - if(!resolved) { - resolved = true; - resolve(arg); - } - }) - .once('error', function() { - if(resolved) { - console.log(new Error('Failed to connect to server').stack); - process.exit(0); - } else { - resolved = true; - reject(new Error('Failed to connect to server')); + function done(err) { + if (!cbCalled) { + cb(err); + cbCalled = true; + } + } } - }) - .connect({ - host: options.server, - port: options.port, - username: options.user, - password: process.env.PASSWORD - }); - }) + return new Promise(function(resolve, reject) { + console.log(' Uploading '+localfile+' to '+remotefile); + copyFile(localfile, remoteFS.remotedir+'/'+remotefile, function(err) { + if(err) console.log(' [Warning] Error uploading '+localfile+' to '+remotefile); + else console.log(' Uploaded',localfile,'to',remotefile); + resolve(); + }); + }) + }, + readFile: function(file) { + return new Promise(function(resolve, reject) { + var data = ''; + fs.createReadStream(remoteFS.remotedir+'/'+file, 'utf-8') + .on('data', function(chunk) { + data+=chunk; + }) + .on('end', function() { + resolve(data); + }) + .on('error', function(e) { + reject(e); + }) + }) + }, + delete: function(file) { + return new Promise(function(resolve, reject) { + fs.unlink(remoteFS.remotedir+'/'+file, function(err) { + if(err) console.log(' [Warning] Error deleting '+file); + resolve(); + }) + }) + }, + mkdir: function(file) { + return new Promise(function(resolve, reject) { + console.log(' Creating directory '+file); + fs.mkdir(remoteFS.remotedir+'/'+file,function() { + console.log(' Created directory '+file); + resolve(); + }) + }) + } + } +} else { + remoteFS = { + conn: new (require('ssh2').Client)(), + sftp: null, + remotedir: '', + setDir: function(dir) { + remoteFS.remotedir = dir; + }, + connect: function() { + return new Promise(function(resolve, reject) { + var connReady = false; + + remoteFS.conn.once('ready', function() { + console.log('Client :: ready'); + remoteFS.conn.sftp(function(err, sftp) { + if(err) { + reject(new Error('Failed to initialize SFTP')); + } else { + remoteFS.sftp = sftp; + resolve(); + } + }) + }) + .once('error', function() { + reject(new Error('Failed to connect to server')); + }) + .connect({ + host: options.server, + port: options.port, + username: options.user, + password: process.env.PASSWORD + }); + }) + }, + close: function() { + remoteFS.conn.end(); + }, + uploadFile: function(localfile, remotefile) { + return new Promise(function(resolve, reject) { + console.log(' Uploading '+localfile+' to '+remotefile); + remoteFS.sftp.fastPut(localfile, remoteFS.remotedir+'/'+remotefile, function(err) { + if(err) console.log(' [Warning] Error uploading '+localfile+' to '+remotefile); + else console.log(' Uploaded',localfile,'to',remotefile); + resolve(); + }) + }) + }, + readFile: function(file) { + return new Promise(function(resolve, reject) { + var data = ''; + remoteFS.sftp.createReadStream(remoteFS.remotedir+'/'+file, 'utf-8') + .on('data', function(chunk) { + data+=chunk; + }) + .on('end', function() { + resolve(data); + }) + .on('error', function(e) { + reject(e); + }) + }) + }, + delete: function(file) { + return new Promise(function(resolve, reject) { + remoteFS.sftp.unlink(remoteFS.remotedir+'/'+file, function(err) { + if(err) console.log(' [Warning] Error deleting '+file); + resolve(); + }) + }) + }, + mkdir: function(file) { + return new Promise(function(resolve, reject) { + console.log(' Creating directory '+file); + remoteFS.sftp.mkdir(remoteFS.remotedir+'/'+file,function() { + console.log(' Created directory '+file); + resolve(); + }) + }) + } + } } function checksum (str, algorithm, encoding) { @@ -102,24 +230,26 @@ new Promise(function(resolve, reject) { }) return r; }) -.then(connect) +.then(function(arg) { + console.log('Upload step: Connecting to server'); + return remoteFS.connect() + .then(function() { + remoteFS.setDir(options.targetdir); + console.log(' Connected'); + return arg; + }) +}) .then(function(localchecksums) { console.log('Upload step: Getting remote checksums'); - return new Promise(function(resolve, reject) { - conn.sftp(function(err, sftp) { - if (err) reject(err); - var data = ''; - sftp.createReadStream(options.remotedir+'/checksums.json', 'utf-8') - .on('data', function(chunk) { - data+=chunk; - }) - .on('end', function() { - resolve({local: localchecksums, remote: JSON.parse(data), sftp: sftp}); - }) - .on('error', function(e) { - resolve({local: localchecksums, remote: [], sftp: sftp}); - }) - }); + return new Promise(function(resolve,reject){ + return remoteFS.readFile('checksums.json') + .then(function(data) { + resolve({local: localchecksums, remote: JSON.parse(data)}); + }) + .catch(function(e) { + console.log('Failed to read remote checksums.json'); + resolve({local: localchecksums, remote: []}); + }) }) }) /* @@ -148,7 +278,7 @@ new Promise(function(resolve, reject) { var flatten = function(array) { return [].concat.apply([], array); } - readDir(options.remotedir) + readDir(options.targetdir) .then(flatten) .then(function(list) { obj.remoteFiles = list; @@ -170,12 +300,7 @@ new Promise(function(resolve, reject) { obj.remote.forEach(function(entry) { if(localAssoc[entry.file] != entry.checksum && entry.file != 'checksums.json') { console.log(' deleting',entry.file); - promises.push(new Promise(function(resolve, reject) { - obj.sftp.unlink(options.remotedir+'/'+entry.file, function(err) { - if(err) console.log(' [Warning] Error deleting '+entry.file); - resolve(); - }) - })); + promises.push(remoteFS.delete(entry.file)); } }) @@ -219,12 +344,7 @@ new Promise(function(resolve, reject) { var promises = []; toUpload.forEach(function(dir) { - promises.push(new Promise(function(resolve, reject) { - console.log('Creating directory '+dir); - obj.sftp.mkdir(options.remotedir+'/'+dir,function() { - resolve(); - }) - })) + promises.push(remoteFS.mkdir(dir)); }) return Promise.all(promises) .then(function() { @@ -243,14 +363,7 @@ new Promise(function(resolve, reject) { obj.local.forEach(function(entry) { if(remoteAssoc[entry.file] != entry.checksum) { - console.log(' Uploading',entry.file); - promises.push(new Promise(function(resolve, reject) { - obj.sftp.fastPut(options.sourcedir+'/'+entry.file,options.remotedir+'/'+entry.file, function(err) { - if(err)console.log(' [Warning] Error uploading '+entry.file); - else console.log(' Uploaded',entry.file); - resolve(); - }) - })); + promises.push(remoteFS.uploadFile(options.sourcedir+'/'+entry.file,entry.file)); } }) @@ -269,20 +382,11 @@ new Promise(function(resolve, reject) { }) .then(function() { console.log(' Removing remote checksums.json'); - return new Promise(function(resolve, reject) { - obj.sftp.unlink(options.remotedir+'/checksums.json', function(err) { - resolve(); - }) - }) + return remoteFS.delete('checksums.json') }) .then(function() { console.log(' Uploading checksums.json'); - return new Promise(function(resolve, reject){ - obj.sftp.fastPut(options.sourcedir+'/checksums.json',options.remotedir+'/checksums.json', function(err) { - if(err)console.log(' [Warning] Error uploading checksums.json'); - resolve(); - }) - }); + return remoteFS.uploadFile(options.sourcedir+'/checksums.json','checksums.json'); }) .then(function() { console.log(' Removing local checksums.json'); @@ -294,9 +398,9 @@ new Promise(function(resolve, reject) { }) }) .then(function() { - conn.end(); + remoteFS.close(); }) .catch(function(e) { - conn.end(); + remoteFS.close(); console.log(e.stack); })