<commit_before>#!/usr/bin/env node
'use strict';
/**
 * a nifty tool to sync your local folder with remote folder
 * --------------------------------------------------------------
 * INSTALLATION, just clone this to any place on your computer
 * and cd to sync folder, then run: `npm install` and run server
 * by calling `nodejs sync.js`
 * --------------------------------------------------------------
 * For best usage, create a config file as ~/.ssh/config
 * and put this inside
---------- ~/.ssh/config ------------------
Host *
    ControlMaster auto
    ControlPath ~/.ssh/master-%r@%h:%p

Host dev
    HostName {HOST URL OR IP}
    User {YOUR USERNAME}
-------------------------------------------
 * so script can maintain a persistent SSH connection and be faster
 */
var sync = {
    // for colored and powerful command line
    clc: require('cli-color'),
    // shortcut to write to console
    write: process.stdout.write.bind(process.stdout),
    // execute a child process
    exec: require('child_process').exec,
    // Date Time parsing easy way
    moment: require('moment'),
    // self explanatory sprintf method
    sprintf: require('underscore.string').sprintf,
    // Check if string ends with given substring
    endsWith: require('underscore.string').endsWith,
    // read users input, for command line
    readline: require('readline'),
    // when was the last time script checked changes
    lastRun: +(new Date()),
    // how many seconds it took to start checking again
    timeDiff: 0,
    // if true, stop checking changes
    paused: false,
    // time handler for print dots functions
    pdTime: 0,
    /**
     * Read config file and check it's existance
     */
    getConfig: function(){
        // Check the existance of the config file
        try{
            this.config = require("./config.json");
        }catch(e){
            console.log(this.clc.red('Please create a config file by copying the config_example.json'));
            process.exit(1);
        }
        // Make sure remote path ends with slash
        if(!this.endsWith(this.config.remote_path, '/')){
            this.config.remote_path += '/';
        }
    },
    /**
     * Start read line process, and check if installed nodejs compatible with it
     * @return {[type]} [description]
     */
    initReadline: function(){
        // Ubuntu uses the old version of node.js and it doesn't have support for readline features
        // So check here for errors and warn user about node upgrade
        try{
            this.rl = this.readline.createInterface({
                input: process.stdin,
                output: process.stdout
            });
        }catch(e){
            console.log('You need to upgrade your nodejs');
            console.log('http://slopjong.de/2012/10/31/how-to-install-the-latest-nodejs-in-ubuntu/');
            process.exit(1);
        }
    },
    /**
     * Start printing dots to screen, show script is working
     */
    dotsStart: function(){
        var self = this;
        this.pdTime = setInterval(function(){
            self.write(self.clc.yellow('.'));
        }, 200);
    },
    /**
     * Stop printing dots when process ends
     */
    dotsStop: function(){
        clearInterval(this.pdTime);
    },
    /**
     * Convert date time string to unix time
     */
    getSecondsOf: function (time) {
        var t = this.moment(time);
        if (t !== null) {
            return t.unix();
        }
        return 0;
    },
    /**
     * Generate a find command for both OSX and Ubuntu
     * ubuntu should cover most linux versions
     */
    generateFindCMD: function(){
        // ls command powered with find to get recently changed files
        // Command to get changed files
        if (process.platform === 'darwin'){
            // for mac os x
            this.cmd = 'ls -1CloTtr $( find . -type f -mtime -5m -print ) | grep -v \'.git/\' | awk \'{ print  $5, $6, $7, $8, "-", $9 }\'';
        }else{
            // for linux
            this.cmd = 'ls -1Clotr --time-style=+\'%d-%b-%Y %T\' $( find . -type f -mmin -5 -print ) | grep -v \'.git/\' | awk \'{ print  $5, $6, "-", $7 }\'';
        }
    },
    /**
     * Upload given file to server then call given callback
     */
    uploadFile: function (line, callback, i){
        var self = this;
        // notify user that upload has started
        this.write( this.clc.magenta(i) + ' ' + this.clc.yellow(this.sprintf('Uploading file: %s ', line[0])));
        // create scp command
        var scp = this.sprintf('scp %s %s:%s', line[0], this.host, line[1]);
        // start printing dots
        this.dotsStart();
        //execute command
        this.exec(scp, function(e){
            // command completed, stop dots
            self.dotsStop();
            // if there is an error during upload, print it otherwise give user success message
            if (e !== null) {
                self.write(self.clc.red(self.sprintf('ERROR on: %s Message: %s\n', line[1], e)));
            }else{
                self.write(self.clc.green(' Saved.\n'));
            }
            // call callback no matter what
            callback();
        });
    },
    /**
     * Returns a list of changed files
     */
    getChangedFiles: function (lines){
        var self = this;
        // an empty array to fill changed files
        var changedFiles = [];
        // create a unix time right before our last check
        var anIntervalAgo = this.moment().unix() - this.timeDiff;
        // loop all returned files
        lines.forEach(function(line){
            // split the file list so that we can have the file name and changed date
            var details = line.split(' - ');
            // Convert date to seconds, so we can compare it unix time of now
            var fileSeconds = self.getSecondsOf(details[0]);
            // merge rest of the details to re-genrate the file name
            details.shift();
            var filename = details.join('');

            // if file changed at least an interval ago, then it is a recent change
            if (fileSeconds > anIntervalAgo) {
                // create remote file name and add to the array
                var remoteFile = filename.replace('./', self.remotePath);
                if(!self.endsWith(filename, '.swp') && !self.endsWith(filename, '.pyc')){
                    changedFiles.push([filename, remoteFile]);
                }
            }
        });

        return changedFiles;
    },
    /**
     * Keep the script explanation at the top of the page
     */
    printTitle: function(){
        this.write(this.clc.reset + '\n');
        if(this.paused){
            this.write('Currently paused, type "'+ this.clc.green('resume') + '" to start again.\n');
        }else{
            this.write(this.sprintf('Started monitoring, checking every %s seconds.\n', this.secondsInterval));
        }
        this.write('Quit the script with CONTROL-C or type "'+this.clc.green('exit')+'".\n');
        this.write(this.clc.magenta('-----------------------------------------------------------\n'));
        this.showPrompt();
    },
    /**
     * Show prompt line so user can run commands
     */
    showPrompt: function(){
        var self = this;
        this.rl.question(">>> ", function(answer) {
          self.handleInput(answer);
          // as soon as a command is run, show promt again just a like a real shell
          self.showPrompt();
        });
    },
    /**
     * Short cut to generate a help text
     */
    getHelp: function(command, text){
        return this.sprintf("%s: %s\n", this.clc.green(command), text);
    },
    /**
     * a little command line interface to control the script
     */
    handleInput: function(input){
        input = input.split(' ');
        var cmd = input[0];
        var arg1 = input[1];
        switch(cmd){
            case "help":
                var helpText = "";
                helpText += this.getHelp('pause', "Stops observing file changes");
                helpText += this.getHelp('resume', "Continue checking files");
                helpText += this.getHelp('resume -u', "Continue checking files and upload all the changed files while paused.");
                helpText += this.getHelp('interval [s]', 'Sets the check interval duration. Example: "interval 2.5" check for every 2.5 seconds');
                helpText += this.getHelp('help', "Displays this text");
                helpText += this.getHelp('clear', "Clears the screen");
                helpText += this.getHelp('exit', "Exits the script");
                this.write(helpText);
            break;
            case "clear":
                this.printTitle();
            break;
            case "exit":
                process.exit(0);
            break;
            case "pause":
                this.paused = true;
            break;
            case "resume":
                if(this.paused){
                    if(arg1 != "-u"){
                        this.lastRun = +(new Date());
                        this.timeDiff = 0;
                    }
                    this.paused = false;
                    this.printTitle();
                    if(arg1 == "-u"){
                        this.write('Finding all changed files while waiting.\n');
                    }
                    this.startChecking();
                }else{
                    this.write('Already running\n');
                }
            break;
            case "interval":
                if(arg1){
                    this.secondsInterval = parseFloat(arg1) || this.secondsInterval;
                    this.printTitle();
                }
                this.write('Check interval is '+this.secondsInterval+' Seconds\n');
            break;
            case "":break;
            default:
                console.log(this.clc.red('Unknown command: "'+cmd+'"\nType "help" to see commands'));
        }
    },
    /**
     * Creates a master SSH connection, keeps the connection open
     * so `scp` commands work faster by sharing the same persisten connection
     */
    createMasterConection: function(){
        var self = this;
        // Show starting text
        this.write('Connecting.');
        this.dotsStart();

        // Keep a connection open to make scp commands faster
        this.ssh = this.exec('ssh -Mv '+this.host, function(){
            self.write(this.clc.red('SSH Connection ended.\n'));
            // stop showing dots
            self.dotsStop();
        });

        // Wait for SSH connection to be connected
        this.ssh.stderr.on('data', function (data) {
            // if asks for password, stop printing dots
            if(data.toString().indexOf('Next authentication method: password') != -1){
                self.dotsStop();
            }
            // when password is correct, continue printing dots
            if(data.toString().indexOf('Authentication succeeded') != -1){
                self.dotsStart();
            }
            // SSH initially throws an exception, when first executed from node.
            // just ignoring that message is enough
            if(data.toString().indexOf('Entering interactive session') != -1){
                // stop showing dots
                self.dotsStop();

                // Let user know what's happening
                self.printTitle();

                // Start Checking changes
                self.startChecking();
            }
        });
    },
    /**
     * Start checking changes, since we don't use setInterval
     * this function is called for starting each loop
     */
    startChecking: function(){
        // calculate the last time it run, so we can check back to that point and get the changes while file was uploaded
        this.timeDiff = (+(new Date()) - this.lastRun) / 1000;
        this.lastRun = +(new Date());
        if (this.paused){
            this.printTitle();
            return false;
        }
        // start again
        setTimeout(this.checkChanges.bind(this), this.secondsInterval * 1000);
    },
    /**
     * Execude find command and upload any changed file
     * @return {[type]} [description]
     */
    checkChanges: function(){
        // Execute the find command and get all recently changed files
        this.exec(this.cmd, this.onFindComplete.bind(this)); // EO exec
    },
    /**
     * When find command is completed, check for changed files and upload them
     * if necessary
     */
    onFindComplete: function(error, stdout, stderr) {
        //console.log(stdout);
        //console.log(error, stderr);
        // if there is an error, print and exit
        if (error !== null) {
            this.write(this.clc.red('exec error: ' + error) + '\n');
        }else{
            // Get all the lines from the output
            var lines = stdout.split(/\n/);
            // run only if there is an output
            if(lines.length > 0){
                // an index to gradually step changed files list
                this.cfIndex = 0;
                // Get only the files that are changed from the last time we checked
                this.cf = this.getChangedFiles(lines);
                // If there are changed files
                if(this.cf.length > 0){
                    // Clear the screen
                    this.printTitle();
                    if(this.config.visorSupport){
                        this.exec("osascript -e 'set prev_ to name of (info for (path to frontmost application))' " +
                                            "-e 'tell application \"Terminal\" to activate' " +
                                            "-e 'delay 1' " +
                                            "-e 'tell application prev_ to activate'");
                    }
                    // Display how many files were changed
                    this.write(this.cf.length + ' file'+(this.cf.length>1? 's':'')+' changed.' + '\n');

                    // Start uploading files as soon as function is created
                    this.uploadAll();
                }else{
                    this.startChecking(); // if no changed file found, start checking again
                }
            }else{
                this.startChecking(); // if no file returned, start checking again
            }
        }
    },
    /**
     * Instead of looping changed files list, create a callback loop
     * so each iteration will start after the previous one is completed
     * this is needed to prevent creating too many connections with `scp`
     */
    uploadAll: function(){
        var self = this;
        // while index is less than changed files length
        if(this.cfIndex < this.cf.length){
            // Upload the current file, and when it's finished
            // switch to next one
            this.uploadFile(this.cf[this.cfIndex], function(){
                self.uploadAll(++self.cfIndex);
            }, this.cf.length>1? this.sprintf('[%s - %s]', this.cfIndex+1, this.cf.length) : '>'); // [1 - 25] like string to show the status of the upload
        }else{
            if(this.cf.length > 1){
                this.write('All files are uploaded.\n');
            }
            this.showPrompt();
            this.startChecking();
        }
    },
    /**
     * Initiate the script
     */
    init: function(){
        // initiate moment
        this.moment().format();
        this.getConfig();
        this.initReadline();

        // How many seconds should script wait for each interval?
        this.secondsInterval = this.config.interval_duration || 1.5;
        // Path names to sync
        this.localPath = this.config.local_path;
        this.remotePath = this.config.remote_path;
        this.generateFindCMD();
        this.host = this.config.host;
        // switch to project path to run commands relatively
        process.chdir(this.localPath);

        this.createMasterConection();
    }
};
// Let the games begin!!!
sync.init();
<commit_msg>fix scope problem, and dot printing issue<commit_after>#!/usr/bin/env node
'use strict';
/**
 * a nifty tool to sync your local folder with remote folder
 * --------------------------------------------------------------
 * INSTALLATION, just clone this to any place on your computer
 * and cd to sync folder, then run: `npm install` and run server
 * by calling `nodejs sync.js`
 * --------------------------------------------------------------
 * For best usage, create a config file as ~/.ssh/config
 * and put this inside
---------- ~/.ssh/config ------------------
Host *
    ControlMaster auto
    ControlPath ~/.ssh/master-%r@%h:%p

Host dev
    HostName {HOST URL OR IP}
    User {YOUR USERNAME}
-------------------------------------------
 * so script can maintain a persistent SSH connection and be faster
 */
