diff --git a/.gitignore b/.gitignore index 3023049..f216162 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules npm-debug.log *.iml .idea +.cache test/tmp \ No newline at end of file diff --git a/README.md b/README.md index 6dfd40e..f16876a 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,35 @@ Type: `Function` Read more: https://github.com/mscdex/ssh2#connection-methods +### Task arguments + +You can control uploading only newer files to server and clean cache of uploaded files. +This additional arguments can be added only to task with specified target. + +#### :newer +Uploads only file which has been changed after previous upload. + +#### :clean +Cleans cache of uploaded files. Uploads all specified files. + +#### Examples + +on command line +``` +grunt scp:your_target:newer +grunt scp:your_target:clean +``` + +in gruntfile +```js +grunt.task.run('scp:your_target:newer'); +``` + +WARNING! Next example fails because there is no specified target. +``` +grunt scp:newer +``` + ## Changelog **2013-11-14** `0.1.6` diff --git a/package.json b/package.json index d0e88c4..55c38e6 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,9 @@ }, "dependencies": { "async": "~0.2.6", - "scp2": "~0.1.4", - "inquirer": "~0.3.5" + "chalk": "^1.1.1", + "inquirer": "~0.3.5", + "scp2": "~0.1.4" }, "devDependencies": { "grunt-contrib-jshint": "^0.9.2", @@ -44,4 +45,4 @@ "keywords": [ "gruntplugin" ] -} \ No newline at end of file +} diff --git a/tasks/scp.js b/tasks/scp.js index 2460fb3..78d8daf 100644 --- a/tasks/scp.js +++ b/tasks/scp.js @@ -12,6 +12,8 @@ var path = require('path'); var async = require('async'); var Client = require('scp2').Client; var inquirer = require('inquirer'); +var chalk = require('chalk'); +var fs = require('fs'); module.exports = function(grunt) { @@ -21,26 +23,72 @@ module.exports = function(grunt) { tryKeyboard: true }); + var ARG_NEWER = 'newer'; + var ARG_CLEAN = 'clean'; + var done = this.async(); var filename, destfile; var client = new Client(options); var files = this.files; + var uploadedFiles = 0; + var skippedFiles = 0; + + var cache = new (function (name, args) { + + var newer = args.indexOf(ARG_NEWER) !== -1; + var clean = args.indexOf(ARG_CLEAN) !== -1; + + this.dir = path.join(__dirname, '..', '.cache'); + this.file = path.join(this.dir, (name || 'scp') + '.json'); + this.data = {}; + + this.isUpToDate = function (filepath) { + stats = fs.statSync(filepath); + if (!clean && newer && (this.data.hasOwnProperty(filepath) && this.data[filepath] === stats.ctime.toJSON())) { + return true; + } + this.data[filepath] = stats.ctime; + return false; + }; + + this.store = function () { + if (!clean) { + grunt.file.write(this.file, JSON.stringify(this.data)); + } + }; + + this.clean = function () { + if (grunt.file.exists(this.file)) { + grunt.file.delete(this.file); + } + }; + + /** CONSTRUCTOR **/ + if (clean) { + this.clean(); + } + if (!grunt.file.exists(this.dir)) { + fs.mkdir(this.dir); + } + this.data = grunt.file.exists(this.file) ? grunt.file.readJSON(this.file) : {}; + + })(this.target, this.args); client.on('connect', function() { - grunt.log.writeln('ssh connect ' + options.host); + grunt.verbose.writeln('ssh connect ' + options.host); }); client.on('keyboard-interactive', function(name, instructions, instructionsLang, prompts, finish) { finish([options.password]); }); client.on('close', function() { - grunt.log.writeln('ssh close ' + options.host); + grunt.verbose.writeln('ssh close ' + options.host); done(); }); client.on('mkdir', function(dir) { - grunt.log.writeln('mkdir ' + dir); + grunt.verbose.writeln('mkdir ' + dir); }); client.on('write', function(o) { - grunt.log.writeln('write ' + o.destination); + grunt.verbose.writeln('write ' + o.destination).or.write('.'); if (options.log) { options.log(o); } @@ -49,12 +97,13 @@ module.exports = function(grunt) { up = up + 1; if (up < total) { if ((Math.floor(up / 550)) === (up / 550)) { - grunt.log.writeln('transfer ' + Math.floor(up / total * 100) + '% data'); + grunt.verbose.writeln('transfer ' + Math.floor(up / total * 100) + '% data'); } else if (up === 1) { - grunt.log.writeln('transfer 1% data'); + grunt.verbose.writeln('transfer 1% data'); } } else { - grunt.log.writeln('transfer ' + Math.floor(up / total * 100) + '% data'); + grunt.verbose.writeln('transfer ' + Math.floor(up / total * 100) + '% data'); + uploadedFiles++; } }); client.on('error', function(err) { @@ -74,6 +123,10 @@ module.exports = function(grunt) { if (err) { grunt.log.error('Error ' + err); } + grunt.log.writeln((uploadedFiles > 0 ? "\n" : "") + + "Uploaded " + chalk.cyan(uploadedFiles) + " " + (uploadedFiles !== 1 ? "files" : "file") + + " skipped " + chalk.cyan(skippedFiles) + " " + (skippedFiles !== 1 ? "files" : "file")); + cache.store(); client.close(); }); } @@ -86,14 +139,20 @@ module.exports = function(grunt) { } else { filename = path.relative(fileObj.orig.cwd, filepath); } - destfile = path.join(fileObj.dest, filename); - client.upload(filepath, destfile, cb); + if (!cache.isUpToDate(filepath)) { + destfile = path.join(fileObj.dest, filename); + client.upload(filepath, destfile, cb); + } else { + grunt.verbose.writeln(filepath + "...skipped"); + skippedFiles++; + cb.call(null); + } }, function(err) { cb(err); }); } - if (options.password || options.privateKey) { + if (options.password || options.privateKey || options.agent) { execUploads(); } else { inquirer.prompt([{