<commit_before>module.exports = function (RED) {
    "use strict";

    var Milight = require('node-milight-promise');
    var packageFile = require('./package.json');
    var Color = require('tinycolor2');

    function node(config) {

        RED.nodes.createNode(this, config);
        var node = this;

        // backwards compatibility with previous versions
        if (config.bridgetype == null || config.bridgetype === '') {
            config.bridgetype = 'legacy'
        }
        var light = new Milight.MilightController({
                ip: config.ip,
                delayBetweenCommands: (config.bridgetype !== 'v6') ? 200 : 100,
                commandRepeat: 1,
                type: config.bridgetype,
                broadcastMode: config.broadcast
            }),
            zone = Number(config.zone),
            bulb = config.bulbtype;

        if (config.bridgetype === 'v6') {
            var commands = Milight.commandsV6[bulb];
        }
        else if (bulb === 'white') {
            var commands = Milight.commands[bulb];
        }
        else {
            var commands = Milight.commands2[bulb];
        }

        this.on('input', function (msg) {
            function argsHelper(vargs) {
                var argsArray = [].slice.call(arguments);
                if (config.bridgetype === 'v6' && bulb !== 'bridge') {
                    return [zone].concat(argsArray);
                }
                return argsArray;
            }
            function getSelectedObjectValues(sourceObject, keys) {
                var values = [];
                keys.forEach(function(key) { values.push(sourceObject[key]) });
                return values;
            }

            light.ready().then(function () {
                var command = msg.command ? msg.command : msg.topic;
                if (commands == null) {
                    node.error("Selected combination of bridge type and bulb type is not supported");
                    return;
                }
                if (bulb !== 'white') {
                    switch (msg.payload) {
                        case 'off':
                            light.sendCommands(commands.off(zone));
                            break;
                        case 'on':
                            light.sendCommands(commands.on(zone));
                            break;
                        case 'disco':
                            light.sendCommands(commands.on(zone));
                            for (var x = 0; x < 256; x += 5) {
                                light.sendCommands(
                                    commands.hue.apply(commands, argsHelper(x)));
                                light.pause(100);
                            }
                            break;
                        case 'mode':
                            light.sendCommands(commands.on(zone), commands.effectModeNext(zone));
                            break;
                        case 'speed_up':
                            light.sendCommands(commands.on(zone), commands.effectSpeedUp(zone));
                            break;
                        case 'speed_down':
                            light.sendCommands(commands.on(zone), commands.effectSpeedDown(zone));
                            break;
                        case 'white':
                            light.sendCommands(commands.on(zone), commands.whiteMode(zone));
                            break;
                        case 'night':
                            // nightMode command needs to be sent twice with some bulb types
                            light.sendCommands(commands.nightMode(zone), commands.nightMode(zone));
                            break;
                        default:
                            var value = Number(msg.payload);
                            if (command === 'rgb') {
                                var color = new Color(msg.payload);
                                if (color.isValid()) {
                                    var args = argsHelper.apply(
                                        node,
                                        getSelectedObjectValues(color.toRgb(), ['r', 'g', 'b']));
                                    light.sendCommands(commands.on(zone),
                                        commands.rgb.apply(commands, args));
                                }
                                else {
                                    throw(new Error("Invalid color value: " + msg.payload))
                                }
                            }
                            else if (!isNaN(value)) {
                                if (command === 'brightness')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.brightness.apply(commands, argsHelper(value)));
                                else if (command === 'color')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.hue.apply(commands, argsHelper(value, true)));
                                else if (command === 'saturation' && bulb === 'fullColor')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.saturation(zone, value, true));
                                else if (command === 'temperature' && bulb === 'fullColor')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.whiteTemperature(zone, value));
                            }
                            break;
                    }
                } else {
                    switch (msg.payload) {
                        case 'off':
                            light.sendCommands(commands.off(zone));
                            break;
                        case 'on':
                            light.sendCommands(commands.on(zone));
                            break;
                        case 'bright_up':
                            light.sendCommands(commands.brightUp(zone));
                            break;
                        case 'bright_down':
                            light.sendCommands(commands.brightDown(zone));
                            break;
                        case 'cooler':
                            light.sendCommands(commands.cooler(zone));
                            break;
                        case 'warmer':
                            light.sendCommands(commands.warmer(zone));
                            break;
                        case 'bright_max':
                            light.sendCommands(commands.maxBright(zone));
                            break;
                        case 'night':
                            light.sendCommands(commands.nightMode(zone));
                            break;
                    }
                }
            }).catch(function (error) {
                node.error('Milight error: ' + error);
            });
        });

        this.on('close', function (done) {
            light.close()
                .catch(function (error) {
                    // just log the error as a normal log message
                    // as it is safe to ignore the error at this point
                    node.log(error)
                })
                .finally(function () {
                    done()
                });
        });
    }

    RED.nodes.registerType("MiLight", node);
    RED.log.info(packageFile.name + '@' + packageFile.version + ' started');
};<commit_msg>add ability to trigger specific modes<commit_after>module.exports = function (RED) {
    "use strict";

    var Milight = require('node-milight-promise');
    var packageFile = require('./package.json');
    var Color = require('tinycolor2');

    function node(config) {

        RED.nodes.createNode(this, config);
        var node = this;

        // backwards compatibility with previous versions
        if (config.bridgetype == null || config.bridgetype === '') {
            config.bridgetype = 'legacy'
        }
        var light = new Milight.MilightController({
                ip: config.ip,
                delayBetweenCommands: (config.bridgetype !== 'v6') ? 200 : 100,
                commandRepeat: 1,
                type: config.bridgetype,
                broadcastMode: config.broadcast
            }),
            zone = Number(config.zone),
            bulb = config.bulbtype;

        if (config.bridgetype === 'v6') {
            var commands = Milight.commandsV6[bulb];
        }
        else if (bulb === 'white') {
            var commands = Milight.commands[bulb];
        }
        else {
            var commands = Milight.commands2[bulb];
        }

        this.on('input', function (msg) {
            function argsHelper(vargs) {
                var argsArray = [].slice.call(arguments);
                if (config.bridgetype === 'v6' && bulb !== 'bridge') {
                    return [zone].concat(argsArray);
                }
                return argsArray;
            }
            function getSelectedObjectValues(sourceObject, keys) {
                var values = [];
                keys.forEach(function(key) { values.push(sourceObject[key]) });
                return values;
            }

            light.ready().then(function () {
                var command = msg.command ? msg.command : msg.topic;
                if (commands == null) {
                    node.error("Selected combination of bridge type and bulb type is not supported");
                    return;
                }
                if (bulb !== 'white') {
                    switch (msg.payload) {
                        case 'off':
                            light.sendCommands(commands.off(zone));
                            break;
                        case 'on':
                            light.sendCommands(commands.on(zone));
                            break;
                        case 'disco':
                            light.sendCommands(commands.on(zone));
                            for (var x = 0; x < 256; x += 5) {
                                light.sendCommands(
                                    commands.hue.apply(commands, argsHelper(x)));
                                light.pause(100);
                            }
                            break;
                        case 'mode':
                            light.sendCommands(commands.on(zone), commands.effectModeNext(zone));
                            break;
                        case 'speed_up':
                            light.sendCommands(commands.on(zone), commands.effectSpeedUp(zone));
                            break;
                        case 'speed_down':
                            light.sendCommands(commands.on(zone), commands.effectSpeedDown(zone));
                            break;
                        case 'white':
                            light.sendCommands(commands.on(zone), commands.whiteMode(zone));
                            break;
                        case 'night':
                            // nightMode command needs to be sent twice with some bulb types
                            light.sendCommands(commands.nightMode(zone), commands.nightMode(zone));
                            break;
                        default:
                            var value = Number(msg.payload);
                            if (command === 'rgb') {
                                var color = new Color(msg.payload);
                                if (color.isValid()) {
                                    var args = argsHelper.apply(
                                        node,
                                        getSelectedObjectValues(color.toRgb(), ['r', 'g', 'b']));
                                    light.sendCommands(commands.on(zone),
                                        commands.rgb.apply(commands, args));
                                }
                                else {
                                    throw(new Error("Invalid color value: " + msg.payload))
                                }
                            }
                            else if (!isNaN(value)) {
                                if (command === 'brightness')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.brightness.apply(commands, argsHelper(value)));
                                else if (command === 'color')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.hue.apply(commands, argsHelper(value, true)));
                                else if (command === 'saturation' && bulb === 'fullColor')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.saturation(zone, value, true));
                                else if (command === 'temperature' && bulb === 'fullColor')
                                    light.sendCommands(
                                        commands.on(zone),
                                        commands.whiteTemperature(zone, value));
                                else if (command === 'mode')
                                        light.sendCommands(commands.on(zone), commands.effectMode(zone, value));
                            }
                            break;
                    }
                } else {
                    switch (msg.payload) {
                        case 'off':
                            light.sendCommands(commands.off(zone));
                            break;
                        case 'on':
                            light.sendCommands(commands.on(zone));
                            break;
                        case 'bright_up':
                            light.sendCommands(commands.brightUp(zone));
                            break;
                        case 'bright_down':
                            light.sendCommands(commands.brightDown(zone));
                            break;
                        case 'cooler':
                            light.sendCommands(commands.cooler(zone));
                            break;
                        case 'warmer':
                            light.sendCommands(commands.warmer(zone));
                            break;
                        case 'bright_max':
                            light.sendCommands(commands.maxBright(zone));
                            break;
                        case 'night':
                            light.sendCommands(commands.nightMode(zone));
                            break;
                    }
                }
            }).catch(function (error) {
                node.error('Milight error: ' + error);
            });
        });

        this.on('close', function (done) {
            light.close()
                .catch(function (error) {
                    // just log the error as a normal log message
                    // as it is safe to ignore the error at this point
                    node.log(error)
                })
                .finally(function () {
                    done()
                });
        });
    }

    RED.nodes.registerType("MiLight", node);
    RED.log.info(packageFile.name + '@' + packageFile.version + ' started');
};
<|endoftext|>