var sync = {
    // for colored and powerful command line
    clc: require('cli-color'),
    // shortcut to write to console
    write: process.stdout.write.bind(process.stdout),
    // execute a child process
    exec: require('child_process').exec,
    // Date Time parsing easy way
    moment: require('moment'),
    // self explanatory sprintf method
    sprintf: require('underscore.string').sprintf,
    // Check if string ends with given substring
    endsWith: require('underscore.string').endsWith,
    // read users input, for command line
    readline: require('readline'),
    // when was the last time script checked changes
    lastRun: +(new Date()),
    // how many seconds it took to start checking again
    timeDiff: 0,
    // if true, stop checking changes
    paused: false,
    // time handler for print dots functions
    pdTime: 0,
    /**
     * Read config file and check it's existance
     */
    getConfig: function(){
        // Check the existance of the config file
        try{
            this.config = require("./config.json");
        }catch(e){
            console.log(this.clc.red('Please create a config file by copying the config_example.json'));
            process.exit(1);
        }
        // Make sure remote path ends with slash
        if(!this.endsWith(this.config.remote_path, '/')){
            this.config.remote_path += '/';
        }
    },
    /**
     * Start read line process, and check if installed nodejs compatible with it
     * @return {[type]} [description]
     */
    initReadline: function(){
        // Ubuntu uses the old version of node.js and it doesn't have support for readline features
        // So check here for errors and warn user about node upgrade
        try{
            this.rl = this.readline.createInterface({
                input: process.stdin,
                output: process.stdout
            });
        }catch(e){
            console.log('You need to upgrade your nodejs');
            console.log('http://slopjong.de/2012/10/31/how-to-install-the-latest-nodejs-in-ubuntu/');
            process.exit(1);
        }
    },
    /**
     * Start printing dots to screen, show script is working
     */
    dotsStart: function(){
        var self = this;
        this.pdTime = setInterval(function(){
            self.write(self.clc.yellow('.'));
        }, 200);
    },
    /**
     * Stop printing dots when process ends
     */
    dotsStop: function(){
        clearInterval(this.pdTime);
    },
    /**
     * Convert date time string to unix time
     */
    getSecondsOf: function (time) {
        var t = this.moment(time);
        if (t !== null) {
            return t.unix();
        }
        return 0;
    },
    /**
     * Generate a find command for both OSX and Ubuntu
     * ubuntu should cover most linux versions
     */
    generateFindCMD: function(){
        // ls command powered with find to get recently changed files
        // Command to get changed files
        if (process.platform === 'darwin'){
            // for mac os x
            this.cmd = 'ls -1CloTtr $( find . -type f -mtime -5m -print ) | grep -v \'.git/\' | awk \'{ print  $5, $6, $7, $8, "-", $9 }\'';
        }else{
            // for linux
            this.cmd = 'ls -1Clotr --time-style=+\'%d-%b-%Y %T\' $( find . -type f -mmin -5 -print ) | grep -v \'.git/\' | awk \'{ print  $5, $6, "-", $7 }\'';
        }
    },
    /**
     * Upload given file to server then call given callback
     */
    uploadFile: function (line, callback, i){
        var self = this;
        // notify user that upload has started
        this.write( this.clc.magenta(i) + ' ' + this.clc.yellow(this.sprintf('Uploading file: %s ', line[0])));
        // create scp command
        var scp = this.sprintf('scp %s %s:%s', line[0], this.host, line[1]);
        // start printing dots
        this.dotsStart();
        //execute command
        this.exec(scp, function(e){
            // command completed, stop dots
            self.dotsStop();
            // if there is an error during upload, print it otherwise give user success message
            if (e !== null) {
                self.write(self.clc.red(self.sprintf('ERROR on: %s Message: %s\n', line[1], e)));
            }else{
                self.write(self.clc.green(' Saved.\n'));
            }
            // call callback no matter what
            callback();
        });
    },
    /**
     * Returns a list of changed files
     */
    getChangedFiles: function (lines){
        var self = this;
        // an empty array to fill changed files
        var changedFiles = [];
        // create a unix time right before our last check
        var anIntervalAgo = this.moment().unix() - this.timeDiff;
        // loop all returned files
        lines.forEach(function(line){
            // split the file list so that we can have the file name and changed date
            var details = line.split(' - ');
            // Convert date to seconds, so we can compare it unix time of now
            var fileSeconds = self.getSecondsOf(details[0]);
            // merge rest of the details to re-genrate the file name
            details.shift();
            var filename = details.join('');

            // if file changed at least an interval ago, then it is a recent change
            if (fileSeconds > anIntervalAgo) {
                // create remote file name and add to the array
                var remoteFile = filename.replace('./', self.remotePath);
                if(!self.endsWith(filename, '.swp') && !self.endsWith(filename, '.pyc')){
                    changedFiles.push([filename, remoteFile]);
                }
            }
        });

        return changedFiles;
    },
    /**
     * Keep the script explanation at the top of the page
     */
    printTitle: function(){
        this.write(this.clc.reset + '\n');
        if(this.paused){
            this.write('Currently paused, type "'+ this.clc.green('resume') + '" to start again.\n');
        }else{
            this.write(this.sprintf('Started monitoring, checking every %s seconds.\n', this.secondsInterval));
        }
        this.write('Quit the script with CONTROL-C or type "'+this.clc.green('exit')+'".\n');
        this.write(this.clc.magenta('-----------------------------------------------------------\n'));
        this.showPrompt();
    },
    /**
     * Show prompt line so user can run commands
     */
    showPrompt: function(){
        var self = this;
        this.rl.question(">>> ", function(answer) {
          self.handleInput(answer);
          // as soon as a command is run, show promt again just a like a real shell
          self.showPrompt();
        });
    },
    /**
     * Short cut to generate a help text
     */
    getHelp: function(command, text){
        return this.sprintf("%s: %s\n", this.clc.green(command), text);
    },
    /**
     * a little command line interface to control the script
     */
    handleInput: function(input){
        input = input.split(' ');
        var cmd = input[0];
        var arg1 = input[1];
        switch(cmd){
            case "help":
                var helpText = "";
                helpText += this.getHelp('pause', "Stops observing file changes");
                helpText += this.getHelp('resume', "Continue checking files");
                helpText += this.getHelp('resume -u', "Continue checking files and upload all the changed files while paused.");
                helpText += this.getHelp('interval [s]', 'Sets the check interval duration. Example: "interval 2.5" check for every 2.5 seconds');
                helpText += this.getHelp('help', "Displays this text");
                helpText += this.getHelp('clear', "Clears the screen");
                helpText += this.getHelp('exit', "Exits the script");
                this.write(helpText);
            break;
            case "clear":
                this.printTitle();
            break;
            case "exit":
                process.exit(0);
            break;
            case "pause":
                this.paused = true;
            break;
            case "resume":
                if(this.paused){
                    if(arg1 != "-u"){
                        this.lastRun = +(new Date());
                        this.timeDiff = 0;
                    }
                    this.paused = false;
                    this.printTitle();
                    if(arg1 == "-u"){
                        this.write('Finding all changed files while waiting.\n');
                    }
                    this.startChecking();
                }else{
                    this.write('Already running\n');
                }
            break;
            case "interval":
                if(arg1){
                    this.secondsInterval = parseFloat(arg1) || this.secondsInterval;
                    this.printTitle();
                }
                this.write('Check interval is '+this.secondsInterval+' Seconds\n');
            break;
            case "":break;
            default:
                console.log(this.clc.red('Unknown command: "'+cmd+'"\nType "help" to see commands'));
        }
    },
    /**
     * Creates a master SSH connection, keeps the connection open
     * so `scp` commands work faster by sharing the same persisten connection
     */
    createMasterConection: function(){
        var self = this;
        // Show starting text
        this.write('Connecting.');
        this.dotsStart();

        // Keep a connection open to make scp commands faster
        this.ssh = this.exec('ssh -Mv '+this.host, function(){
            self.write(self.clc.red('SSH Connection ended.\n'));
            // stop showing dots
            self.dotsStop();
        });

        // Wait for SSH connection to be connected
        this.ssh.stderr.on('data', function (data) {
            // if asks for password, stop printing dots
            if(data.toString().indexOf('Next authentication method: password') != -1){
                self.dotsStop();
            }
            // SSH initially throws an exception, when first executed from node.
            // just ignoring that message is enough
            if(data.toString().indexOf('Entering interactive session') != -1){
                // stop showing dots
                self.dotsStop();

                // Let user know what's happening
                self.printTitle();

                // Start Checking changes
                self.startChecking();
            }
        });
    },
    /**
     * Start checking changes, since we don't use setInterval
     * this function is called for starting each loop
     */
    startChecking: function(){
        // calculate the last time it run, so we can check back to that point and get the changes while file was uploaded
        this.timeDiff = (+(new Date()) - this.lastRun) / 1000;
        this.lastRun = +(new Date());
        if (this.paused){
            this.printTitle();
            return false;
        }
        // start again
        setTimeout(this.checkChanges.bind(this), this.secondsInterval * 1000);
    },
    /**
     * Execude find command and upload any changed file
     * @return {[type]} [description]
     */
    checkChanges: function(){
        // Execute the find command and get all recently changed files
        this.exec(this.cmd, this.onFindComplete.bind(this)); // EO exec
    },
    /**
     * When find command is completed, check for changed files and upload them
     * if necessary
     */
    onFindComplete: function(error, stdout, stderr) {
        //console.log(stdout);
        //console.log(error, stderr);
        // if there is an error, print and exit
        if (error !== null) {
            this.write(this.clc.red('exec error: ' + error) + '\n');
        }else{
            // Get all the lines from the output
            var lines = stdout.split(/\n/);
            // run only if there is an output
            if(lines.length > 0){
                // an index to gradually step changed files list
                this.cfIndex = 0;
                // Get only the files that are changed from the last time we checked
                this.cf = this.getChangedFiles(lines);
                // If there are changed files
                if(this.cf.length > 0){
                    // Clear the screen
                    this.printTitle();
                    if(this.config.visorSupport){
                        this.exec("osascript -e 'set prev_ to name of (info for (path to frontmost application))' " +
                                            "-e 'tell application \"Terminal\" to activate' " +
                                            "-e 'delay 1' " +
                                            "-e 'tell application prev_ to activate'");
                    }
                    // Display how many files were changed
                    this.write(this.cf.length + ' file'+(this.cf.length>1? 's':'')+' changed.' + '\n');

                    // Start uploading files as soon as function is created
                    this.uploadAll();
                }else{
                    this.startChecking(); // if no changed file found, start checking again
                }
            }else{
                this.startChecking(); // if no file returned, start checking again
            }
        }
    },
    /**
     * Instead of looping changed files list, create a callback loop
     * so each iteration will start after the previous one is completed
     * this is needed to prevent creating too many connections with `scp`
     */
    uploadAll: function(){
        var self = this;
        // while index is less than changed files length
        if(this.cfIndex < this.cf.length){
            // Upload the current file, and when it's finished
            // switch to next one
            this.uploadFile(this.cf[this.cfIndex], function(){
                self.uploadAll(++self.cfIndex);
            }, this.cf.length>1? this.sprintf('[%s - %s]', this.cfIndex+1, this.cf.length) : '>'); // [1 - 25] like string to show the status of the upload
        }else{
            if(this.cf.length > 1){
                this.write('All files are uploaded.\n');
            }
            this.showPrompt();
            this.startChecking();
        }
    },
    /**
     * Initiate the script
     */
    init: function(){
        // initiate moment
        this.moment().format();
        this.getConfig();
        this.initReadline();

        // How many seconds should script wait for each interval?
        this.secondsInterval = this.config.interval_duration || 1.5;
        // Path names to sync
        this.localPath = this.config.local_path;
        this.remotePath = this.config.remote_path;
        this.generateFindCMD();
        this.host = this.config.host;
        // switch to project path to run commands relatively
        process.chdir(this.localPath);

        this.createMasterConection();
    }
};
// Let the games begin!!!
sync.init();
<|endoftext|>


