<commit_before>(function() {
    'use strict';

    var DIR_SERVER      = './',

        path            = require('path'),
        child_process   = require('child_process'),
        exec            = child_process.exec,
        spawn           = child_process.spawn,

        Util            = require('util-io'),

        win             = require('win32'),
        socket          = require(DIR_SERVER    + 'socket'),
        find            = require('glob'),

        ClientFuncs     = [],
        ClientDirs      = [],
        Clients         = [],
        WIN             = process.platform === 'win32',

        addNewLine      = function (text) {
            var newLine    = '',
                n           = text && text.length;

            if(n && text[n-1] !== '\n')
                newLine = '\n';

            return text + newLine;
        },

        ConNum          = 0,

        CHANNEL         = 'console-data';

    /**
     * function listen on servers port
     * @pServer {Object} started server object
     */
    exports.init = function() {
        var ret;

        ret = socket.on('connection', function(clientSocket) {
            onConnection(clientSocket, function(json) {
                socket.emit(CHANNEL, json, clientSocket);
            });
        });

        return ret;
    };

    function onConnection(clientSocket, callback) {
        var msg, onDisconnect, onMessage;

        ++ConNum;

        if (!Clients[ConNum]) {
            msg = log(ConNum, 'console connected');

            callback({
                stdout  : addNewLine(msg),
                path    : process.cwd()
            });

            Clients[ConNum]             = true;

            onMessage                   = Util.exec.with(getOnMessage, ConNum, callback);
            onDisconnect                = function(conNum) {
                Clients[conNum]         =
                ClientFuncs[conNum]     = null;

                log(conNum, 'console disconnected');

                socket.removeListener(CHANNEL, onMessage, clientSocket);
                socket.removeListener('disconnect', onDisconnect, clientSocket);
            }.bind(null, ConNum);

            socket.on(CHANNEL, onMessage, clientSocket);
            socket.on('disconnect', onDisconnect, clientSocket);
        } else {
            msg = log(ConNum, ' in use. Reconnecting...\n');

            Util.exec(callback, {
                stdout: msg
            });

            socket.disconnect();
        }
    }

    /**
     * function gets onMessage function
     * that execute needed command
     *
     * @param pConnNum
     * @param callback
     */
    function getOnMessage(connNum, callback, command) {
        var funcExec, firstChar,
            connName, ret,

            isVolume    = win.isChangeVolume(command),
            isCD        = command.match(new RegExp('^cd ?')),
            isCDWin     = command.match(new RegExp('^cd ?', 'i')),

            symbolsExec = ['*', '&', '{', '}', '|', '\'', '"'],
            isSymbol    = Util.isContainStr(command, symbolsExec),

            CWD         = process.cwd(),
            dir         = ClientDirs[connNum],
            options     = {
                cwd:    dir || CWD
            };

        if (!dir)
            dir = ClientDirs[connNum] = CWD;

        connName = '#' + connNum + ': ';
        Util.log(connName + command);

        if (isCD || isCDWin && WIN || isVolume) {
            ret = true;

            onCD(command, dir, function(error, json) {
                var path;

                if (json.path) {
                    path                = json.path;
                    ClientDirs[connNum] = path;
                }

                callback(json);
            });
        }

        if (!ret) {
            if (WIN)
                command    = 'cmd /C ' + command;

            if (!ClientFuncs[connNum])
                ClientFuncs[connNum] = Util.exec.with(setExec, function(json, error, stderr) {
                    log(connNum, error, 'error');
                    log(connNum, stderr, 'stderror');

                    Util.exec(callback, json);
                });

            funcExec        = ClientFuncs[connNum];
            firstChar       = command[0];

            if (firstChar === '#') {
                command     = command.slice(1);
                command     = connName + command;
                command     = addNewLine(command);

                Util.exec(callback, {
                    stdout: command
                }, true);
            } else if (WIN || firstChar === ' ' || isSymbol)
                exec(command, options, funcExec);
            else
                setSpawn(command, options, callback);
        }
    }

    /**
     * function send result of command to client
     * @param callback
     */
    function setExec(callback, error, stdout, stderr) {
        var json,
            errorStr    = '';

        if (stderr)
            errorStr    = stderr;
        else if (error)
            errorStr    = error.message;

        json            = {
            stdout : stdout,
            stderr : errorStr
        };

        Util.exec(callback, json, error, stderr);
    }

    function setSpawn(сommand, options, callback) {
        var cmd, error,
            isSended    = false,
            args        = сommand.split(' '),
            send        = function(error, data) {
                isSended = true;

                callback({
                    stderr: error,
                    stdout: data
                });
            },
            sendError     = function(error) {
                isSended = true;

                send(error, null);
            };

        сommand    = args.shift();

        error       = Util.exec.tryLog(function() {
            cmd = spawn(сommand, args, options);
        });

        if (error) {
            sendError(error);
        } else {
            cmd.stderr.setEncoding('utf8');
            cmd.stdout.setEncoding('utf8');

            cmd.stdout.on('data', function(data) {
                send(null, data);
            });

            cmd.stderr.on('data', function(error) {
                sendError(error);
            });

            cmd.on('error', function(error) {
                var errorStr    = addNewLine(error + '');

                Util.log(error);
                sendError(errorStr);
            });

            cmd.on('close', function () {
                cmd = null;

                if (!isSended)
                    send(null, null);
            });
        }
    }

        function onCD(command, currDir, callback) {
        var CD              = 'cd ',
            HOME            = process.env.HOME,

            isChangeVolume  = win.isChangeVolume(command),
            isVolume        = win.isVolume(command),
            paramDir        = Util.rmStrOnce(command, [CD, 'cd']),

            regExpHome      = new RegExp('^~'),
            regExpRoot      = new RegExp('^[/\\\\]'),

            isWildCard      = Util.isContainStr(paramDir, ['*', '?']),
            isHome          = paramDir.match(regExpHome) && !WIN,
            isRoot          = paramDir.match(regExpRoot),

            onExec          = function (error, stdout, stderr) {
                var path        = paramDir,
                    errorStr    = '';

                if (stderr) {
                    errorStr    = stderr;
                } else if (error) {
                    errorStr    = error.message;
                    path        = '';
                }

                callback(error || stderr, {
                    stderr  : addNewLine(errorStr),
                    stdout  : stdout,
                    path    : path
                });
            };

        if (isHome) {
            command     = command.replace('~', HOME);
            paramDir    = paramDir.replace('~', HOME);
        }

        if (!paramDir && !WIN)
            paramDir = '.';

        if (!isChangeVolume || isVolume) {
            paramDir    = getFirstWord(paramDir);
            paramDir    = path.normalize(paramDir);

            command     = Util.rmStrOnce(command, [
                CD,
                paramDir,
                '\'' + paramDir + '\'',
                '"'  + paramDir + '"',
            ]);

            if (!isHome && !isRoot)
                paramDir    = path.join(currDir, paramDir);

            if (isWildCard)
                command = CD + paramDir + ' ' + command;
            else
                command = CD + '"' + paramDir + '" ' + command;
        }

        if (!isWildCard)
            exec(command, {cwd: paramDir}, onExec);
        else
            find(paramDir, function(error, dirs) {
                var dir;

                if (!error)
                    dir = dirs[0];

                paramDir = dir;
                exec(command, {cwd: dir}, onExec);
            });
    }

    function getFirstWord(str) {
        var word, result,
            regStrEnd       = getRegStrEnd(),
            regStr          = '^(.*?)',
            regStrQuotes    = '^"(.*)"',
            regExp          = new RegExp(regStr + regStrEnd),
            regExpQuotes    = new RegExp(regStrQuotes + regStrEnd + '?'),
            is              = Util.isString(str);

        if (is) {
            result  = str.match(regExpQuotes);

            if (result) {
                word    = result[1];
            } else {
                result  = str.match(regExp);
                word    = result && result[1];
            }

            if (!word)
                word    = str;
        }

        return word;
    }

    function getRegStrEnd() {
        var regStrEnd = '(\\s|\\;|&&|\\|\\|)';

        return regStrEnd;
    }

    function log(connNum, str, typeParam) {
        var ret,
            type       = ' ';

        if (str) {

            if (typeParam)
                type  += typeParam + ':';

            ret        = 'client #' + connNum + type + str;

            Util.log(ret);
        }

        return ret;
    }
})();
<commit_msg>fix(console) add processing of ";"<commit_after>(function() {
    'use strict';

    var DIR_SERVER      = './',

        path            = require('path'),
        child_process   = require('child_process'),
        exec            = child_process.exec,
        spawn           = child_process.spawn,

        Util            = require('util-io'),

        win             = require('win32'),
        socket          = require(DIR_SERVER    + 'socket'),
        find            = require('glob'),

        ClientFuncs     = [],
        ClientDirs      = [],
        Clients         = [],
        WIN             = process.platform === 'win32',

        addNewLine      = function (text) {
            var newLine    = '',
                n           = text && text.length;

            if(n && text[n-1] !== '\n')
                newLine = '\n';

            return text + newLine;
        },

        ConNum          = 0,

        CHANNEL         = 'console-data';

    /**
     * function listen on servers port
     * @pServer {Object} started server object
     */
    exports.init = function() {
        var ret;

        ret = socket.on('connection', function(clientSocket) {
            onConnection(clientSocket, function(json) {
                socket.emit(CHANNEL, json, clientSocket);
            });
        });

        return ret;
    };

    function onConnection(clientSocket, callback) {
        var msg, onDisconnect, onMessage;

        ++ConNum;

        if (!Clients[ConNum]) {
            msg = log(ConNum, 'console connected');

            callback({
                stdout  : addNewLine(msg),
                path    : process.cwd()
            });

            Clients[ConNum]             = true;

            onMessage                   = Util.exec.with(getOnMessage, ConNum, callback);
            onDisconnect                = function(conNum) {
                Clients[conNum]         =
                ClientFuncs[conNum]     = null;

                log(conNum, 'console disconnected');

                socket.removeListener(CHANNEL, onMessage, clientSocket);
                socket.removeListener('disconnect', onDisconnect, clientSocket);
            }.bind(null, ConNum);

            socket.on(CHANNEL, onMessage, clientSocket);
            socket.on('disconnect', onDisconnect, clientSocket);
        } else {
            msg = log(ConNum, ' in use. Reconnecting...\n');

            Util.exec(callback, {
                stdout: msg
            });

            socket.disconnect();
        }
    }

    /**
     * function gets onMessage function
     * that execute needed command
     *
     * @param pConnNum
     * @param callback
     */
    function getOnMessage(connNum, callback, command) {
        var funcExec, firstChar,
            connName, ret,

            isVolume    = win.isChangeVolume(command),
            isCD        = command.match(new RegExp('^cd ?')),
            isCDWin     = command.match(new RegExp('^cd ?', 'i')),

            symbolsExec = ['*', '&', '{', '}', '|', '\'', '"', ';'],
            isSymbol    = Util.isContainStr(command, symbolsExec),

            CWD         = process.cwd(),
            dir         = ClientDirs[connNum],
            options     = {
                cwd:    dir || CWD
            };

        if (!dir)
            dir = ClientDirs[connNum] = CWD;

        connName = '#' + connNum + ': ';
        Util.log(connName + command);

        if (isCD || isCDWin && WIN || isVolume) {
            ret = true;

            onCD(command, dir, function(error, json) {
                var path;

                if (json.path) {
                    path                = json.path;
                    ClientDirs[connNum] = path;
                }

                callback(json);
            });
        }

        if (!ret) {
            if (WIN)
                command    = 'cmd /C ' + command;

            if (!ClientFuncs[connNum])
                ClientFuncs[connNum] = Util.exec.with(setExec, function(json, error, stderr) {
                    log(connNum, error, 'error');
                    log(connNum, stderr, 'stderror');

                    Util.exec(callback, json);
                });

            funcExec        = ClientFuncs[connNum];
            firstChar       = command[0];

            if (firstChar === '#') {
                command     = command.slice(1);
                command     = connName + command;
                command     = addNewLine(command);

                Util.exec(callback, {
                    stdout: command
                }, true);
            } else if (WIN || firstChar === ' ' || isSymbol)
                exec(command, options, funcExec);
            else
                setSpawn(command, options, callback);
        }
    }

    /**
     * function send result of command to client
     * @param callback
     */
    function setExec(callback, error, stdout, stderr) {
        var json,
            errorStr    = '';

        if (stderr)
            errorStr    = stderr;
        else if (error)
            errorStr    = error.message;

        json            = {
            stdout : stdout,
            stderr : errorStr
        };

        Util.exec(callback, json, error, stderr);
    }

    function setSpawn(сommand, options, callback) {
        var cmd, error,
            isSended    = false,
            args        = сommand.split(' '),
            send        = function(error, data) {
                isSended = true;

                callback({
                    stderr: error,
                    stdout: data
                });
            },
            sendError     = function(error) {
                isSended = true;

                send(error, null);
            };

        сommand    = args.shift();

        error       = Util.exec.tryLog(function() {
            cmd = spawn(сommand, args, options);
        });

        if (error) {
            sendError(error);
        } else {
            cmd.stderr.setEncoding('utf8');
            cmd.stdout.setEncoding('utf8');

            cmd.stdout.on('data', function(data) {
                send(null, data);
            });

            cmd.stderr.on('data', function(error) {
                sendError(error);
            });

            cmd.on('error', function(error) {
                var errorStr    = addNewLine(error + '');

                Util.log(error);
                sendError(errorStr);
            });

            cmd.on('close', function () {
                cmd = null;

                if (!isSended)
                    send(null, null);
            });
        }
    }

        function onCD(command, currDir, callback) {
        var CD              = 'cd ',
            HOME            = process.env.HOME,

            isChangeVolume  = win.isChangeVolume(command),
            isVolume        = win.isVolume(command),
            paramDir        = Util.rmStrOnce(command, [CD, 'cd']),

            regExpHome      = new RegExp('^~'),
            regExpRoot      = new RegExp('^[/\\\\]'),

            isWildCard      = Util.isContainStr(paramDir, ['*', '?']),
            isHome          = paramDir.match(regExpHome) && !WIN,
            isRoot          = paramDir.match(regExpRoot),

            onExec          = function (error, stdout, stderr) {
                var path        = paramDir,
                    errorStr    = '';

                if (stderr) {
                    errorStr    = stderr;
                } else if (error) {
                    errorStr    = error.message;
                    path        = '';
                }

                callback(error || stderr, {
                    stderr  : addNewLine(errorStr),
                    stdout  : stdout,
                    path    : path
                });
            };

        if (isHome) {
            command     = command.replace('~', HOME);
            paramDir    = paramDir.replace('~', HOME);
        }

        if (!paramDir && !WIN)
            paramDir = '.';

        if (!isChangeVolume || isVolume) {
            paramDir    = getFirstWord(paramDir);
            paramDir    = path.normalize(paramDir);

            command     = Util.rmStrOnce(command, [
                CD,
                paramDir,
                '\'' + paramDir + '\'',
                '"'  + paramDir + '"',
            ]);

            if (!isHome && !isRoot)
                paramDir    = path.join(currDir, paramDir);

            if (isWildCard)
                command = CD + paramDir + ' ' + command;
            else
                command = CD + '"' + paramDir + '" ' + command;
        }

        if (!isWildCard)
            exec(command, {cwd: paramDir}, onExec);
        else
            find(paramDir, function(error, dirs) {
                var dir;

                if (!error)
                    dir = dirs[0];

                paramDir = dir;
                exec(command, {cwd: dir}, onExec);
            });
    }

    function getFirstWord(str) {
        var word, result,
            regStrEnd       = getRegStrEnd(),
            regStr          = '^(.*?)',
            regStrQuotes    = '^"(.*)"',
            regExp          = new RegExp(regStr + regStrEnd),
            regExpQuotes    = new RegExp(regStrQuotes + regStrEnd + '?'),
            is              = Util.isString(str);

        if (is) {
            result  = str.match(regExpQuotes);

            if (result) {
                word    = result[1];
            } else {
                result  = str.match(regExp);
                word    = result && result[1];
            }

            if (!word)
                word    = str;
        }

        return word;
    }

    function getRegStrEnd() {
        var regStrEnd = '(\\s|\\;|&&|\\|\\|)';

        return regStrEnd;
    }

    function log(connNum, str, typeParam) {
        var ret,
            type       = ' ';

        if (str) {

            if (typeParam)
                type  += typeParam + ':';

            ret        = 'client #' + connNum + type + str;

            Util.log(ret);
        }

        return ret;
    }
})();
<|endoftext|>


