UNPKG

webdav-sync

Version:

Basic local sync to WebDAV servers

232 lines (193 loc) 7.47 kB
/* webdav_sync.js * This is where the business happens. Fixes and Features * Status indicators on commands. (Yellow circle for ongoing, Green disc for completed, red square for error) * quote wraps to fix bug with files having space characters in their names * Check connection of --remote_base at startup, saves a lot of hassle and initial confusion. * * TODO: * -interactive 'Clean All' command to force wipe & re-push all files. * * KNOWN ISSUE: * -(Side-effect) Because of the odd text mode we use to draw the status circle/squares, this can cause a console to go into a weird mode if we force-break out of it */ var reqStack = {}; //object of ongoing queue of active requests, pop on complete/error // Will ultimately be formatted like: reqStack[request, lineNum] var currLine = 0; //rolling iterator of how many lines have been printed var __indexOf = [].indexOf || function (item) { var i, l; for (i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item){ return i; } } return -1; }; var exec, util, colors, normalize, fs, watch, url; //libraries watch = require("watch"); fs = require("fs"); colors = require("colors"); util = require("util"); normalize = require("path").normalize; exec = require("child_process").exec; url = require("url"); module.exports = function (options) { var changed, created, removed, ignoreFile, processed, run, _runCommand; if (options === null) { options = {}; } processed = {}; //Object of the History of processed execs in the format of "d"+theCommand+mtime of file options.monitor || (options.monitor = { ignoreDotFiles: true }); if (!options.remote_base) { throw new Error("remote_base can't be ommited"); } if (!options.local_base) { throw new Error("local_base can't be ommited"); } run = function(command, message) { //PRINT: disable line break, then the CIRCLE then message //util.puts('\033[?7l'+"○ ".yellow + message); console.log('\033[?7l'+"○ ".yellow + message); //TESTING reqStack[command]=currLine;//push this command with currLine in currLine++;//then go on...(?) return _runCommand(command, function(err, success) { moveUpRows = currLine - reqStack[command]; var goBackStr = new Array(moveUpRows+1).join("\n"); if (err) { //RED SQUARE process.stdout.write('\033[' + moveUpRows + 'A\r' + "■".red + goBackStr); return console.log(err.red); //print the full curl error (does this throw off the currLine count?) }else{ //GREEN DOT process.stdout.write('\033[' + moveUpRows + 'A\r' + "●".green + goBackStr); } }); }; _runCommand = function(command, callback) { if(options.verbose){ currLine++; console.log((new Date()).toString().underline); currLine++; command += " --verbose"; console.log(command.yellow); } //console.log(command); return exec(command, function(error, stdout, stderr){ //BEN: need to get more out of curl about failed attempts to connect! if (!stderr) { return callback(null, true); } else { //we've got an error! //Ben: we do not util.error() here... should we allow for that with another --option? return callback(stderr.trim().red, false); } }); }; created = function(path, stats) { // Fix paths on windows path = path.replace(/\\/g, "/"); //BUG: this is firing twice? var command, destination, message, rel_path, _ref; //check if we've tried this filechange already... if (!stats || (_ref = "w" + path + stats.mtime, __indexOf.call(processed, _ref) >= 0)) { return; } processed["w" + path + stats.mtime] = currLine; rel_path = path.replace(options.local_base, ""); rel_path = normalize(url.parse(options.remote_base).path + path.replace(options.local_base, "")); destination = url.resolve(options.remote_base, rel_path); //ignore it? if (ignoreFile(rel_path)) { return; } if (stats.isFile()) { command = options.curl + ' -T "' + path + '" "' + destination + '" '; } else if (stats.isDirectory()) { command = options.curl + ' -X MKCOL "' + destination + '" '; } if (command != null) { message = "[created] ".bold.green + rel_path.green; return run(command, message); } }; changed = function(path, stats) { // Fix paths on windows path = path.replace(/\\/g, "/"); var command, destination, message, rel_path, _ref; //check to see if we've tried to send this yet... if (!stats || (_ref = "w" + path + stats.mtime, __indexOf.call(processed, _ref) >= 0)) { //util.puts("huh"); return; } processed["m" + path + stats.mtime] = currLine; //remember that we modified path at mtime... rel_path = normalize(url.parse(options.remote_base).path + path.replace(options.local_base, "")); destination = url.resolve(options.remote_base, rel_path); if (ignoreFile(rel_path)) { return; } if (stats.isFile()) { command = options.curl + ' -T "' + path + '" ' + destination; } else if (stats.isDirectory()) { command = options.curl + ' -X MKCOL ' + destination; } if (command != null) { message = "[changed] ".bold.cyan + rel_path.cyan; return run(command, message); } }; removed = function(path, stats) { // Fix paths on windows path = path.replace(/\\/g, "/"); var command, destination, message, rel_path, _ref; if (!stats || (_ref = "d" + path + stats.mtime, __indexOf.call(processed, _ref) >= 0)) { return; } processed["d" + path + stats.mtime]=currLine; rel_path = path.replace(options.local_base, ""); destination = url.resolve(options.remote_base, rel_path); if (ignoreFile(rel_path)) { return; } command = options.curl + ' -X DELETE "' + destination + '" '; if (command != null) { message = "[removed] ".bold + rel_path.red; return run(command, message); } }; //Utility for checking if a file is in our comma-delimited list to "--ignore" ignoreFile = function(path) { return __indexOf.call(options.ignored, path) >= 0; }; return { start: function() { //disable console line wrap (necessary for status icons) console.log('\033[0m\r');//reset any funny text modes console.log('\033[?7h\r');//enable line-break currLine++; //STARTUP! console.log("STARTING".bold.underline + " \nwebdav-sync from " + options.local_base.green + " \nto " + options.remote_base.yellow); //first check to see if we can even get into options.remote_base var command; if(options.username != null){ //user has passed in user/pass, so we're rigging options.curl with that always options.curl += " -u " + options.username + ":" + options.password; //console.log(options.curl); } command = options.curl + " " + options.remote_base; //testing the connection when we start! message="[CONNECTION TEST]".bold; run(command, message); //rig the watch events to local_base return watch.createMonitor(options.local_base, options.monitor, function(monitor) { monitor.on("created", created); monitor.on("changed", changed); return monitor.on("removed", removed); //why do we return on this? }); } }; };