<commit_before>/**********************************************************************************
 * (c) 2016-2019, Master Technology
 * Licensed under the MIT license or contact me for a Support or Commercial License
 *
 * I do contract work in most languages, so let me solve your problems!
 *
 * Any questions please feel free to email me or put a issue up on the github repo
 * Version 1.3.7                                      Nathan@master-technology.com
 *********************************************************************************/
"use strict";

/* jshint camelcase: false */
/* global android, Promise, java, require, exports */

const application = require('application');

//noinspection JSUnresolvedVariable,JSUnresolvedFunction
if (typeof application.AndroidApplication.activityRequestPermissionsEvent === 'undefined') {
	throw new Error("You must be using at least version 2.0 of the TNS runtime and core-modules!");
}

// Variables to track any pending promises
let pendingPromises = {}, promiseId = 3000, eventsAdded=0;
let androidSupport=null;

//noinspection JSUnresolvedVariable,JSUnresolvedFunction
/**
 * This handles the results of getting the permissions!
 */
function handlePermissionResults(args) {

	// get current promise set
	//noinspection JSUnresolvedVariable
	const promises = pendingPromises[args.requestCode];

	// We have either gotten a promise from somewhere else or a bug has occurred and android has called us twice
	// In either case we will ignore it...
	if (!promises || typeof promises.granted !== 'function') {
		return;
	}

	// Delete it, since we no longer need to track it
	//noinspection JSUnresolvedVariable
	delete pendingPromises[args.requestCode];

	let trackingResults = promises.results;

	//noinspection JSUnresolvedVariable
	const length = args.permissions.length;
	for (let i = 0; i < length; i++) {
		// Convert back to JS String
		//noinspection JSUnresolvedVariable
		const name = args.permissions[i].toString();

		//noinspection RedundantIfStatementJS,JSUnresolvedVariable,JSUnresolvedFunction
		if (args.grantResults[i] === global.android.content.pm.PackageManager.PERMISSION_GRANTED) {
			trackingResults[name] = true;
		} else {
			trackingResults[name] = false;
		}
	}

	// Any Failures
	let failureCount = 0;
	for (let key in trackingResults) {
		if (!trackingResults.hasOwnProperty(key)) continue;
		if (trackingResults[key] === false) failureCount++;
	}

	if (Object.keys(pendingPromises).length === 0) {
		removeEventListeners();
	}

	if (failureCount === 0) {
		promises.granted(trackingResults);
	} else {
		promises.failed(trackingResults);
	}

}