<commit_before>/*jlint node: true */
/*global require, console, __dirname, module, setInterval, clearInterval */

/*
 * jenkins.js
 *
 * Copyright (C) 2015 by James Jenner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Note that this uses the Jenkins SDK.JavaScript, refer to resources mentioned
 * below for further information about the Jenkins SDK for javascript.
 */

var EventEmitter = require('events').EventEmitter;
var util         = require('util');
var async        = require('async');

var jenkinsapi = require('./jenkins/jenkins');

var JenkinsState = require('../shared/jenkinsState.js');

module.exports = Jenkins;

util.inherits(Jenkins, EventEmitter);

Jenkins.DEFAULT_POLL_MINS = 1;
Jenkins.DEFAULT_TEAM_NAME = 'kitchen_ellipse';

Jenkins.EVENT_DATA_RECEIVED = "dataReceived";

function Jenkins(options) {
  EventEmitter.call(this);

  this.host = ((options.host !== null && options.host !== undefined) ? options.host : 'http://awsjenkins.ventyx.abb.com:8080');
  this.name = ((options.name !== null && options.name !== undefined) ? options.name : Jenkins.DEFAULT_NAME);

  this.pollFrequencyMinutes = ((options.pollFrequencyMinutes !== null && options.pollFrequencyMinutes !== undefined) ? options.pollFrequencyMinutes : Jenkins.DEFAULT_POLL_MINS);
  this.pollFrequencyMinutes = 0.5;
  this.pollFrequencyMilliseconds = this.pollFrequencyMinutes * 60 * 1000;
  // TODO: stop this from being hard coded
  this.maxHistory = 5;
  this.history = [];

}