<commit_before>define([], function() {
  function Matrix(n) {
    // Square matrix n x n
    this.rows = [];

    // Initialize to zeroes
    for (var i = 0; i < n; ++i) {
      var row = [];
      for (var j = 0; j < n; ++j) {
        row.push(0);
      }
      this.rows.push(row);
    }
  }

  Matrix.prototype.solve = function() {
    // Solve the matrix, returning a list of length n containing the solutions
    // (in the original order they were entered)
    // This *will* modify the matrix
    // Return false if the matrix is not solvable (a bad time)

    // Augment the matrix such that we can solve it
    this.rows.forEach(function(row) {
      row.push(1);
    });
    this.augmented = true;

    // We may end up swapping rows around, so keep a map of what rows are where
    this.rowMap = [];
    for (var i = 0; i < this.rows.length; ++i) {
      this.rowMap.push(i);
    }

    // Now row-reduce the augmented matrix using some basic strategy
    for (var i = 0; i < this.rows.length; ++i) {
      // Unable to create a 1 in this column
      if (!this.reduceColumn(i)) return false;
    }

    var ans = [];
    for (var i = 0; i < this.rows.length; ++i) {
      // Put the answer into the correct return slot
      ans[this.rowMap[i]] = this.rows[i].pop();
    }
    return ans;
  };

  Matrix.prototype.reduceColumn = function(n) {
    // Reduce the specified column
    if (this.rows[n][n] === 0) {
      // We have a zero in the spot we are supposed to be using to reduce
      // Find a new row, or return false if no such row exists
      var newRow = n;
      for (var newRow = n; newRow < this.rows.length; ++newRow) {
        // Found one
        if (this.rows[newRow][newRow] !== 0) break;
      }
      // None found
      if (newRow === this.rows.length) return false;

      // Swap the rows
      var tmp = this.rowMap[newRow];
      this.rowMap[newRow] = this.rows[n];
      this.rowMap[n] = tmp;

      tmp = this.rows[n];
      this.rows[n] = this.rows[newRow];
      this.rows[newRow] = tmp;

      n = newRow;
    }

    this.multiplyRow(this.rows[n], 1 / this.rows[n][n]);
    // Exact
    this.rows[n][n] = 1;

    // Create zeros in lower rows
    for (var i = 0; i < this.rows.length; ++i) {
      if (i == n) continue;
      // Already have a zero here
      if (this.rows[i][n] === 0) continue;
      var mult = this.rows[i][n];// / this.rows[n][n];
      // Subtract to create a 0
      this.addToRow(this.rows[i], this.rows[n], -mult);
      // Exact
      this.rows[i][n] = 0;
    }

    return true;
  };

  Matrix.prototype.multiplyRow = function(row, mult) {
    for (var i = 0 ; i < row.length; ++i) {
      if (row[i]) row[i] *= mult;
    }
  };

  Matrix.prototype.addToRow = function(dest, source, mult) {
    // Add (mult * source) to dest row
    for (var i = 0; i < dest.length; ++i) {
      dest[i] += mult * source[i];
    }
  };

  Matrix.prototype.toString = function() {
    var strs = [''];

    function toString(n) {
      // Two decimal places (or none for whole numbers)
      return (n % 1 === 0) ? n : n.toFixed(2);
    }

    // Collect the maximum length of a number
    var maxLen = 0;
    for (var i = 0; i < this.rows.length; ++i) {
      for (var j = 0; j < this.rows[i].length; ++j) {
        maxLen = Math.max(maxLen, toString(this.rows[i][j]).length);
      }
    }

    for (var i = 0; i < this.rows.length; ++i) {
      var numStrs = [];
      for (var j = 0; j < this.rows[i].length; ++j) {
        // Add to the string
        var n = toString(this.rows[i][j]);
        var s = '';
        // Left pad
        for (var k = n.length; k < maxLen; ++k) s += ' ';
        numStrs.push(s + n);
      }
      strs.push('| ' + numStrs.join('  ') + ' |');
    }

    return strs.join('\n');
  };

  return Matrix;
});
<commit_msg>Update matrix<commit_after>define([], function() {
  function Matrix(n) {
    // Square matrix n x n
    this.rows = [];

    // Initialize to zeroes
    for (var i = 0; i < n; ++i) {
      var row = [];
      for (var j = 0; j < n; ++j) {
        row.push(0);
      }
      this.rows.push(row);
    }
  }

  Matrix.prototype.solve = function() {
    // Solve the matrix, returning a list of length n containing the solutions
    // (in the original order they were entered)
    // This *will* modify the matrix
    // Return false if the matrix is not solvable (a bad time)

    // Augment the matrix such that we can solve it
    this.rows.forEach(function(row) {
      row.push(1);
    });
    this.augmented = true;

    // Now row-reduce the augmented matrix using some basic strategy
    for (var i = 0; i < this.rows.length; ++i) {
      // Unable to create a 1 in this column
      if (!this.reduceColumn(i)) return false;
    }

    var ans = [];
    for (var i = 0; i < this.rows.length; ++i) {
      // Put the answer into the correct return slot
      ans.push(this.rows[i].pop());
    }
    return ans;
  };

  Matrix.prototype.reduceColumn = function(n) {
    // Reduce the specified column
    if (this.rows[n][n] === 0) {
      // We have a zero in the spot we are supposed to be using to reduce
      // Find a new row, or return false if no such row exists
      for (var newRow = n + 1; newRow < this.rows.length; ++newRow) {
        // Found one
        if (this.rows[newRow][newRow] !== 0) break;
      }
      // None found
      if (newRow === this.rows.length) return false;

      // Swap the rows
      tmp = this.rows[n];
      this.rows[n] = this.rows[newRow];
      this.rows[newRow] = tmp;
    }

    this.multiplyRow(this.rows[n], 1 / this.rows[n][n]);
    // Exact
    this.rows[n][n] = 1;

    // Create zeros in lower rows
    for (var i = 0; i < this.rows.length; ++i) {
      if (i == n) continue;
      // Already have a zero here
      if (this.rows[i][n] === 0) continue;
      var mult = this.rows[i][n];// / this.rows[n][n];
      // Subtract to create a 0
      this.addToRow(this.rows[i], this.rows[n], -mult);
      // Exact
      this.rows[i][n] = 0;
    }

    return true;
  };

  Matrix.prototype.multiplyRow = function(row, mult) {
    for (var i = 0 ; i < row.length; ++i) {
      if (row[i]) row[i] *= mult;
    }
  };

  Matrix.prototype.addToRow = function(dest, source, mult) {
    // Add (mult * source) to dest row
    for (var i = 0; i < dest.length; ++i) {
      dest[i] += mult * source[i];
    }
  };

  Matrix.prototype.toString = function() {
    var strs = [''];

    function toString(n) {
      // Two decimal places (or none for whole numbers)
      return (n % 1 === 0) ? n : n.toFixed(2);
    }

    // Collect the maximum length of a number
    var maxLen = 0;
    for (var i = 0; i < this.rows.length; ++i) {
      for (var j = 0; j < this.rows[i].length; ++j) {
        maxLen = Math.max(maxLen, toString(this.rows[i][j]).length);
      }
    }

    for (var i = 0; i < this.rows.length; ++i) {
      var numStrs = [];
      for (var j = 0; j < this.rows[i].length; ++j) {
        // Add to the string
        var n = toString(this.rows[i][j]);
        var s = '';
        // Left pad
        for (var k = n.length; k < maxLen; ++k) s += ' ';
        numStrs.push(s + n);
      }
      strs.push('| ' + numStrs.join('  ') + ' |');
    }

    return strs.join('\n');
  };

  return Matrix;
});
<|endoftext|>


<commit_before>var LESSONS = {
	'count': 62,
	'otherKeysLessonsStart': 30,
	'numberRowLessonsStart': 40,
	'prefix' : 'Programmer Dvorak typing tutorial',
	//Woods' lessons
	1: 'Lesson 1: Introducing U and H: Home row, Index fingers',
	2: 'Lesson 2: Introducing E and T: Home row, Second fingers',
	3: 'Lesson 3: Comprehensive: E, H, T, U',
	4: 'Lesson 4: Introducing O and N: Home row, third fingers',
	5: 'Lesson 5: Comprehensive, including O and N',
	6: 'Lesson 6: Introducing A and S: Home row, fourth fingers',
	7: 'Lesson 7: Comprehensive, including A and S',
	8: 'Lesson 8: Introducing I and D: Index finger stretching in the home row',
	9: 'Lesson 9: Comprehensive, including I and D (entire home row)',
	10: 'Lesson 10: Introducing P and G: First fingers reaching up',
	11: 'Lesson 11: Comprehensive, including P and G',
	12: 'Lesson 12: Introducing . and C: Second fingers reaching up',
	13: 'Lesson 13: Comprehensive, including . and C',
	14: 'Lesson 14: Introducing , and R: Third fingers reaching up',
	15: 'Lesson 15: Comprehensive, including , and R',
	16: 'Lesson 16: Introducing \' and L: Fourth fingers',
	17: 'Lesson 17: Comprehensive, including \' and L',
	18: 'Lesson 18: Introducing Y and F: Index fingers stretching up',
	19: 'Lesson 19: Comprehensive, including Y and F (full upper/home rows)',
	20: 'Lesson 20: Introducing K and M: Index fingers reaching down',
	21: 'Lesson 21: Comprehensive, including K and M',
	22: 'Lesson 22: Introducing J and W: Second fingers reaching down',
	23: 'Lesson 23: Comprehensive, including J and W',
	24: 'Lesson 24: Introducing Q and V: Third fingers reaching down',
	25: 'Lesson 25: Comprehensive, including Q and V',
	26: 'Lesson 26: Introducing ; and Z: Fourth fingers',
	27: 'Lesson 27: Comprehensive, including ; and Z',
	28: 'Lesson 28: Introducing X and B: Index fingers stretching down',
	29: 'Lesson 29: Comprehensive, including X and B',
	//other keys
	30: 'Lesson 30: Introducing < and >: Right third finger, right second finger',
	31: 'Lesson 31: Comprehensive, including < and >',
	32: 'Lesson 32: Introducing - and _: Right forth finger',
	33: 'Lesson 33: Comprehensive, including - and _',
	34: 'Lesson 34: Introducing / and ?: Right forth finger',
	35: 'Lesson 35: Comprehensive, including / and ?',
	36: 'Lesson 36: Introducing @ and ^: Right forth finger',
	37: 'Lesson 37: Comprehensive, including @ and ^',
	38: 'Lesson 38: Introducing \\ and |: Right forth finger',
	39: 'Lesson 39: Comprehensive, including \\ and |',
	//number row
	40: 'Lesson 40: Introducing (, * and =: Number row, index fingers',
	41: 'Lesson 41: Introducing } and ): Number row, left second finger, right index finger',
	42: 'Lesson 42: Comprehensive: (, *, =, }, )',
	43: 'Lesson 43: Introducing { and +: Number row, third fingers',
	44: 'Lesson 44: Comprehensive, including { and +',
	45: 'Lesson 45: Introducing [ and ]: Number row, fourth fingers',
	46: 'Lesson 46: Comprehensive, including [ and ]',
	47: 'Lesson 47: introducing & and !: Number row, fourth fingers',
	48: 'Lesson 48: Comprehensive, including & and !',
	49: 'Lesson 49: introducing $ and #: Number row, fourth fingers',
	50: 'Lesson 50: Comprehensive, including $ and #',
	51: 'Lesson 51: Introducing 1, 9 and 0: Number row, index fingers',
	52: 'Lesson 52: Comprehensive, including 1, 9 and 0',
	53: 'Lesson 53: Introducing 3 and 2: Number row, left second finger, right index finger',
	54: 'Lesson 54: Comprehensive, including 3 and 2',
	55: 'Lesson 55: Introducing 5 and 4: Number row, third fingers',
	56: 'Lesson 56: Comprehensive, including 5 and 4',
	57: 'Lesson 57: Introducing 7 and 6: Number row, fourth fingers',
	58: 'Lesson 58: Comprehensive, including 7 and 6',
	59: 'Lesson 59: Introducing % and 8: Number row, fourth fingers',
	60: 'Lesson 60: Comprehensive, including % and 8',
	61: 'Lesson 61: Introducing ~ and `: Number row, fourth fingers',
	62: 'Lesson 62: Comprehensive, including ~ and ` (You rock!)'
};

/**
 * Creates links for the lessons in the given range inside the given container
 *
 * @param lessonsContainer Container to create the lessons in
 * @param start Start index in lessons.js, inclusive
 * @param end End index in LESSONS, inclusive
 */
