<commit_before>exports.run = function (client, msg, args) {
    if (!config.prop.owners.includes(msg.author.id)) return msg.channel.createMessage({ embed: {
        color: config.options.embedColour,
        title: ':warning: Restricted Command',
        description: 'This command is locked to the developer only.'
    }});

    try {
        let code = eval(args.join(' '));

        if (typeof code !== 'string')
            code = require('util').inspect(code, { depth:0 });

        code = code.replace(new RegExp(client.token.slice(4), 'gi'), '*');
        msg.channel.createMessage(`\`\`\`js\n${code}\n\`\`\``);
    } catch(e) {
        msg.channel.createMessage(`\`\`\`js\n${e}\n\`\`\``);
    }
};

exports.usage = {
    main: '{prefix}{command}',
    args: '',
    description: 'Developer command'
};
<commit_msg>Missing space<commit_after>exports.run = function (client, msg, args) {
    if (!config.prop.owners.includes(msg.author.id)) return msg.channel.createMessage({ embed: {
        color: config.options.embedColour,
        title: ':warning: Restricted Command',
        description: 'This command is locked to the developer only.'
    }});

    try {
        let code = eval(args.join(' '));

        if (typeof code !== 'string')
            code = require('util').inspect(code, { depth: 0 });

        code = code.replace(new RegExp(client.token.slice(4), 'gi'), '*');
        msg.channel.createMessage(`\`\`\`js\n${code}\n\`\`\``);
    } catch(e) {
        msg.channel.createMessage(`\`\`\`js\n${e}\n\`\`\``);
    }
};

exports.usage = {
    main: '{prefix}{command}',
    args: '',
    description: 'Developer command'
};
<|endoftext|>


<commit_before>'use strict';

angular.module('kiwiWeb', [
  'ngSanitize',
  'ui.router',
  'pascalprecht.translate'
])
.config(function ($stateProvider, $urlRouterProvider) {

  //delete $httpProvider.defaults.headers.common['X-Requested-With'];

  $urlRouterProvider.otherwise('/');

  $stateProvider
  .state('main', {
    url: '/',
    templateUrl: 'app/main/main.tpl.html',
    controller:'MainCtrl'
  })
  .state('/landing', {
    url: '/landing',
    templateUrl: 'app/landing/landing.tpl.html',
    controller:'MainCtrl'
  });
});
<commit_msg>Sets Spanish as default language.<commit_after>'use strict';

angular.module('kiwiWeb', [
  'ngSanitize',
  'ui.router',
  'pascalprecht.translate'
])
.config(function ($stateProvider, $urlRouterProvider, $translateProvider) {

  //delete $httpProvider.defaults.headers.common['X-Requested-With'];

  $translateProvider.preferredLanguage('es');

  $urlRouterProvider.otherwise('/');

  $stateProvider
  .state('main', {
    url: '/',
    templateUrl: 'app/main/main.tpl.html',
    controller:'MainCtrl'
  })
  .state('/landing', {
    url: '/landing',
    templateUrl: 'app/landing/landing.tpl.html',
    controller:'MainCtrl'
  });
});
<|endoftext|>


<commit_before>
var path = require('path')
  ;

exports.require = function _require(what) {
  if (process.env.npm_config_coverage || process.env.BUILDTYPE === 'Coverage') {
    return require(path.join('../../build/Coverage/lib/' + what))
  }
  return require('../../lib/' + what)
}

var mdns  = exports.require('mdns');

var legal_chars = "abcefghijklmnopqrstuvwxyz"

exports.suffixedServiceType = function suffixedServiceType(serviceName, protocol) {
  serviceName += '-';
  while (serviceName.length < 14) {
    serviceName += legal_chars[Math.floor(Math.random() * legal_chars.length)];
  }
  return mdns.makeServiceType.apply(this, arguments)
}

exports.runTestAd = function runTestAd(type, port, uptime, cooltime, cb) {
  if ( ! cb ) {
    cb = cooltime;
    cooltime = uptime;
  }
  if ( ! cb) {
    cb = cooltime;
    uptime = cooltime = 2000;
  }
  var ad = new mdns.createAdvertisement(type, port, function(error, service) {
        if (error) throw error;
        setTimeout(function() { ad.stop(); setTimeout(cb, cooltime); }, uptime);
      });
  ad.start();
  return ad;
}

if (0) {
exports.lifeExpectancy = 5000;

var lifeTimer;

process.nextTick(function() {
  lifeTimer = setTimeout(function() {
    throw new Error("test did not finish within " + (exports.lifeExpectancy / 1000) + " seconds.");
    process.exit(-1);
  }, exports.lifeExpectancy);
});

exports.done = function done() { clearTimeout( lifeTimer ); }
}
<commit_msg>restore numbers removed mistakenly.<commit_after>
var path = require('path')
  ;

exports.require = function _require(what) {
  if (process.env.npm_config_coverage || process.env.BUILDTYPE === 'Coverage') {
    return require(path.join('../../build/Coverage/lib/' + what))
  }
  return require('../../lib/' + what)
}

var mdns  = exports.require('mdns');

var legal_chars = "abcefghijklmnopqrstuvwxyz0123456789"

exports.suffixedServiceType = function suffixedServiceType(serviceName, protocol) {
  serviceName += '-';
  while (serviceName.length < 14) {
    serviceName += legal_chars[Math.floor(Math.random() * legal_chars.length)];
  }
  return mdns.makeServiceType.apply(this, arguments)
}

exports.runTestAd = function runTestAd(type, port, uptime, cooltime, cb) {
  if ( ! cb ) {
    cb = cooltime;
    cooltime = uptime;
  }
  if ( ! cb) {
    cb = cooltime;
    uptime = cooltime = 2000;
  }
  var ad = new mdns.createAdvertisement(type, port, function(error, service) {
        if (error) throw error;
        setTimeout(function() { ad.stop(); setTimeout(cb, cooltime); }, uptime);
      });
  ad.start();
  return ad;
}

if (0) {
exports.lifeExpectancy = 5000;

var lifeTimer;

process.nextTick(function() {
  lifeTimer = setTimeout(function() {
    throw new Error("test did not finish within " + (exports.lifeExpectancy / 1000) + " seconds.");
    process.exit(-1);
  }, exports.lifeExpectancy);
});

exports.done = function done() { clearTimeout( lifeTimer ); }
}
<|endoftext|>


<commit_before>/**
 * signalFactory will generate an object containing signals functions.
 *
 * It will have a default function to fire, but an optional overriding
 * curry function can be used instead of the default.
 *
 * @param  {Array} names An array of camelcased names
 * @param  {Function, optional} curryFnc A curry function. The name from the names array will be passed to it to generate a function
 * @param  {Object, optional} stack The object that will contain all the signal functions. One will be generated if not passed as an argument
 * @return {Object} Return the stack object containing all the functions.
 */
exports = module.exports = function(names, curryFnc, stack){
    if (!Type.isFunction(curryFnc)) {
        stack = curryFnc;
        curryFnc = undefined;
    }

    stack = stack || {};

    Array.from(names).each(function(name){
        stack['signal' + name.capitalize()] = curryFnc ? curryFnc(name) : function(){
            !this.isSilent() && this.fireEvent(name, this);
            return this;
        };
    });

    return stack;
};<commit_msg>Parse colon separated names.<commit_after>/**
 * signalFactory will generate an object containing signals functions.
 *
 * It will have a default function to fire, but an optional overriding
 * curry function can be used instead of the default.
 *
 * @param  {Array} names An array of camelcased names
 * @param  {Function, optional} curryFnc A curry function. The name from the names array will be passed to it to generate a function
 * @param  {Object, optional} stack The object that will contain all the signal functions. One will be generated if not passed as an argument
 * @return {Object} Return the stack object containing all the functions.
 */
var prefix = 'signal',
    hyphen = '-',
    colon = ':';

exports = module.exports = function(names, curryFnc, stack){
    if (!Type.isFunction(curryFnc)) {
        stack = curryFnc;
        curryFnc = undefined;
    }

    stack = stack || {};

    Array.from(names).each(function(name){
        // Convert name to camelCase
        var property = (prefix + hyphen + name.replace(colon, hyphen)).camelCase();

        stack[property] = curryFnc ? curryFnc(name) : function(){
            !this.isSilent() && this.fireEvent(name, [this]);
            return this;
        };
    });

    return stack;
};<|endoftext|>


<commit_before>/* eslint-disable no-process-env */
'use strict';

const prefix = 'REST_ON_COUCH_';
const debug = require('../util/debug')('roc:env-config');

const envConfig = {};
for (const name in process.env) {
  if (name.startsWith(prefix)) {
    const realName = name
      .substring(prefix.length)
      .toLowerCase()
      .replace(/_([a-z])/g, function (value) {
        return value[1].toUpperCase();
      });
    debug('setting config from env, %s: %s', realName, process.env[name]);
    envConfig[realName] = process.env[name];
  }
}

module.exports = envConfig;
<commit_msg>fix: convert "true" and "false" env values to booleans (#239)<commit_after>/* eslint-disable no-process-env */
'use strict';

const prefix = 'REST_ON_COUCH_';
const debug = require('../util/debug')('roc:env-config');

const envConfig = {};
for (const name in process.env) {
  if (name.startsWith(prefix)) {
    const realName = name
      .substring(prefix.length)
      .toLowerCase()
      .replace(/_([a-z])/g, function (value) {
        return value[1].toUpperCase();
      });

    let envValue = process.env[name];
    if (envValue === 'true') {
      envValue = true;
    } else if (envValue === 'false') {
      envValue = false;
    }

    debug('setting config from env, %s: %s', realName, envValue);
    envConfig[realName] = envValue;
  }
}

module.exports = envConfig;
<|endoftext|>


<commit_before>/**
 * EveSpriteSet
 * @property {string} name
 * @property {Array.<EveSpriteSetItem>} sprites
 * @property {Tw2Effect} effect
 * @property {boolean} display
 * @property {number} _time
 * @property {WebGlBuffer} _vertexBuffer
 * @property {WebGlBuffer} _indexBuffer
 * @property {Tw2VertexDeclaration} _decl
 * @constructor
 */
function EveSpriteSet()
{
    this.name = '';
    this.sprites = [];
    this.effect = null;
    this.display = true;
    this._time = 0;

    this._vertexBuffer = null;
    this._indexBuffer = null;
    this._decl = new Tw2VertexDeclaration();
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 5, device.gl.FLOAT, 2, 0));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_POSITION, 0, device.gl.FLOAT, 3, 8));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_COLOR, 0, device.gl.FLOAT, 3, 20));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 0, device.gl.FLOAT, 1, 32));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 1, device.gl.FLOAT, 1, 36));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 2, device.gl.FLOAT, 1, 40));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 3, device.gl.FLOAT, 1, 44));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 4, device.gl.FLOAT, 1, 48));
    this._decl.RebuildHash();
}

/**
 * Initializes the sprite set
 */
EveSpriteSet.prototype.Initialize = function()
{
    this.RebuildBuffers();
};