Jenkins.prototype.startMonitor = function () {
  console.log("Jenkins.startMonitor -> " + this.name);
  if (this.host === '' || this.name === '') {
    return;
  }

  this.jenkins = jenkinsapi.init(this.host);

  this._monitor();
};

Jenkins.prototype._monitor = function () {
  // TODO: change to setTimeout?
  // in prod poll interval should be larger than time to fetch, however cannot garentee

  this.sprintMontiorInterval = setInterval(function() {
    this._monitorExecute();
  }.bind(this), this.pollFrequencyMilliseconds);
};

Jenkins.prototype._monitorExecute = function () {

  this.jenkins.job_info('/' + this.name, function(err, data) {
    if(err) {
      console.log("ERROR (job info kitchen_ellipse): " + err);
      return;
    }

    this.jenkinsState = new JenkinsState();
    this.jenkinsState.displayName = data.displayName;
    this.jenkinsState.description = data.description;
    this.jenkinsState.name = data.name;
    this.jenkinsState.buildable = data.buildable;
    this.jenkinsState.healthScore = data.healthReport[0].score;
    this.jenkinsState.healthDescription = data.healthReport[0].description;

    console.log("\t display name: " + data.displayName);
    console.log("\t  description: " + data.description);
    console.log("\t         name: " + data.name);
    console.log("\t          url: " + data.url);
    console.log("\t    buildable: " + data.buildable);
    console.log("\t        color: " + data.color);
    console.log("\tHealth Report: " + data.healthReport[0].description);
    console.log("\t     icon URL: " + data.healthReport[0].iconUrl);
    console.log("\t        score: " + data.healthReport[0].score);
    console.log("\t       builds: ");

    // increase by 1 as if build is in progress then it's in the job results
    var maxBuilds = this.maxHistory + 1;
    var isLastBuild = false;
    // console.log("builds: " + JSON.stringify(data.builds, null, " "));
    data.builds.sort(compareJenkinsNumber);
    var processBuilds = [];

    for(var i = 0; i < data.builds.length && i < maxBuilds; i++) {
      processBuilds.push(data.builds[i].number);
    }

    console.log("about to process individual builds: " + processBuilds);
    async.map(processBuilds, this._getJobResult.bind(this), this._buildDetailsReceived.bind(this));
  }.bind(this));
};

Jenkins.prototype._getJobResult = function (jobNbr, done) {
  this.jenkins.build_info('/' + this.name, jobNbr, function(err, data) {
    if(!err) {
      if(data.building) {
        return done(null, {buildNbr: jobNbr, result: data.result, building: data.building, progress: Math.round((new Date().getTime() - data.timestamp) / data.estimatedDuration * 100)});
      } else {
        return done(null, {buildNbr: jobNbr, result: data.result, building: data.building, progress: 0});
      }
    } else {
      return done(err);
    }
  });
};

Jenkins.prototype._getBuildDetails = function(buildNbr, isLastBuild) {
};

Jenkins.prototype._buildDetailsReceived = function(err, result) {

  for(var i = 0; i < result.length; i++) {
    if(result[i].building) {
      console.log("\t               " + result[i].buildNbr + " -> " + result[i].progress + "%");
      this.jenkinsState.building = true;
      this.jenkinsState.buildingNumber = result[i].buildNbr;
      this.jenkinsState.progress = result[i].progress;
    } else {
      console.log("\t               " + result[i].buildNbr + " : " + result[i].result);
      this.jenkinsState.builds.push({buildNumber: result[i].buildNbr, result: result[i].result});
    }
  }
  this._processResults();
};

Jenkins.prototype._processResults = function () {
  var i = 0;
  var j = 0;
  var exists = false;
  var newBuilds = [];

  this.jenkinsState.builds.sort(compareBuilds);

  for (i = 0; i < this.jenkinsState.builds.length; i++) {
    exists = false;
    for (j = 0; j < this.history.length && !exists; j++) {
      if(this.history[j].buildNumber === this.jenkinsState.builds[i].buildNumber) {
        exists = true;
      }
    }

    if(!exists) {
      if(this.history.length > this.maxHistory) {
        this.history.shift();
      }

      this.history.push(this.jenkinsState.builds[i]);
      newBuilds.push(this.jenkinsState.builds[i]);
    }
  }

  this.jenkinsState.builds = newBuilds;

  // sort the
  this.emit(Jenkins.EVENT_DATA_RECEIVED, this.name, this.jenkinsState);
};

Jenkins.prototype.stopMonitor = function(type) {
  this._stopMonitor(this.sprintMontiorInterval);
};

Jenkins.prototype._stopMonitor = function(interval) {
  if(interval !== undefined && interval !== null) {
    clearInterval(interval);
  }
};

function Job(value, color) {
  this.color = color;
  this.value = value;
}

function compareJenkinsNumber (a, b) {
  if (a.number < b.number) {
     return 1;
  }

  if (a.number > b.number) {
    return -1;
  }

  return 0;
}

function compareBuilds (a, b) {
  if (a.buildNumber < b.buildNumber) {
     return -1;
  }

  if (a.buildNumber > b.buildNumber) {
    return 1;
  }

  return 0;
}<commit_msg>Added mostRecent build information to jenkinsState<commit_after>/*jlint node: true */
/*global require, console, __dirname, module, setInterval, clearInterval */

/*
 * jenkins.js
 *
 * Copyright (C) 2015 by James Jenner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Note that this uses the Jenkins SDK.JavaScript, refer to resources mentioned
 * below for further information about the Jenkins SDK for javascript.
 */

var EventEmitter = require('events').EventEmitter;
var util         = require('util');
var async        = require('async');

var jenkinsapi = require('./jenkins/jenkins');

var JenkinsState = require('../shared/jenkinsState.js');

module.exports = Jenkins;

util.inherits(Jenkins, EventEmitter);

Jenkins.DEFAULT_POLL_MINS = 1;
Jenkins.DEFAULT_TEAM_NAME = 'kitchen_ellipse';

Jenkins.EVENT_DATA_RECEIVED = "dataReceived";

function Jenkins(options) {
  EventEmitter.call(this);

  this.host = ((options.host !== null && options.host !== undefined) ? options.host : 'http://awsjenkins.ventyx.abb.com:8080');
  this.name = ((options.name !== null && options.name !== undefined) ? options.name : Jenkins.DEFAULT_NAME);

  this.pollFrequencyMinutes = ((options.pollFrequencyMinutes !== null && options.pollFrequencyMinutes !== undefined) ? options.pollFrequencyMinutes : Jenkins.DEFAULT_POLL_MINS);
  this.pollFrequencyMinutes = 0.5;
  this.pollFrequencyMilliseconds = this.pollFrequencyMinutes * 60 * 1000;
  // TODO: stop this from being hard coded
  this.maxHistory = 5;
  this.history = [];

}

Jenkins.prototype.startMonitor = function () {
  console.log("Jenkins.startMonitor -> " + this.name);
  if (this.host === '' || this.name === '') {
    return;
  }

  this.jenkins = jenkinsapi.init(this.host);

  this._monitor();
};

Jenkins.prototype._monitor = function () {
  // TODO: change to setTimeout?
  // in prod poll interval should be larger than time to fetch, however cannot garentee

  this.sprintMontiorInterval = setInterval(function() {
    this._monitorExecute();
  }.bind(this), this.pollFrequencyMilliseconds);
};

Jenkins.prototype._monitorExecute = function () {

  this.jenkins.job_info('/' + this.name, function(err, data) {
    if(err) {
      console.log("ERROR (job info kitchen_ellipse): " + err);
      return;
    }

    this.jenkinsState = new JenkinsState();
    this.jenkinsState.displayName = data.displayName;
    this.jenkinsState.description = data.description;
    this.jenkinsState.name = data.name;
    this.jenkinsState.buildable = data.buildable;
    this.jenkinsState.healthScore = data.healthReport[0].score;
    this.jenkinsState.healthDescription = data.healthReport[0].description;

    console.log("\t display name: " + data.displayName);
    console.log("\t  description: " + data.description);
    console.log("\t         name: " + data.name);
    console.log("\t          url: " + data.url);
    console.log("\t    buildable: " + data.buildable);
    console.log("\t        color: " + data.color);
    console.log("\tHealth Report: " + data.healthReport[0].description);
    console.log("\t     icon URL: " + data.healthReport[0].iconUrl);
    console.log("\t        score: " + data.healthReport[0].score);
    console.log("\t       builds: ");

    // increase by 1 as if build is in progress then it's in the job results
    var maxBuilds = this.maxHistory + 1;
    var isLastBuild = false;
    // console.log("builds: " + JSON.stringify(data.builds, null, " "));
    data.builds.sort(compareJenkinsNumber);
    var processBuilds = [];

    for(var i = 0; i < data.builds.length && i < maxBuilds; i++) {
      processBuilds.push(data.builds[i].number);
    }

    console.log("about to process individual builds: " + processBuilds);
    async.map(processBuilds, this._getJobResult.bind(this), this._buildDetailsReceived.bind(this));
  }.bind(this));
};