function createLessonLinks(lessonsContainer, start, end) {
	for(var i = start; i <= end; i++) {
		var prefix = i < 10 ? '0' : '';
		lessonsContainer.append('' + LESSONS[i] + '');
	}
}
<commit_msg>Use correct fingers for lesson 30<commit_after>var LESSONS = {
	'count': 62,
	'otherKeysLessonsStart': 30,
	'numberRowLessonsStart': 40,
	'prefix' : 'Programmer Dvorak typing tutorial',
	//Woods' lessons
	1: 'Lesson 1: Introducing U and H: Home row, Index fingers',
	2: 'Lesson 2: Introducing E and T: Home row, Second fingers',
	3: 'Lesson 3: Comprehensive: E, H, T, U',
	4: 'Lesson 4: Introducing O and N: Home row, third fingers',
	5: 'Lesson 5: Comprehensive, including O and N',
	6: 'Lesson 6: Introducing A and S: Home row, fourth fingers',
	7: 'Lesson 7: Comprehensive, including A and S',
	8: 'Lesson 8: Introducing I and D: Index finger stretching in the home row',
	9: 'Lesson 9: Comprehensive, including I and D (entire home row)',
	10: 'Lesson 10: Introducing P and G: First fingers reaching up',
	11: 'Lesson 11: Comprehensive, including P and G',
	12: 'Lesson 12: Introducing . and C: Second fingers reaching up',
	13: 'Lesson 13: Comprehensive, including . and C',
	14: 'Lesson 14: Introducing , and R: Third fingers reaching up',
	15: 'Lesson 15: Comprehensive, including , and R',
	16: 'Lesson 16: Introducing \' and L: Fourth fingers',
	17: 'Lesson 17: Comprehensive, including \' and L',
	18: 'Lesson 18: Introducing Y and F: Index fingers stretching up',
	19: 'Lesson 19: Comprehensive, including Y and F (full upper/home rows)',
	20: 'Lesson 20: Introducing K and M: Index fingers reaching down',
	21: 'Lesson 21: Comprehensive, including K and M',
	22: 'Lesson 22: Introducing J and W: Second fingers reaching down',
	23: 'Lesson 23: Comprehensive, including J and W',
	24: 'Lesson 24: Introducing Q and V: Third fingers reaching down',
	25: 'Lesson 25: Comprehensive, including Q and V',
	26: 'Lesson 26: Introducing ; and Z: Fourth fingers',
	27: 'Lesson 27: Comprehensive, including ; and Z',
	28: 'Lesson 28: Introducing X and B: Index fingers stretching down',
	29: 'Lesson 29: Comprehensive, including X and B',
	//other keys
	30: 'Lesson 30: Introducing < and >: Left third finger, left second finger',
	31: 'Lesson 31: Comprehensive, including < and >',
	32: 'Lesson 32: Introducing - and _: Right forth finger',
	33: 'Lesson 33: Comprehensive, including - and _',
	34: 'Lesson 34: Introducing / and ?: Right forth finger',
	35: 'Lesson 35: Comprehensive, including / and ?',
	36: 'Lesson 36: Introducing @ and ^: Right forth finger',
	37: 'Lesson 37: Comprehensive, including @ and ^',
	38: 'Lesson 38: Introducing \\ and |: Right forth finger',
	39: 'Lesson 39: Comprehensive, including \\ and |',
	//number row
	40: 'Lesson 40: Introducing (, * and =: Number row, index fingers',
	41: 'Lesson 41: Introducing } and ): Number row, left second finger, right index finger',
	42: 'Lesson 42: Comprehensive: (, *, =, }, )',
	43: 'Lesson 43: Introducing { and +: Number row, third fingers',
	44: 'Lesson 44: Comprehensive, including { and +',
	45: 'Lesson 45: Introducing [ and ]: Number row, fourth fingers',
	46: 'Lesson 46: Comprehensive, including [ and ]',
	47: 'Lesson 47: introducing & and !: Number row, fourth fingers',
	48: 'Lesson 48: Comprehensive, including & and !',
	49: 'Lesson 49: introducing $ and #: Number row, fourth fingers',
	50: 'Lesson 50: Comprehensive, including $ and #',
	51: 'Lesson 51: Introducing 1, 9 and 0: Number row, index fingers',
	52: 'Lesson 52: Comprehensive, including 1, 9 and 0',
	53: 'Lesson 53: Introducing 3 and 2: Number row, left second finger, right index finger',
	54: 'Lesson 54: Comprehensive, including 3 and 2',
	55: 'Lesson 55: Introducing 5 and 4: Number row, third fingers',
	56: 'Lesson 56: Comprehensive, including 5 and 4',
	57: 'Lesson 57: Introducing 7 and 6: Number row, fourth fingers',
	58: 'Lesson 58: Comprehensive, including 7 and 6',
	59: 'Lesson 59: Introducing % and 8: Number row, fourth fingers',
	60: 'Lesson 60: Comprehensive, including % and 8',
	61: 'Lesson 61: Introducing ~ and `: Number row, fourth fingers',
	62: 'Lesson 62: Comprehensive, including ~ and ` (You rock!)'
};

/**
 * Creates links for the lessons in the given range inside the given container
 *
 * @param lessonsContainer Container to create the lessons in
 * @param start Start index in lessons.js, inclusive
 * @param end End index in LESSONS, inclusive
 */
function createLessonLinks(lessonsContainer, start, end) {
	for(var i = start; i <= end; i++) {
		var prefix = i < 10 ? '0' : '';
		lessonsContainer.append('' + LESSONS[i] + '');
	}
}
<|endoftext|>