/**
 * Rebuilds the sprite set's buffers
 */
EveSpriteSet.prototype.RebuildBuffers = function()
{
    var vertexSize = 13;
    var array = new Float32Array(this.sprites.length * 4 * vertexSize);
    for (var i = 0; i < this.sprites.length; ++i)
    {
        var offset = i * 4 * vertexSize;
        array[offset + 0 * vertexSize] = 0;
        array[offset + 1 * vertexSize] = 1;
        array[offset + 2 * vertexSize] = 2;
        array[offset + 3 * vertexSize] = 3;
        for (var j = 0; j < 4; ++j)
        {
            var vtxOffset = offset + j * vertexSize;
            array[vtxOffset + 1] = this.sprites[i].boneIndex;
            array[vtxOffset + 2] = this.sprites[i].position[0];
            array[vtxOffset + 3] = this.sprites[i].position[1];
            array[vtxOffset + 4] = this.sprites[i].position[2];
            array[vtxOffset + 5] = this.sprites[i].color[0];
            array[vtxOffset + 6] = this.sprites[i].color[1];
            array[vtxOffset + 7] = this.sprites[i].color[2];
            array[vtxOffset + 8] = this.sprites[i].blinkPhase;
            array[vtxOffset + 9] = this.sprites[i].blinkRate;
            array[vtxOffset + 10] = this.sprites[i].minScale;
            array[vtxOffset + 11] = this.sprites[i].maxScale;
            array[vtxOffset + 12] = this.sprites[i].falloff;
        }
    }
    this._vertexBuffer = device.gl.createBuffer();
    device.gl.bindBuffer(device.gl.ARRAY_BUFFER, this._vertexBuffer);
    device.gl.bufferData(device.gl.ARRAY_BUFFER, array, device.gl.STATIC_DRAW);
    device.gl.bindBuffer(device.gl.ARRAY_BUFFER, null);

    var indexes = new Uint16Array(this.sprites.length * 6);
    for (var i = 0; i < this.sprites.length; ++i)
    {
        var offset = i * 6;
        var vtxOffset = i * 4;
        indexes[offset] = vtxOffset;
        indexes[offset + 1] = vtxOffset + 2;
        indexes[offset + 2] = vtxOffset + 1;
        indexes[offset + 3] = vtxOffset + 0;
        indexes[offset + 4] = vtxOffset + 3;
        indexes[offset + 5] = vtxOffset + 2;
    }
    this._indexBuffer = device.gl.createBuffer();
    device.gl.bindBuffer(device.gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
    device.gl.bufferData(device.gl.ELEMENT_ARRAY_BUFFER, indexes, device.gl.STATIC_DRAW);
    device.gl.bindBuffer(device.gl.ELEMENT_ARRAY_BUFFER, null);
    this._indexBuffer.count = this.sprites.length * 6;
};

/**
 * Sprite set render batch
 * @inherits Tw2RenderBatch
 * @constructor
 */
function EveSpriteSetBatch()
{
    this._super.constructor.call(this);
    this.spriteSet = null;
}

/**
 * Commits the sprite set
 * @param {Tw2Effect} overrideEffect
 */
EveSpriteSetBatch.prototype.Commit = function(overrideEffect)
{
    this.spriteSet.Render(overrideEffect);
};

Inherit(EveSpriteSetBatch, Tw2RenderBatch);

/**
 * Gets render batches
 * @param {RenderMode} mode
 * @param {Tw2BatchAccumulator} accumulator
 * @param {Tw2PerObjectData} perObjectData
 */
EveSpriteSet.prototype.GetBatches = function(mode, accumulator, perObjectData)
{
    if (this.display && mode == device.RM_ADDITIVE)
    {
        var batch = new EveSpriteSetBatch();
        batch.renderMode = device.RM_ADDITIVE;
        batch.spriteSet = this;
        batch.perObjectData = perObjectData;
        accumulator.Commit(batch);
    }
};

/**
 * Renders the sprite set
 * @param {Tw2Effect} overrideEffect
 */
EveSpriteSet.prototype.Render = function(overrideEffect)
{
    var effect = (!overrideEffect) ? this.effect : overrideEffect;
    if (!effect || !this._vertexBuffer)
    {
        return;
    }
    var effectRes = effect.GetEffectRes();
    if (!effectRes.IsGood())
    {
        return;
    }
    device.SetStandardStates(device.RM_ADDITIVE);

    device.gl.bindBuffer(device.gl.ARRAY_BUFFER, this._vertexBuffer);
    device.gl.bindBuffer(device.gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);

    for (var pass = 0; pass < effect.GetPassCount(); ++pass)
    {
        effect.ApplyPass(pass);
        if (!this._decl.SetDeclaration(effect.GetPassInput(pass), 52))
        {
            return;
        }
        device.ApplyShadowState();
        device.gl.drawElements(device.gl.TRIANGLES, this._indexBuffer.count, device.gl.UNSIGNED_SHORT, 0);
    }
};

/**
 * Per frame update
 * @param {number} dt - Delta time
 */
EveSpriteSet.prototype.Update = function(dt)
{
    this._time += dt;
};

/**
 * Clears the sprite set's sprites
 */
EveSpriteSet.prototype.Clear = function()
{
    this.sprites = [];
};

/**
 * Adds a sprite set item to the sprite set
 * @param {vec3} pos
 * @param {number} blinkRate
 * @param {number} blinkPhase
 * @param {number} minScale
 * @param {number} maxScale
 * @param {number} falloff
 * @param {quat4} color
 * @constructor
 */
EveSpriteSet.prototype.Add = function(pos, blinkRate, blinkPhase, minScale, maxScale, falloff, color)
{
    var item = new EveSpriteSetItem();
    item.position = vec3.create(pos);
    item.blinkRate = blinkRate;
    item.blinkPhase = blinkPhase;
    item.minScale = minScale;
    item.maxScale = maxScale;
    item.falloff = falloff;
    item.color = quat4.create(color);
    this.sprites[this.sprites.length] = item;
};

/**
 * EveSpriteSetItem
 * @property {string} name
 * @property {vec3} position
 * @property {number} blinkRate
 * @property {number} minScale
 * @property {number} falloff
 * @property {quat4} color
 * @property {number} boneIndex
 * @property {number} groupIndex
 * @constructor
 */
function EveSpriteSetItem()
{
    this.name = '';
    this.position = vec3.create();
    this.blinkRate = 0;
    this.blinkPhase = 0;
    this.minScale = 1;
    this.maxScale = 1;
    this.falloff = 0;
    this.color = quat4.create();
    this.boneIndex = 0;
    this.groupIndex = -1;
}
<commit_msg>Added `display` option to EveSpriteSetItem<commit_after>/**
 * EveSpriteSet
 * @property {string} name
 * @property {Array.<EveSpriteSetItem>} sprites
 * @property {Tw2Effect} effect
 * @property {boolean} display
 * @property {number} _time
 * @property {WebGlBuffer} _vertexBuffer
 * @property {WebGlBuffer} _indexBuffer
 * @property {Tw2VertexDeclaration} _decl
 * @constructor
 */
function EveSpriteSet()
{
    this.name = '';
    this.sprites = [];
    this.effect = null;
    this.display = true;
    this._time = 0;

    this._vertexBuffer = null;
    this._indexBuffer = null;
    this._decl = new Tw2VertexDeclaration();
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 5, device.gl.FLOAT, 2, 0));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_POSITION, 0, device.gl.FLOAT, 3, 8));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_COLOR, 0, device.gl.FLOAT, 3, 20));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 0, device.gl.FLOAT, 1, 32));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 1, device.gl.FLOAT, 1, 36));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 2, device.gl.FLOAT, 1, 40));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 3, device.gl.FLOAT, 1, 44));
    this._decl.elements.push(new Tw2VertexElement(Tw2VertexDeclaration.DECL_TEXCOORD, 4, device.gl.FLOAT, 1, 48));
    this._decl.RebuildHash();
}

/**
 * Initializes the sprite set
 */
EveSpriteSet.prototype.Initialize = function()
{
    this.RebuildBuffers();
};

/**
 * Rebuilds the sprite set's buffers
 */