function handleApplicationResults(args) {
	// get current promise set
	//noinspection JSUnresolvedVariable
	const promises = pendingPromises[args.requestCode];

	// We have either gotten a promise from somewhere else or a bug has occurred and android has called us twice
	// In either case we will ignore it...
	if (!promises || typeof promises.granted !== 'function') {
		return;
	}

	// Delete it, since we no longer need to track it
	//noinspection JSUnresolvedVariable
	delete pendingPromises[args.requestCode];

	let trackingResults = promises.results;
	trackingResults["android.permission.WRITE_SETTINGS"] = global.android.provider.Settings.System.canWrite(getContext());

	// Any Failures
	let failureCount = 0;
	for (let key in trackingResults) {
		if (!trackingResults.hasOwnProperty(key)) continue;
		if (trackingResults[key] === false) failureCount++;
	}

	if (Object.keys(pendingPromises).length === 0) {
		removeEventListeners();
	}

	if (failureCount === 0) {
		promises.granted(trackingResults);
	} else {
		promises.failed(trackingResults);
	}

}

function removeEventListeners() {
	if (!eventsAdded) { return; }
	application.android.off(application.AndroidApplication.activityRequestPermissionsEvent, handlePermissionResults);
	application.android.off(application.AndroidApplication.activityResultEvent, handleApplicationResults);
	eventsAdded = 0;
}

function addEventListeners(type) {
	if (type === 1) {
		if ((eventsAdded & 1) === 1) {
			return
		}
		application.android.on(application.AndroidApplication.activityRequestPermissionsEvent, handlePermissionResults);
		eventsAdded |= 1;
	} else {
		if ((eventsAdded & 2) === 2) {
			return;
		}
		application.android.on(application.AndroidApplication.activityResultEvent, handleApplicationResults);
		eventsAdded |= 2;
	}
}

function setupSupport() {
	if (hasAndroidX()) {
		androidSupport = global.androidx.core;
	} else if (hasSupportVersion4()) {
		androidSupport = global.android.support.v4;
	}
}

setupSupport();

exports.hasPermission = hasPermission;
exports.requestPermission = request;
exports.requestPermissions = request;

/**
 * Checks to see if v4 is installed and has the proper calls with it
 * @returns {boolean}
 */
function hasSupportVersion4() {
	//noinspection JSUnresolvedVariable
	if (!global.android.support || !global.android.support.v4 || !global.android.support.v4.content || !global.android.support.v4.content.ContextCompat || !global.android.support.v4.content.ContextCompat.checkSelfPermission) {
		return false;
	}
	return true;
}

/**
 * Checks to see if androidx is installed and has the proper calls for it.
 * @returns {boolean}
 */
function hasAndroidX() {
	//noinspection JSUnresolvedVariable
	if (!global.androidx || !global.androidx.core || !global.androidx.core.content || !global.androidx.core.content.ContextCompat || !global.androidx.core.content.ContextCompat.checkSelfPermission) {
		return false;
	}
	return true;
}

/**
 *
 * @param perm
 * @returns {boolean}
 */
function hasPermission(perm) {

	if (androidSupport === null) {

		// If we are on Android M we are going to fail the permission, since one of these two methods should have existed!
		if (global.android.os.Build.VERSION.SDK_INT >= 23) { return false; }

		// If we don't have support v4 or androidx loaded; then we can't run any checks and have to assume
		// that they have put the permission in the manifest and everything is good to go
		return true;
	}

	// Check for permission
	return global.android.content.pm.PackageManager.PERMISSION_GRANTED ===
		androidSupport.content.ContextCompat.checkSelfPermission(getContext(), perm);

}

/**
 * gets the current application context
 * @returns {*}
 * @private
 */
function getContext() {
	if (application.android.context) {
		return (application.android.context);
	}
	if (typeof application.getNativeApplication === 'function') {
		let ctx = application.getNativeApplication();
		if (ctx) {
			return ctx;
		}
	}

	//noinspection JSUnresolvedFunction,JSUnresolvedVariable
	let ctx = java.lang.Class.forName("android.app.AppGlobals").getMethod("getInitialApplication", null).invoke(null, null);
	if (ctx) return ctx;

	//noinspection JSUnresolvedFunction,JSUnresolvedVariable
	ctx = java.lang.Class.forName("android.app.ActivityThread").getMethod("currentApplication", null).invoke(null, null);
	if (ctx) return ctx;

	return ctx;
}

function request(inPerms, explanation) {
	let perms;
	if (Array.isArray(inPerms)) {
		perms = inPerms;
	} else {
		perms = [inPerms];
	}

	return new Promise(function (granted, failed) {
		let totalFailures = 0, totalSuccesses = 0, hasSpecial=0, version=global.android.os.Build.VERSION.SDK_INT;
		const totalCount = perms.length;
		let permTracking = [], permResults = {};
		for (let i = 0; i < totalCount; i++) {

			// This is a very special permission; have to handle it differently!
			if (perms[i] === "android.permission.WRITE_SETTINGS" && version >= 23 ) {
				hasSpecial++;
				if (global.android.provider.Settings.System.canWrite(getContext())) {
					permTracking[i] = true;
					permResults[perms[i]] = true;
					totalSuccesses++;
				} else {
					permTracking[i] = false;
					permResults[perms[i]] = false;
					totalFailures++;
				}
			} else

			// Check if we already have permissions, then we can grant automatically
			if (hasPermission(perms[i])) {
				permTracking[i] = true;
				permResults[perms[i]] = true;
				totalSuccesses++;
			} else {
				permTracking[i] = false;
				permResults[perms[i]] = false;
				totalFailures++;
			}
		}

		// If we have all perms, we don't need to continue
		if (totalSuccesses === totalCount) {
			granted(permResults);
			return;
		}

		//noinspection JSUnresolvedVariable
		if (totalFailures > 0 && global.android.os.Build.VERSION.SDK_INT < 23) {
			// If we are on API < 23 and we get a false back, then this means they forgot to put a manifest permission in...
			failed(permResults);
			return;
		}

		if (hasSpecial) {
			// Because this permission has to go through a completely different flow; we can currently only handle it alone...
			// TODO: Possible fix; add a second level of callbacks; so that then when both are resovled; then the main promise is resolved???
			if (totalCount > 1) {
				throw new Error("You can only request WRITE_SETTINGS permission by itself!")
			}
			handleWriteRequest(granted, failed, explanation, permResults);
			return;
		}

		handleRequest(granted, failed, perms, explanation, permResults, permTracking);
	});
}