Jenkins.prototype._getJobResult = function (jobNbr, done) {
  this.jenkins.build_info('/' + this.name, jobNbr, function(err, data) {
    if(!err) {
      if(data.building) {
        return done(null, {buildNbr: jobNbr, result: data.result, building: data.building, progress: Math.round((new Date().getTime() - data.timestamp) / data.estimatedDuration * 100)});
      } else {
        return done(null, {buildNbr: jobNbr, result: data.result, building: data.building, progress: 0});
      }
    } else {
      return done(err);
    }
  });
};

Jenkins.prototype._getBuildDetails = function(buildNbr, isLastBuild) {
};

Jenkins.prototype._buildDetailsReceived = function(err, result) {

  for(var i = 0; i < result.length; i++) {
    if(result[i].building) {
      console.log("\t               " + result[i].buildNbr + " -> " + result[i].progress + "%");
      this.jenkinsState.building = true;
      this.jenkinsState.buildingNumber = result[i].buildNbr;
      this.jenkinsState.progress = result[i].progress;
    } else {
      console.log("\t               " + result[i].buildNbr + " : " + result[i].result);
      this.jenkinsState.builds.push({buildNumber: result[i].buildNbr, result: result[i].result});
    }
  }
  this._processResults();
};

Jenkins.prototype._processResults = function () {
  var i = 0;
  var j = 0;
  var exists = false;
  var newBuilds = [];

  this.jenkinsState.builds.sort(compareBuilds);

  for (i = 0; i < this.jenkinsState.builds.length; i++) {
    exists = false;
    for (j = 0; j < this.history.length && !exists; j++) {
      if(this.history[j].buildNumber === this.jenkinsState.builds[i].buildNumber) {
        exists = true;
      }
    }

    if(!exists) {
      if(this.history.length > this.maxHistory) {
        this.history.shift();
      }

      this.history.push(this.jenkinsState.builds[i]);
      newBuilds.push(this.jenkinsState.builds[i]);
    }
  }

  this.jenkinsState.builds = newBuilds;
  this.jenkinsState.mostRecentBuildNumber = this.history[this.history.length - 1].buildNumber;
  this.jenkinsState.mostRecentBuildState = this.history[this.history.length - 1].result;
  if(this.jenkinsState.building) {
    this.jenkinsState.mostRecentBuildNumber = this.jenkinsState.buildingNumber
    this.jenkinsState.mostRecentBuildState = '';
  }

  // sort the
  this.emit(Jenkins.EVENT_DATA_RECEIVED, this.name, this.jenkinsState);
};

Jenkins.prototype.stopMonitor = function(type) {
  this._stopMonitor(this.sprintMontiorInterval);
};

Jenkins.prototype._stopMonitor = function(interval) {
  if(interval !== undefined && interval !== null) {
    clearInterval(interval);
  }
};

function Job(value, color) {
  this.color = color;
  this.value = value;
}

function compareJenkinsNumber (a, b) {
  if (a.number < b.number) {
     return 1;
  }

  if (a.number > b.number) {
    return -1;
  }

  return 0;
}

function compareBuilds (a, b) {
  if (a.buildNumber < b.buildNumber) {
     return -1;
  }

  if (a.buildNumber > b.buildNumber) {
    return 1;
  }

  return 0;
}<|endoftext|>


<commit_before>let fs = Npm.require('fs');
let path = Npm.require('path');
let mkdirp = Npm.require('mkdirp');
let open = Npm.require('open');

function writeFile(filePath, data, done) {
  mkdirp(path.dirname(filePath), function (err) {
    if (err) return done(err);
    fs.writeFile(filePath, data, done);
  });
}

if (process.env.NODE_ENV === 'development') {
  Meteor.startup(function () {
    if (!Package.email) {
      mailman.warnNoEmailPackage();
      return;
    }

    mailman.originalSend = Email.send;
    mailman.send = function (options) {
      let emailPath = path.resolve(process.env.PWD, `.tmp/${Date.now()}.html`) ;
      let header = 'from:' + options.from + '
' + 'to:' + options.to + '

' +
                   'subject:' + options.subject + '
';
      let content = header + options.html;

      writeFile(emailPath, content, function (err) {
        if (err) return console.log(err);

        console.log('### Mailman detected an outgoing email ###');
        open(emailPath);
      });
    };

    Meteor.call('activateMailman', true);
  });
}
<commit_msg>Should work with text emails<commit_after>let fs = Npm.require('fs');
let path = Npm.require('path');
let mkdirp = Npm.require('mkdirp');
let open = Npm.require('open');

function writeFile(filePath, data, done) {
  mkdirp(path.dirname(filePath), function (err) {
    if (err) return done(err);
    fs.writeFile(filePath, data, done);
  });
}

if (process.env.NODE_ENV === 'development') {
  Meteor.startup(function () {
    if (!Package.email) {
      mailman.warnNoEmailPackage();
      return;
    }

    mailman.originalSend = Email.send;
    mailman.send = function (options) {
      let emailPath = path.resolve(process.env.PWD, `.tmp/${Date.now()}.html`) ;
      let header = 'from:' + options.from + '
' + 'to:' + options.to + '

' +
                   'subject:' + options.subject + '
';
      let content = header + (options.html || options.text);

      writeFile(emailPath, content, function (err) {
        if (err) return console.log(err);

        console.log('### Mailman detected an outgoing email ###');
        open(emailPath);
      });
    };

    Meteor.call('activateMailman', true);
  });
}
<|endoftext|>


<commit_before>var fs = require("fs");
var path = require('path');
var brocWriter = require("broccoli-writer");
var helpers = require("broccoli-kitchen-sink-helpers");
var funnel = require('broccoli-funnel');
var swToolboxFile = require.resolve('sw-toolbox/sw-toolbox.js');
var swToolboxMapFile = require.resolve('sw-toolbox/sw-toolbox.map.json');
var toolboxLocation = 'sw-toolbox.js';
var rsvp= require('rsvp');
var BroccoliServiceWorker = function BroccoliServiceWorker(inTree, options) {
  if (!(this instanceof BroccoliServiceWorker)) {
    return new BroccoliServiceWorker(inTree, options);
  }
  this.inTree = inTree;
  options = options || {};
  if (options.skipWaiting === false) {
    this.skipWaiting = false;
  } else {
    this.skipWaiting = true;
  }
  this.cacheFirstURLs = options.cacheFirstURLs || [];
  this.cacheOnlyURLs = options.cacheOnlyURLs || [];
  this.fastestURLs = options.fastestURLs || [];
  this.debug = options.debug || false;
  this.networkFirstURLs = options.networkFirstURLs || options.dynamicCache || [];
  this.excludePaths = options.excludePaths || ['tests/*'];
  this.fallback = options.fallback || [];
  this.rootURL = options.rootURL || '/';
  this.precacheURLs = options.precacheURLs || [];
  if (options.includeRegistration === false) {
    this.includeRegistration = false;
  } else {
    this.includeRegistration = true;
  }
  if (this.includeRegistration === true || !options.serviceWorkerFile) {
    this.serviceWorkerFile = "service-worker.js";
  } else {
    this.serviceWorkerFile = options.serviceWorkerFile;
  }
  this.swIncludeTree = options.swIncludeTree;
  this.swIncludeFiles = options.swIncludeFiles;
};

BroccoliServiceWorker.prototype = Object.create(brocWriter.prototype);
BroccoliServiceWorker.prototype.constructor = BroccoliServiceWorker;