EveSpriteSet.prototype.RebuildBuffers = function()
{
    var vertexSize = 13;
    var visibleItems = [];
    for (var i = 0; i < this.sprites.length; i++)
    {
        if (this.sprites[i].display)
        {
            visibleItems.push(this.sprites[i]);
        }
    }

    var array = new Float32Array(visibleItems.length * 4 * vertexSize);
    for (var i = 0; i < visibleItems.length; ++i)
    {
        var offset = i * 4 * vertexSize;
        array[offset + 0 * vertexSize] = 0;
        array[offset + 1 * vertexSize] = 1;
        array[offset + 2 * vertexSize] = 2;
        array[offset + 3 * vertexSize] = 3;
        for (var j = 0; j < 4; ++j)
        {
            var vtxOffset = offset + j * vertexSize;
            array[vtxOffset + 1] = visibleItems[i].boneIndex;
            array[vtxOffset + 2] = visibleItems[i].position[0];
            array[vtxOffset + 3] = visibleItems[i].position[1];
            array[vtxOffset + 4] = visibleItems[i].position[2];
            array[vtxOffset + 5] = visibleItems[i].color[0];
            array[vtxOffset + 6] = visibleItems[i].color[1];
            array[vtxOffset + 7] = visibleItems[i].color[2];
            array[vtxOffset + 8] = visibleItems[i].blinkPhase;
            array[vtxOffset + 9] = visibleItems[i].blinkRate;
            array[vtxOffset + 10] = visibleItems[i].minScale;
            array[vtxOffset + 11] = visibleItems[i].maxScale;
            array[vtxOffset + 12] = visibleItems[i].falloff;
        }
    }
    this._vertexBuffer = device.gl.createBuffer();
    device.gl.bindBuffer(device.gl.ARRAY_BUFFER, this._vertexBuffer);
    device.gl.bufferData(device.gl.ARRAY_BUFFER, array, device.gl.STATIC_DRAW);
    device.gl.bindBuffer(device.gl.ARRAY_BUFFER, null);

    var indexes = new Uint16Array(visibleItems.length * 6);
    for (var i = 0; i < visibleItems.length; ++i)
    {
        var offset = i * 6;
        var vtxOffset = i * 4;
        indexes[offset] = vtxOffset;
        indexes[offset + 1] = vtxOffset + 2;
        indexes[offset + 2] = vtxOffset + 1;
        indexes[offset + 3] = vtxOffset + 0;
        indexes[offset + 4] = vtxOffset + 3;
        indexes[offset + 5] = vtxOffset + 2;
    }
    this._indexBuffer = device.gl.createBuffer();
    device.gl.bindBuffer(device.gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
    device.gl.bufferData(device.gl.ELEMENT_ARRAY_BUFFER, indexes, device.gl.STATIC_DRAW);
    device.gl.bindBuffer(device.gl.ELEMENT_ARRAY_BUFFER, null);
    this._indexBuffer.count = visibleItems.length * 6;
};

/**
 * Sprite set render batch
 * @inherits Tw2RenderBatch
 * @constructor
 */
function EveSpriteSetBatch()
{
    this._super.constructor.call(this);
    this.spriteSet = null;
}

/**
 * Commits the sprite set
 * @param {Tw2Effect} overrideEffect
 */
EveSpriteSetBatch.prototype.Commit = function(overrideEffect)
{
    this.spriteSet.Render(overrideEffect);
};

Inherit(EveSpriteSetBatch, Tw2RenderBatch);

/**
 * Gets render batches
 * @param {RenderMode} mode
 * @param {Tw2BatchAccumulator} accumulator
 * @param {Tw2PerObjectData} perObjectData
 */
EveSpriteSet.prototype.GetBatches = function(mode, accumulator, perObjectData)
{
    if (this.display && mode == device.RM_ADDITIVE)
    {
        var batch = new EveSpriteSetBatch();
        batch.renderMode = device.RM_ADDITIVE;
        batch.spriteSet = this;
        batch.perObjectData = perObjectData;
        accumulator.Commit(batch);
    }
};

/**
 * Renders the sprite set
 * @param {Tw2Effect} overrideEffect
 */
EveSpriteSet.prototype.Render = function(overrideEffect)
{
    var effect = typeof(overrideEffect) == 'undefined' ? this.effect : overrideEffect;
    if (!effect || !this._vertexBuffer)
    {
        return;
    }
    var effectRes = effect.GetEffectRes();
    if (!effectRes.IsGood())
    {
        return;
    }
    device.SetStandardStates(device.RM_ADDITIVE);

    device.gl.bindBuffer(device.gl.ARRAY_BUFFER, this._vertexBuffer);
    device.gl.bindBuffer(device.gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);

    for (var pass = 0; pass < effect.GetPassCount(); ++pass)
    {
        effect.ApplyPass(pass);
        if (!this._decl.SetDeclaration(effect.GetPassInput(pass), 52))
        {
            return;
        }
        device.ApplyShadowState();
        device.gl.drawElements(device.gl.TRIANGLES, this._indexBuffer.count, device.gl.UNSIGNED_SHORT, 0);
    }
};

/**
 * Per frame update
 * @param {number} dt - Delta time
 */
EveSpriteSet.prototype.Update = function(dt)
{
    this._time += dt;
};

/**
 * Clears the sprite set's sprites
 */
EveSpriteSet.prototype.Clear = function()
{
    this.sprites = [];
};

/**
 * Adds a sprite set item to the sprite set
 * @param {vec3} pos
 * @param {number} blinkRate
 * @param {number} blinkPhase
 * @param {number} minScale
 * @param {number} maxScale
 * @param {number} falloff
 * @param {quat4} color
 * @constructor
 */
EveSpriteSet.prototype.Add = function(pos, blinkRate, blinkPhase, minScale, maxScale, falloff, color)
{
    var item = new EveSpriteSetItem();
    item.display = true;
    item.position = vec3.create(pos);
    item.blinkRate = blinkRate;
    item.blinkPhase = blinkPhase;
    item.minScale = minScale;
    item.maxScale = maxScale;
    item.falloff = falloff;
    item.color = quat4.create(color);
    this.sprites[this.sprites.length] = item;
};

/**
 * EveSpriteSetItem
 * @property {string} name
 * @property {vec3} position
 * @property {number} blinkRate
 * @property {number} minScale
 * @property {number} falloff
 * @property {quat4} color
 * @property {number} boneIndex
 * @property {number} groupIndex
 * @constructor
 */
function EveSpriteSetItem()
{
    this.display = true;
    this.name = '';
    this.position = vec3.create();
    this.blinkRate = 0;
    this.blinkPhase = 0;
    this.minScale = 1;
    this.maxScale = 1;
    this.falloff = 0;
    this.color = quat4.create();
    this.boneIndex = 0;
    this.groupIndex = -1;
}
<|endoftext|>


<commit_before>/*
 Preferences Library

 Usage:
   var value = window['piro.sakura.ne.jp'].prefs.getPref('my.extension.pref');
   window['piro.sakura.ne.jp'].prefs.setPref('my.extension.pref', true);
   window['piro.sakura.ne.jp'].prefs.clearPref('my.extension.pref');
   var listener = {
         domains : [
           'browser.tabs',
           'extensions.someextension'
         ],
         observe : function(aSubject, aTopic, aData)
         {
           if (aTopic != 'nsPref:changed') return;
           var value = window['piro.sakura.ne.jp'].prefs.getPref(aData);
         }
       };
   window['piro.sakura.ne.jp'].prefs.addPrefListener(listener);
   window['piro.sakura.ne.jp'].prefs.removePrefListener(listener);

 lisence: The MIT License, Copyright (c) 2009 SHIMODA "Piro" Hiroshi
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/license.txt
 original:
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/prefs.js
*/
(function() {
	const currentRevision = 2;

	if (!('piro.sakura.ne.jp' in window)) window['piro.sakura.ne.jp'] = {};

	var loadedRevision = 'prefs' in window['piro.sakura.ne.jp'] ?
			window['piro.sakura.ne.jp'].prefs.revision :
			0 ;
	if (loadedRevision && loadedRevision > currentRevision) {
		return;
	}

	window['piro.sakura.ne.jp'].prefs = {
		revision : currentRevision,

		get Prefs()
		{
			if (!this._Prefs) {
				this._Prefs = Components.classes['@mozilla.org/preferences;1'].getService(Components.interfaces.nsIPrefBranch);
			}
			return this._Prefs;
		},
		_Prefs : null,

		get DefaultPrefs()
		{
			if (!this._DefaultPrefs) {
				this._DefaultPrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getDefaultBranch(null);
			}
			return this._DefaultPrefs;
		},
		_DefaultPrefs : null,

		getPref : function(aPrefstring, aBranch)
		{
			if (!aBranch) aBranch = this.Prefs;
			try {
				switch (aBranch.getPrefType(aPrefstring))
				{
					case this.Prefs.PREF_STRING:
						return decodeURIComponent(escape(aBranch.getCharPref(aPrefstring)));
						break;
					case this.Prefs.PREF_INT:
						return aBranch.getIntPref(aPrefstring);
						break;
					default:
						return aBranch.getBoolPref(aPrefstring);
						break;
				}
			}
			catch(e) {
			}

			return null;
		},

		getDefaultPref : function(aPrefstring)
		{
			return this.getPref(aPrefstring, this.DefaultPrefs);
		},

		setPref : function(aPrefstring, aNewValue, aBranch)
		{
			if (!aBranch) aBranch = this.Prefs;

			var type;
			try {
				type = typeof aNewValue;
			}
			catch(e) {
				type = null;
			}

			switch (type)
			{
				case 'string':
					aBranch.setCharPref(aPrefstring, unescape(encodeURIComponent(aNewValue)));
					break;
				case 'number':
					aBranch.setIntPref(aPrefstring, parseInt(aNewValue));
					break;
				default:
					aBranch.setBoolPref(aPrefstring, aNewValue);
					break;
			}
			return true;
		},

		setDefaultPref : function(aPrefstring)
		{
			return this.setPref(aPrefstring, aNewValue, this.DefaultPrefs);
		},

		clearPref : function(aPrefstring)
		{
			try {
				this.Prefs.clearUserPref(aPrefstring);
			}
			catch(e) {
			}

			return;
		},

		addPrefListener : function(aObserver)
		{
			var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ;
			try {
				var pbi = this.Prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
				for (var i = 0; i < domains.length; i++)
					pbi.addObserver(domains[i], aObserver, false);
			}
			catch(e) {
			}
		},

		removePrefListener : function(aObserver)
		{
			var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ;
			try {
				var pbi = this.Prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
				for (var i = 0; i < domains.length; i++)
					pbi.removeObserver(domains[i], aObserver, false);
			}
			catch(e) {
			}
		}
	};
})();
<commit_msg>引数が足りていなかった<commit_after>/*
 Preferences Library

 Usage:
   var value = window['piro.sakura.ne.jp'].prefs.getPref('my.extension.pref');
   window['piro.sakura.ne.jp'].prefs.setPref('my.extension.pref', true);
   window['piro.sakura.ne.jp'].prefs.clearPref('my.extension.pref');
   var listener = {
         domains : [
           'browser.tabs',
           'extensions.someextension'
         ],
         observe : function(aSubject, aTopic, aData)
         {
           if (aTopic != 'nsPref:changed') return;
           var value = window['piro.sakura.ne.jp'].prefs.getPref(aData);
         }
       };
   window['piro.sakura.ne.jp'].prefs.addPrefListener(listener);
   window['piro.sakura.ne.jp'].prefs.removePrefListener(listener);

 lisence: The MIT License, Copyright (c) 2009 SHIMODA "Piro" Hiroshi
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/license.txt
 original:
   http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/prefs.js
*/
(function() {
	const currentRevision = 3;

	if (!('piro.sakura.ne.jp' in window)) window['piro.sakura.ne.jp'] = {};

	var loadedRevision = 'prefs' in window['piro.sakura.ne.jp'] ?
			window['piro.sakura.ne.jp'].prefs.revision :
			0 ;
	if (loadedRevision && loadedRevision > currentRevision) {
		return;
	}

	window['piro.sakura.ne.jp'].prefs = {
		revision : currentRevision,

		get Prefs()
		{
			if (!this._Prefs) {
				this._Prefs = Components.classes['@mozilla.org/preferences;1'].getService(Components.interfaces.nsIPrefBranch);
			}
			return this._Prefs;
		},
		_Prefs : null,

		get DefaultPrefs()
		{
			if (!this._DefaultPrefs) {
				this._DefaultPrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getDefaultBranch(null);
			}
			return this._DefaultPrefs;
		},
		_DefaultPrefs : null,

		getPref : function(aPrefstring, aBranch)
		{
			if (!aBranch) aBranch = this.Prefs;
			try {
				switch (aBranch.getPrefType(aPrefstring))
				{
					case this.Prefs.PREF_STRING:
						return decodeURIComponent(escape(aBranch.getCharPref(aPrefstring)));
						break;
					case this.Prefs.PREF_INT:
						return aBranch.getIntPref(aPrefstring);
						break;
					default:
						return aBranch.getBoolPref(aPrefstring);
						break;
				}
			}
			catch(e) {
			}

			return null;
		},

		getDefaultPref : function(aPrefstring)
		{
			return this.getPref(aPrefstring, this.DefaultPrefs);
		},

		setPref : function(aPrefstring, aNewValue, aBranch)
		{
			if (!aBranch) aBranch = this.Prefs;

			var type;
			try {
				type = typeof aNewValue;
			}
			catch(e) {
				type = null;
			}

			switch (type)
			{
				case 'string':
					aBranch.setCharPref(aPrefstring, unescape(encodeURIComponent(aNewValue)));
					break;
				case 'number':
					aBranch.setIntPref(aPrefstring, parseInt(aNewValue));
					break;
				default:
					aBranch.setBoolPref(aPrefstring, aNewValue);
					break;
			}
			return true;
		},

		setDefaultPref : function(aPrefstring, aNewValue)
		{
			return this.setPref(aPrefstring, aNewValue, this.DefaultPrefs);
		},

		clearPref : function(aPrefstring)
		{
			try {
				this.Prefs.clearUserPref(aPrefstring);
			}
			catch(e) {
			}

			return;
		},

		addPrefListener : function(aObserver)
		{
			var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ;
			try {
				var pbi = this.Prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
				for (var i = 0; i < domains.length; i++)
					pbi.addObserver(domains[i], aObserver, false);
			}
			catch(e) {
			}
		},

		removePrefListener : function(aObserver)
		{
			var domains = ('domains' in aObserver) ? aObserver.domains : [aObserver.domain] ;
			try {
				var pbi = this.Prefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
				for (var i = 0; i < domains.length; i++)
					pbi.removeObserver(domains[i], aObserver, false);
			}
			catch(e) {
			}
		}
	};
})();
<|endoftext|>


<commit_before>var net             = require('net');
var tls             = require('tls');
var util            = require('util');
var DuplexStream    = require('stream').Duplex;
var Socks           = require('socksjs');
var TerminatedStream = require('./terminatedstream');
var ircLineParser   = require('./irclineparser');
var iconv           = require('iconv-lite');
var _               = require('lodash');

function Connection(options) {
    DuplexStream.call(this, { readableObjectMode: true });

    this.options = options || {};

    this.connected = false;
    this.requested_disconnect = false;

    this.auto_reconnect = options.auto_reconnect || false;
    this.reconnect_attempts = 0;

    // When an IRC connection was successfully registered.
    this.registered = false;

    this.read_buffer = [];
    this.reading_buffer = false;

    this.read_command_buffer = [];

    this.localAddress = this.options.localAddress;

    // Buffers for data sent from the IRCd
    this.hold_last = false;
    this.held_data = null;

    this._timers = [];
}

util.inherits(Connection, DuplexStream);

module.exports = Connection;

Connection.prototype.registeredSuccessfully = function() {
    this.registered = Date.now();
};

Connection.prototype.connect = function() {
    var that = this;
    var socket_connect_event_name = 'connect';
    var options = this.options;
    var last_socket_error;
    var outgoing_addr = that.localAddress || '0.0.0.0';
    var ircd_host = options.host;
    var ircd_port = options.port || 6667;

    this.disposeSocket();
    this.requested_disconnect = false;

    if (!options.encoding || !this.setEncoding(options.encoding)) {
        this.setEncoding('utf8');
    }

    if (options.socks) {
        that.socket = Socks.connect({
            host: ircd_host,
            port: ircd_port,
            ssl: options.tls,
            rejectUnauthorized: options.rejectUnauthorized
        }, {
            host: options.socks.host,
            port: options.socks.port || 8080,
            user: options.socks.user,
            pass: options.socks.pass,
            localAddress: outgoing_addr
        });
    } else {
        if (options.tls || options.ssl) {
            that.socket = tls.connect({
                host: ircd_host,
                port: ircd_port,
                rejectUnauthorized: options.rejectUnauthorized,
                localAddress: outgoing_addr
            });

            socket_connect_event_name = 'secureConnect';

        } else {
            that.socket = net.connect({
                host: ircd_host,
                port: ircd_port,
                localAddress: outgoing_addr
            });

            socket_connect_event_name = 'connect';
        }
    }

    // We need the raw socket connect event.
    // node.js 0.12 no longer has a .socket property.
    (that.socket.socket || that.socket).on('connect', rawSocketConnect);
    that.socket.on(socket_connect_event_name, socketFullyConnected);

    // Called when the socket is connected and before any TLS handshaking if applicable.
    // This is when it's ideal to read socket pairs for identd.
    function rawSocketConnect() {
        that.emit('raw socket connected', (that.socket.socket || that.socket));
    }

    // Called when the socket is connected and ready to start sending/receiving data.
    function socketFullyConnected() {
        that.connected = true;
        last_socket_error = null;
        that.emit('socket connected');
    }

    that.socket.on('error', function socketErrorCb(err) {
        last_socket_error = err;
        that.emit('socket error', err);
    });

    // 1024 bytes is the maximum length of two RFC1459 IRC messages.
    // May need tweaking when IRCv3 message tags are more widespread
    that.socket.pipe(new TerminatedStream({max_buffer_size: 1024}))
        .on('data', (line) => {
            that.read_buffer.push(line);
            that.processReadBuffer();
        });

    that.socket.on('close', function socketCloseCb(had_error) {
        var was_connected = that.connected;
        var should_reconnect = false;
        var safely_registered = false;
        var registered_ms_ago = Date.now() - that.registered;

        // Some networks use aKills which kill a user after succesfully
        // registering instead of a ban, so we must wait some time after
        // being registered to be sure that we are connected properly.
        safely_registered = that.registered !== false && registered_ms_ago > 5000;

        that.connected = false;
        that.disposeSocket();
        that.clearTimers();

        that.emit('socket close', had_error);

        if (that.requested_disconnect || !that.auto_reconnect) {
            should_reconnect = false;

        // If trying to reconnect, continue with it
        } else if (that.reconnect_attempts && that.reconnect_attempts < 3) {
            should_reconnect = true;

        // If we were originally connected OK, reconnect
        } else if (was_connected && safely_registered) {
            should_reconnect = true;

        } else {
            should_reconnect = false;
        }

        if (should_reconnect) {
            that.reconnect_attempts++;
            that.emit('reconnecting');
        } else {
            that.emit('close', last_socket_error ? true : false);
            that.reconnect_attempts = 0;
        }

        if (should_reconnect) {
            that.setTimeout(function() {
                that.connect();
            }, 4000);
        }
    });
};

Connection.prototype._write = function(chunk, encoding, callback) {
    if (!this.connected) {
        return 0;
    }

    var encoded_buffer = iconv.encode(chunk + '\r\n', this.encoding);
    //console.log('Raw C:', chunk.toString());
    return this.socket.write(encoded_buffer, callback);
};

Connection.prototype._read = function() {
    var message;
    var continue_pushing = true;

    this._reading = true;

    while (continue_pushing && this.read_command_buffer.length > 0) {
        message = this.read_command_buffer.shift();
        continue_pushing = this.push(message);
        if (!continue_pushing) {
            this._reading = false;
        }
    }
};

Connection.prototype.pushCommandBuffer = function(command) {
    this.read_command_buffer.push(command);
    if (this._reading) {
        this._read();
    }
};

Connection.prototype.disposeSocket = function() {
    if (this.socket && this.connected) {
        this.requested_disconnect = true;
        this.socket.destroy();
    }

    if (this.socket) {
        this.socket.removeAllListeners();
        this.socket = null;
    }
};

/**
 * Create and keep track of all timers so they can be easily removed
 */
Connection.prototype.setTimeout = function(/*fn, length, argN */) {
    var that = this;
    var tmr = null;
    var callback = arguments[0];

    arguments[0] = function() {
       _.pull(that._timers, tmr);
       callback.apply(null, arguments);
    };

    tmr = setTimeout.apply(null, arguments);
    this._timers.push(tmr);
    return tmr;
};

Connection.prototype.clearTimeout = function(tmr) {
	clearTimeout(tmr);
	_.pull(this._timers, tmr);
};

Connection.prototype.clearTimers = function() {
    this._timers.forEach(function(tmr) {
        clearTimeout(tmr);
    });

    this._timers = [];
};

/**
 * Close the connection to the IRCd after forcing one last line
 */
Connection.prototype.end = function(data, callback) {
    var that = this;

    if (this.connected && data) {
        // Once the last bit of data has been sent, then re-run this function to close the socket
        this.write(data, function() {
            that.requested_disconnect = true;
            that.socket.end();
        });

        return;
    }

    this.disposeSocket();
};

/**
 * Set a new encoding for this connection
 * Return true in case of success
 */

Connection.prototype.setEncoding = function(encoding) {
    var encoded_test;

    try {
        encoded_test = iconv.encode('TEST', encoding);
        // This test is done to check if this encoding also supports
        // the ASCII charset required by the IRC protocols
        // (Avoid the use of base64 or incompatible encodings)
        if (encoded_test == 'TEST') { // jshint ignore:line
            this.encoding = encoding;
            return true;
        }
        return false;
    } catch (err) {
        return false;
    }
};

/**
 * Process the messages recieved from the IRCd that are buffered on an IrcConnection object
 * Will only process 4 lines per JS tick so that node can handle any other events while
 * handling a large buffer
 */
Connection.prototype.processReadBuffer = function(continue_processing) {
    // If we already have the read buffer being iterated through, don't start
    // another one.
    if (this.reading_buffer && !continue_processing) {
        return;
    }

    var lines_per_js_tick = 4;
    var processed_lines = 0;
    var line;
    var message;

    this.reading_buffer = true;

    while (processed_lines < lines_per_js_tick && this.read_buffer.length > 0) {
        line = iconv.decode(this.read_buffer.shift(), this.encoding);
        if (!line) {
            continue;
        }

        message = ircLineParser(line);

        if (!message) {
            // A malformed IRC line
            continue;
        }

        this.pushCommandBuffer(message);

        processed_lines++;
    }

    // If we still have items left in our buffer then continue reading them in a few ticks
    if (this.read_buffer.length > 0) {
        this.setTimeout(processReadBuffer.bind(this), 1, true);
    } else {
        this.reading_buffer = false;
    }
}
<commit_msg>Removing unused vars from connection.js<commit_after>var net             = require('net');
var tls             = require('tls');
var util            = require('util');
var DuplexStream    = require('stream').Duplex;
var Socks           = require('socksjs');
var TerminatedStream = require('./terminatedstream');
var ircLineParser   = require('./irclineparser');
var iconv           = require('iconv-lite');
var _               = require('lodash');

function Connection(options) {
    DuplexStream.call(this, { readableObjectMode: true });

    this.options = options || {};

    this.connected = false;
    this.requested_disconnect = false;

    this.auto_reconnect = options.auto_reconnect || false;
    this.reconnect_attempts = 0;

    // When an IRC connection was successfully registered.
    this.registered = false;

    this.read_buffer = [];
    this.reading_buffer = false;

    this.read_command_buffer = [];

    this.localAddress = this.options.localAddress;

    this._timers = [];
}

util.inherits(Connection, DuplexStream);

module.exports = Connection;

Connection.prototype.registeredSuccessfully = function() {
    this.registered = Date.now();
};

Connection.prototype.connect = function() {
    var that = this;
    var socket_connect_event_name = 'connect';
    var options = this.options;
    var last_socket_error;
    var outgoing_addr = that.localAddress || '0.0.0.0';
    var ircd_host = options.host;
    var ircd_port = options.port || 6667;

    this.disposeSocket();
    this.requested_disconnect = false;

    if (!options.encoding || !this.setEncoding(options.encoding)) {
        this.setEncoding('utf8');
    }

    if (options.socks) {
        that.socket = Socks.connect({
            host: ircd_host,
            port: ircd_port,
            ssl: options.tls,
            rejectUnauthorized: options.rejectUnauthorized
        }, {
            host: options.socks.host,
            port: options.socks.port || 8080,
            user: options.socks.user,
            pass: options.socks.pass,
            localAddress: outgoing_addr
        });
    } else {
        if (options.tls || options.ssl) {
            that.socket = tls.connect({
                host: ircd_host,
                port: ircd_port,
                rejectUnauthorized: options.rejectUnauthorized,
                localAddress: outgoing_addr
            });

            socket_connect_event_name = 'secureConnect';

        } else {
            that.socket = net.connect({
                host: ircd_host,
                port: ircd_port,
                localAddress: outgoing_addr
            });

            socket_connect_event_name = 'connect';
        }
    }

    // We need the raw socket connect event.
    // node.js 0.12 no longer has a .socket property.
    (that.socket.socket || that.socket).on('connect', rawSocketConnect);
    that.socket.on(socket_connect_event_name, socketFullyConnected);

    // Called when the socket is connected and before any TLS handshaking if applicable.
    // This is when it's ideal to read socket pairs for identd.
    function rawSocketConnect() {
        that.emit('raw socket connected', (that.socket.socket || that.socket));
    }

    // Called when the socket is connected and ready to start sending/receiving data.
    function socketFullyConnected() {
        that.connected = true;
        last_socket_error = null;
        that.emit('socket connected');
    }

    that.socket.on('error', function socketErrorCb(err) {
        last_socket_error = err;
        that.emit('socket error', err);
    });

    // 1024 bytes is the maximum length of two RFC1459 IRC messages.
    // May need tweaking when IRCv3 message tags are more widespread
    that.socket.pipe(new TerminatedStream({max_buffer_size: 1024}))
        .on('data', (line) => {
            that.read_buffer.push(line);
            that.processReadBuffer();
        });

    that.socket.on('close', function socketCloseCb(had_error) {
        var was_connected = that.connected;
        var should_reconnect = false;
        var safely_registered = false;
        var registered_ms_ago = Date.now() - that.registered;

        // Some networks use aKills which kill a user after succesfully
        // registering instead of a ban, so we must wait some time after
        // being registered to be sure that we are connected properly.
        safely_registered = that.registered !== false && registered_ms_ago > 5000;

        that.connected = false;
        that.disposeSocket();
        that.clearTimers();

        that.emit('socket close', had_error);

        if (that.requested_disconnect || !that.auto_reconnect) {
            should_reconnect = false;

        // If trying to reconnect, continue with it
        } else if (that.reconnect_attempts && that.reconnect_attempts < 3) {
            should_reconnect = true;

        // If we were originally connected OK, reconnect
        } else if (was_connected && safely_registered) {
            should_reconnect = true;

        } else {
            should_reconnect = false;
        }

        if (should_reconnect) {
            that.reconnect_attempts++;
            that.emit('reconnecting');
        } else {
            that.emit('close', last_socket_error ? true : false);
            that.reconnect_attempts = 0;
        }

        if (should_reconnect) {
            that.setTimeout(function() {
                that.connect();
            }, 4000);
        }
    });
};

Connection.prototype._write = function(chunk, encoding, callback) {
    if (!this.connected) {
        return 0;
    }

    var encoded_buffer = iconv.encode(chunk + '\r\n', this.encoding);
    //console.log('Raw C:', chunk.toString());
    return this.socket.write(encoded_buffer, callback);
};

Connection.prototype._read = function() {
    var message;
    var continue_pushing = true;

    this._reading = true;

    while (continue_pushing && this.read_command_buffer.length > 0) {
        message = this.read_command_buffer.shift();
        continue_pushing = this.push(message);
        if (!continue_pushing) {
            this._reading = false;
        }
    }
};

Connection.prototype.pushCommandBuffer = function(command) {
    this.read_command_buffer.push(command);
    if (this._reading) {
        this._read();
    }
};

Connection.prototype.disposeSocket = function() {
    if (this.socket && this.connected) {
        this.requested_disconnect = true;
        this.socket.destroy();
    }

    if (this.socket) {
        this.socket.removeAllListeners();
        this.socket = null;
    }
};

/**
 * Create and keep track of all timers so they can be easily removed
 */
Connection.prototype.setTimeout = function(/*fn, length, argN */) {
    var that = this;
    var tmr = null;
    var callback = arguments[0];

    arguments[0] = function() {
       _.pull(that._timers, tmr);
       callback.apply(null, arguments);
    };

    tmr = setTimeout.apply(null, arguments);
    this._timers.push(tmr);
    return tmr;
};

Connection.prototype.clearTimeout = function(tmr) {
	clearTimeout(tmr);
	_.pull(this._timers, tmr);
};

Connection.prototype.clearTimers = function() {
    this._timers.forEach(function(tmr) {
        clearTimeout(tmr);
    });

    this._timers = [];
};

/**
 * Close the connection to the IRCd after forcing one last line
 */
Connection.prototype.end = function(data, callback) {
    var that = this;

    if (this.connected && data) {
        // Once the last bit of data has been sent, then re-run this function to close the socket
        this.write(data, function() {
            that.requested_disconnect = true;
            that.socket.end();
        });

        return;
    }

    this.disposeSocket();
};

/**
 * Set a new encoding for this connection
 * Return true in case of success
 */

Connection.prototype.setEncoding = function(encoding) {
    var encoded_test;

    try {
        encoded_test = iconv.encode('TEST', encoding);
        // This test is done to check if this encoding also supports
        // the ASCII charset required by the IRC protocols
        // (Avoid the use of base64 or incompatible encodings)
        if (encoded_test == 'TEST') { // jshint ignore:line
            this.encoding = encoding;
            return true;
        }
        return false;
    } catch (err) {
        return false;
    }
};

/**
 * Process the messages recieved from the IRCd that are buffered on an IrcConnection object
 * Will only process 4 lines per JS tick so that node can handle any other events while
 * handling a large buffer
 */
Connection.prototype.processReadBuffer = function(continue_processing) {
    // If we already have the read buffer being iterated through, don't start
    // another one.
    if (this.reading_buffer && !continue_processing) {
        return;
    }

    var lines_per_js_tick = 4;
    var processed_lines = 0;
    var line;
    var message;

    this.reading_buffer = true;

    while (processed_lines < lines_per_js_tick && this.read_buffer.length > 0) {
        line = iconv.decode(this.read_buffer.shift(), this.encoding);
        if (!line) {
            continue;
        }

        message = ircLineParser(line);

        if (!message) {
            // A malformed IRC line
            continue;
        }

        this.pushCommandBuffer(message);

        processed_lines++;
    }

    // If we still have items left in our buffer then continue reading them in a few ticks
    if (this.read_buffer.length > 0) {
        this.setTimeout(processReadBuffer.bind(this), 1, true);
    } else {
        this.reading_buffer = false;
    }
}
<|endoftext|>


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

    var cfg = require('nconf'),
        fs = require('fs'),
        join = require('path').join,
        _ = require('underscore');

    module.exports = function (req, res, next) {
        var tpl, robotsLocals = {
                disallow: cfg.get('app:robots:disallow'),
                allow: cfg.get('app:robots:allow')
            },
            path = join(cfg.get('paths:coreShared'), 'robots.txt'),
            locals = _.extend({}, robotsLocals, res.app.locals);

        // Check if a file can be served from cache
        if (tpl) return res.type('txt').end(tpl(locals));

        // Render template from a robots file
        fs.readFile(path, function (err, file) {
            if (err) return next(err);

            tpl = _.template(file.toString('utf8'));
            res.type('txt').end(tpl(locals));
        });
    };

})();<commit_msg>Robots fix<commit_after>(function () {
    'use strict';

    var cfg = require('nconf'),
        fs = require('fs'),
        join = require('path').join,
        _ = require('underscore');

    module.exports = function (req, res, next) {
        var tpl, robotsLocals = {
                disallow: cfg.get('app:robots:disallow'),
                allow: cfg.get('app:robots:allow')
            },
            path = join(cfg.get('paths:coreShared'), 'robots.txt'),
            locals = _.extend({}, robotsLocals, res.locals);

        // Check if a file can be served from cache
        if (tpl) return res.type('txt').end(tpl(locals));

        // Render template from a robots file
        fs.readFile(path, function (err, file) {
            if (err) return next(err);

            tpl = _.template(file.toString('utf8'));
            res.type('txt').end(tpl(locals));
        });
    };

})();<|endoftext|>