function handleWriteRequest(granted, failed, explanation, permResults) {
	const activity = application.android.foregroundActivity || application.android.startActivity;
	if (activity == null) {
		// Throw this off into the future since an activity is not available....
		setTimeout(function() {
			handleWriteRequest(granted, failed, explanation, permResults);
		}, 250);
		return;
	}

	if (typeof explanation === "function") {
		explanation();
	} else if (explanation && explanation.length) {
		//noinspection JSUnresolvedVariable,JSUnresolvedFunction
		const toast = global.android.widget.Toast.makeText(getContext(), explanation, global.android.widget.Toast.LENGTH_LONG);
		//noinspection JSUnresolvedFunction
		toast.setGravity((49), 0, 0);
		toast.show();
	}

	// Ask for permissions
	promiseId++;

	// Wrap the promise id; as the number can't be bigger than the lower 16 bits
	if (promiseId > 65535) { promiseId = 1; }

	pendingPromises[promiseId] = {granted: granted, failed: failed, results: permResults};

	// Add the ActivityResult permissions handler
	addEventListeners(2);

	const intent = new global.android.content.Intent(global.android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
	intent.setData(global.android.net.Uri.parse("package:" + activity.getPackageName()));
	activity.startActivityForResult(intent, promiseId);
}

function handleRequest(granted, failed, perms, explanation, permResults, permTracking) {
	//noinspection JSUnresolvedVariable
	const activity = application.android.foregroundActivity || application.android.startActivity;
	if (activity == null) {
		// Throw this off into the future since an activity is not available....
		setTimeout(function() {
			handleRequest(granted, failed, perms, explanation, permResults, permTracking);
		}, 250);
		return;
	}

	const totalCount = perms.length;
	// Check if we need to show a explanation , if so show it only once.
	for (let i = 0; i < totalCount; i++) {
		if (permTracking[i] === false) {
			//noinspection JSUnresolvedVariable,JSUnresolvedFunction
			if (androidSupport.app.ActivityCompat.shouldShowRequestPermissionRationale(activity, perms[i])) {
				if (typeof explanation === "function") {
					explanation();
				} else if (explanation && explanation.length) {
					//noinspection JSUnresolvedVariable,JSUnresolvedFunction
					const toast = global.android.widget.Toast.makeText(getContext(), explanation, global.android.widget.Toast.LENGTH_LONG);
					//noinspection JSUnresolvedFunction
					toast.setGravity((49), 0, 0);
					toast.show();
				}

				// We don't need to show the explanation more than one time, if we even need to at all
				break;
			}
		}
	}

	// Build list of Perms we actually need to request
	let requestPerms = [];
	for (let i = 0; i < totalCount; i++) {
		if (permTracking[i] === false) {
			requestPerms.push(perms[i]);
		}
	}

	// Ask for permissions
	promiseId++;

	// Wrap the promise id; as the number can't be bigger than the lower 16 bits
	if (promiseId > 65535) { promiseId = 1; }

	pendingPromises[promiseId] = {granted: granted, failed: failed, results: permResults};

	// Add the Permissions Event handler
	addEventListeners(1);

	//noinspection JSUnresolvedVariable,JSUnresolvedFunction
	androidSupport.app.ActivityCompat.requestPermissions(activity, requestPerms, promiseId);
}
<commit_msg>chore: don't use short imports<commit_after>/**********************************************************************************
 * (c) 2016-2019, Master Technology
 * Licensed under the MIT license or contact me for a Support or Commercial License
 *
 * I do contract work in most languages, so let me solve your problems!
 *
 * Any questions please feel free to email me or put a issue up on the github repo
 * Version 1.3.7                                      Nathan@master-technology.com
 *********************************************************************************/
"use strict";

/* jshint camelcase: false */
/* global android, Promise, java, require, exports */

const application = require('tns-core-modules/application');

//noinspection JSUnresolvedVariable,JSUnresolvedFunction
if (typeof application.AndroidApplication.activityRequestPermissionsEvent === 'undefined') {
	throw new Error("You must be using at least version 2.0 of the TNS runtime and core-modules!");
}

// Variables to track any pending promises
let pendingPromises = {}, promiseId = 3000, eventsAdded=0;
let androidSupport=null;

//noinspection JSUnresolvedVariable,JSUnresolvedFunction
/**
 * This handles the results of getting the permissions!
 */
function handlePermissionResults(args) {

	// get current promise set
	//noinspection JSUnresolvedVariable
	const promises = pendingPromises[args.requestCode];

	// We have either gotten a promise from somewhere else or a bug has occurred and android has called us twice
	// In either case we will ignore it...
	if (!promises || typeof promises.granted !== 'function') {
		return;
	}

	// Delete it, since we no longer need to track it
	//noinspection JSUnresolvedVariable
	delete pendingPromises[args.requestCode];

	let trackingResults = promises.results;

	//noinspection JSUnresolvedVariable
	const length = args.permissions.length;
	for (let i = 0; i < length; i++) {
		// Convert back to JS String
		//noinspection JSUnresolvedVariable
		const name = args.permissions[i].toString();

		//noinspection RedundantIfStatementJS,JSUnresolvedVariable,JSUnresolvedFunction
		if (args.grantResults[i] === global.android.content.pm.PackageManager.PERMISSION_GRANTED) {
			trackingResults[name] = true;
		} else {
			trackingResults[name] = false;
		}
	}

	// Any Failures
	let failureCount = 0;
	for (let key in trackingResults) {
		if (!trackingResults.hasOwnProperty(key)) continue;
		if (trackingResults[key] === false) failureCount++;
	}

	if (Object.keys(pendingPromises).length === 0) {
		removeEventListeners();
	}

	if (failureCount === 0) {
		promises.granted(trackingResults);
	} else {
		promises.failed(trackingResults);
	}

}

function handleApplicationResults(args) {
	// get current promise set
	//noinspection JSUnresolvedVariable
	const promises = pendingPromises[args.requestCode];

	// We have either gotten a promise from somewhere else or a bug has occurred and android has called us twice
	// In either case we will ignore it...
	if (!promises || typeof promises.granted !== 'function') {
		return;
	}

	// Delete it, since we no longer need to track it
	//noinspection JSUnresolvedVariable
	delete pendingPromises[args.requestCode];

	let trackingResults = promises.results;
	trackingResults["android.permission.WRITE_SETTINGS"] = global.android.provider.Settings.System.canWrite(getContext());

	// Any Failures
	let failureCount = 0;
	for (let key in trackingResults) {
		if (!trackingResults.hasOwnProperty(key)) continue;
		if (trackingResults[key] === false) failureCount++;
	}

	if (Object.keys(pendingPromises).length === 0) {
		removeEventListeners();
	}

	if (failureCount === 0) {
		promises.granted(trackingResults);
	} else {
		promises.failed(trackingResults);
	}

}

function removeEventListeners() {
	if (!eventsAdded) { return; }
	application.android.off(application.AndroidApplication.activityRequestPermissionsEvent, handlePermissionResults);
	application.android.off(application.AndroidApplication.activityResultEvent, handleApplicationResults);
	eventsAdded = 0;
}

function addEventListeners(type) {
	if (type === 1) {
		if ((eventsAdded & 1) === 1) {
			return
		}
		application.android.on(application.AndroidApplication.activityRequestPermissionsEvent, handlePermissionResults);
		eventsAdded |= 1;
	} else {
		if ((eventsAdded & 2) === 2) {
			return;
		}
		application.android.on(application.AndroidApplication.activityResultEvent, handleApplicationResults);
		eventsAdded |= 2;
	}
}

function setupSupport() {
	if (hasAndroidX()) {
		androidSupport = global.androidx.core;
	} else if (hasSupportVersion4()) {
		androidSupport = global.android.support.v4;
	}
}

setupSupport();

exports.hasPermission = hasPermission;
exports.requestPermission = request;
exports.requestPermissions = request;

/**
 * Checks to see if v4 is installed and has the proper calls with it
 * @returns {boolean}
 */
function hasSupportVersion4() {
	//noinspection JSUnresolvedVariable
	if (!global.android.support || !global.android.support.v4 || !global.android.support.v4.content || !global.android.support.v4.content.ContextCompat || !global.android.support.v4.content.ContextCompat.checkSelfPermission) {
		return false;
	}
	return true;
}

/**
 * Checks to see if androidx is installed and has the proper calls for it.
 * @returns {boolean}
 */
function hasAndroidX() {
	//noinspection JSUnresolvedVariable
	if (!global.androidx || !global.androidx.core || !global.androidx.core.content || !global.androidx.core.content.ContextCompat || !global.androidx.core.content.ContextCompat.checkSelfPermission) {
		return false;
	}
	return true;
}

/**
 *
 * @param perm
 * @returns {boolean}
 */
function hasPermission(perm) {

	if (androidSupport === null) {

		// If we are on Android M we are going to fail the permission, since one of these two methods should have existed!
		if (global.android.os.Build.VERSION.SDK_INT >= 23) { return false; }

		// If we don't have support v4 or androidx loaded; then we can't run any checks and have to assume
		// that they have put the permission in the manifest and everything is good to go
		return true;
	}

	// Check for permission
	return global.android.content.pm.PackageManager.PERMISSION_GRANTED ===
		androidSupport.content.ContextCompat.checkSelfPermission(getContext(), perm);

}

/**
 * gets the current application context
 * @returns {*}
 * @private
 */
function getContext() {
	if (application.android.context) {
		return (application.android.context);
	}
	if (typeof application.getNativeApplication === 'function') {
		let ctx = application.getNativeApplication();
		if (ctx) {
			return ctx;
		}
	}

	//noinspection JSUnresolvedFunction,JSUnresolvedVariable
	let ctx = java.lang.Class.forName("android.app.AppGlobals").getMethod("getInitialApplication", null).invoke(null, null);
	if (ctx) return ctx;

	//noinspection JSUnresolvedFunction,JSUnresolvedVariable
	ctx = java.lang.Class.forName("android.app.ActivityThread").getMethod("currentApplication", null).invoke(null, null);
	if (ctx) return ctx;

	return ctx;
}

function request(inPerms, explanation) {
	let perms;
	if (Array.isArray(inPerms)) {
		perms = inPerms;
	} else {
		perms = [inPerms];
	}

	return new Promise(function (granted, failed) {
		let totalFailures = 0, totalSuccesses = 0, hasSpecial=0, version=global.android.os.Build.VERSION.SDK_INT;
		const totalCount = perms.length;
		let permTracking = [], permResults = {};
		for (let i = 0; i < totalCount; i++) {

			// This is a very special permission; have to handle it differently!
			if (perms[i] === "android.permission.WRITE_SETTINGS" && version >= 23 ) {
				hasSpecial++;
				if (global.android.provider.Settings.System.canWrite(getContext())) {
					permTracking[i] = true;
					permResults[perms[i]] = true;
					totalSuccesses++;
				} else {
					permTracking[i] = false;
					permResults[perms[i]] = false;
					totalFailures++;
				}
			} else

			// Check if we already have permissions, then we can grant automatically
			if (hasPermission(perms[i])) {
				permTracking[i] = true;
				permResults[perms[i]] = true;
				totalSuccesses++;
			} else {
				permTracking[i] = false;
				permResults[perms[i]] = false;
				totalFailures++;
			}
		}

		// If we have all perms, we don't need to continue
		if (totalSuccesses === totalCount) {
			granted(permResults);
			return;
		}

		//noinspection JSUnresolvedVariable
		if (totalFailures > 0 && global.android.os.Build.VERSION.SDK_INT < 23) {
			// If we are on API < 23 and we get a false back, then this means they forgot to put a manifest permission in...
			failed(permResults);
			return;
		}

		if (hasSpecial) {
			// Because this permission has to go through a completely different flow; we can currently only handle it alone...
			// TODO: Possible fix; add a second level of callbacks; so that then when both are resovled; then the main promise is resolved???
			if (totalCount > 1) {
				throw new Error("You can only request WRITE_SETTINGS permission by itself!")
			}
			handleWriteRequest(granted, failed, explanation, permResults);
			return;
		}

		handleRequest(granted, failed, perms, explanation, permResults, permTracking);
	});
}

function handleWriteRequest(granted, failed, explanation, permResults) {
	const activity = application.android.foregroundActivity || application.android.startActivity;
	if (activity == null) {
		// Throw this off into the future since an activity is not available....
		setTimeout(function() {
			handleWriteRequest(granted, failed, explanation, permResults);
		}, 250);
		return;
	}

	if (typeof explanation === "function") {
		explanation();
	} else if (explanation && explanation.length) {
		//noinspection JSUnresolvedVariable,JSUnresolvedFunction
		const toast = global.android.widget.Toast.makeText(getContext(), explanation, global.android.widget.Toast.LENGTH_LONG);
		//noinspection JSUnresolvedFunction
		toast.setGravity((49), 0, 0);
		toast.show();
	}

	// Ask for permissions
	promiseId++;

	// Wrap the promise id; as the number can't be bigger than the lower 16 bits
	if (promiseId > 65535) { promiseId = 1; }

	pendingPromises[promiseId] = {granted: granted, failed: failed, results: permResults};

	// Add the ActivityResult permissions handler
	addEventListeners(2);

	const intent = new global.android.content.Intent(global.android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
	intent.setData(global.android.net.Uri.parse("package:" + activity.getPackageName()));
	activity.startActivityForResult(intent, promiseId);
}

function handleRequest(granted, failed, perms, explanation, permResults, permTracking) {
	//noinspection JSUnresolvedVariable
	const activity = application.android.foregroundActivity || application.android.startActivity;
	if (activity == null) {
		// Throw this off into the future since an activity is not available....
		setTimeout(function() {
			handleRequest(granted, failed, perms, explanation, permResults, permTracking);
		}, 250);
		return;
	}

	const totalCount = perms.length;
	// Check if we need to show a explanation , if so show it only once.
	for (let i = 0; i < totalCount; i++) {
		if (permTracking[i] === false) {
			//noinspection JSUnresolvedVariable,JSUnresolvedFunction
			if (androidSupport.app.ActivityCompat.shouldShowRequestPermissionRationale(activity, perms[i])) {
				if (typeof explanation === "function") {
					explanation();
				} else if (explanation && explanation.length) {
					//noinspection JSUnresolvedVariable,JSUnresolvedFunction
					const toast = global.android.widget.Toast.makeText(getContext(), explanation, global.android.widget.Toast.LENGTH_LONG);
					//noinspection JSUnresolvedFunction
					toast.setGravity((49), 0, 0);
					toast.show();
				}

				// We don't need to show the explanation more than one time, if we even need to at all
				break;
			}
		}
	}

	// Build list of Perms we actually need to request
	let requestPerms = [];
	for (let i = 0; i < totalCount; i++) {
		if (permTracking[i] === false) {
			requestPerms.push(perms[i]);
		}
	}

	// Ask for permissions
	promiseId++;

	// Wrap the promise id; as the number can't be bigger than the lower 16 bits
	if (promiseId > 65535) { promiseId = 1; }

	pendingPromises[promiseId] = {granted: granted, failed: failed, results: permResults};

	// Add the Permissions Event handler
	addEventListeners(1);

	//noinspection JSUnresolvedVariable,JSUnresolvedFunction
	androidSupport.app.ActivityCompat.requestPermissions(activity, requestPerms, promiseId);
}
<|endoftext|>


<commit_before>define(['build/Objects/Page/Form/email'], function (Item) {
    describe('Email form item', function () {

        beforeEach(function () {
            this.addMatchers(require('jasmine_matchers'));
        });

        it('should return corresponding attributes', function () {
            var item = new Item({
                order: 10,
                name: 'thename',
                label: 'The name',
                value: 'James',
                required: true
            });

            expect(item.getAttributes()).toHaveKeys([
                'type',
                'value',
                'name'
            ]);

        });

        it('should implement a form item', function () {

            var item = new Item({
                order: 10,
                name: 'thename',
                label: 'The name',
                value: 'James',
                required: true
            });

            //Text spec of transferring initial data
            expect(item.getType()).toBe('email');
            expect(item.getOrder()).toBe(10);
            expect(item.getName()).toBe('thename');
            expect(item.getLabel()).toBe('The name');
            expect(item.getValue()).toBe('James');
            expect(item.isRequired()).toBe(true);

            //Test spec of setters
            item.setOrder(2);
            item.setName('somename');
            item.setLabel('Your name');
            item.setValue('Charles');
            item.setRequired(false);

            expect(item.getOrder()).toBe(2);
            expect(item.getName()).toBe('somename');
            expect(item.getLabel()).toBe('Your name');
            expect(item.getValue()).toBe('Charles');
            expect(item.isRequired()).toBe(false);

        });

    });
});
<commit_msg>Adjusted spec-test<commit_after>define(['build/Objects/Page/Form/email'], function (Item) {
    describe('Email form item', function () {

        beforeEach(function () {
            this.addMatchers(require('jasmine_matchers'));
        });

        it('should return corresponding attributes', function () {
            var item = new Item({
                order: 10,
                name: 'thename',
                label: 'The name',
                value: 'joris@label305.com',
                required: true
            });

            expect(item.getAttributes()).toHaveKeys([
                'type',
                'value',
                'name'
            ]);

        });

        it('should implement a form item', function () {

            var item = new Item({
                order: 10,
                name: 'thename',
                label: 'The name',
                value: 'joris@label305.com',
                required: true
            });

            //Text spec of transferring initial data
            expect(item.getType()).toBe('email');
            expect(item.getOrder()).toBe(10);
            expect(item.getName()).toBe('thename');
            expect(item.getLabel()).toBe('The name');
            expect(item.getValue()).toBe('joris@label305.com');
            expect(item.isRequired()).toBe(true);

            //Test spec of setters
            item.setOrder(2);
            item.setName('somename');
            item.setLabel('Your name');
            item.setValue('alexander@label305.com');
            item.setRequired(false);

            expect(item.getOrder()).toBe(2);
            expect(item.getName()).toBe('somename');
            expect(item.getLabel()).toBe('Your name');
            expect(item.getValue()).toBe('alexander@label305.com');
            expect(item.isRequired()).toBe(false);

        });

    });
});
<|endoftext|>


<commit_before>function onGoogleMapsApiReady() {
    angular.bootstrap(document.getElementsByTagName("body"), ['fancyTraveller']);
}

var app = angular.module('fancyTraveller', ['ui.bootstrap', 'ui.map', 'ui.event']);

app.controller('suggestingCities', function ($scope, routeService) {

    routeService.listOfAvailableCitites().then(function (response) {
        $scope.cities = response.data;
    }, $scope.handleInternalError);
});

app.controller('route', function($scope, $http, routeService) {

    $scope.loader = $scope.disabledSendBtn = $scope.internalError = false;

    $scope.block = function() {
        $scope.loader = $scope.disabledSendBtn = true;
    };

    $scope.unblock = function() {
        $scope.loader = false;
        $scope.disabledSendBtn = undefined;
    };

    $scope.query = {
        sourceCity: undefined,
        destinationCity: undefined,
        citiesToSkip: []
    };

    $scope.result = {
        show: {
            map: false,
            summary: false
        },
        positions: {
            source: undefined,
            destination: undefined
        },
        distance: 0,
        visitedCities: []
    };

    $scope.findRouteFor = function () {
        $scope.result.show.summary = false;

        var selectCititesIds = function() {
            var result = [];
            for (var city in $scope.query.citiesToSkip)
                if ($scope.query.citiesToSkip.hasOwnProperty(city))
                    result.push($scope.query.citiesToSkip[city].Id);

            return result;
        };

        var query = {
            sourceCity: $scope.query.sourceCity,
            destinationCity: $scope.query.destinationCity,
            citiesToSkip: selectCititesIds()
        };

        $scope.block();

        routeService.findShortestRoute(query).then(function (response) {
            $scope.unblock();
            $scope.displayResult(response.data);

        }, $scope.handleInternalError);
    };

    $scope.handleInternalError = function (response) { // onError
        $scope.unblock();
        $scope.internalError = true;

        console.log(response);
    };

    $scope.displayResult = function(result) {
        $scope.result.show.map = $scope.result.show.summary = true;

        $scope.result.distance = result.Distance;

        $scope.result.positions = {
            source: new google.maps.LatLng(result.SourceCity.Location.Latitude, result.SourceCity.Location.Longitude),
            destination: new google.maps.LatLng(result.DestinationCity.Location.Latitude, result.DestinationCity.Location.Longitude)
        };

        $scope.result.visitedCities = result.VisitedCities;
    };

    $scope.removeCityToSkipById = function (cityId) {
        for (var index in $scope.query.citiesToSkip)
            if ($scope.query.citiesToSkip[index] !== undefined && $scope.query.citiesToSkip[index].Id === cityId)
                $scope.query.citiesToSkip.splice(index, 1);
    };
});

app.controller('placesToSkipManagement', function($scope) {

   $scope.removeCityToSkipByIndex = function (indexOfTheCity) {

       if ($scope.query.citiesToSkip[indexOfTheCity] !== undefined)
            $scope.query.citiesToSkip.splice(indexOfTheCity, 1);
   };

    $scope.addCityToSkip = function (selectedCity, ctrl) {

        var isValid = function (city) {
            var source = $scope.query.sourceCity || {};
            var destination = $scope.query.destinationCity || {};

            return (source.Name !== city.Name && destination.Name !== city.Name);
        };

        var valid = isValid(selectedCity);
        ctrl.$setValidity('blacklist', valid);

        if (valid && $scope.query.citiesToSkip.indexOf(selectedCity) === -1)
                $scope.query.citiesToSkip.push(selectedCity);

        document.getElementById('blacklistInput').value = null;
    };
});

app.controller('map', ['$scope', function ($scope) {

    var markersArray = [];
    var line = new google.maps.Polyline({});

    $scope.mapOptions = {
        zoom: 4,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };

    $scope.$watch("result.show.summary", function (val) {
        window.setTimeout(function () {
            google.maps.event.trigger($scope.mapWithTheRoute, 'resize');
        }, 100);

        line.setMap(null);

        if ($scope.result.positions.source === undefined || $scope.result.positions.destination === undefined) return;

        (function clearOverlays() {
            for (var i = 0; i < markersArray.length; i++) {
                markersArray[i].setMap(null);
            }
            markersArray.length = 0;
        }());

        var source = new google.maps.Marker({
            map: $scope.mapWithTheRoute,
            position: $scope.result.positions.source
        });

        var destination = new google.maps.Marker({
            map: $scope.mapWithTheRoute,
            position: $scope.result.positions.destination
        });

        $scope.myMarkers = [source, destination];

        line = new google.maps.Polyline({
            path: [$scope.result.positions.source, $scope.result.positions.destination],
            strokeColor: "#FF0000",
            strokeOpacity: 0.3,
            strokeWeight: 10,
            map: $scope.mapWithTheRoute,
        });

        markersArray.push(source);
        markersArray.push(destination);

        google.maps.event.trigger($scope.mapWithTheRoute, 'resize');
        $scope.mapWithTheRoute.setCenter(new google.maps.LatLng(source.position.lat(), source.position.lng()));
    });
}]);

app.factory('routeService', function ($http) {

    var citiesPath = '/api/AvailableCities/';
    var shortestPathUrl = '/api/FindShortestRoute/';

    return {
        listOfAvailableCitites: function () {
            return $http.get(citiesPath);
        },
        findShortestRoute: function(query) {
            return $http.post(shortestPathUrl, query);
        }
    };
});

app.directive('differentSourceAndLocation', function () {
    return {
        // restrict to an attribute type.
        restrict: 'A',

        // element must have ng-model attribute.
        require: 'ngModel',

        // scope = the parent scope
        // elem = the element the directive is on
        // attr = a dictionary of attributes on the element
        // ctrl = the controller for ngModel.
        link: function (scope, elem, attr, ctrl) {

            var compareCities = function(source, destination) {
                return JSON.stringify(source) !== JSON.stringify(destination);
            };

            var validation = function(value) {
                // test and set the validity after update.
                var valid = compareCities(value, scope.$eval(attr.cityToCompare));
                ctrl.$setValidity('differentSourceAndLocation', valid);

                if (valid)
                    scope.$eval(attr.dependsOn).$setValidity('differentSourceAndLocation', valid);

                // if it's valid, return the value to the model,
                // otherwise return undefined.
                return value;
            };

           // add a parser that will process each time the value is
            // parsed into the model when the user updates it.
            ctrl.$parsers.unshift(validation);

            // add a formatter that will process each time the value
            // is updated on the DOM element.
            ctrl.$formatters.unshift(validation);
        }
    };
});<commit_msg>Polyline connects all visited cities<commit_after>function onGoogleMapsApiReady() {
    angular.bootstrap(document.getElementsByTagName("body"), ['fancyTraveller']);
}

var app = angular.module('fancyTraveller', ['ui.bootstrap', 'ui.map', 'ui.event']);

app.controller('suggestingCities', function ($scope, routeService) {

    routeService.listOfAvailableCitites().then(function (response) {
        $scope.cities = response.data;
    }, $scope.handleInternalError);
});

app.controller('route', function($scope, $http, routeService) {

    $scope.loader = $scope.disabledSendBtn = $scope.internalError = false;

    $scope.block = function() {
        $scope.loader = $scope.disabledSendBtn = true;
    };

    $scope.unblock = function() {
        $scope.loader = false;
        $scope.disabledSendBtn = undefined;
    };

    $scope.query = {
        sourceCity: undefined,
        destinationCity: undefined,
        citiesToSkip: []
    };

    $scope.result = {
        show: {
            map: false,
            summary: false
        },
        positions: {
            source: undefined,
            destination: undefined
        },
        distance: 0,
        visitedCities: []
    };

    $scope.findRouteFor = function () {
        $scope.result.show.summary = false;

        var selectCititesIds = function() {
            var result = [];
            for (var city in $scope.query.citiesToSkip)
                if ($scope.query.citiesToSkip.hasOwnProperty(city))
                    result.push($scope.query.citiesToSkip[city].Id);

            return result;
        };

        var query = {
            sourceCity: $scope.query.sourceCity,
            destinationCity: $scope.query.destinationCity,
            citiesToSkip: selectCititesIds()
        };

        $scope.block();

        routeService.findShortestRoute(query).then(function (response) {
            $scope.unblock();
            $scope.displayResult(response.data);

        }, $scope.handleInternalError);
    };

    $scope.handleInternalError = function (response) { // onError
        $scope.unblock();
        $scope.internalError = true;

        console.log(response);
    };

    $scope.displayResult = function(result) {
        $scope.result.show.map = $scope.result.show.summary = true;

        $scope.result.distance = result.Distance;

        $scope.result.positions = {
            source: new google.maps.LatLng(result.SourceCity.Location.Latitude, result.SourceCity.Location.Longitude),
            destination: new google.maps.LatLng(result.DestinationCity.Location.Latitude, result.DestinationCity.Location.Longitude)
        };

        $scope.result.visitedCities = result.VisitedCities;
    };

    $scope.removeCityToSkipById = function (cityId) {
        for (var index in $scope.query.citiesToSkip)
            if ($scope.query.citiesToSkip[index] !== undefined && $scope.query.citiesToSkip[index].Id === cityId)
                $scope.query.citiesToSkip.splice(index, 1);
    };
});

app.controller('placesToSkipManagement', function($scope) {

   $scope.removeCityToSkipByIndex = function (indexOfTheCity) {

       if ($scope.query.citiesToSkip[indexOfTheCity] !== undefined)
            $scope.query.citiesToSkip.splice(indexOfTheCity, 1);
   };

    $scope.addCityToSkip = function (selectedCity, ctrl) {

        var isValid = function (city) {
            var source = $scope.query.sourceCity || {};
            var destination = $scope.query.destinationCity || {};

            return (source.Name !== city.Name && destination.Name !== city.Name);
        };

        var valid = isValid(selectedCity);
        ctrl.$setValidity('blacklist', valid);

        if (valid && $scope.query.citiesToSkip.indexOf(selectedCity) === -1)
                $scope.query.citiesToSkip.push(selectedCity);

        document.getElementById('blacklistInput').value = null;
    };
});

app.controller('map', ['$scope', function ($scope) {

    var markersArray = [];

    var line = new google.maps.Polyline({});

    $scope.mapOptions = {
        zoom: 6,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };

    $scope.$watch("result.show.summary", function (val) {
        window.setTimeout(function () {
            google.maps.event.trigger($scope.mapWithTheRoute, 'resize');
        }, 100);

        line.setMap(null);

        if ($scope.result.positions.source === undefined || $scope.result.positions.destination === undefined) return;

        (function clearOverlays() {
            for (var i = 0; i < markersArray.length; i++) {
                markersArray[i].setMap(null);
            }
            markersArray.length = 0;
        }());

        var source = new google.maps.Marker({
            map: $scope.mapWithTheRoute,
            position: $scope.result.positions.source
        });

        var destination = new google.maps.Marker({
            map: $scope.mapWithTheRoute,
            position: $scope.result.positions.destination
        });

        $scope.myMarkers = [source, destination];

        var allVisitedCitiesCoordinates = [];

        allVisitedCitiesCoordinates.push($scope.result.positions.source);

        for (var visitedCity in $scope.result.visitedCities) {

            allVisitedCitiesCoordinates.push(new google.maps.LatLng($scope.result.visitedCities[visitedCity].Location.Latitude, $scope.result.visitedCities[visitedCity].Location.Longitude));
        }

        allVisitedCitiesCoordinates.push($scope.result.positions.destination);

        line = new google.maps.Polyline({
            path: allVisitedCitiesCoordinates,
            strokeColor: "#FF0000",
            strokeOpacity: 0.3,
            strokeWeight: 10,
            map: $scope.mapWithTheRoute,
        });

        markersArray.push(source);
        markersArray.push(destination);

        google.maps.event.trigger($scope.mapWithTheRoute, 'resize');
        $scope.mapWithTheRoute.setCenter(new google.maps.LatLng(source.position.lat(), source.position.lng()));
    });
}]);

app.factory('routeService', function ($http) {

    var citiesPath = '/api/AvailableCities/';
    var shortestPathUrl = '/api/FindShortestRoute/';

    return {
        listOfAvailableCitites: function () {
            return $http.get(citiesPath);
        },
        findShortestRoute: function(query) {
            return $http.post(shortestPathUrl, query);
        }
    };
});

app.directive('differentSourceAndLocation', function () {
    return {
        // restrict to an attribute type.
        restrict: 'A',

        // element must have ng-model attribute.
        require: 'ngModel',

        // scope = the parent scope
        // elem = the element the directive is on
        // attr = a dictionary of attributes on the element
        // ctrl = the controller for ngModel.
        link: function (scope, elem, attr, ctrl) {

            var compareCities = function(source, destination) {
                return JSON.stringify(source) !== JSON.stringify(destination);
            };

            var validation = function(value) {
                // test and set the validity after update.
                var valid = compareCities(value, scope.$eval(attr.cityToCompare));
                ctrl.$setValidity('differentSourceAndLocation', valid);

                if (valid)
                    scope.$eval(attr.dependsOn).$setValidity('differentSourceAndLocation', valid);

                // if it's valid, return the value to the model,
                // otherwise return undefined.
                return value;
            };

           // add a parser that will process each time the value is
            // parsed into the model when the user updates it.
            ctrl.$parsers.unshift(validation);

            // add a formatter that will process each time the value
            // is updated on the DOM element.
            ctrl.$formatters.unshift(validation);
        }
    };
});<|endoftext|>


<commit_before>moduleAid.VERSION = '2.0.0';

this.commandPP = function(e, button) {
	if(e.button != 0) { return; }
	toggleBar(button._bar.id);
	dispatch(button, { type: 'ToggledPuzzleBarThroughButton', cancelable: false });
};

this.activatePPs = function(e) {
	toggleAttribute(e.target._pp, 'active', !e.target.collapsed);
};

this.handleFullScreen = function(m) {
	setAttribute(window, objName+'-noAnimation', 'true');
	toggleAttribute(window, objName+'-fullscreen', m.data);
	aSync(function() {
		removeAttribute(window, objName+'-noAnimation');
	});
};

moduleAid.LOADMODULE = function() {
	messenger.loadInWindow(window, 'placePP');
	messenger.listenWindow(window, 'inFullScreen', handleFullScreen);

	listenerAid.add(window, 'ToggledPuzzleBar', activatePPs);
	listenerAid.add(window, 'LoadedPuzzleBar', activatePPs);
};

moduleAid.UNLOADMODULE = function() {
	listenerAid.remove(window, 'ToggledPuzzleBar', activatePPs);
	listenerAid.remove(window, 'LoadedPuzzleBar', activatePPs);

	messenger.unlistenWindow(window, 'inFullScreen', handleFullScreen);
	messenger.unloadFromWindow(window, 'placePP');
};
<commit_msg>i'm an idiot<commit_after>moduleAid.VERSION = '2.0.1';

this.commandPP = function(e, button) {
	if(e.button != 0) { return; }
	toggleBar(button._bar.id);
	dispatch(button, { type: 'ToggledPuzzleBarThroughButton', cancelable: false });
};

this.activatePPs = function(e) {
	toggleAttribute(e.target._pp, 'active', !e.target.collapsed);
};

this.handleFullScreen = function(m) {
	setAttribute(document.documentElement, objName+'-noAnimation', 'true');
	toggleAttribute(document.documentElement, objName+'-fullscreen', m.data);
	aSync(function() {
		removeAttribute(document.documentElement, objName+'-noAnimation');
	});
};

moduleAid.LOADMODULE = function() {
	messenger.loadInWindow(window, 'placePP');
	messenger.listenWindow(window, 'inFullScreen', handleFullScreen);

	listenerAid.add(window, 'ToggledPuzzleBar', activatePPs);
	listenerAid.add(window, 'LoadedPuzzleBar', activatePPs);
};

moduleAid.UNLOADMODULE = function() {
	listenerAid.remove(window, 'ToggledPuzzleBar', activatePPs);
	listenerAid.remove(window, 'LoadedPuzzleBar', activatePPs);

	messenger.unlistenWindow(window, 'inFullScreen', handleFullScreen);
	messenger.unloadFromWindow(window, 'placePP');
};
<|endoftext|>


<commit_before>var yustClient = (function (options) {
	'use strict';

	var socket,
        appId = getParameterByName('appId'),
        type;

	function emit(event, value) {
		console.log('emit', type, event, value)
		socket.emit('gamepad', {
			'm': event,
			'v': value
		});
	}

	function create(options) {
		socket = io.connect(options.uri);

		socket.on('connect', function(){
	        console.log('connected!');
            console.log('joiingin game: ', appId);
	        socket.emit('joinGame', appId);
	    });

	    socket.on('clientPaired', function (data) {
	    	if (type === 'trackPad') {
	    		var objToShow = $('.draw');
	    	} else {
	    		var objToShow = $('.pad');
	    	}

	    	if (data.success) {
	    		type = data.type;
	    		$('.warning, .draw, .pad').addClass('is-hidden');
	    		objToShow.removeClass('is-hidden');
	    	} else {
	    		// TODO: show not connected screen
	    		console.log('No possible to connect!', data);
	    	}
	    });

	    socket.on('appClosed', function (data) {
	    	console.log('sorry');
	    });

	    socket.on('clientUnpaired', function(){
	        console.log('disconnected!');
	    });
	}

    function getParameterByName(name) {
        name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }

	function expose(fn) {
		return function () {
			if (!socket) {
				throw 'Error: yustClient not initialized!';
			} else {
				fn.apply(this, arguments);
				return this;
			}
		};
	}

	return {
		create: create,
		emit: expose(emit)
	};
}());
<commit_msg>wip: gamepad<commit_after>var yustClient = (function (options) {
	'use strict';

	var socket,
        appId = getParameterByName('appId'),
        type;

	function emit(event, value) {
		console.log('emit', type, event, value)
		socket.emit(type, {
			'm': event,
			'v': value
		});
	}

	function create(options) {
		socket = io.connect(options.uri);

		socket.on('connect', function(){
	        console.log('connected!');
            console.log('joiingin game: ', appId);
	        socket.emit('joinGame', appId);
	    });

	    socket.on('clientPaired', function (data) {
	    	if (type === 'trackPad') {
	    		var objToShow = $('.draw');
	    	} else {
	    		var objToShow = $('.pad');
	    	}

	    	if (data.success) {
	    		type = data.type;
	    		$('.warning, .draw, .pad').addClass('is-hidden');
	    		objToShow.removeClass('is-hidden');
	    	} else {
	    		// TODO: show not connected screen
	    		console.log('No possible to connect!', data);
	    	}
	    });

	    socket.on('appClosed', function (data) {
	    	console.log('sorry');
	    });

	    socket.on('clientUnpaired', function(){
	        console.log('disconnected!');
	    });
	}

    function getParameterByName(name) {
        name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(location.search);
        return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }

	function expose(fn) {
		return function () {
			if (!socket) {
				throw 'Error: yustClient not initialized!';
			} else {
				fn.apply(this, arguments);
				return this;
			}
		};
	}

	return {
		create: create,
		emit: expose(emit)
	};
}());
<|endoftext|>