<commit_before>define('feed_previews',
    ['feed', 'l10n', 'templates', 'utils_local', 'z'],
    function(feed, l10n, nunjucks, utils, z) {

    var gettext = l10n.gettext;

    // Constants are constant.
    var THUMB = 'https://marketplace.cdn.mozilla.net/img/uploads/addon_icons/461/461685-64.png';
    var SAMPLE_BG = '/media/img/sample_bg.jpg';

    String.prototype.escape = function() {
        var tagsToReplace = {
            '<': '<',
            '>': '>'
        };
        return this.replace(/[&<>]/g, function(tag) {
            return tagsToReplace[tag] || tag;
        });
    };

    function app_factory() {
        return $.extend({
            name: 'A Sample App',
            author: 'Kevin Ngo',
            icons: {
                64: THUMB
            },
            ratings: {
                average: 3
            },
            price: '$0.81',
            price_locale: '$0.81',
            slug: 'test-slug',
            url: '#',
        }, $('[name="app"]').data('app') || {});
    }

    function multi_app_factory() {
        var apps = [];
        var $results = $('.apps-widget .result:not(.app-group)');

        if (!$results.length) {
            return apps;
        }

        var grouped = $('.app-group').length;

        $results.each(function(i) {
            var $this = $(this);

            var app = {
                author: $this.find('.author').text(),
                icons: {64: $this.find('.icon').attr('src')},
                name: $this.find('a.name').text(),
                ratings: {
                    average: $this.data('rating')
                },
                price: $this.data('price'),
                price_locale: $this.data('price'),
                slug: 'test-slug',
                url: '#',
            };

            if (grouped) {
                app.group = $($this.prevAll('.app-group')[0]).find(
                    'input.localized:not(.hidden)').val();
            }

            apps.push(app);
        });

        return apps;
    }

    function collection_apps_factory(n) {
        var apps = multi_app_factory();
        if (!apps.length) {
            apps = [];
            for (var i = 0; i < n; i++ ) {
                apps.push(app_factory());
            }
        }
        return apps;
    }

    function preview_factory() {
        return {
            thumbnail_size: $('.screenshots li.selected .screenshot').data('image-size'),
            thumbnail_url: $('.screenshots li.selected a').attr('href'),
        };
    }

    function feed_app_factory() {
        return {
            app: app_factory(),
            background_color: $('.bg-color input:checked').val(),
            background_image: $('.background-image-input .preview').attr('src'),
            description: $('.description .localized:not(.hidden').val().escape() || '',
            preview: preview_factory(),
            pullquote_attribution: $('[name="pq-attribution"]').val().escape() || '',
            pullquote_rating: $('.pq-rating input:checked').val() || 0,
            pullquote_text: $('.pq-text .localized:not(.hidden').val().escape() || '',
            type: $('.featured-type-choices').val() || 'icon',
        };
    };

    function brand_factory() {
        return {
            apps: collection_apps_factory(feed.MAX_BRAND_APPS + 1),
            layout: $('#brand-layout').val() || 'grid',
            type: $('#brand-type').val() || 'apps-for-albania',
        }
    }

    function collection_factory() {
        return {
            apps: collection_apps_factory(feed.MAX_BRAND_APPS + 1),
            background_color: $('.bg-color input:checked').val(),
            background_image: $('.background-image-input .preview').attr('src'),
            description: $('.description .localized:not(.hidden').val().escape() || '',
            name: $('.name .localized:not(.hidden').val().escape() || 'A Sample Collection',
            type: $('.collection-type-choices').val()
        }
    }

    function shelf_factory() {
        var apps = multi_app_factory();
        apps = apps.length ? apps : [app_factory(), app_factory(), app_factory()];
        return {
            apps: apps,
            background_color: $('.bg-color input:checked').val(),
            background_image: $('.background-image-input .preview').attr('src') || SAMPLE_BG,
            description: $('.description .localized:not(.hidden').val().escape() || '',
            name: $('.name .localized:not(.hidden').val().escape() || 'A Sample Shelf',
            type: $('.collection-type-choices input:checked').val() || feed.COLL_PROMO,
        }
    }

    // Listeners.
    z.page.on('change keyup input', 'input, textarea, select', _.throttle(refresh, 250));
    z.page.on('refresh_preview', _.throttle(refresh, 250));

    function refresh() {
        empty();

        var type = $('.transonic-form').data('type');
        if (type == 'apps') {
            refresh_preview(feed_app_factory(), 'app');
        } else if (type == 'brands') {
            refresh_preview(brand_factory(), 'brand');
        } else if (type == 'collections') {
            refresh_preview(collection_factory(), 'collection');
        } else if (type == 'shelves') {
            refresh_preview(shelf_factory(), 'shelf');
        } else if ($('.feed-builder').length) {
            refresh_feed_preview();
        }
    }

    function refresh_feed_preview() {
        stub_globals();

        $('.region-feed:not(.hidden) .feed-elements li').each(function(i, feed_element) {
            var $feed_element = $(feed_element);
            var type = $feed_element.data('type');
            var slug = $feed_element.data('slug');

            var feed_element = require('models')('feed-' + type).lookup(slug);
            $('.feed').append(
                nunjucks.env.render('feed_item_preview.html', {
                    cast_app: function() {},
                    obj: feed_element,
                    item_type: type,
                    url: function() {return '#';}
                })
            );
        });
        load_images();
    }

    function refresh_preview(obj, item_type) {
        stub_globals();

        $('.feed').append(
            nunjucks.env.render('feed_item_preview.html', {
                cast_app: function() {},
                obj: obj,
                item_type: item_type,
                url: function() {return '#';},
            })
        );
        if (item_type != 'app') {
            $('.phone.feed-landing').find('.screen').append(
                nunjucks.env.render('feed/feed_preview_landing.html', {
                    landing: true,
                    obj: obj,
                    type: item_type,
                    url: function() {return '#';}
                })
            );
        }
        load_images();
    }

    function load_images() {
        // Mock image deferrer.
        setTimeout(function() {
            $('.deferred').each(function(i, deferred) {
                var $deferred = $(deferred);
                $deferred.attr('src', $deferred.attr('data-src')).removeClass('deferred');
            });
        });
        setTimeout(function() {
            $('.deferred-background').each(function(i, deferred) {
                var $deferred = $(deferred);
                $deferred.css('background-image', 'url(' + $deferred.attr('data-src') + ')')
                         .removeClass('deferred-background');
            });
        });
    }

    function stub_globals() {
        // Stub out Fireplace-specific helpers.
        var stub_globals = nunjucks.require('globals');
        stub_globals.app_incompat = function() {};
        stub_globals.has_installed = function() {};
        stub_globals.imgAlreadyDeferred = function() {return true;};
    }

    function empty() {
        $('.feed, .phone.feed-landing .screen').empty();
    }

    return {
        empty: empty,
        refresh: refresh,
    };
});
<commit_msg>fix view all apps logic in preview (bug 1056243)<commit_after>define('feed_previews',
    ['feed', 'l10n', 'templates', 'utils_local', 'z'],
    function(feed, l10n, nunjucks, utils, z) {

    var gettext = l10n.gettext;

    // Constants are constant.
    var THUMB = 'https://marketplace.cdn.mozilla.net/img/uploads/addon_icons/461/461685-64.png';
    var SAMPLE_BG = '/media/img/sample_bg.jpg';

    String.prototype.escape = function() {
        var tagsToReplace = {
            '<': '<',
            '>': '>'
        };
        return this.replace(/[&<>]/g, function(tag) {
            return tagsToReplace[tag] || tag;
        });
    };

    function app_factory() {
        return $.extend({
            name: 'A Sample App',
            author: 'Kevin Ngo',
            icons: {
                64: THUMB
            },
            ratings: {
                average: 3
            },
            price: '$0.81',
            price_locale: '$0.81',
            slug: 'test-slug',
            url: '#',
        }, $('[name="app"]').data('app') || {});
    }

    function multi_app_factory() {
        var apps = [];
        var $results = $('.apps-widget .result:not(.app-group)');

        if (!$results.length) {
            return apps;
        }

        var grouped = $('.app-group').length;

        $results.each(function(i) {
            var $this = $(this);

            var app = {
                author: $this.find('.author').text(),
                icons: {64: $this.find('.icon').attr('src')},
                name: $this.find('a.name').text(),
                ratings: {
                    average: $this.data('rating')
                },
                price: $this.data('price'),
                price_locale: $this.data('price'),
                slug: 'test-slug',
                url: '#',
            };

            if (grouped) {
                app.group = $($this.prevAll('.app-group')[0]).find(
                    'input.localized:not(.hidden)').val();
            }

            apps.push(app);
        });

        return apps;
    }

    function collection_apps_factory(n) {
        var apps = multi_app_factory();
        if (!apps.length) {
            apps = [];
            for (var i = 0; i < n; i++ ) {
                apps.push(app_factory());
            }
        }
        return apps;
    }

    function preview_factory() {
        return {
            thumbnail_size: $('.screenshots li.selected .screenshot').data('image-size'),
            thumbnail_url: $('.screenshots li.selected a').attr('href'),
        };
    }

    function feed_app_factory() {
        return {
            app: app_factory(),
            background_color: $('.bg-color input:checked').val(),
            background_image: $('.background-image-input .preview').attr('src'),
            description: $('.description .localized:not(.hidden').val().escape() || '',
            preview: preview_factory(),
            pullquote_attribution: $('[name="pq-attribution"]').val().escape() || '',
            pullquote_rating: $('.pq-rating input:checked').val() || 0,
            pullquote_text: $('.pq-text .localized:not(.hidden').val().escape() || '',
            type: $('.featured-type-choices').val() || 'icon',
        };
    }

    function brand_factory() {
        var apps = collection_apps_factory(feed.MAX_BRAND_APPS + 1);
        return {
            apps: apps,
            app_count: apps.length,
            layout: $('#brand-layout').val() || 'grid',
            type: $('#brand-type').val() || 'apps-for-albania',
        };
    }

    function collection_factory() {
        var apps = collection_apps_factory(feed.MAX_BRAND_APPS + 1);
        return {
            apps: apps,
            app_count: apps.length,
            background_color: $('.bg-color input:checked').val(),
            background_image: $('.background-image-input .preview').attr('src'),
            description: $('.description .localized:not(.hidden').val().escape() || '',
            name: $('.name .localized:not(.hidden').val().escape() || 'A Sample Collection',
            type: $('.collection-type-choices').val()
        };
    }

    function shelf_factory() {
        var apps = collection_apps_factory(feed.MAX_BRAND_APPS + 1);
        return {
            apps: apps,
            app_count: apps.length,
            background_color: $('.bg-color input:checked').val(),
            background_image: $('.background-image-input .preview').attr('src') || SAMPLE_BG,
            description: $('.description .localized:not(.hidden').val().escape() || '',
            name: $('.name .localized:not(.hidden').val().escape() || 'A Sample Shelf',
            type: $('.collection-type-choices input:checked').val() || feed.COLL_PROMO,
        };
    }

    // Listeners.
    z.page.on('change keyup input', 'input, textarea, select', _.throttle(refresh, 250));
    z.page.on('refresh_preview', _.throttle(refresh, 250));

    function refresh() {
        empty();

        var type = $('.transonic-form').data('type');
        if (type == 'apps') {
            refresh_preview(feed_app_factory(), 'app');
        } else if (type == 'brands') {
            refresh_preview(brand_factory(), 'brand');
        } else if (type == 'collections') {
            refresh_preview(collection_factory(), 'collection');
        } else if (type == 'shelves') {
            refresh_preview(shelf_factory(), 'shelf');
        } else if ($('.feed-builder').length) {
            refresh_feed_preview();
        }
    }

    function refresh_feed_preview() {
        stub_globals();

        $('.region-feed:not(.hidden) .feed-elements li').each(function(i, feed_element) {
            var $feed_element = $(feed_element);
            var type = $feed_element.data('type');
            var slug = $feed_element.data('slug');

            var feed_element = require('models')('feed-' + type).lookup(slug);
            $('.feed').append(
                nunjucks.env.render('feed_item_preview.html', {
                    cast_app: function() {},
                    obj: feed_element,
                    item_type: type,
                    url: function() {return '#';}
                })
            );
        });
        load_images();
    }

    function refresh_preview(obj, item_type) {
        stub_globals();

        $('.feed').append(
            nunjucks.env.render('feed_item_preview.html', {
                cast_app: function() {},
                obj: obj,
                item_type: item_type,
                url: function() {return '#';},
            })
        );
        if (item_type != 'app') {
            $('.phone.feed-landing').find('.screen').append(
                nunjucks.env.render('feed/feed_preview_landing.html', {
                    landing: true,
                    obj: obj,
                    type: item_type,
                    url: function() {return '#';}
                })
            );
        }
        load_images();
    }

    function load_images() {
        // Mock image deferrer.
        setTimeout(function() {
            $('.deferred').each(function(i, deferred) {
                var $deferred = $(deferred);
                $deferred.attr('src', $deferred.attr('data-src')).removeClass('deferred');
            });
        });
        setTimeout(function() {
            $('.deferred-background').each(function(i, deferred) {
                var $deferred = $(deferred);
                $deferred.css('background-image', 'url(' + $deferred.attr('data-src') + ')')
                         .removeClass('deferred-background');
            });
        });
    }

    function stub_globals() {
        // Stub out Fireplace-specific helpers.
        var stub_globals = nunjucks.require('globals');
        stub_globals.app_incompat = function() {};
        stub_globals.has_installed = function() {};
        stub_globals.imgAlreadyDeferred = function() {return true;};
    }

    function empty() {
        $('.feed, .phone.feed-landing .screen').empty();
    }

    return {
        empty: empty,
        refresh: refresh,
    };
});
<|endoftext|>


<commit_before>"use strict";
var React = require("react/addons");

module.exports = {

  getInitialState () {
    return {
      googleMapsApi: this.props.googleMapsApi,
      map: null
    };
  },

  childContextTypes: {
    getMap: React.PropTypes.func,
    getApi: React.PropTypes.func,
    hasMap: React.PropTypes.func,
    getInstanceByRef: React.PropTypes.func,
    _set_map: React.PropTypes.func
  },

  getChildContext () {
    return {
      getMap: this._get_map,
      getApi: this._get_api,
      hasMap: this._has_map,
      getInstanceByRef: this._get_instance_by_ref,
      _set_map: this._set_map
    };
  },

  _get_map () {
    return this.state.map;
  },

  _get_api () {
    return this.state.googleMapsApi;
  },

  _has_map () {
    return !!this._get_map();
  },

  _get_instance_by_ref (key) {
    return this.refs[key].state._instance;
  },

  _set_map (map) {
    this.setState({ map });
    return map;
  }
};
<commit_msg>fix(GoogleMapsMixin): respond to update of googleMapsApi<commit_after>"use strict";
var React = require("react/addons");

module.exports = {

  getInitialState () {
    return {
      googleMapsApi: this.props.googleMapsApi,
      map: null
    };
  },

  childContextTypes: {
    getMap: React.PropTypes.func,
    getApi: React.PropTypes.func,
    hasMap: React.PropTypes.func,
    getInstanceByRef: React.PropTypes.func,
    _set_map: React.PropTypes.func
  },

  getChildContext () {
    return {
      getMap: this._get_map,
      getApi: this._get_api,
      hasMap: this._has_map,
      getInstanceByRef: this._get_instance_by_ref,
      _set_map: this._set_map
    };
  },

  componentWillReceiveProps (nextProps) {
    if (!this.state.googleMapsApi && nextProps.googleMapsApi) {
      this.setState({
        googleMapsApi: nextProps.googleMapsApi
      });
    }
  },

  _get_map () {
    return this.state.map;
  },

  _get_api () {
    return this.state.googleMapsApi;
  },

  _has_map () {
    return !!this._get_map();
  },

  _get_instance_by_ref (key) {
    return this.refs[key].state._instance;
  },

  _set_map (map) {
    this.setState({ map });
    return map;
  }
};
<|endoftext|>


<commit_before>import { toUrl } from '@utils/url.utils';
import { prefetch as prefetchImage } from '@utils/image.utils';
import * as StorageUtils from '@utils/storage.utils';
import apiConfigs from './configs/api.config';
import cacheConfigs from './configs/cache.config';

const { client_id, base, uris } = apiConfigs;
const randomPhotoApiUrl = `${base}${uris.randomPhoto}`;

const DEFAULTS = {
    featured: true, orientation: 'landscape',
    w: 1920, h: 1048, count: 20,
    client_id,
};

const PREFETCH_THRESHOLD = 10;
const prefetchedPhotoCacheKey = cacheConfigs.prefetchedPhotos;

/**
 * Returns the photo url to use from the photo object.
 *
 * @param  {Object} photo   Photo object returned by the API
 * @return {String}         Photo url to use
 */
export function getPhotoUrl(photo = {}) {
    const { urls = {} } = photo;
    return urls.custom || urls.regular;
}

/**
 * Fetched random photos from the API.
 *
 * @see https://unsplash.com/documentation#get-a-random-photo
 *
 * @param  {Object} params  Supported parameters
 * @return {Promise}        Promise that returns JSON data (Array of photos)
 */
export function fetchRandomPhotos(params = {}) {
    params = {...DEFAULTS, ...params};

    const url = toUrl(randomPhotoApiUrl, params);

    return fetch(url).then(response => response.json())
            .catch(response => []);
}

/**
 * Prefetches the usable photo urls and
 * stores corresponding photo objects in the cache.
 */