<commit_before>function factorial(num) {
  var result = 1;
  for (var i = 2; i <= num; i++) {
    result *= i;
  }
  return result;
}
<commit_msg>removes extra file<commit_after><|endoftext|>


<commit_before>const assert = require('assert');
const plans = require('./plans');
const migrations = require('./migrations');
const schemaVersion = require('../version.json').schema;
const Promise = require('bluebird');

class Contractor {

  static constructionPlans(schema){
    let exportPlans = plans.create(schema);
    exportPlans.push(plans.insertVersion(schema).replace('$1', `'${schemaVersion}'`));

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

  static migrationPlans(schema, version, uninstall){
    let migration = migrations.get(schema, version, uninstall);
    assert(migration, `migration not found from version ${version}. schema: ${schema}`);
    return migration.commands.join(';\n\n');
  }

  constructor(db, config){
    this.config = config;
    this.db = db;
  }

  version() {
    return this.db.executeSql(plans.getVersion(this.config.schema))
      .then(result => result.rows.length ? result.rows[0].version : null);
  }

  isCurrent(){
    return this.version().then(version => version === schemaVersion);
  }

  isInstalled() {
    return this.db.executeSql(plans.versionTableExists(this.config.schema))
      .then(result => result.rows.length ? result.rows[0].name : null);
  }

  ensureCurrent() {
    return this.version()
      .then(version => {
        if (schemaVersion !== version)
          return this.update(version);
      });
  }

  create(){
    // use transaction, in case one query fails, it will automatically rollback to avoid inconsistency
    let queryInTransaction = `
    BEGIN;
    ${plans.create(this.config.schema).join(';')}
    ${plans.insertVersion(this.config.schema).replace('$1', `'${schemaVersion}'`)}
    COMMIT;`;
    return this.db.executeSql(queryInTransaction);
  }

  update(current) {
    if(current === '0.0.2') current = '0.0.1';

    return this.migrate(current)
      .then(version => {
        if(version !== schemaVersion) return this.update(version);
      });
  }

  start(){
    return this.isInstalled()
      .then(installed => installed ? this.ensureCurrent() : this.create());
  }

  connect(){
    let connectErrorMessage = 'this version of pg-boss does not appear to be installed in your database. I can create it for you via start().';

    return this.isInstalled()
      .then(installed => {
        if(!installed)
          throw new Error(connectErrorMessage);

        return this.isCurrent();
      })
      .then(current => {
        if(!current)
          throw new Error(connectErrorMessage);
      });
  }

  migrate(version, uninstall){
    let migration = migrations.get(this.config.schema, version, uninstall);

    if(!migration){
      let errorMessage = `Migration to version ${version} failed because it could not be found.  Your database may have been upgraded by a newer version of pg-boss`;
      return Promise.reject(new Error(errorMessage));
    }
    // use transaction, in case one query fails, it will automatically rollback to avoid inconsistency
    let queryInTransaction = `
    BEGIN;
    ${migration.commands.join(';')}
    COMMIT;`;
    return this.db.executeSql(queryInTransaction)
      .then(() => migration.version);
  }
}

module.exports = Contractor;
<commit_msg>added ; to terminate last query<commit_after>const assert = require('assert');
const plans = require('./plans');
const migrations = require('./migrations');
const schemaVersion = require('../version.json').schema;
const Promise = require('bluebird');

class Contractor {