BroccoliServiceWorker.prototype.write = function(readTree, destDir) {
  var skipWaiting = this.skipWaiting;
  var cacheFirstURLs = this.cacheFirstURLs;
  var cacheOnlyURLs = this.cacheOnlyURLs;
  var debug = this.debug;
  var networkFirstURLs = this.networkFirstURLs;
  var fallback = this.fallback;
  var rootURL = this.rootURL;
  var fastestURLs = this.fastestURLs;
  var precacheURLs = this.precacheURLs;
  var serviceWorkerFile = this.serviceWorkerFile;
  var serviceWorkerTree = funnel(this.inTree, {
    exclude: this.excludePaths
  });
  var swIncludeFiles = this.swIncludeFiles;
  var swIncludeTree = this.swIncludeTree;

  return readSwIncludeTree(readTree, this.swIncludeTree).then(function(swIncludeDir){
    return readTree(serviceWorkerTree).then(function (srcDir) {
      var lines = [];
      lines.push("importScripts('"+toolboxLocation+"');");
      lines.push("var CACHE_PREFIX = 'brocsw-v';");
      lines.push("var CACHE_VERSION = CACHE_PREFIX+'"+(new Date()).getTime()+"';");
      lines.push("toolbox.options.cache.name = CACHE_VERSION;");
      if (debug) {
        lines.push("toolbox.options.debug = true;");
      }
      lines.push("var urlsToPrefetch = [");
      lines.push("    '"+rootURL+"',");
      getFilesRecursively(srcDir, [ "**/*" ]).forEach(function (file, idx, array) {
        lines.push(createArrayLine('    '+JSON.stringify(file), idx, array.length));
      });
      lines.push("];");
      precacheURLs.forEach(function (file, idx, array) {
        lines.push("urlsToPrefetch.push('"+file+"');");
      });
      lines.push("urlsToPrefetch.forEach(function(url) {");
      lines.push("  toolbox.router.any(url, toolbox.cacheFirst);");
      lines.push("});");
      lines.push("toolbox.precache(urlsToPrefetch);");
      setupRoutes(cacheFirstURLs, 'toolbox.cacheFirst', lines);
      setupRoutes(cacheOnlyURLs, 'toolbox.cacheOnly', lines);
      setupRoutes(fastestURLs, 'toolbox.fastest', lines);
      setupRoutes(networkFirstURLs, 'toolbox.networkFirst', lines);

      if (fallback.length) {
        fallback.forEach(function(fallback, idx, array) {
          var fallbackParts = fallback.split(' ');
          if (fallbackParts.length > 1) {
            var fallbackOptions = "{fallbackURL:'"+fallbackParts[1]+"'}";
            lines.push("toolbox.router.any('"+fallbackParts[0]+"',fallbackResponse, "+fallbackOptions+");");
          }
        });
      }
      if (swIncludeFiles && swIncludeFiles.length) {
        swIncludeFiles.forEach(function(file) {
          lines.push(fs.readFileSync(file));
        });
      }
      if (swIncludeDir) {
        getFilesRecursively(swIncludeDir, [ "**/*" ]).forEach(function (file) {
          var srcFile = path.join(swIncludeDir, file);
          lines.push(fs.readFileSync(srcFile));
        });
      }
      if (skipWaiting || debug) {
        lines.push("self.addEventListener('install', function(event) {");
         addDebugLine("'Handling install event. Resources to pre-fetch:', urlsToPrefetch", debug, lines);
        if (skipWaiting) {
          lines.push("  if (self.skipWaiting) { self.skipWaiting(); }");
        }
        lines.push("});");
      }

      lines.push(getFileContents('delete-old-caches.js'));
      if (fallback.length) {
        lines.push(getFileContents('fallback-response.js'));
      }

      lines.push(getFileContents('log-debug.js'));
      fs.writeFileSync(path.join(destDir, serviceWorkerFile), lines.join("\n"));
      fs.writeFileSync(path.join(destDir, toolboxLocation), fs.readFileSync(swToolboxFile));
      if (debug) {
        fs.writeFileSync(path.join(destDir, 'sw-toolbox.map.json'), fs.readFileSync(swToolboxMapFile));
      }
    });
  });
};

BroccoliServiceWorker.prototype.addExternalFile = function(file) {
  this.externalFiles.push(file);
};

function addDebugLine(consoleText, debug, lines) {
  if (debug) {
    lines.push("console.log("+consoleText+");");
  }
}

function createArrayLine(line, idx, arrayLength) {
  var arrayLine = line;
  if ((idx+1) < arrayLength) {
    arrayLine += ",";
  }
  return arrayLine;
}

function getFilesRecursively(dir, globPatterns) {
  try {
    return helpers.multiGlob(globPatterns, { cwd: dir }).filter(function(file){
      var srcFile = path.join(dir, file);
      var stat = fs.lstatSync(srcFile);
      return (stat.isFile()  || stat.isSymbolicLink());
    });
  } catch (ex) {
    return [];
  }
}

function getFileContents(fileName) {
  var filePath = [ __dirname, fileName].join('/');
  return fs.readFileSync(filePath);
}

function printPath(path) {
  if (path instanceof RegExp) {
    return path.toString();
  } else {
    return JSON.stringify(path);
  }
}

function setupRoutes(routeUrls, handler, lines) {
  if (routeUrls.length) {
    routeUrls.forEach(function(cacheURL, idx, array) {
      var method = 'any';
      var options;

      if (typeof cacheURL === 'object' && (cacheURL instanceof RegExp === false)) {
        options = JSON.stringify(cacheURL.options);
        if (cacheURL.method) {
          method = cacheURL.method;
        }
        cacheURL = cacheURL.route;
      }
      var line = 'toolbox.router.'+method+'('+printPath(cacheURL)+',' + handler;

      if (options) {
        line += ', ';
        line += options;
      }

      line += ');';

      lines.push(line);
    });
  }
}

function readSwIncludeTree(readTree, swIncludeTree) {
  if (swIncludeTree) {
    return readTree(swIncludeTree);
  } else {
    return rsvp.resolve();
  }
}

module.exports = BroccoliServiceWorker;
<commit_msg>Dont cache root path<commit_after>var fs = require("fs");
var path = require('path');
var brocWriter = require("broccoli-writer");
var helpers = require("broccoli-kitchen-sink-helpers");
var funnel = require('broccoli-funnel');
var swToolboxFile = require.resolve('sw-toolbox/sw-toolbox.js');
var swToolboxMapFile = require.resolve('sw-toolbox/sw-toolbox.map.json');
var toolboxLocation = 'sw-toolbox.js';
var rsvp= require('rsvp');
var BroccoliServiceWorker = function BroccoliServiceWorker(inTree, options) {
  if (!(this instanceof BroccoliServiceWorker)) {
    return new BroccoliServiceWorker(inTree, options);
  }
  this.inTree = inTree;
  options = options || {};
  if (options.skipWaiting === false) {
    this.skipWaiting = false;
  } else {
    this.skipWaiting = true;
  }
  this.cacheFirstURLs = options.cacheFirstURLs || [];
  this.cacheOnlyURLs = options.cacheOnlyURLs || [];
  this.fastestURLs = options.fastestURLs || [];
  this.debug = options.debug || false;
  this.networkFirstURLs = options.networkFirstURLs || options.dynamicCache || [];
  this.excludePaths = options.excludePaths || ['tests/*'];
  this.fallback = options.fallback || [];
  this.rootURL = (options.rootURL === false ? false : options.rootURL || '/');
  this.precacheURLs = options.precacheURLs || [];
  if (options.includeRegistration === false) {
    this.includeRegistration = false;
  } else {
    this.includeRegistration = true;
  }
  if (this.includeRegistration === true || !options.serviceWorkerFile) {
    this.serviceWorkerFile = "service-worker.js";
  } else {
    this.serviceWorkerFile = options.serviceWorkerFile;
  }
  this.swIncludeTree = options.swIncludeTree;
  this.swIncludeFiles = options.swIncludeFiles;
};

BroccoliServiceWorker.prototype = Object.create(brocWriter.prototype);
BroccoliServiceWorker.prototype.constructor = BroccoliServiceWorker;