export function prefetchRandomPhotos() {
    const prefetchedPhotos = StorageUtils.get(prefetchedPhotoCacheKey);
    const exisitngPrefetchedPhotosCount = prefetchedPhotos
        && Object.keys(prefetchedPhotos).length;

    // if sufficient buffer, do not prefetch
    if (exisitngPrefetchedPhotosCount >= PREFETCH_THRESHOLD) return;

    // call random photos api
    // then use image prefetch technique to prefetch the photo url
    // then store each prefetched photo object in the cache
    // { [photo id] : { photo object }, ... }
    fetchRandomPhotos().then(photos => {
        photos.forEach(p => {
            const url = getPhotoUrl(p);
            prefetchImage(url)
                .then(img => StorageUtils.update(prefetchedPhotoCacheKey, {[p.id]: p}))
                .catch(img => img)
        });
    });
}

/**
 * Returns a random prefetched photo object from the cache.
 * The returned photo is removed from the cache.
 * If the cache is empty, it initiates a prefetch.
 *
 * @return {Object}     Photo object as per the API
 */
export function getRandomPrefetchedPhoto() {
    const prefetchedPhotos = StorageUtils.get(prefetchedPhotoCacheKey);

    // no pre-fetched photos
    // start pre-fetching
    if (!prefetchedPhotos || !Object.keys(prefetchedPhotos).length) {
        prefetchRandomPhotos();
        return;
    }

    // there is at least one unused photo, pick the first one
    const prefetchedPhotosIds = Object.keys(prefetchedPhotos);
    const selectedId = prefetchedPhotosIds[0];
    const unusedPhoto = prefetchedPhotos[selectedId];
    // delete from cache and return the photo
    delete prefetchedPhotos[selectedId];
    StorageUtils.set(prefetchedPhotoCacheKey, prefetchedPhotos);
    return unusedPhoto;
}
<commit_msg>:art: avoid implicit prefetch from unrelated method<commit_after>import { toUrl } from '@utils/url.utils';
import { prefetch as prefetchImage } from '@utils/image.utils';
import * as StorageUtils from '@utils/storage.utils';
import apiConfigs from './configs/api.config';
import cacheConfigs from './configs/cache.config';

const { client_id, base, uris } = apiConfigs;
const randomPhotoApiUrl = `${base}${uris.randomPhoto}`;
const prefetchedPhotoCacheKey = cacheConfigs.prefetchedPhotos;

// random photos api defaults
const DEFAULTS = {
    featured: true, orientation: 'landscape',
    w: 1920, h: 1048, count: 20,
    client_id,
};

// max photos to prefetch
const PREFETCH_THRESHOLD = 10;

/**
 * Returns the photo url to use from the photo object.
 *
 * @param  {Object} photo   Photo object returned by the API
 * @return {String}         Photo url to use
 */
export function getPhotoUrl(photo = {}) {
    const { urls = {} } = photo;
    return urls.custom || urls.regular;
}

/**
 * Fetched random photos from the API.
 *
 * @see https://unsplash.com/documentation#get-a-random-photo
 *
 * @param  {Object} params  Supported parameters
 * @return {Promise}        Promise that returns JSON data (Array of photos)
 */
export function fetchRandomPhotos(params = {}) {
    params = {...DEFAULTS, ...params};

    const url = toUrl(randomPhotoApiUrl, params);

    return fetch(url).then(response => response.json())
            .catch(response => []);
}

/**
 * Prefetches the usable photo urls and
 * stores corresponding photo objects in the cache.
 *
 * @todo: configurable categories option
 */
export function prefetchRandomPhotos() {
    const prefetchedPhotos = StorageUtils.get(prefetchedPhotoCacheKey);
    const exisitngPrefetchedPhotosCount = prefetchedPhotos
        && Object.keys(prefetchedPhotos).length;

    // if sufficient buffer, do not prefetch
    if (exisitngPrefetchedPhotosCount >= PREFETCH_THRESHOLD) return;

    // call random photos api
    // then use image prefetch technique to prefetch the photo url
    // then store each prefetched photo object in the cache
    // { [photo id] : { photo object }, ... }
    fetchRandomPhotos().then(photos => {
        photos.forEach(p => {
            const url = getPhotoUrl(p);
            prefetchImage(url)
                .then(img => StorageUtils.update(prefetchedPhotoCacheKey, {[p.id]: p}))
                .catch(img => img)
        });
    });
}

/**
 * Returns a random prefetched photo object from the cache.
 * The returned photo is removed from the cache.
 *
 * @return {Object}     Photo object as per the API
 */
export function getRandomPrefetchedPhoto() {
    const prefetchedPhotos = StorageUtils.get(prefetchedPhotoCacheKey);

    // no pre-fetched photos, return
    if (!prefetchedPhotos || !Object.keys(prefetchedPhotos).length) {
        return;
    }

    // there is at least one unused photo, pick the first one
    const prefetchedPhotosIds = Object.keys(prefetchedPhotos);
    const selectedId = prefetchedPhotosIds[0];
    const unusedPhoto = prefetchedPhotos[selectedId];
    // delete from cache and return the photo
    delete prefetchedPhotos[selectedId];
    StorageUtils.set(prefetchedPhotoCacheKey, prefetchedPhotos);
    return unusedPhoto;
}
<|endoftext|>


<commit_before>/**
 * @license MPL 1.1/GPL 2.0/LGPL 2.1, see license.txt
 * @author William Elwood <we9@kent.ac.uk>
 * @copyright 2011 JSONovich Team. All Rights Reserved.
 * @description Adds custom logic to the buttons on our options dialog.
 */

'use strict';

XPCOMUtils.defineLazyGetter(this, 'l', function() {
    return require('l10n').bundle('options');
});
XPCOMUtils.defineLazyGetter(this, 'valid', function() {
    return require('validate');
});

var prefNameConv = 'mime.conversions',
prefNameExt = 'mime.extensionMap',
events = {},
observer = {
    observe: function(aSubject, aTopic, aData) {
        if(aTopic != 'addon-options-displayed' || aData != ADDON_LNAME + '@' + ADDON_DOMAIN) {
            return;
        }
        var container = aSubject.getElementById('detail-rows'); // normal add-ons manager
        if(!container) { // fennec XBL add-ons manager
            container = aSubject.getAnonymousElementByAttribute(aSubject.querySelector('[addonID="'+aData+'"]'), 'anonid', 'options-box');
        }
        [].forEach.call(container.querySelectorAll('setting,setting button,.preferences-description'), function(node) {
            ['title', 'desc', 'label'].forEach(function(attr) {
                var t = node.getAttribute(attr);
                if(t) {
                    node.setAttribute(attr, l(t));
                }
            });
            if(node.textContent) {
                node.textContent = l(node.textContent);
            }
        });
        for(var k in events) {
            aSubject.getElementById(k).addEventListener('click', events[k], false);
        }
    }
},
observing = false,
prefs = require('prefs').branch,
prefBranch = null,

clearPrefs = exports.clear = function clearPrefs() {
    prefs(ADDON_PREFROOT, true).uninstall();
    prefs(ADDON_PREFROOT + '@' + ADDON_DOMAIN, true).uninstall();
};

function resetPrefs() {
    clearPrefs();
    require('chrome/DefaultPrefs').set(prefs(ADDON_PREFROOT, true).set);
}
events[ADDON_LNAME + '-pref-reset'] = resetPrefs;

function promptAdd(options) {
    var textbox = {}, checkbox = {};
    if(typeof options.textbox !== 'undefined') {
        textbox.value = options.textbox;
    }
    if(typeof options.checkbox !== 'undefined') {
        checkbox.value = options.checkbox;
    }
    while(Services.prompt.prompt(null, l('prompt.add.' + options.type + '.title'), l('prompt.add.' + options.type + '.text', options.msgParams ? options.msgParams : null), textbox, options.checkbox ? l('prompt.add.' + options.type + '.checkbox') : null, checkbox)) {
        if(options.test && !options.test(textbox.value)) {
            Services.prompt.alert(null, l('error.invalid.' + (options.testType ? options.testType : options.type) + '.title'), l('error.invalid.' + (options.invalidType ? options.invalidType : options.type) + '.text', options.invalidMsgParams ? options.invalidMsgParams : null));
            continue;
        }
        if(options.onOk && options.onOk(textbox.value, checkbox.value)) {
            continue;
        } else {
            return true;
        }
    }
    if(options.onCancel) {
        options.onCancel();
    }
    return false;
}

function promptRemove(options) {
    var selected = {};
    if(!options.choices.length || (options.choices.length == 1 && !options.choices[0].length)) {
        Services.prompt.alert(null, l('error.none.' + options.type + '.title'), l('error.none.' + options.type + '.text'));
        return false;
    } else if(Services.prompt.select(null, l('prompt.remove.' + options.type + '.title'), l('prompt.remove.' + options.type + '.text'), options.choices.length, options.choices, selected)) {
        if(options.onOk) {
            options.onOk(selected.value);
        }
        return true;
    }
    if(options.onCancel) {
        options.onCancel();
    }
    return false;
}

function addAccept() {
    promptAdd({
        type: 'host',
        checkbox: !prefBranch.get('acceptHeader.json', 'boolean'),
        test: valid.host,
        invalidMsgParams: valid.host.maxLen,
        onOk: function(host, checked) {
            var q = 0;
            if(checked && !promptAdd({
                type: 'q',
                textbox: '1',
                test: valid.q,
                onOk: function(textbox) {
                    q = parseFloat(textbox); // silently allow 0 here even though user could have just unticked box on 1st prompt
                }
            })) {
                return true; // q-value prompt cancelled, go back to host prompt
            }
            prefBranch.set('acceptHeaderOverride.json.' + host, 'string', q);
        }
    });
}
events[ADDON_LNAME + '-pref-host-add'] = addAccept;

function removeAccept() {
    var overrideBranch = prefs(ADDON_PREFROOT + '.acceptHeaderOverride.json'),
    overrideHosts = overrideBranch.getChildList(),
    overrides = [];
    for(let i = 0; i < overrideHosts.length; i++) {
        overrides.push('[q=' + overrideBranch.get(overrideHosts[i], 'string') + ']: ' + overrideHosts[i]);
    }
    promptRemove({
        type: 'host',
        choices: overrides,
        onOk: function(sel) {
            overrideBranch.unset(overrideHosts[sel]);
        }
    });
}
events[ADDON_LNAME + '-pref-host-remove'] = removeAccept;

function addMime() {
    promptAdd({
        type: 'mime',
        test: valid.mime,
        invalidMsgParams: valid.mime.maxLen,
        onOk: function(mime) {
            var conversions = (prefBranch.get(prefNameConv, 'string') || '').split('|');
            mime = mime.toLowerCase();
            if(conversions.indexOf(mime) !== -1) {
                Services.prompt.alert(null, l('error.already.mime.title'), l('error.already.mime.text'));
                return true;
            }
            conversions.push(mime);
            prefBranch.set(prefNameConv, 'string', conversions.join('|'));
        }
    });
}
events[ADDON_LNAME + '-pref-mime-add'] = addMime;

function removeMime() {
    var conversions = (prefBranch.get(prefNameConv, 'string') || '').split('|');
    promptRemove({
        type: 'mime',
        choices: conversions,
        onOk: function(sel) {
            conversions.splice(sel, 1);
            prefBranch.set(prefNameConv, 'string', conversions.join('|'));
        }
    });
}
events[ADDON_LNAME + '-pref-mime-remove'] = removeMime;