  static constructionPlans(schema){
    let exportPlans = plans.create(schema);
    exportPlans.push(plans.insertVersion(schema).replace('$1', `'${schemaVersion}'`));

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

  static migrationPlans(schema, version, uninstall){
    let migration = migrations.get(schema, version, uninstall);
    assert(migration, `migration not found from version ${version}. schema: ${schema}`);
    return migration.commands.join(';\n\n');
  }

  constructor(db, config){
    this.config = config;
    this.db = db;
  }

  version() {
    return this.db.executeSql(plans.getVersion(this.config.schema))
      .then(result => result.rows.length ? result.rows[0].version : null);
  }

  isCurrent(){
    return this.version().then(version => version === schemaVersion);
  }

  isInstalled() {
    return this.db.executeSql(plans.versionTableExists(this.config.schema))
      .then(result => result.rows.length ? result.rows[0].name : null);
  }

  ensureCurrent() {
    return this.version()
      .then(version => {
        if (schemaVersion !== version)
          return this.update(version);
      });
  }

  create(){
    // use transaction, in case one query fails, it will automatically rollback to avoid inconsistency
    let queryInTransaction = `
    BEGIN;
    ${plans.create(this.config.schema).join(';')};
    ${plans.insertVersion(this.config.schema).replace('$1', `'${schemaVersion}'`)}
    COMMIT;`;
    return this.db.executeSql(queryInTransaction);
  }

  update(current) {
    if(current === '0.0.2') current = '0.0.1';

    return this.migrate(current)
      .then(version => {
        if(version !== schemaVersion) return this.update(version);
      });
  }

  start(){
    return this.isInstalled()
      .then(installed => installed ? this.ensureCurrent() : this.create());
  }

  connect(){
    let connectErrorMessage = 'this version of pg-boss does not appear to be installed in your database. I can create it for you via start().';

    return this.isInstalled()
      .then(installed => {
        if(!installed)
          throw new Error(connectErrorMessage);

        return this.isCurrent();
      })
      .then(current => {
        if(!current)
          throw new Error(connectErrorMessage);
      });
  }

  migrate(version, uninstall){
    let migration = migrations.get(this.config.schema, version, uninstall);

    if(!migration){
      let errorMessage = `Migration to version ${version} failed because it could not be found.  Your database may have been upgraded by a newer version of pg-boss`;
      return Promise.reject(new Error(errorMessage));
    }
    // use transaction, in case one query fails, it will automatically rollback to avoid inconsistency
    let queryInTransaction = `
    BEGIN;
    ${migration.commands.join(';')};
    COMMIT;`;
    return this.db.executeSql(queryInTransaction)
      .then(() => migration.version);
  }
}

module.exports = Contractor;
<|endoftext|>


<commit_before>const _ = require('lodash');
const Promise = require('bluebird');
const debug = require('@tryghost/debug')('mega');
const url = require('url');
const moment = require('moment');
const ObjectID = require('bson-objectid');
const errors = require('@tryghost/errors');
const events = require('../../lib/common/events');
const i18n = require('../../../shared/i18n');
const logging = require('@tryghost/logging');
const settingsCache = require('../settings/cache');
const membersService = require('../members');
const limitService = require('../limits');
const bulkEmailService = require('../bulk-email');
const jobsService = require('../jobs');
const db = require('../../data/db');
const models = require('../../models');
const postEmailSerializer = require('./post-email-serializer');
const {getSegmentsFromHtml} = require('./segment-parser');

const getFromAddress = () => {
    let fromAddress = membersService.config.getEmailFromAddress();

    if (/@localhost$/.test(fromAddress) || /@ghost.local$/.test(fromAddress)) {
        const localAddress = 'localhost@example.com';
        logging.warn(`Rewriting bulk email from address ${fromAddress} to ${localAddress}`);
        fromAddress = localAddress;
    }

    const siteTitle = settingsCache.get('title') ? settingsCache.get('title').replace(/"/g, '\\"') : '';

    return siteTitle ? `"${siteTitle}"<${fromAddress}>` : fromAddress;
};

const getReplyToAddress = () => {
    const fromAddress = membersService.config.getEmailFromAddress();
    const supportAddress = membersService.config.getEmailSupportAddress();
    const replyAddressOption = settingsCache.get('members_reply_address');

    return (replyAddressOption === 'support') ? supportAddress : fromAddress;
};

/**
 *
 * @param {Object} postModel - post model instance
 * @param {Object} options
 * @param {ValidAPIVersion} options.apiVersion - api version to be used when serializing email data
 */
const getEmailData = async (postModel, options) => {
    const {subject, html, plaintext} = await postEmailSerializer.serialize(postModel, options);

    return {
        subject,
        html,
        plaintext,
        from: getFromAddress(),
        replyTo: getReplyToAddress()
    };
};

/**
 *
 * @param {Object} postModel - post model instance
 * @param {[string]} toEmails - member email addresses to send email to
 * @param {ValidAPIVersion} options.apiVersion - api version to be used when serializing email data
 */
const sendTestEmail = async (postModel, toEmails, apiVersion) => {
    const emailData = await getEmailData(postModel, {apiVersion});
    emailData.subject = `[Test] ${emailData.subject}`;

    // fetch any matching members so that replacements use expected values
    const recipients = await Promise.all(toEmails.map(async (email) => {
        const member = await membersService.api.members.get({email});
        if (member) {
            return {
                member_uuid: member.get('uuid'),
                member_email: member.get('email'),
                member_name: member.get('name')
            };
        }

        return {
            member_email: email
        };
    }));

    // enable tracking for previews to match real-world behaviour
    emailData.track_opens = !!settingsCache.get('email_track_opens');

    const response = await bulkEmailService.send(emailData, recipients);

    if (response instanceof bulkEmailService.FailedBatch) {
        return Promise.reject(response.error);
    }

    return response;
};

/**
 * addEmail
 *
 * Accepts a post model and creates an email record based on it. Only creates one
 * record per post
 *
 * @param {object} postModel Post Model Object
 * @param {object} options
 * @param {ValidAPIVersion} options.apiVersion - api version to be used when serializing email data
 */

const addEmail = async (postModel, options) => {
    if (limitService.isLimited('emails')) {
        await limitService.errorIfWouldGoOverLimit('emails');
    }

    const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
    const filterOptions = Object.assign({}, knexOptions, {limit: 1});

    const emailRecipientFilter = postModel.get('email_recipient_filter');

    switch (emailRecipientFilter) {
    // `paid` and `free` were swapped out for NQL filters in 4.5.0, we shouldn't see them here now
    case 'paid':
    case 'free':
        throw new Error(`Unexpected email_recipient_filter value "${emailRecipientFilter}", expected an NQL equivalent`);
    case 'all':
        filterOptions.filter = 'subscribed:true';
        break;
    case 'none':
        throw new Error('Cannot sent email to "none" email_recipient_filter');
    default:
        filterOptions.filter = `subscribed:true+${emailRecipientFilter}`;
    }

    const startRetrieve = Date.now();
    debug('addEmail: retrieving members count');
    const {meta: {pagination: {total: membersCount}}} = await membersService.api.members.list(Object.assign({}, knexOptions, filterOptions));
    debug(`addEmail: retrieved members count - ${membersCount} members (${Date.now() - startRetrieve}ms)`);

    // NOTE: don't create email object when there's nobody to send the email to
    if (membersCount === 0) {
        return null;
    }

    if (limitService.isLimited('emails')) {
        await limitService.errorIfWouldGoOverLimit('emails', {addedCount: membersCount});
    }

    const postId = postModel.get('id');
    const existing = await models.Email.findOne({post_id: postId}, knexOptions);

    if (!existing) {
        // get email contents and perform replacements using no member data so
        // we have a decent snapshot of email content for later display
        const emailData = await getEmailData(postModel, options);

        return models.Email.add({
            post_id: postId,
            status: 'pending',
            email_count: membersCount,
            subject: emailData.subject,
            from: emailData.from,
            reply_to: emailData.replyTo,
            html: emailData.html,
            plaintext: emailData.plaintext,
            submitted_at: moment().toDate(),
            track_opens: !!settingsCache.get('email_track_opens'),
            recipient_filter: emailRecipientFilter
        }, knexOptions);
    } else {
        return existing;
    }
};

/**
 * retryFailedEmail
 *
 * Accepts an Email model and resets it's fields to trigger retry listeners
 *
 * @param {Email} emailModel Email model
 */
const retryFailedEmail = async (emailModel) => {
    return await models.Email.edit({
        status: 'pending'
    }, {
        id: emailModel.get('id')
    });
};

/**
 * handleUnsubscribeRequest
 *
 * Takes a request/response pair and reads the `unsubscribe` query parameter,
 * using the content to update the members service to set the `subscribed` flag
 * to false on the member
 *
 * If any operation fails, or the request is invalid the function will error - so using
 * as middleware should consider wrapping with `try/catch`
 *
 * @param {Request} req
 * @returns {Promise<void>}
 */
async function handleUnsubscribeRequest(req) {
    if (!req.url) {
        throw new errors.BadRequestError({
            message: 'Unsubscribe failed! Could not find member'
        });
    }

    const {query} = url.parse(req.url, true);
    if (!query || !query.uuid) {
        throw new errors.BadRequestError({
            message: (query.preview ? 'Unsubscribe preview' : 'Unsubscribe failed! Could not find member')
        });
    }

    const member = await membersService.api.members.get({
        uuid: query.uuid
    });

    if (!member) {
        throw new errors.BadRequestError({
            message: 'Unsubscribe failed! Could not find member'
        });
    }

    try {
        const memberModel = await membersService.api.members.update({subscribed: false}, {id: member.id});
        return memberModel.toJSON();
    } catch (err) {
        throw new errors.InternalServerError({
            err,
            message: 'Failed to unsubscribe member'
        });
    }
}

async function pendingEmailHandler(emailModel, options) {
    // CASE: do not send email if we import a database
    // TODO: refactor post.published events to never fire on importing
    if (options && options.importing) {
        return;
    }

    if (emailModel.get('status') !== 'pending') {
        return;
    }

    // make sure recurring background analytics jobs are running once we have emails
    const emailAnalyticsJobs = require('../email-analytics/jobs');
    emailAnalyticsJobs.scheduleRecurringJobs();

    return jobsService.addJob({
        job: sendEmailJob,
        data: {emailModel},
        offloaded: false
    });
}

async function sendEmailJob({emailModel, options}) {
    let startEmailSend = null;

    try {
        // Check host limit for allowed member count and throw error if over limit
        // - do this even if it's a retry so that there's no way around the limit
        if (limitService.isLimited('members')) {
            await limitService.errorIfIsOverLimit('members');
        }

        // Check host limit for disabled emails or going over emails limit
        if (limitService.isLimited('emails')) {
            await limitService.errorIfWouldGoOverLimit('emails');
        }

        // Create email batch and recipient rows unless this is a retry and they already exist
        const existingBatchCount = await emailModel.related('emailBatches').count('id');

        if (existingBatchCount === 0) {
            let newBatchCount;

            await models.Base.transaction(async (transacting) => {
                newBatchCount = await createSegmentedEmailBatches({emailModel, options: {transacting}});
            });

            if (newBatchCount === 0) {
                return;
            }
        }

        debug('sendEmailJob: sending email');
        startEmailSend = Date.now();
        await bulkEmailService.processEmail({emailId: emailModel.get('id'), options});
        debug(`sendEmailJob: sent email (${Date.now() - startEmailSend}ms)`);
    } catch (error) {
        if (startEmailSend) {
            debug(`sendEmailJob: send email failed (${Date.now() - startEmailSend}ms)`);
        }

        let errorMessage = error.message;
        if (errorMessage.length > 2000) {
            errorMessage = errorMessage.substring(0, 2000);
        }

        await emailModel.save({
            status: 'failed',
            error: errorMessage
        }, {patch: true});

        throw new errors.GhostError({
            err: error,
            context: i18n.t('errors.services.mega.requestFailed.error')
        });
    }
}

/**
 * Fetch rows of members that should receive an email.
 * Uses knex directly rather than bookshelf to avoid thousands of bookshelf model
 * instantiations and associated processing and event loop blocking
 *
 * @param {Object} options
 * @param {Object} options.emailModel - instance of Email model
 * @param {string} [options.memberSegment] - NQL filter to apply in addition to the one defined in emailModel
 * @param {Object} options.options - knex options
 *
 * @returns {Promise<Object[]>} instances of filtered knex member rows
 */
async function getEmailMemberRows({emailModel, memberSegment, options}) {
    const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
    const filterOptions = Object.assign({}, knexOptions);

    const recipientFilter = emailModel.get('recipient_filter');

    switch (recipientFilter) {
    // `paid` and `free` were swapped out for NQL filters in 4.5.0, we shouldn't see them here now
    case 'paid':
    case 'free':
        throw new Error(`Unexpected recipient_filter value "${recipientFilter}", expected an NQL equivalent`);
    case 'all':
        filterOptions.filter = 'subscribed:true';
        break;
    case 'none':
        throw new Error('Cannot sent email to "none" recipient_filter');
    default:
        filterOptions.filter = `subscribed:true+${recipientFilter}`;
    }

    if (memberSegment) {
        filterOptions.filter = `${filterOptions.filter}+${memberSegment}`;
    }

    const startRetrieve = Date.now();
    debug('getEmailMemberRows: retrieving members list');
    // select('members.*') is necessary here to avoid duplicate `email` columns in the result set
    // without it we do `select *` which pulls in the Stripe customer email too which overrides the member email
    const memberRows = await models.Member.getFilteredCollectionQuery(filterOptions).select('members.*').distinct();
    debug(`getEmailMemberRows: retrieved members list - ${memberRows.length} members (${Date.now() - startRetrieve}ms)`);

    return memberRows;
}

/**
 * Detects segment filters in emailModel's html and creates separate batches per segment
 *
 * @param {Object} options
 * @param {Object} options.emailModel - instance of Email model
 * @param {Object} options.options - knex options
 */
async function createSegmentedEmailBatches({emailModel, options}) {
    const segments = getSegmentsFromHtml(emailModel.get('html'));
    const batchIds = [];

    if (segments.length) {
        for (const memberSegment of segments) {
            const emailBatchIds = await createEmailBatches({
                emailModel,
                memberSegment,
                options
            });
            batchIds.push(emailBatchIds);
        }
    } else {
        const emailBatchIds = createEmailBatches({emailModel, options});
        batchIds.push(emailBatchIds);
    }

    return batchIds;
}

/**
 * Store email_batch and email_recipient records for an email.
 * Uses knex directly rather than bookshelf to avoid thousands of bookshelf model
 * instantiations and associated processing and event loop blocking.
 *
 * @param {Object} options
 * @param {Object} options.emailModel - instance of Email model
 * @param {string} [options.memberSegment] - NQL filter to apply in addition to the one defined in emailModel
 * @param {Object} options.options - knex options
 * @returns {Promise<string[]>} - created batch ids
 */
async function createEmailBatches({emailModel, memberSegment, options}) {
    const memberRows = await getEmailMemberRows({emailModel, memberSegment, options});

    if (!memberRows.length) {
        return [];
    }

    const storeRecipientBatch = async function (recipients) {
        const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
        // TODO: store `memberSegment` in EmailBatch once the table migration is merged
        const batchModel = await models.EmailBatch.add({email_id: emailModel.id}, knexOptions);

        const recipientData = [];

        recipients.forEach((memberRow) => {
            if (!memberRow.id || !memberRow.uuid || !memberRow.email) {
                logging.warn(`Member row not included as email recipient due to missing data - id: ${memberRow.id}, uuid: ${memberRow.uuid}, email: ${memberRow.email}`);
                return;
            }

            recipientData.push({
                id: ObjectID().toHexString(),
                email_id: emailModel.id,
                member_id: memberRow.id,
                batch_id: batchModel.id,
                member_uuid: memberRow.uuid,
                member_email: memberRow.email,
                member_name: memberRow.name
            });
        });

        const insertQuery = db.knex('email_recipients').insert(recipientData);

        if (knexOptions.transacting) {
            insertQuery.transacting(knexOptions.transacting);
        }

        await insertQuery;

        return batchModel.id;
    };

    debug('createEmailBatches: storing recipient list');
    const startOfRecipientStorage = Date.now();
    const batches = _.chunk(memberRows, bulkEmailService.BATCH_SIZE);
    const batchIds = await Promise.mapSeries(batches, storeRecipientBatch);
    debug(`createEmailBatches: stored recipient list (${Date.now() - startOfRecipientStorage}ms)`);

    return batchIds;
}

const statusChangedHandler = (emailModel, options) => {
    const emailRetried = emailModel.wasChanged()
        && emailModel.get('status') === 'pending'
        && emailModel.previous('status') === 'failed';

    if (emailRetried) {
        pendingEmailHandler(emailModel, options);
    }
};

function listen() {
    events.on('email.added', pendingEmailHandler);
    events.on('email.edited', statusChangedHandler);
}

// Public API
module.exports = {
    listen,
    addEmail,
    retryFailedEmail,
    sendTestEmail,
    handleUnsubscribeRequest
};

/**
 * @typedef {'v2' | 'v3' | 'v4' | 'canary' } ValidAPIVersion
 */
<commit_msg>Added member_segment persistance to email_batches<commit_after>const _ = require('lodash');
const Promise = require('bluebird');
const debug = require('@tryghost/debug')('mega');
const url = require('url');
const moment = require('moment');
const ObjectID = require('bson-objectid');
const errors = require('@tryghost/errors');
const events = require('../../lib/common/events');
const i18n = require('../../../shared/i18n');
const logging = require('@tryghost/logging');
const settingsCache = require('../settings/cache');
const membersService = require('../members');
const limitService = require('../limits');
const bulkEmailService = require('../bulk-email');
const jobsService = require('../jobs');
const db = require('../../data/db');
const models = require('../../models');
const postEmailSerializer = require('./post-email-serializer');
const {getSegmentsFromHtml} = require('./segment-parser');

const getFromAddress = () => {
    let fromAddress = membersService.config.getEmailFromAddress();

    if (/@localhost$/.test(fromAddress) || /@ghost.local$/.test(fromAddress)) {
        const localAddress = 'localhost@example.com';
        logging.warn(`Rewriting bulk email from address ${fromAddress} to ${localAddress}`);
        fromAddress = localAddress;
    }

    const siteTitle = settingsCache.get('title') ? settingsCache.get('title').replace(/"/g, '\\"') : '';

    return siteTitle ? `"${siteTitle}"<${fromAddress}>` : fromAddress;
};

const getReplyToAddress = () => {
    const fromAddress = membersService.config.getEmailFromAddress();
    const supportAddress = membersService.config.getEmailSupportAddress();
    const replyAddressOption = settingsCache.get('members_reply_address');

    return (replyAddressOption === 'support') ? supportAddress : fromAddress;
};

/**
 *
 * @param {Object} postModel - post model instance
 * @param {Object} options
 * @param {ValidAPIVersion} options.apiVersion - api version to be used when serializing email data
 */
const getEmailData = async (postModel, options) => {
    const {subject, html, plaintext} = await postEmailSerializer.serialize(postModel, options);

    return {
        subject,
        html,
        plaintext,
        from: getFromAddress(),
        replyTo: getReplyToAddress()
    };
};

/**
 *
 * @param {Object} postModel - post model instance
 * @param {[string]} toEmails - member email addresses to send email to
 * @param {ValidAPIVersion} options.apiVersion - api version to be used when serializing email data
 */
const sendTestEmail = async (postModel, toEmails, apiVersion) => {
    const emailData = await getEmailData(postModel, {apiVersion});
    emailData.subject = `[Test] ${emailData.subject}`;

    // fetch any matching members so that replacements use expected values
    const recipients = await Promise.all(toEmails.map(async (email) => {
        const member = await membersService.api.members.get({email});
        if (member) {
            return {
                member_uuid: member.get('uuid'),
                member_email: member.get('email'),
                member_name: member.get('name')
            };
        }

        return {
            member_email: email
        };
    }));

    // enable tracking for previews to match real-world behaviour
    emailData.track_opens = !!settingsCache.get('email_track_opens');

    const response = await bulkEmailService.send(emailData, recipients);

    if (response instanceof bulkEmailService.FailedBatch) {
        return Promise.reject(response.error);
    }

    return response;
};

/**
 * addEmail
 *
 * Accepts a post model and creates an email record based on it. Only creates one
 * record per post
 *
 * @param {object} postModel Post Model Object
 * @param {object} options
 * @param {ValidAPIVersion} options.apiVersion - api version to be used when serializing email data
 */

const addEmail = async (postModel, options) => {
    if (limitService.isLimited('emails')) {
        await limitService.errorIfWouldGoOverLimit('emails');
    }

    const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
    const filterOptions = Object.assign({}, knexOptions, {limit: 1});

    const emailRecipientFilter = postModel.get('email_recipient_filter');

    switch (emailRecipientFilter) {
    // `paid` and `free` were swapped out for NQL filters in 4.5.0, we shouldn't see them here now
    case 'paid':
    case 'free':
        throw new Error(`Unexpected email_recipient_filter value "${emailRecipientFilter}", expected an NQL equivalent`);
    case 'all':
        filterOptions.filter = 'subscribed:true';
        break;
    case 'none':
        throw new Error('Cannot sent email to "none" email_recipient_filter');
    default:
        filterOptions.filter = `subscribed:true+${emailRecipientFilter}`;
    }

    const startRetrieve = Date.now();
    debug('addEmail: retrieving members count');
    const {meta: {pagination: {total: membersCount}}} = await membersService.api.members.list(Object.assign({}, knexOptions, filterOptions));
    debug(`addEmail: retrieved members count - ${membersCount} members (${Date.now() - startRetrieve}ms)`);

    // NOTE: don't create email object when there's nobody to send the email to
    if (membersCount === 0) {
        return null;
    }

    if (limitService.isLimited('emails')) {
        await limitService.errorIfWouldGoOverLimit('emails', {addedCount: membersCount});
    }

    const postId = postModel.get('id');
    const existing = await models.Email.findOne({post_id: postId}, knexOptions);

    if (!existing) {
        // get email contents and perform replacements using no member data so
        // we have a decent snapshot of email content for later display
        const emailData = await getEmailData(postModel, options);

        return models.Email.add({
            post_id: postId,
            status: 'pending',
            email_count: membersCount,
            subject: emailData.subject,
            from: emailData.from,
            reply_to: emailData.replyTo,
            html: emailData.html,
            plaintext: emailData.plaintext,
            submitted_at: moment().toDate(),
            track_opens: !!settingsCache.get('email_track_opens'),
            recipient_filter: emailRecipientFilter
        }, knexOptions);
    } else {
        return existing;
    }
};

/**
 * retryFailedEmail
 *
 * Accepts an Email model and resets it's fields to trigger retry listeners
 *
 * @param {Email} emailModel Email model
 */
const retryFailedEmail = async (emailModel) => {
    return await models.Email.edit({
        status: 'pending'
    }, {
        id: emailModel.get('id')
    });
};

/**
 * handleUnsubscribeRequest
 *
 * Takes a request/response pair and reads the `unsubscribe` query parameter,
 * using the content to update the members service to set the `subscribed` flag
 * to false on the member
 *
 * If any operation fails, or the request is invalid the function will error - so using
 * as middleware should consider wrapping with `try/catch`
 *
 * @param {Request} req
 * @returns {Promise<void>}
 */
async function handleUnsubscribeRequest(req) {
    if (!req.url) {
        throw new errors.BadRequestError({
            message: 'Unsubscribe failed! Could not find member'
        });
    }

    const {query} = url.parse(req.url, true);
    if (!query || !query.uuid) {
        throw new errors.BadRequestError({
            message: (query.preview ? 'Unsubscribe preview' : 'Unsubscribe failed! Could not find member')
        });
    }

    const member = await membersService.api.members.get({
        uuid: query.uuid
    });

    if (!member) {
        throw new errors.BadRequestError({
            message: 'Unsubscribe failed! Could not find member'
        });
    }

    try {
        const memberModel = await membersService.api.members.update({subscribed: false}, {id: member.id});
        return memberModel.toJSON();
    } catch (err) {
        throw new errors.InternalServerError({
            err,
            message: 'Failed to unsubscribe member'
        });
    }
}

async function pendingEmailHandler(emailModel, options) {
    // CASE: do not send email if we import a database
    // TODO: refactor post.published events to never fire on importing
    if (options && options.importing) {
        return;
    }

    if (emailModel.get('status') !== 'pending') {
        return;
    }

    // make sure recurring background analytics jobs are running once we have emails
    const emailAnalyticsJobs = require('../email-analytics/jobs');
    emailAnalyticsJobs.scheduleRecurringJobs();

    return jobsService.addJob({
        job: sendEmailJob,
        data: {emailModel},
        offloaded: false
    });
}

async function sendEmailJob({emailModel, options}) {
    let startEmailSend = null;

    try {
        // Check host limit for allowed member count and throw error if over limit
        // - do this even if it's a retry so that there's no way around the limit
        if (limitService.isLimited('members')) {
            await limitService.errorIfIsOverLimit('members');
        }

        // Check host limit for disabled emails or going over emails limit
        if (limitService.isLimited('emails')) {
            await limitService.errorIfWouldGoOverLimit('emails');
        }

        // Create email batch and recipient rows unless this is a retry and they already exist
        const existingBatchCount = await emailModel.related('emailBatches').count('id');

        if (existingBatchCount === 0) {
            let newBatchCount;

            await models.Base.transaction(async (transacting) => {
                newBatchCount = await createSegmentedEmailBatches({emailModel, options: {transacting}});
            });

            if (newBatchCount === 0) {
                return;
            }
        }

        debug('sendEmailJob: sending email');
        startEmailSend = Date.now();
        await bulkEmailService.processEmail({emailId: emailModel.get('id'), options});
        debug(`sendEmailJob: sent email (${Date.now() - startEmailSend}ms)`);
    } catch (error) {
        if (startEmailSend) {
            debug(`sendEmailJob: send email failed (${Date.now() - startEmailSend}ms)`);
        }

        let errorMessage = error.message;
        if (errorMessage.length > 2000) {
            errorMessage = errorMessage.substring(0, 2000);
        }

        await emailModel.save({
            status: 'failed',
            error: errorMessage
        }, {patch: true});

        throw new errors.GhostError({
            err: error,
            context: i18n.t('errors.services.mega.requestFailed.error')
        });
    }
}

/**
 * Fetch rows of members that should receive an email.
 * Uses knex directly rather than bookshelf to avoid thousands of bookshelf model
 * instantiations and associated processing and event loop blocking
 *
 * @param {Object} options
 * @param {Object} options.emailModel - instance of Email model
 * @param {string} [options.memberSegment] - NQL filter to apply in addition to the one defined in emailModel
 * @param {Object} options.options - knex options
 *
 * @returns {Promise<Object[]>} instances of filtered knex member rows
 */
async function getEmailMemberRows({emailModel, memberSegment, options}) {
    const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
    const filterOptions = Object.assign({}, knexOptions);

    const recipientFilter = emailModel.get('recipient_filter');

    switch (recipientFilter) {
    // `paid` and `free` were swapped out for NQL filters in 4.5.0, we shouldn't see them here now
    case 'paid':
    case 'free':
        throw new Error(`Unexpected recipient_filter value "${recipientFilter}", expected an NQL equivalent`);
    case 'all':
        filterOptions.filter = 'subscribed:true';
        break;
    case 'none':
        throw new Error('Cannot sent email to "none" recipient_filter');
    default:
        filterOptions.filter = `subscribed:true+${recipientFilter}`;
    }

    if (memberSegment) {
        filterOptions.filter = `${filterOptions.filter}+${memberSegment}`;
    }

    const startRetrieve = Date.now();
    debug('getEmailMemberRows: retrieving members list');
    // select('members.*') is necessary here to avoid duplicate `email` columns in the result set
    // without it we do `select *` which pulls in the Stripe customer email too which overrides the member email
    const memberRows = await models.Member.getFilteredCollectionQuery(filterOptions).select('members.*').distinct();
    debug(`getEmailMemberRows: retrieved members list - ${memberRows.length} members (${Date.now() - startRetrieve}ms)`);

    return memberRows;
}

/**
 * Detects segment filters in emailModel's html and creates separate batches per segment
 *
 * @param {Object} options
 * @param {Object} options.emailModel - instance of Email model
 * @param {Object} options.options - knex options
 */
async function createSegmentedEmailBatches({emailModel, options}) {
    const segments = getSegmentsFromHtml(emailModel.get('html'));
    const batchIds = [];

    if (segments.length) {
        for (const memberSegment of segments) {
            const emailBatchIds = await createEmailBatches({
                emailModel,
                memberSegment,
                options
            });
            batchIds.push(emailBatchIds);
        }
    } else {
        const emailBatchIds = createEmailBatches({emailModel, options});
        batchIds.push(emailBatchIds);
    }

    return batchIds;
}

/**
 * Store email_batch and email_recipient records for an email.
 * Uses knex directly rather than bookshelf to avoid thousands of bookshelf model
 * instantiations and associated processing and event loop blocking.
 *
 * @param {Object} options
 * @param {Object} options.emailModel - instance of Email model
 * @param {string} [options.memberSegment] - NQL filter to apply in addition to the one defined in emailModel
 * @param {Object} options.options - knex options
 * @returns {Promise<string[]>} - created batch ids
 */
async function createEmailBatches({emailModel, memberSegment, options}) {
    const memberRows = await getEmailMemberRows({emailModel, memberSegment, options});

    if (!memberRows.length) {
        return [];
    }

    const storeRecipientBatch = async function (recipients) {
        const knexOptions = _.pick(options, ['transacting', 'forUpdate']);
        const batchModel = await models.EmailBatch.add({
            email_id: emailModel.id,
            member_segment: memberSegment
        }, knexOptions);

        const recipientData = [];

        recipients.forEach((memberRow) => {
            if (!memberRow.id || !memberRow.uuid || !memberRow.email) {
                logging.warn(`Member row not included as email recipient due to missing data - id: ${memberRow.id}, uuid: ${memberRow.uuid}, email: ${memberRow.email}`);
                return;
            }

            recipientData.push({
                id: ObjectID().toHexString(),
                email_id: emailModel.id,
                member_id: memberRow.id,
                batch_id: batchModel.id,
                member_uuid: memberRow.uuid,
                member_email: memberRow.email,
                member_name: memberRow.name
            });
        });

        const insertQuery = db.knex('email_recipients').insert(recipientData);

        if (knexOptions.transacting) {
            insertQuery.transacting(knexOptions.transacting);
        }

        await insertQuery;

        return batchModel.id;
    };

    debug('createEmailBatches: storing recipient list');
    const startOfRecipientStorage = Date.now();
    const batches = _.chunk(memberRows, bulkEmailService.BATCH_SIZE);
    const batchIds = await Promise.mapSeries(batches, storeRecipientBatch);
    debug(`createEmailBatches: stored recipient list (${Date.now() - startOfRecipientStorage}ms)`);

    return batchIds;
}

const statusChangedHandler = (emailModel, options) => {
    const emailRetried = emailModel.wasChanged()
        && emailModel.get('status') === 'pending'
        && emailModel.previous('status') === 'failed';

    if (emailRetried) {
        pendingEmailHandler(emailModel, options);
    }
};

function listen() {
    events.on('email.added', pendingEmailHandler);
    events.on('email.edited', statusChangedHandler);
}

// Public API
module.exports = {
    listen,
    addEmail,
    retryFailedEmail,
    sendTestEmail,
    handleUnsubscribeRequest
};

/**
 * @typedef {'v2' | 'v3' | 'v4' | 'canary' } ValidAPIVersion
 */
<|endoftext|>