BroccoliServiceWorker.prototype.write = function(readTree, destDir) {
  var skipWaiting = this.skipWaiting;
  var cacheFirstURLs = this.cacheFirstURLs;
  var cacheOnlyURLs = this.cacheOnlyURLs;
  var debug = this.debug;
  var networkFirstURLs = this.networkFirstURLs;
  var fallback = this.fallback;
  var rootURL = this.rootURL;
  var fastestURLs = this.fastestURLs;
  var precacheURLs = this.precacheURLs;
  var serviceWorkerFile = this.serviceWorkerFile;
  var serviceWorkerTree = funnel(this.inTree, {
    exclude: this.excludePaths
  });
  var swIncludeFiles = this.swIncludeFiles;
  var swIncludeTree = this.swIncludeTree;

  return readSwIncludeTree(readTree, this.swIncludeTree).then(function(swIncludeDir){
    return readTree(serviceWorkerTree).then(function (srcDir) {
      var lines = [];
      lines.push("importScripts('"+toolboxLocation+"');");
      lines.push("var CACHE_PREFIX = 'brocsw-v';");
      lines.push("var CACHE_VERSION = CACHE_PREFIX+'"+(new Date()).getTime()+"';");
      lines.push("toolbox.options.cache.name = CACHE_VERSION;");
      if (debug) {
        lines.push("toolbox.options.debug = true;");
      }
      lines.push("var urlsToPrefetch = [");
      if (rootURL) {
        lines.push("    '"+rootURL+"',");
      }
      getFilesRecursively(srcDir, [ "**/*" ]).forEach(function (file, idx, array) {
        lines.push(createArrayLine('    '+JSON.stringify(file), idx, array.length));
      });
      lines.push("];");
      precacheURLs.forEach(function (file, idx, array) {
        lines.push("urlsToPrefetch.push('"+file+"');");
      });
      lines.push("urlsToPrefetch.forEach(function(url) {");
      lines.push("  toolbox.router.any(url, toolbox.cacheFirst);");
      lines.push("});");
      lines.push("toolbox.precache(urlsToPrefetch);");
      setupRoutes(cacheFirstURLs, 'toolbox.cacheFirst', lines);
      setupRoutes(cacheOnlyURLs, 'toolbox.cacheOnly', lines);
      setupRoutes(fastestURLs, 'toolbox.fastest', lines);
      setupRoutes(networkFirstURLs, 'toolbox.networkFirst', lines);

      if (fallback.length) {
        fallback.forEach(function(fallback, idx, array) {
          var fallbackParts = fallback.split(' ');
          if (fallbackParts.length > 1) {
            var fallbackOptions = "{fallbackURL:'"+fallbackParts[1]+"'}";
            lines.push("toolbox.router.any('"+fallbackParts[0]+"',fallbackResponse, "+fallbackOptions+");");
          }
        });
      }
      if (swIncludeFiles && swIncludeFiles.length) {
        swIncludeFiles.forEach(function(file) {
          lines.push(fs.readFileSync(file));
        });
      }
      if (swIncludeDir) {
        getFilesRecursively(swIncludeDir, [ "**/*" ]).forEach(function (file) {
          var srcFile = path.join(swIncludeDir, file);
          lines.push(fs.readFileSync(srcFile));
        });
      }
      if (skipWaiting || debug) {
        lines.push("self.addEventListener('install', function(event) {");
         addDebugLine("'Handling install event. Resources to pre-fetch:', urlsToPrefetch", debug, lines);
        if (skipWaiting) {
          lines.push("  if (self.skipWaiting) { self.skipWaiting(); }");
        }
        lines.push("});");
      }

      lines.push(getFileContents('delete-old-caches.js'));
      if (fallback.length) {
        lines.push(getFileContents('fallback-response.js'));
      }

      lines.push(getFileContents('log-debug.js'));
      fs.writeFileSync(path.join(destDir, serviceWorkerFile), lines.join("\n"));
      fs.writeFileSync(path.join(destDir, toolboxLocation), fs.readFileSync(swToolboxFile));
      if (debug) {
        fs.writeFileSync(path.join(destDir, 'sw-toolbox.map.json'), fs.readFileSync(swToolboxMapFile));
      }
    });
  });
};

BroccoliServiceWorker.prototype.addExternalFile = function(file) {
  this.externalFiles.push(file);
};

function addDebugLine(consoleText, debug, lines) {
  if (debug) {
    lines.push("console.log("+consoleText+");");
  }
}

function createArrayLine(line, idx, arrayLength) {
  var arrayLine = line;
  if ((idx+1) < arrayLength) {
    arrayLine += ",";
  }
  return arrayLine;
}

function getFilesRecursively(dir, globPatterns) {
  try {
    return helpers.multiGlob(globPatterns, { cwd: dir }).filter(function(file){
      var srcFile = path.join(dir, file);
      var stat = fs.lstatSync(srcFile);
      return (stat.isFile()  || stat.isSymbolicLink());
    });
  } catch (ex) {
    return [];
  }
}

function getFileContents(fileName) {
  var filePath = [ __dirname, fileName].join('/');
  return fs.readFileSync(filePath);
}

function printPath(path) {
  if (path instanceof RegExp) {
    return path.toString();
  } else {
    return JSON.stringify(path);
  }
}

function setupRoutes(routeUrls, handler, lines) {
  if (routeUrls.length) {
    routeUrls.forEach(function(cacheURL, idx, array) {
      var method = 'any';
      var options;

      if (typeof cacheURL === 'object' && (cacheURL instanceof RegExp === false)) {
        options = JSON.stringify(cacheURL.options);
        if (cacheURL.method) {
          method = cacheURL.method;
        }
        cacheURL = cacheURL.route;
      }
      var line = 'toolbox.router.'+method+'('+printPath(cacheURL)+',' + handler;

      if (options) {
        line += ', ';
        line += options;
      }

      line += ');';

      lines.push(line);
    });
  }
}

function readSwIncludeTree(readTree, swIncludeTree) {
  if (swIncludeTree) {
    return readTree(swIncludeTree);
  } else {
    return rsvp.resolve();
  }
}

module.exports = BroccoliServiceWorker;
<|endoftext|>


<commit_before>var fs = require("fs");
var path = require('path');
var brocWriter = require("broccoli-writer");
var helpers = require("broccoli-kitchen-sink-helpers");
var funnel = require('broccoli-funnel');
var swCachePolyFillFile =  require.resolve('serviceworker-cache-polyfill');

var BroccoliServiceWorker = function BroccoliServiceWorker(inTree, options) {
  if (!(this instanceof BroccoliServiceWorker)) {
    return new BroccoliServiceWorker(inTree, options);
  }
  this.inTree = inTree;
  options = options || {};
  this.addPolyfill = options.addPolyfill || true;
  this.debug = options.debug || true;
  this.dynamicCache = options.dynamicCache || [];
  this.excludePaths = options.excludePaths || ['tests/*'];
  this.fallback = options.fallback || [];
  this.includePaths = options.includePaths || [];
  this.polyFillLocation = options.polyFillLocation || 'serviceworker-cache-polyfill.js';
  this.serviceWorkerFile = options.serviceWorkerFile || "service-worker.js";
};

BroccoliServiceWorker.prototype = Object.create(brocWriter.prototype);
BroccoliServiceWorker.prototype.constructor = BroccoliServiceWorker;