function addExtMap() {
    promptAdd({
        type: 'fileExt',
        test: valid.fileExt,
        invalidMsgParams: valid.fileExt.maxLen,
        onOk: function(ext) {
            var exts = [], mappings = (prefBranch.get(prefNameExt, 'string') || '').split('|');
            mappings.map(function(v) {
                exts.push(v.split(':')[0]);
            });
            ext = ext.toLowerCase();
            if(exts.indexOf(ext) !== -1) {
                Services.prompt.alert(null, l('error.already.fileExt.title'), l('error.already.fileExt.text'));
                return true;
            }
            return !promptAdd({
                type: 'mapping',
                msgParams: ext,
                test: valid.mime,
                testType: 'mime',
                invalidMsgParams: valid.mime.maxLen,
                onOk: function(mime) {
                    var conversions = (prefBranch.get(prefNameConv, 'string') || '').split('|');
                    mime = mime.toLowerCase();
                    if(conversions.indexOf(mime) === -1) {
                        Services.prompt.alert(null, l('error.invalid.mapping.title'), l('error.invalid.mapping.text'));
                        return true;
                    } else if(mappings.indexOf(ext + ':' + mime) !== -1) {
                        Services.prompt.alert(null, l('error.already.mapping.title'), l('error.already.mapping.text'));
                        return true;
                    }
                    mappings.push(ext + ':' + mime);
                    prefBranch.set(prefNameExt, 'string', mappings.join('|'));
                }
            });
        }
    });
}
events[ADDON_LNAME + '-pref-fileExt-add'] = addExtMap;

function removeExtMap() {
    var mappings = (prefBranch.get(prefNameExt, 'string') || '').split('|');
    promptRemove({
        type: 'mapping',
        choices: mappings,
        onOk: function(sel) {
            mappings.splice(sel, 1);
            prefBranch.set(prefNameExt, 'string', mappings.join('|'));
        }
    });
}
events[ADDON_LNAME + '-pref-fileExt-remove'] = removeExtMap;

/**
 * Dynamically add functionality to buttons on inline options
 *
 * @param branch <object>  Reference to the branch object for the appropriate preferences,
 *                         require('prefs').branch(<branch>) - optional, provide it if
 *                         you're already using the branch to save mem.
 */
exports.observe = function observeOptions(branch) {
    prefBranch = branch || prefs(ADDON_PREFROOT);
    if(observing) {
        return;
    }
    Services.obs.addObserver(observer, 'addon-options-displayed', false);
    observing = true;
    require('unload').unload(function(){
        Services.obs.removeObserver(observer, 'addon-options-displayed');
        prefBranch = null;
        observing = false;
    });
};
<commit_msg>Potential quick fix for Android options<commit_after>/**
 * @license MPL 1.1/GPL 2.0/LGPL 2.1, see license.txt
 * @author William Elwood <we9@kent.ac.uk>
 * @copyright 2011 JSONovich Team. All Rights Reserved.
 * @description Adds custom logic to the buttons on our options dialog.
 */

'use strict';

XPCOMUtils.defineLazyGetter(this, 'l', function() {
    return require('l10n').bundle('options');
});
XPCOMUtils.defineLazyGetter(this, 'valid', function() {
    return require('validate');
});

var prefNameConv = 'mime.conversions',
prefNameExt = 'mime.extensionMap',
events = {},
observer = {
    observe: function(aSubject, aTopic, aData) {
        if(aTopic != 'addon-options-displayed' || aData != ADDON_LNAME + '@' + ADDON_DOMAIN) {
            return;
        }
        var container = aSubject.getElementById('detail-rows'); // normal add-ons manager
        if(!container) { // Android add-ons manager (hopefully)
            container = aSubject.querySelector('.options-box');
        }
        if(!container) { // fennec XBL add-ons manager
            container = aSubject.getAnonymousElementByAttribute(aSubject.querySelector('[addonID="'+aData+'"]'), 'anonid', 'options-box');
        }
        [].forEach.call(container.querySelectorAll('setting,setting button,.preferences-description'), function(node) {
            ['title', 'desc', 'label'].forEach(function(attr) {
                var t = node.getAttribute(attr);
                if(t) {
                    node.setAttribute(attr, l(t));
                }
            });
            if(node.textContent) {
                node.textContent = l(node.textContent);
            }
        });
        for(var k in events) {
            aSubject.getElementById(k).addEventListener('click', events[k], false);
        }
    }
},
observing = false,
prefs = require('prefs').branch,
prefBranch = null,

clearPrefs = exports.clear = function clearPrefs() {
    prefs(ADDON_PREFROOT, true).uninstall();
    prefs(ADDON_PREFROOT + '@' + ADDON_DOMAIN, true).uninstall();
};

function resetPrefs() {
    clearPrefs();
    require('chrome/DefaultPrefs').set(prefs(ADDON_PREFROOT, true).set);
}
events[ADDON_LNAME + '-pref-reset'] = resetPrefs;

function promptAdd(options) {
    var textbox = {}, checkbox = {};
    if(typeof options.textbox !== 'undefined') {
        textbox.value = options.textbox;
    }
    if(typeof options.checkbox !== 'undefined') {
        checkbox.value = options.checkbox;
    }
    while(Services.prompt.prompt(null, l('prompt.add.' + options.type + '.title'), l('prompt.add.' + options.type + '.text', options.msgParams ? options.msgParams : null), textbox, options.checkbox ? l('prompt.add.' + options.type + '.checkbox') : null, checkbox)) {
        if(options.test && !options.test(textbox.value)) {
            Services.prompt.alert(null, l('error.invalid.' + (options.testType ? options.testType : options.type) + '.title'), l('error.invalid.' + (options.invalidType ? options.invalidType : options.type) + '.text', options.invalidMsgParams ? options.invalidMsgParams : null));
            continue;
        }
        if(options.onOk && options.onOk(textbox.value, checkbox.value)) {
            continue;
        } else {
            return true;
        }
    }
    if(options.onCancel) {
        options.onCancel();
    }
    return false;
}

function promptRemove(options) {
    var selected = {};
    if(!options.choices.length || (options.choices.length == 1 && !options.choices[0].length)) {
        Services.prompt.alert(null, l('error.none.' + options.type + '.title'), l('error.none.' + options.type + '.text'));
        return false;
    } else if(Services.prompt.select(null, l('prompt.remove.' + options.type + '.title'), l('prompt.remove.' + options.type + '.text'), options.choices.length, options.choices, selected)) {
        if(options.onOk) {
            options.onOk(selected.value);
        }
        return true;
    }
    if(options.onCancel) {
        options.onCancel();
    }
    return false;
}

function addAccept() {
    promptAdd({
        type: 'host',
        checkbox: !prefBranch.get('acceptHeader.json', 'boolean'),
        test: valid.host,
        invalidMsgParams: valid.host.maxLen,
        onOk: function(host, checked) {
            var q = 0;
            if(checked && !promptAdd({
                type: 'q',
                textbox: '1',
                test: valid.q,
                onOk: function(textbox) {
                    q = parseFloat(textbox); // silently allow 0 here even though user could have just unticked box on 1st prompt
                }
            })) {
                return true; // q-value prompt cancelled, go back to host prompt
            }
            prefBranch.set('acceptHeaderOverride.json.' + host, 'string', q);
        }
    });
}
events[ADDON_LNAME + '-pref-host-add'] = addAccept;

function removeAccept() {
    var overrideBranch = prefs(ADDON_PREFROOT + '.acceptHeaderOverride.json'),
    overrideHosts = overrideBranch.getChildList(),
    overrides = [];
    for(let i = 0; i < overrideHosts.length; i++) {
        overrides.push('[q=' + overrideBranch.get(overrideHosts[i], 'string') + ']: ' + overrideHosts[i]);
    }
    promptRemove({
        type: 'host',
        choices: overrides,
        onOk: function(sel) {
            overrideBranch.unset(overrideHosts[sel]);
        }
    });
}
events[ADDON_LNAME + '-pref-host-remove'] = removeAccept;

function addMime() {
    promptAdd({
        type: 'mime',
        test: valid.mime,
        invalidMsgParams: valid.mime.maxLen,
        onOk: function(mime) {
            var conversions = (prefBranch.get(prefNameConv, 'string') || '').split('|');
            mime = mime.toLowerCase();
            if(conversions.indexOf(mime) !== -1) {
                Services.prompt.alert(null, l('error.already.mime.title'), l('error.already.mime.text'));
                return true;
            }
            conversions.push(mime);
            prefBranch.set(prefNameConv, 'string', conversions.join('|'));
        }
    });
}
events[ADDON_LNAME + '-pref-mime-add'] = addMime;

function removeMime() {
    var conversions = (prefBranch.get(prefNameConv, 'string') || '').split('|');
    promptRemove({
        type: 'mime',
        choices: conversions,
        onOk: function(sel) {
            conversions.splice(sel, 1);
            prefBranch.set(prefNameConv, 'string', conversions.join('|'));
        }
    });
}
events[ADDON_LNAME + '-pref-mime-remove'] = removeMime;

function addExtMap() {
    promptAdd({
        type: 'fileExt',
        test: valid.fileExt,
        invalidMsgParams: valid.fileExt.maxLen,
        onOk: function(ext) {
            var exts = [], mappings = (prefBranch.get(prefNameExt, 'string') || '').split('|');
            mappings.map(function(v) {
                exts.push(v.split(':')[0]);
            });
            ext = ext.toLowerCase();
            if(exts.indexOf(ext) !== -1) {
                Services.prompt.alert(null, l('error.already.fileExt.title'), l('error.already.fileExt.text'));
                return true;
            }
            return !promptAdd({
                type: 'mapping',
                msgParams: ext,
                test: valid.mime,
                testType: 'mime',
                invalidMsgParams: valid.mime.maxLen,
                onOk: function(mime) {
                    var conversions = (prefBranch.get(prefNameConv, 'string') || '').split('|');
                    mime = mime.toLowerCase();
                    if(conversions.indexOf(mime) === -1) {
                        Services.prompt.alert(null, l('error.invalid.mapping.title'), l('error.invalid.mapping.text'));
                        return true;
                    } else if(mappings.indexOf(ext + ':' + mime) !== -1) {
                        Services.prompt.alert(null, l('error.already.mapping.title'), l('error.already.mapping.text'));
                        return true;
                    }
                    mappings.push(ext + ':' + mime);
                    prefBranch.set(prefNameExt, 'string', mappings.join('|'));
                }
            });
        }
    });
}
events[ADDON_LNAME + '-pref-fileExt-add'] = addExtMap;

function removeExtMap() {
    var mappings = (prefBranch.get(prefNameExt, 'string') || '').split('|');
    promptRemove({
        type: 'mapping',
        choices: mappings,
        onOk: function(sel) {
            mappings.splice(sel, 1);
            prefBranch.set(prefNameExt, 'string', mappings.join('|'));
        }
    });
}
events[ADDON_LNAME + '-pref-fileExt-remove'] = removeExtMap;

/**
 * Dynamically add functionality to buttons on inline options
 *
 * @param branch <object>  Reference to the branch object for the appropriate preferences,
 *                         require('prefs').branch(<branch>) - optional, provide it if
 *                         you're already using the branch to save mem.
 */
exports.observe = function observeOptions(branch) {
    prefBranch = branch || prefs(ADDON_PREFROOT);
    if(observing) {
        return;
    }
    Services.obs.addObserver(observer, 'addon-options-displayed', false);
    observing = true;
    require('unload').unload(function(){
        Services.obs.removeObserver(observer, 'addon-options-displayed');
        prefBranch = null;
        observing = false;
    });
};
<|endoftext|>