BroccoliServiceWorker.prototype.write = function(readTree, destDir) {
  var addPolyfill = this.addPolyfill;
  var debug = this.debug;
  var dynamicCache = this.dynamicCache;
  var fallback = this.fallback;
  var includePaths = this.includePaths;
  var polyFillLocation = this.polyFillLocation;
  var serviceWorkerFile = this.serviceWorkerFile;
  var serviceWorkerTree = funnel(this.inTree, {
    exclude: this.excludePaths
  });

  return readTree(serviceWorkerTree).then(function (srcDir) {
    var cacheVersion = (new Date()).getTime();
    var lines = [];
    if (addPolyfill) {
      lines.push("importScripts('"+polyFillLocation+"');");
    }
    lines.push("var CACHE_VERSION = '" + cacheVersion + "';");
    lines.push("var CURRENT_CACHES = {");
    lines.push("  prefetch: 'prefetch-cache-v' + CACHE_VERSION");
    lines.push("};");
    if (dynamicCache.length) {
      lines.push("CURRENT_CACHES['dynamic'] =  'dynamic-cache-v' + CACHE_VERSION;");
    }
    if (dynamicCache.length) {
      lines.push("var DYNAMIC_URLS = [");
      dynamicCache.forEach(function(cacheURL, idx, array) {
        lines.push(createArrayLine("new RegExp('"+escapeRegExp(cacheURL)+"')", idx, array.length));
      });
      lines.push("];");
    }
    if (fallback.length) {
      lines.push("var FALLBACK_URLS = [");
      fallback.forEach(function(fallback, idx, array) {
        var fallbackParts = fallback.split(' ');
        if (fallbackParts.length > 1) {
          var matchLine = "{match: new RegExp('"+escapeRegExp(fallbackParts[0])+"'), fallback:'"+fallbackParts[1]+"'}";
          lines.push(createArrayLine(matchLine, idx, array.length));
        }
      });
      lines.push("];");
    }

    lines.push("self.addEventListener('install', function(event) {");
    lines.push("  var urlsToPrefetch = [");
    getFilesRecursively(srcDir, [ "**/*" ]).forEach(function (file, idx, array) {
      var srcFile = path.join(srcDir, file);
      var stat = fs.lstatSync(srcFile);

      if (!stat.isFile() && !stat.isSymbolicLink())
        return;
      lines.push(createArrayLine("'"+file+"'", idx, array.length));
    });
    lines.push("];");

    includePaths.forEach(function (file, idx, array) {
      lines.push("urlsToPrefetch.push('"+file+"');");
    });

    //ServiceWorker code derived from examples at https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker
    addDebugLine("'Handling install event. Resources to pre-fetch:', urlsToPrefetch", debug, lines);
    lines.push("  event.waitUntil(");
    lines.push("    caches.open(CURRENT_CACHES['prefetch']).then(function(cache) {");
    lines.push("      return cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {");
    lines.push("        return new Request(urlToPrefetch, {mode: 'no-cors'});");
    lines.push("      })).then(function() {");
    addDebugLine("'All resources have been fetched and cached.'", debug, lines);
    lines.push("      });");
    lines.push("    }).catch(function(error) {");
    lines.push("      console.error('Pre-fetching failed:', error);");
    lines.push("    })");
    lines.push("  );");
    lines.push("});");
    lines.push("self.addEventListener('activate', function(event) {");
    lines.push("  // Delete all caches that aren't named in CURRENT_CACHES.");
    lines.push("  // While there is only one cache in this example, the same logic will handle the case where");
    lines.push("  // there are multiple versioned caches.");
    lines.push("  var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {");
    lines.push("    return CURRENT_CACHES[key];");
    lines.push("  });");

    lines.push("  event.waitUntil(");
    lines.push("    caches.keys().then(function(cacheNames) {");
    lines.push("      return Promise.all(");
    lines.push("        cacheNames.map(function(cacheName) {");
    lines.push("          if (expectedCacheNames.indexOf(cacheName) === -1) {");
    lines.push("            // If this cache name isn't present in the array of \"expected\" cache names, then delete it.");
    addDebugLine("'Deleting out of date cache:', cacheName", debug, lines);
    lines.push("            return caches.delete(cacheName);");
    lines.push("          }");
    lines.push("        })");
    lines.push("      );");
    lines.push("    })");
    lines.push("  );");
    lines.push("});");

    lines.push("self.addEventListener('fetch', function(event) {");
    // This is temporarily needed because of https://bugzilla.mozilla.org/show_bug.cgi?id=1203359
    lines.push("  if (new URL(event.request.url).origin !== new URL(self.location).origin) {");
    lines.push("    return;");
    lines.push("  }");

    addDebugLine("'Handling fetch event for', event.request.url", debug, lines);
    if (dynamicCache.length) {
      lines.push("  if(dynamicCacheResponse(event)) {");
      addDebugLine("'Found dynamic cache response:', event.request.url", debug, lines);
      lines.push("    return;");
      lines.push("  }");
    }
    addDebugLine("'Looking in caches for:', event.request.url", debug, lines);
    lines.push("  event.respondWith(");

    lines.push("    // caches.match() will look for a cache entry in all of the caches available to the service worker.");
    lines.push("    // It's an alternative to first opening a specific named cache and then matching on that.");
    lines.push("    caches.match(event.request).then(function(response) {");
    lines.push("      if (response) {");
    addDebugLine("'Found response in cache:', response", debug, lines);
    if (fallback.length) {
      lines.push("      if (response.status >= 400) {");
      addDebugLine("'Got error status, checking for fallback. Response status was:', response.status", debug, lines);
      lines.push("        return fallbackResponse(event.request, response);");
      lines.push("      }");
    }
    lines.push("        return response;");
    lines.push("      }");
    addDebugLine("'No response found in cache. About to fetch from network:'+event.request", debug, lines);
    lines.push("      // event.request will always have the proper mode set ('cors, 'no-cors', etc.) so we don't");
    lines.push("      // have to hardcode 'no-cors' like we do when fetch()ing in the install handler.");
    lines.push("      return fetch(event.request).then(function(response) {");
    addDebugLine("'Response from network is:', response", debug, lines);
    lines.push("        return response;");
    lines.push("      }).catch(function(error) {");
    lines.push("        // This catch() will handle exceptions thrown from the fetch() operation.");
    lines.push("        // Note that a HTTP error response (e.g. 404) will NOT trigger an exception.");
    lines.push("        // It will return a normal response object that has the appropriate error code set.");
    if (fallback.length) {
      addDebugLine("'Got error, checking for fallback.  Error was:', error", debug, lines);
      lines.push("        return fallbackResponse(event.request, response);");
    } else {
      lines.push("        console.error('Fetching failed:', error);");
      lines.push("        throw error;");
    }
    lines.push("      });");
    lines.push("    })");
    lines.push("  );");
    lines.push("});");
    if (dynamicCache.length) {
      lines.push("function dynamicCacheResponse(event) {");
      lines.push("  var matchingUrls = DYNAMIC_URLS.filter(function(dynamicURL) {");
      lines.push("    return (event.request.url.match(dynamicURL) !== null);");
      lines.push("  });");
      lines.push("  if (matchingUrls.length) {");
      addDebugLine("'Pulling dynamic url: '+event.request.url+' from network and adding to cache.'", debug, lines);
      lines.push("    event.respondWith(");
      lines.push("      caches.open('dynamic-cache-v"+cacheVersion+"').then(function(cache) {");
      lines.push("        return fetch(event.request).then(function(response) {");
      addDebugLine("'Got response for dynamic url: '+event.request.url+' now adding to cache.', response", debug, lines);
      lines.push("          if (response.status >= 400) {");
      addDebugLine("'Got response error for dynamic url, try to pull from cache: ',response.status", debug, lines);
      lines.push("          caches.match(event.request).then(function(response) {");
      lines.push("            return response;");
      lines.push("          });");
      lines.push("          } else {");
      lines.push("            cache.put(event.request, response.clone());");
      lines.push("            return response;");
      lines.push("          }");
      lines.push("        }).catch(function(error) {");
      addDebugLine("'Got error for dynamic url, try to pull from cache: ',error", debug, lines);
      lines.push("          caches.match(event.request).then(function(response) {");
      lines.push("            return response;");
      lines.push("          });");
      lines.push("        });");
      lines.push("      })");
      lines.push("    );");
      lines.push("    return true;");
      lines.push("  } else {");
      lines.push("    return false;");
      lines.push("  }");
      lines.push("}");
    }
    if (fallback.length) {
      lines.push("function fallbackResponse(request, response) {");
      addDebugLine("'Looking for fallback for:', request.url", debug, lines);
      lines.push("  var matchingUrls =FALLBACK_URLS.filter(function(fallbackURL) {");
      addDebugLine("'Checking for fallback match with:', fallbackURL", debug, lines);
      lines.push("    return (request.url.match(fallbackURL.match) !== null);");
      lines.push("  });");
      lines.push("  if (matchingUrls.length) {");
      addDebugLine("'Fetching from fallback url: '+ matchingUrls[0].fallback +'for url: '+event.request.url", debug, lines);
      lines.push("    return caches.match(matchingUrls[0].fallback);");
      lines.push("  } else {");
      lines.push("    return response; ");
      lines.push("  } ");
      lines.push("}");
    }
    fs.writeFileSync(path.join(destDir, serviceWorkerFile), lines.join("\n"));
    if (addPolyfill) {
      fs.createReadStream(swCachePolyFillFile).pipe(fs.createWriteStream(path.join(destDir, polyFillLocation)));
    }
  });
};

BroccoliServiceWorker.prototype.addExternalFile = function(file) {
  this.externalFiles.push(file);
};

function addDebugLine(consoleText, debug, lines) {
  if (debug) {
    lines.push("console.log("+consoleText+");");
  }
}

function createArrayLine(line, idx, arrayLength) {
  var arrayLine = line;
  if ((idx+1) < arrayLength) {
    arrayLine += ",";
  }
  return arrayLine;
}

//From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string){
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function getFilesRecursively(dir, globPatterns) {
  return helpers.multiGlob(globPatterns, { cwd: dir });
}

module.exports = BroccoliServiceWorker;
<commit_msg>Add a 'custom' cache (that apps can fully control)<commit_after>var fs = require("fs");
var path = require('path');
var brocWriter = require("broccoli-writer");
var helpers = require("broccoli-kitchen-sink-helpers");
var funnel = require('broccoli-funnel');
var swCachePolyFillFile =  require.resolve('serviceworker-cache-polyfill');

var BroccoliServiceWorker = function BroccoliServiceWorker(inTree, options) {
  if (!(this instanceof BroccoliServiceWorker)) {
    return new BroccoliServiceWorker(inTree, options);
  }
  this.inTree = inTree;
  options = options || {};
  this.addPolyfill = options.addPolyfill || true;
  this.debug = options.debug || true;
  this.dynamicCache = options.dynamicCache || [];
  this.excludePaths = options.excludePaths || ['tests/*'];
  this.fallback = options.fallback || [];
  this.includePaths = options.includePaths || [];
  this.polyFillLocation = options.polyFillLocation || 'serviceworker-cache-polyfill.js';
  this.serviceWorkerFile = options.serviceWorkerFile || "service-worker.js";
};

BroccoliServiceWorker.prototype = Object.create(brocWriter.prototype);
BroccoliServiceWorker.prototype.constructor = BroccoliServiceWorker;