<commit_before>var Utils = require('mappersmith').Utils;
var CreateCacheStore = require('./create-cache-store');

var redis = require('redis');

var NodeRedisCacheStore = CreateCacheStore({
  init: function() {
    this.logger = this.options.logger || console;
    this.redisOptions = Utils.extend({}, this.options.redis);
    this.storage = redis.createClient.apply(this, this.redisOptions.client);
    this.storage.on('error', this._redisOnError.bind(this));
  },

  read: function(name, callback) {
    var cacheKey = this.cacheKey(name);
    this.storage.get(cacheKey, function(err, rawData) {

      if (err) {
        this._redisOnError('#read ' + err);

        // In case of error is better send data as null to
        // release the call
        callback(null);

      } else {
        var data = JSON.parse(rawData);
        callback(data ? data.value : data);
      }

    }.bind(this));
  },

  write: function(name, data, opts, doneCallback) {
    if (typeof opts === 'function') {
      doneCallback = opts;
      opts = {};
    }

    var ttl = this.resolveTTL(opts);
    var cacheKey = this.cacheKey(name);
    var payload = JSON.stringify({value: data});

    this.storage.setex(
      cacheKey,
      ttl,
      payload,
      this._newDoneCallback('write', doneCallback)
    );
  },

  delete: function(name, doneCallback) {
    var cacheKey = this.cacheKey(name);
    this.storage.del(
      cacheKey,
      this._newDoneCallback('delete', doneCallback)
    );
  },

  /*
   * No op. Redis will automatically remove the expired keys.
   */
  cleanup: function(doneCallback) {
    if (doneCallback) doneCallback();
  },

  clear: function(doneCallback) {
    var clearPattern = this.cacheKey('*');
    this.storage.keys(clearPattern, function(err, keys) {

      if (err) {
        this._redisOnError('#clear ' + err);

      } else {
        this.storage.del(
          keys,
          this._newDoneCallback('clear', doneCallback)
        );
      }

    }.bind(this));
  },

  _newDoneCallback: function(operation, doneCallback) {
    // Using "self" var instead of "bind(this)" to avoid
    // change the context of doneCallback
    var self = this;
    return function(err) {
      if (err) {
        self._redisOnError('#' + operation + ' ' + err);

      } else {
        if (doneCallback) doneCallback();
      }
    }
  },

  _redisOnError: function(err) {
    if (this.logger) this.logger.error('[NodeRedisCacheStore] ' + err);
  }
});

module.exports = NodeRedisCacheStore;
<commit_msg>some refactoring<commit_after>var Utils = require('mappersmith').Utils;
var CreateCacheStore = require('./create-cache-store');

var redis = require('redis');

var NodeRedisCacheStore = CreateCacheStore({
  init: function() {
    this.logger = this.options.logger || console;
    this.redisOptions = Utils.extend({}, this.options.redis);
    this.storage = redis.createClient.apply(this, this.redisOptions.client);
    this.storage.on('error', this._redisOnError.bind(this));
  },

  read: function(name, callback) {
    var cacheKey = this.cacheKey(name);
    this.storage.get(cacheKey, function(err, rawData) {

      if (err) {
        this._redisOnError('#read ' + err);

        // In case of error is better send data as null to
        // release the call
        callback(null);

      } else {
        var data = JSON.parse(rawData);
        callback(data ? data.value : data);
      }

    }.bind(this));
  },

  write: function(name, data, opts, doneCallback) {
    if (typeof opts === 'function') {
      doneCallback = opts;
      opts = {};
    }

    var ttl = this.resolveTTL(opts);
    var cacheKey = this.cacheKey(name);
    var payload = JSON.stringify({value: data});

    this.storage.setex(
      cacheKey,
      ttl,
      payload,
      this._newDoneCallback('write', doneCallback)
    );
  },

  delete: function(name, doneCallback) {
    var cacheKey = this.cacheKey(name);
    this.storage.del(
      cacheKey,
      this._newDoneCallback('delete', doneCallback)
    );
  },

  /*
   * No op. Redis will automatically remove the expired keys.
   */
  cleanup: function(doneCallback) {
    if (doneCallback) doneCallback();
  },

  clear: function(doneCallback) {
    var clearPattern = this.cacheKey('*');
    this.storage.keys(clearPattern, function(err, keys) {

      if (err) {
        this._redisOnError('#clear ' + err);

      } else {
        this.storage.del(
          keys,
          this._newDoneCallback('clear', doneCallback)
        );
      }

    }.bind(this));
  },

  _newDoneCallback: function(operation, doneCallback) {
    return function(err) {
      if (err) {
        this._redisOnError('#' + operation + ' ' + err);

      } else {
        if (doneCallback) doneCallback();
      }
    }.bind(this)
  },

  _redisOnError: function(err) {
    if (this.logger) this.logger.error('[NodeRedisCacheStore] ' + err);
  }
});

module.exports = NodeRedisCacheStore;
<|endoftext|>


<commit_before>// noflo-component-access.js

var _ = require('underscore');
var noflo = require('noflo');

module.exports = facadeComponent;

/**
 * Create a noflo.Component facade from a given component. Access to the component
 * is limited to only select properties and functions. An rpf hash property is
 * also present for arbitrary assignments and is shared for all facades of the
 * same Component instance.
 * This facade mimic the main usage of http://noflojs.org/api/Component.html
 * Usage:
 *  var component = componentAccess(new noflo.Component());
 */
function facadeComponent(component) {
    if (component._noflo_access_facade)
        return component._noflo_access_facade;
    var facade = {};
    _.extend(facade, {
        inPorts: _.mapObject(_.pick(component.inPorts, isInPort), facadePort.bind(this, facade)),
        outPorts: _.mapObject(_.pick(component.outPorts, isOutPort), facadePort.bind(this, facade))
    }, facadeEventEmitter(component));
    return component._noflo_access_facade = facade;
}

/**
 * Create a noflo Port facade from a given noflo Port that includes only a limited
 * subset of properties and functions.
 * This facade mimic the main usage of http://noflojs.org/api/OutPort.html
 */
function facadePort(nodeInstance, port, name) {
    return _.extend({
        name: name,
        nodeInstance: nodeInstance,
        isMulti: port.isAddressable.bind(port)
    }, facadeFunctions(port,
        'isAddressable',
        'isRequired',
        'listAttached',
        'connect',
        'send',
        'disconnect'
    ), facadeEventEmitter(port));
}

/**
 * Returns a facade of the given EventEmitter. The result is not an EventEmitter,
 * but includes the same functions. Each function call is passed to the underlying
 * EventEmitter, while any provided functions are bound to the original context
 * when they are called. This gives the behaviour of an EventEmitter without
 * actually creating one with a distinct state.
 * @see https://nodejs.org/dist/latest-v4.x/docs/api/events.html
 */
function facadeEventEmitter(emitter) {
    return facadeFunctions(emitter,
        'emit',
        'on',
        'once',
        'removeListener'
    );
}

/**
 * Picks all the given functions from object and returns the set bounded to the
 * original object context. If any argument is passed to those functions that is
 * also a function it is bound to the called context.
 * @param object with functions, i.e. noflo.Component, noflo.InPort/OutPort
 * @param keys explicit names of functions to include in result
 */
function facadeFunctions(object /* keys */) {
    var keys = _.toArray(arguments).slice(1);
    return _.mapObject(_.pick(object, keys), function(fn) {
        return _.partial(applyFacade, fn, object)
    });
}

/**
 * If any of the arguments are functions, replace them with a bound version
 * before calling fn.
 * @this context that optional function arguments should be bound to
 * @param fn a function that might take other functions as arguments
 * @param context that fn should be called with
 */
function applyFacade(fn, context /* arguments */) {
    var args = _.toArray(arguments).slice(2);
    return fn.apply(context, _.map(args, facadeFunction, this));
}

/**
 * Binds the given function to this context or returns the arguments if not a
 * function. This function will return the same result for repeated calls.
 * @this context to bind the function to
 * @param fn a function to bind
 */
function facadeFunction(fn) {
    if (!_.isFunction(fn)) return fn;
    if (!_.isArray(fn._bind_facade_noflo_access)) {
        fn._bind_facade_noflo_access = [];
    }
    var idx = fn._bind_facade_noflo_access.indexOf(this);
    if (idx >= 0) return fn._bind_facade_noflo_access[idx + 1];
    fn._bind_facade_noflo_access.push(this, fn.bind(this));
    return _.last(fn._bind_facade_noflo_access);
}

/**
 * Tests if the given object is a noflo.InPort
 * @param port a noflo.InPort if the result is true
 */
function isInPort(port) {
    return port instanceof noflo.InPort;
}

/**
 * Tests if the given object is a noflo.OutPort
 * @param port a noflo.OutPort if the result is true
 */
function isOutPort(port) {
    return port instanceof noflo.OutPort;
}
<commit_msg>Add getComponentIdOn to noflo-access<commit_after>// noflo-component-access.js

var _ = require('underscore');
var noflo = require('noflo');

module.exports = facadeComponent;

/**
 * Create a noflo.Component facade from a given component. Access to the component
 * is limited to only select properties and functions. An rpf hash property is
 * also present for arbitrary assignments and is shared for all facades of the
 * same Component instance.
 * This facade mimic the main usage of http://noflojs.org/api/Component.html
 * Usage:
 *  var component = componentAccess(new noflo.Component());
 */
function facadeComponent(component) {
    if (component._noflo_access_facade)
        return component._noflo_access_facade;
    var facade = {};
    _.extend(facade, {
        inPorts: _.mapObject(_.pick(component.inPorts, isInPort), facadePort.bind(this, facade)),
        outPorts: _.mapObject(_.pick(component.outPorts, isOutPort), facadePort.bind(this, facade))
    }, facadeEventEmitter(component));
    return component._noflo_access_facade = facade;
}

/**
 * Create a noflo Port facade from a given noflo Port that includes only a limited
 * subset of properties and functions.
 * This facade mimic the main usage of http://noflojs.org/api/OutPort.html
 */
function facadePort(nodeInstance, port, name) {
    return _.extend({
        name: name,
        nodeInstance: nodeInstance,
        isMulti: port.isAddressable.bind(port),
        getComponentIdOn: function(socketIndex) {
            if (port.sockets[socketIndex] && port.sockets[socketIndex].from)
                return port.sockets[socketIndex].from.process.id;
        },
        getComponentPortNameOn: function(socketIndex) {
            if (port.sockets[socketIndex] && port.sockets[socketIndex].from)
                return port.sockets[socketIndex].from.port;
        }
    }, facadeFunctions(port,
        'isAddressable',
        'isRequired',
        'listAttached',
        'connect',
        'send',
        'disconnect'
    ), facadeEventEmitter(port));
}

/**
 * Returns a facade of the given EventEmitter. The result is not an EventEmitter,
 * but includes the same functions. Each function call is passed to the underlying
 * EventEmitter, while any provided functions are bound to the original context
 * when they are called. This gives the behaviour of an EventEmitter without
 * actually creating one with a distinct state.
 * @see https://nodejs.org/dist/latest-v4.x/docs/api/events.html
 */
function facadeEventEmitter(emitter) {
    return facadeFunctions(emitter,
        'emit',
        'on',
        'once',
        'removeListener'
    );
}

/**
 * Picks all the given functions from object and returns the set bounded to the
 * original object context. If any argument is passed to those functions that is
 * also a function it is bound to the called context.
 * @param object with functions, i.e. noflo.Component, noflo.InPort/OutPort
 * @param keys explicit names of functions to include in result
 */