BroccoliServiceWorker.prototype.write = function(readTree, destDir) {
  var addPolyfill = this.addPolyfill;
  var debug = this.debug;
  var dynamicCache = this.dynamicCache;
  var fallback = this.fallback;
  var includePaths = this.includePaths;
  var polyFillLocation = this.polyFillLocation;
  var serviceWorkerFile = this.serviceWorkerFile;
  var serviceWorkerTree = funnel(this.inTree, {
    exclude: this.excludePaths
  });

  return readTree(serviceWorkerTree).then(function (srcDir) {
    var cacheVersion = (new Date()).getTime();
    var lines = [];
    if (addPolyfill) {
      lines.push("importScripts('"+polyFillLocation+"');");
    }
    lines.push("var CACHE_VERSION = '" + cacheVersion + "';");
    lines.push("var CURRENT_CACHES = {");
    lines.push("  prefetch: 'prefetch-cache-v' + CACHE_VERSION");
    lines.push("  custom: 'custom'");
    lines.push("};");
    if (dynamicCache.length) {
      lines.push("CURRENT_CACHES['dynamic'] =  'dynamic-cache-v' + CACHE_VERSION;");
    }
    if (dynamicCache.length) {
      lines.push("var DYNAMIC_URLS = [");
      dynamicCache.forEach(function(cacheURL, idx, array) {
        lines.push(createArrayLine("new RegExp('"+escapeRegExp(cacheURL)+"')", idx, array.length));
      });
      lines.push("];");
    }
    if (fallback.length) {
      lines.push("var FALLBACK_URLS = [");
      fallback.forEach(function(fallback, idx, array) {
        var fallbackParts = fallback.split(' ');
        if (fallbackParts.length > 1) {
          var matchLine = "{match: new RegExp('"+escapeRegExp(fallbackParts[0])+"'), fallback:'"+fallbackParts[1]+"'}";
          lines.push(createArrayLine(matchLine, idx, array.length));
        }
      });
      lines.push("];");
    }

    lines.push("self.addEventListener('install', function(event) {");
    lines.push("  var urlsToPrefetch = [");
    getFilesRecursively(srcDir, [ "**/*" ]).forEach(function (file, idx, array) {
      var srcFile = path.join(srcDir, file);
      var stat = fs.lstatSync(srcFile);

      if (!stat.isFile() && !stat.isSymbolicLink())
        return;
      lines.push(createArrayLine("'"+file+"'", idx, array.length));
    });
    lines.push("];");

    includePaths.forEach(function (file, idx, array) {
      lines.push("urlsToPrefetch.push('"+file+"');");
    });

    //ServiceWorker code derived from examples at https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker
    addDebugLine("'Handling install event. Resources to pre-fetch:', urlsToPrefetch", debug, lines);
    lines.push("  event.waitUntil(");
    lines.push("    caches.open(CURRENT_CACHES['prefetch']).then(function(cache) {");
    lines.push("      return cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {");
    lines.push("        return new Request(urlToPrefetch, {mode: 'no-cors'});");
    lines.push("      })).then(function() {");
    addDebugLine("'All resources have been fetched and cached.'", debug, lines);
    lines.push("      });");
    lines.push("    }).catch(function(error) {");
    lines.push("      console.error('Pre-fetching failed:', error);");
    lines.push("    })");
    lines.push("  );");
    lines.push("});");
    lines.push("self.addEventListener('activate', function(event) {");
    lines.push("  // Delete all caches that aren't named in CURRENT_CACHES.");
    lines.push("  // While there is only one cache in this example, the same logic will handle the case where");
    lines.push("  // there are multiple versioned caches.");
    lines.push("  var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {");
    lines.push("    return CURRENT_CACHES[key];");
    lines.push("  });");

    lines.push("  event.waitUntil(");
    lines.push("    caches.keys().then(function(cacheNames) {");
    lines.push("      return Promise.all(");
    lines.push("        cacheNames.map(function(cacheName) {");
    lines.push("          if (expectedCacheNames.indexOf(cacheName) === -1) {");
    lines.push("            // If this cache name isn't present in the array of \"expected\" cache names, then delete it.");
    addDebugLine("'Deleting out of date cache:', cacheName", debug, lines);
    lines.push("            return caches.delete(cacheName);");
    lines.push("          }");
    lines.push("        })");
    lines.push("      );");
    lines.push("    })");
    lines.push("  );");
    lines.push("});");

    lines.push("self.addEventListener('fetch', function(event) {");
    // This is temporarily needed because of https://bugzilla.mozilla.org/show_bug.cgi?id=1203359
    lines.push("  if (new URL(event.request.url).origin !== new URL(self.location).origin) {");
    lines.push("    return;");
    lines.push("  }");

    addDebugLine("'Handling fetch event for', event.request.url", debug, lines);
    if (dynamicCache.length) {
      lines.push("  if(dynamicCacheResponse(event)) {");
      addDebugLine("'Found dynamic cache response:', event.request.url", debug, lines);
      lines.push("    return;");
      lines.push("  }");
    }
    addDebugLine("'Looking in caches for:', event.request.url", debug, lines);
    lines.push("  event.respondWith(");

    lines.push("    // caches.match() will look for a cache entry in all of the caches available to the service worker.");
    lines.push("    // It's an alternative to first opening a specific named cache and then matching on that.");
    lines.push("    caches.match(event.request).then(function(response) {");
    lines.push("      if (response) {");
    addDebugLine("'Found response in cache:', response", debug, lines);
    if (fallback.length) {
      lines.push("      if (response.status >= 400) {");
      addDebugLine("'Got error status, checking for fallback. Response status was:', response.status", debug, lines);
      lines.push("        return fallbackResponse(event.request, response);");
      lines.push("      }");
    }
    lines.push("        return response;");
    lines.push("      }");
    addDebugLine("'No response found in cache. About to fetch from network:'+event.request", debug, lines);
    lines.push("      // event.request will always have the proper mode set ('cors, 'no-cors', etc.) so we don't");
    lines.push("      // have to hardcode 'no-cors' like we do when fetch()ing in the install handler.");
    lines.push("      return fetch(event.request).then(function(response) {");
    addDebugLine("'Response from network is:', response", debug, lines);
    lines.push("        return response;");
    lines.push("      }).catch(function(error) {");
    lines.push("        // This catch() will handle exceptions thrown from the fetch() operation.");
    lines.push("        // Note that a HTTP error response (e.g. 404) will NOT trigger an exception.");
    lines.push("        // It will return a normal response object that has the appropriate error code set.");
    if (fallback.length) {
      addDebugLine("'Got error, checking for fallback.  Error was:', error", debug, lines);
      lines.push("        return fallbackResponse(event.request, response);");
    } else {
      lines.push("        console.error('Fetching failed:', error);");
      lines.push("        throw error;");
    }
    lines.push("      });");
    lines.push("    })");
    lines.push("  );");
    lines.push("});");
    if (dynamicCache.length) {
      lines.push("function dynamicCacheResponse(event) {");
      lines.push("  var matchingUrls = DYNAMIC_URLS.filter(function(dynamicURL) {");
      lines.push("    return (event.request.url.match(dynamicURL) !== null);");
      lines.push("  });");
      lines.push("  if (matchingUrls.length) {");
      addDebugLine("'Pulling dynamic url: '+event.request.url+' from network and adding to cache.'", debug, lines);
      lines.push("    event.respondWith(");
      lines.push("      caches.open('dynamic-cache-v"+cacheVersion+"').then(function(cache) {");
      lines.push("        return fetch(event.request).then(function(response) {");
      addDebugLine("'Got response for dynamic url: '+event.request.url+' now adding to cache.', response", debug, lines);
      lines.push("          if (response.status >= 400) {");
      addDebugLine("'Got response error for dynamic url, try to pull from cache: ',response.status", debug, lines);
      lines.push("          caches.match(event.request).then(function(response) {");
      lines.push("            return response;");
      lines.push("          });");
      lines.push("          } else {");
      lines.push("            cache.put(event.request, response.clone());");
      lines.push("            return response;");
      lines.push("          }");
      lines.push("        }).catch(function(error) {");
      addDebugLine("'Got error for dynamic url, try to pull from cache: ',error", debug, lines);
      lines.push("          caches.match(event.request).then(function(response) {");
      lines.push("            return response;");
      lines.push("          });");
      lines.push("        });");
      lines.push("      })");
      lines.push("    );");
      lines.push("    return true;");
      lines.push("  } else {");
      lines.push("    return false;");
      lines.push("  }");
      lines.push("}");
    }
    if (fallback.length) {
      lines.push("function fallbackResponse(request, response) {");
      addDebugLine("'Looking for fallback for:', request.url", debug, lines);
      lines.push("  var matchingUrls =FALLBACK_URLS.filter(function(fallbackURL) {");
      addDebugLine("'Checking for fallback match with:', fallbackURL", debug, lines);
      lines.push("    return (request.url.match(fallbackURL.match) !== null);");
      lines.push("  });");
      lines.push("  if (matchingUrls.length) {");
      addDebugLine("'Fetching from fallback url: '+ matchingUrls[0].fallback +'for url: '+event.request.url", debug, lines);
      lines.push("    return caches.match(matchingUrls[0].fallback);");
      lines.push("  } else {");
      lines.push("    return response; ");
      lines.push("  } ");
      lines.push("}");
    }
    fs.writeFileSync(path.join(destDir, serviceWorkerFile), lines.join("\n"));
    if (addPolyfill) {
      fs.createReadStream(swCachePolyFillFile).pipe(fs.createWriteStream(path.join(destDir, polyFillLocation)));
    }
  });
};

BroccoliServiceWorker.prototype.addExternalFile = function(file) {
  this.externalFiles.push(file);
};

function addDebugLine(consoleText, debug, lines) {
  if (debug) {
    lines.push("console.log("+consoleText+");");
  }
}

function createArrayLine(line, idx, arrayLength) {
  var arrayLine = line;
  if ((idx+1) < arrayLength) {
    arrayLine += ",";
  }
  return arrayLine;
}

//From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string){
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function getFilesRecursively(dir, globPatterns) {
  return helpers.multiGlob(globPatterns, { cwd: dir });
}

module.exports = BroccoliServiceWorker;
<|endoftext|>