function facadeFunctions(object /* keys */) {
    var keys = _.toArray(arguments).slice(1);
    return _.mapObject(_.pick(object, keys), function(fn) {
        return _.partial(applyFacade, fn, object)
    });
}

/**
 * If any of the arguments are functions, replace them with a bound version
 * before calling fn.
 * @this context that optional function arguments should be bound to
 * @param fn a function that might take other functions as arguments
 * @param context that fn should be called with
 */
function applyFacade(fn, context /* arguments */) {
    var args = _.toArray(arguments).slice(2);
    return fn.apply(context, _.map(args, facadeFunction, this));
}

/**
 * Binds the given function to this context or returns the arguments if not a
 * function. This function will return the same result for repeated calls.
 * @this context to bind the function to
 * @param fn a function to bind
 */
function facadeFunction(fn) {
    if (!_.isFunction(fn)) return fn;
    if (!_.isArray(fn._bind_facade_noflo_access)) {
        fn._bind_facade_noflo_access = [];
    }
    var idx = fn._bind_facade_noflo_access.indexOf(this);
    if (idx >= 0) return fn._bind_facade_noflo_access[idx + 1];
    fn._bind_facade_noflo_access.push(this, fn.bind(this));
    return _.last(fn._bind_facade_noflo_access);
}

/**
 * Tests if the given object is a noflo.InPort
 * @param port a noflo.InPort if the result is true
 */
function isInPort(port) {
    return port instanceof noflo.InPort;
}

/**
 * Tests if the given object is a noflo.OutPort
 * @param port a noflo.OutPort if the result is true
 */
function isOutPort(port) {
    return port instanceof noflo.OutPort;
}
<|endoftext|>


<commit_before>var bouncy = require('bouncy');
var fs = require('fs');
var _ = require('underscore');
var configPath = '/var/www/pm2config.json';
var util = require('util');
var bounceVals = {
	bouncePort: -1,
	_404Port: -1,
	servers:[]
};
var bounceServer;

var user, port;
user = parseInt(process.env.NODEUSERID) || parseInt(process.argv[2]);
if(!user){
	console.error('no user specified, exiting');
	process.exit();
}
bounceVals.bouncePort = parseInt(process.env.NODESERVERPORT) || parseInt(process.argv[3]);
if(!bounceVals.bouncePort){
	console.error('no port specified, exiting');
	process.exit();
}

console.log('starting as user: ' + user);
deEscalate(user);
fs.readFile(configPath, 'utf8', function(err, data){
	if(err){
		console.error('Error: ' + err);
		process.exit();
	}

	try{
		data = JSON.parse(data);
	} catch(e) {
		console.error('error parsing JSON.config ' + e);
		process.exit();
	}
	//console.dir(data);
	checkJSON(data);
});

function checkJSON(iJSON){
	var validParse = true;

	//test bounce server config
	var bounceConfig = _.findWhere(iJSON, {name: 'bounce-server'});
	if(typeof(bounceConfig) === 'undefined'){
		validParse = false;
		console.error('bounce server must be declared in config file');
	}

	//test 404 server config
	var _404Config = _.findWhere(iJSON, {name: '404-server'});
	if(typeof(_404Config) === 'undefined'){
		validParse = false;
		console.error('404 server must be defined in config file');
	}else if(!_404Config.env || !_404Config.env.NODESERVERPORT){
		validParse = false;
		console.error('404 server must have a valid port number');
	}else{
		bounceVals._404Port = parseInt(_404Config.env.NODESERVERPORT);
	}

	//test destination servers
	_.each(_.reject(iJSON, function(elem){
			//don't parse bounce server or 404 server again, skip non bounce items
			return (elem.name == 'bounce-server' || elem.name == '404-server' || elem.bounce != true);
		}),
		function(elem, index){
			if(typeof(elem.name) === "undefined"){
				validParse = false;
				console.error('the server in ' + index + ' place has no name');
			}
			if(!elem.env || !isValidPort(elem.env.NODESERVERPORT)){
				validParse = false;
				console.error(elem.name + ' port is invalid');
			}else{
				//valid destination server
				_.each(elem.urls, function(url, index){
					bounceVals.servers.push({
						port: parseInt(elem.env.NODESERVERPORT),
						url: elem.urls[index]
					});
				}, elem);

				if(elem.urls.length < 1){
					//no URLs allowed but print a warning
					console.error(elem.name + ' has no defined URLs');
				}
			}
		}
	);

	console.log(util.inspect(bounceVals, {depth:null}));

	if(validParse == true){
		//console.log('config parsed successfully!');
		deEscalate();
		startBounceServer();
	}else{
		console.error('problem with config file, exiting');
		process.exit();
	}
}

function deEscalate(){
	//after reading config file (root level ownership) de-escalate user permissions
	try {
		process.setgid(user);
		process.setuid(user);
	} catch (e) {
		console.error('problem setting user/group, exiting');
		console.dir(e);
		process.exit();
	}
	console.log('user changed to: ' + user);
}

function startBounceServer(){
	bounceServer = bouncy(function(req, res, bounce){
		var foundMatch = false;
		var baseHost = trimAt(req.headers.host,':');
		console.log('incoming request for: ' + baseHost);
		console.log('incoming client IP addr: ' + getClientIP(req));
		for(var rep=0;rep<bounceVals.servers.length;rep++){
			//console.log(bounceVals.servers[rep].url);
			if(bounceVals.servers[rep].url == baseHost){
				console.log('loading ' + baseHost);
				bounce(bounceVals.servers[rep].port);
				foundMatch = true;
			}
		}
		if(foundMatch == false){
			console.log('404! ' + baseHost);
			bounce(bounceVals._404Port);
		}
	});

	//engage server
	console.log(bounceVals.bouncePort);
	bounceServer.listen(bounceVals.bouncePort);
	console.log('bounce listening at: ' + bounceVals.bouncePort);
}

function isNumber(iNum){
	return !isNaN(parseFloat(iNum)) && isFinite(iNum);
}

function isValidPort(iNum){
	return isNumber(iNum) && (iNum > 0) && (iNum < 65536) && (iNum = Math.floor(iNum));
}

function trimAt(iString, iChar){
	if(typeof iString === 'undefined') {return ('null string');}
	var pos = iString.indexOf(iChar);
	if(pos > -1){
		return iString.substring(0, pos);
	}
	return iString;
}

function getClientIP(req){
	var ipAddress = req.headers['x-forwarded-for'];
	if(ipAddress) {return ipAddress[0];}
	else {ipAddress = req.connection.remoteAddress;}
	return ipAddress;
}
<commit_msg>reorganized functions<commit_after>var bouncy = require('bouncy');
var fs = require('fs');
var _ = require('underscore');
var configPath = '/var/www/pm2config.json';
var util = require('util');
var bounceVals = {
	bouncePort: -1,
	_404Port: -1,
	servers:[]
};
var bounceServer;

var user, port;
user = parseInt(process.env.NODEUSERID) || parseInt(process.argv[2]);
if(!user){
	console.error('no user specified, exiting');
	process.exit();
}
bounceVals.bouncePort = parseInt(process.env.NODESERVERPORT) || parseInt(process.argv[3]);
if(!bounceVals.bouncePort){
	console.error('no port specified, exiting');
	process.exit();
}

console.log('starting as user: ' + user);

deEscalate(user);

fs.readFile(configPath, 'utf8', function(err, data){
	if(err){
		console.error('Error: ' + err);
		process.exit();
	}

	try{
		data = JSON.parse(data);
	} catch(e) {
		console.error('error parsing JSON.config ' + e);
		process.exit();
	}
	//console.dir(data);
	checkJSON(data);
});

function deEscalate(){
	//after reading config file (root level ownership) de-escalate user permissions
	try {
		process.setgid(user);
		process.setuid(user);
	} catch (e) {
		console.error('problem setting user/group, exiting');
		console.dir(e);
		process.exit();
	}
	console.log('user changed to: ' + user);
}

function checkJSON(iJSON){
	var validParse = true;

	//test bounce server config
	var bounceConfig = _.findWhere(iJSON, {name: 'bounce-server'});
	if(typeof(bounceConfig) === 'undefined'){
		validParse = false;
		console.error('bounce server must be declared in config file');
	}

	//test 404 server config
	var _404Config = _.findWhere(iJSON, {name: '404-server'});
	if(typeof(_404Config) === 'undefined'){
		validParse = false;
		console.error('404 server must be defined in config file');
	}else if(!_404Config.env || !_404Config.env.NODESERVERPORT){
		validParse = false;
		console.error('404 server must have a valid port number');
	}else{
		bounceVals._404Port = parseInt(_404Config.env.NODESERVERPORT);
	}

	//test destination servers
	_.each(_.reject(iJSON, function(elem){
			//don't parse bounce server or 404 server again, skip non bounce items
			return (elem.name == 'bounce-server' || elem.name == '404-server' || elem.bounce != true);
		}),
		function(elem, index){
			if(typeof(elem.name) === "undefined"){
				validParse = false;
				console.error('the server in ' + index + ' place has no name');
			}
			if(!elem.env || !isValidPort(elem.env.NODESERVERPORT)){
				validParse = false;
				console.error(elem.name + ' port is invalid');
			}else{
				//valid destination server
				_.each(elem.urls, function(url, index){
					bounceVals.servers.push({
						port: parseInt(elem.env.NODESERVERPORT),
						url: elem.urls[index]
					});
				}, elem);

				if(elem.urls.length < 1){
					//no URLs allowed but print a warning
					console.error(elem.name + ' has no defined URLs');
				}
			}
		}
	);

	console.log(util.inspect(bounceVals, {depth:null}));

	if(validParse == true){
		//console.log('config parsed successfully!');
		deEscalate();
		startBounceServer();
	}else{
		console.error('problem with config file, exiting');
		process.exit();
	}
}

function startBounceServer(){
	bounceServer = bouncy(function(req, res, bounce){
		var foundMatch = false;
		var baseHost = trimAt(req.headers.host,':');
		console.log('incoming request for: ' + baseHost);
		console.log('incoming client IP addr: ' + getClientIP(req));
		for(var rep=0;rep<bounceVals.servers.length;rep++){
			//console.log(bounceVals.servers[rep].url);
			if(bounceVals.servers[rep].url == baseHost){
				console.log('loading ' + baseHost);
				bounce(bounceVals.servers[rep].port);
				foundMatch = true;
			}
		}
		if(foundMatch == false){
			console.log('404! ' + baseHost);
			bounce(bounceVals._404Port);
		}
	});

	//engage server
	console.log(bounceVals.bouncePort);
	bounceServer.listen(bounceVals.bouncePort);
	console.log('bounce listening at: ' + bounceVals.bouncePort);
}

function isNumber(iNum){
	return !isNaN(parseFloat(iNum)) && isFinite(iNum);
}

function isValidPort(iNum){
	return isNumber(iNum) && (iNum > 0) && (iNum < 65536) && (iNum = Math.floor(iNum));
}

function trimAt(iString, iChar){
	if(typeof iString === 'undefined') {return ('null string');}
	var pos = iString.indexOf(iChar);
	if(pos > -1){
		return iString.substring(0, pos);
	}
	return iString;
}

function getClientIP(req){
	var ipAddress = req.headers['x-forwarded-for'];
	if(ipAddress) {return ipAddress[0];}
	else {ipAddress = req.connection.remoteAddress;}
	return ipAddress;
}
<|endoftext|>