<commit_before>var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var once = require('./util/once').once;

'use strict';

module.exports = {
  Characteristic: Characteristic
}

/**
 * Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a
 * "Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic
 * to a Service in order to store that value. A particular Characteristic is distinguished from others by its
 * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKitTypes.js along with a
 * corresponding concrete subclass.
 *
 * You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added
 * to any native or custom Services, but Siri will likely not be able to work with these.
 *
 * Note that you can get the "value" of a Characteristic by accessing the "value" property directly, but this
 * is really a "cached value". If you want to fetch the latest value, which may involve doing some work, then
 * call getValue().
 *
 * @event 'get' => function(callback(err, newValue), context) { }
 *        Emitted when someone calls getValue() on this Characteristic and desires the latest non-cached
 *        value. If there are any listeners to this event, one of them MUST call the callback in order
 *        for the value to ever be delivered. The `context` object is whatever was passed in by the initiator
 *        of this event (for instance whomever called `getValue`).
 *
 * @event 'set' => function(newValue, callback(err), context) { }
 *        Emitted when someone calls setValue() on this Characteristic with a desired new value. If there
 *        are any listeners to this event, one of them MUST call the callback in order for this.value to
 *        actually be set. The `context` object is whatever was passed in by the initiator of this change
 *        (for instance, whomever called `setValue`).
 *
 * @event 'change' => function({ oldValue, newValue, context }) { }
 *        Emitted after a change in our value has occurred. The new value will also be immediately accessible
 *        in this.value. The event object contains the new value as well as the context object originally
 *        passed in by the initiator of this change (if known).
 */

function Characteristic(displayName, UUID, props) {
  this.displayName = displayName;
  this.UUID = UUID;
  this.iid = null; // assigned by our containing Service
  this.value = null;
  this.props = props || {
    format: null,
    unit: null,
    minValue: null,
    maxValue: null,
    minStep: null,
    perms: []
  };
}

inherits(Characteristic, EventEmitter);

// Known HomeKit formats
Characteristic.Formats = {
  BOOL: 'bool',
  INT: 'int',
  FLOAT: 'float',
  STRING: 'string',
  ARRAY: 'array', // unconfirmed
  DICTIONARY: 'dictionary', // unconfirmed
  UINT8: 'uint8',
  UINT16: 'uint16',
  UINT32: 'uint32',
  UINT64: 'uint64',
  DATA: 'data', // unconfirmed
  TLV8: 'tlv8'
}

// Known HomeKit unit types
Characteristic.Units = {
  // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion.
  CELSIUS: 'celsius',
  PERCENTAGE: 'percentage',
  ARC_DEGREE: 'arcdegrees',
  LUX: 'lux',
  SECONDS: 'seconds'
}

// Known HomeKit permission types
Characteristic.Perms = {
  READ: 'pr',
  WRITE: 'pw',
  NOTIFY: 'ev'
}

/**
 * Copies the given properties to our props member variable,
 * and returns 'this' for chaining.
 *
 * @param 'props' {
 *   format: <one of Characteristic.Formats>,
 *   unit: <one of Characteristic.Units>,
 *   minValue: <minimum value for numeric characteristics>,
 *   maxValue: <maximum value for numeric characteristics>,
 *   minStep: <smallest allowed increment for numeric characteristics>,
 *   perms: array of [Characteristic.Perms] like [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
 * }
 */
Characteristic.prototype.setProps = function(props) {
  for (var key in (props || {}))
    if (Object.prototype.hasOwnProperty.call(props, key))
      this.props[key] = props[key];
  return this;
}

Characteristic.prototype.getValue = function(callback, context) {

  if (this.listeners('get').length > 0) {

    // allow a listener to handle the fetching of this value, and wait for completion
    this.emit('get', once(function(err, newValue) {

      if (err) {
        // pass the error along to our callback
        if (callback) callback(err);
      }
      else {
        // getting the value was a success; we can pass it along and also update our cached value
        var oldValue = this.value;
        this.value = newValue;
        if (callback) callback(null, newValue);

        // emit a change event if necessary
        if (oldValue !== newValue)
          this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
      }

    }.bind(this)), context);
  }
  else {

    // no one is listening to the 'get' event, so just return the cached value
    if (callback)
      callback(null, this.value);
  }
}

Characteristic.prototype.setValue = function(newValue, callback, context) {

  if (this.listeners('set').length > 0) {

    // allow a listener to handle the setting of this value, and wait for completion
    this.emit('set', newValue, once(function(err) {

      if (err) {
        // pass the error along to our callback
        if (callback) callback(err);
      }
      else {
        // setting the value was a success; so we can cache it now
        var oldValue = this.value;
        this.value = newValue;
        if (callback) callback();

        // emit a change event if necessary
        if (oldValue !== newValue)
          this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
      }

    }.bind(this)), context);

  }
  else {

    // no one is listening to the 'set' event, so just assign the value blindly
    var oldValue = this.value;
    this.value = newValue;
    if (callback) callback();

    // emit a change event if necessary
    if (oldValue !== newValue)
      this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
  }

  return this; // for chaining
}

Characteristic.prototype.getDefaultValue = function() {
  switch (this.props.format) {
    case Characteristic.Formats.BOOL: return false;
    case Characteristic.Formats.STRING: return "STRING";
    case Characteristic.Formats.ARRAY: return []; // who knows!
    case Characteristic.Formats.DICTIONARY: return {}; // who knows!
    case Characteristic.Formats.DATA: return null; // who knows!
    case Characteristic.Formats.TLV8: return null; // who knows!
    default: return this.props.minValue || 0;
  }
}

Characteristic.prototype._assignID = function(identifierCache, accessoryName, serviceUUID, serviceSubtype) {

  // generate our IID based on our UUID
  this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID);
}

/**
 * Returns a JSON representation of this Accessory suitable for delivering to HAP clients.
 */
Characteristic.prototype.toHAP = function(opt) {

  // ensure our value fits within our constraints if present
  var value = this.value;
  if (this.props.minValue != null && value < this.props.minValue) value = this.props.minValue;
  if (this.props.maxValue != null && value > this.props.maxValue) value = this.props.maxValue;

  var hap = {
    iid: this.iid,
    type: this.UUID,
    perms: this.props.perms,
    format: this.props.format,
    value: value,
    description: this.displayName

    // These properties used to be sent but do not seem to be used:
    //
    // maxLen: this.format === Characteristic.Formats.STRING ? 255 : 1,
    // events: false,
    // bonjour: false
  };

  // extra properties
  if (this.props.unit != null) hap.unit = this.props.unit;
  if (this.props.maxValue != null) hap.maxValue = this.props.maxValue;
  if (this.props.minValue != null) hap.minValue = this.props.minValue;
  if (this.props.minStep != null) hap.minStep = this.props.minStep;

  // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance
  if (this.props.perms.indexOf(Characteristic.Perms.READ) == -1)
    delete hap.value;

  // delete the "value" property anyway if we were asked to
  if (opt && opt.omitValues)
    delete hap.value;

  return hap;
}
<commit_msg>return null for default string value.<commit_after>var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var once = require('./util/once').once;

'use strict';

module.exports = {
  Characteristic: Characteristic
}

/**
 * Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a
 * "Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic
 * to a Service in order to store that value. A particular Characteristic is distinguished from others by its
 * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKitTypes.js along with a
 * corresponding concrete subclass.
 *
 * You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added
 * to any native or custom Services, but Siri will likely not be able to work with these.
 *
 * Note that you can get the "value" of a Characteristic by accessing the "value" property directly, but this
 * is really a "cached value". If you want to fetch the latest value, which may involve doing some work, then
 * call getValue().
 *
 * @event 'get' => function(callback(err, newValue), context) { }
 *        Emitted when someone calls getValue() on this Characteristic and desires the latest non-cached
 *        value. If there are any listeners to this event, one of them MUST call the callback in order
 *        for the value to ever be delivered. The `context` object is whatever was passed in by the initiator
 *        of this event (for instance whomever called `getValue`).
 *
 * @event 'set' => function(newValue, callback(err), context) { }
 *        Emitted when someone calls setValue() on this Characteristic with a desired new value. If there
 *        are any listeners to this event, one of them MUST call the callback in order for this.value to
 *        actually be set. The `context` object is whatever was passed in by the initiator of this change
 *        (for instance, whomever called `setValue`).
 *
 * @event 'change' => function({ oldValue, newValue, context }) { }
 *        Emitted after a change in our value has occurred. The new value will also be immediately accessible
 *        in this.value. The event object contains the new value as well as the context object originally
 *        passed in by the initiator of this change (if known).
 */

function Characteristic(displayName, UUID, props) {
  this.displayName = displayName;
  this.UUID = UUID;
  this.iid = null; // assigned by our containing Service
  this.value = null;
  this.props = props || {
    format: null,
    unit: null,
    minValue: null,
    maxValue: null,
    minStep: null,
    perms: []
  };
}

inherits(Characteristic, EventEmitter);

// Known HomeKit formats
Characteristic.Formats = {
  BOOL: 'bool',
  INT: 'int',
  FLOAT: 'float',
  STRING: 'string',
  ARRAY: 'array', // unconfirmed
  DICTIONARY: 'dictionary', // unconfirmed
  UINT8: 'uint8',
  UINT16: 'uint16',
  UINT32: 'uint32',
  UINT64: 'uint64',
  DATA: 'data', // unconfirmed
  TLV8: 'tlv8'
}

// Known HomeKit unit types
Characteristic.Units = {
  // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion.
  CELSIUS: 'celsius',
  PERCENTAGE: 'percentage',
  ARC_DEGREE: 'arcdegrees',
  LUX: 'lux',
  SECONDS: 'seconds'
}

// Known HomeKit permission types
Characteristic.Perms = {
  READ: 'pr',
  WRITE: 'pw',
  NOTIFY: 'ev'
}

/**
 * Copies the given properties to our props member variable,
 * and returns 'this' for chaining.
 *
 * @param 'props' {
 *   format: <one of Characteristic.Formats>,
 *   unit: <one of Characteristic.Units>,
 *   minValue: <minimum value for numeric characteristics>,
 *   maxValue: <maximum value for numeric characteristics>,
 *   minStep: <smallest allowed increment for numeric characteristics>,
 *   perms: array of [Characteristic.Perms] like [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
 * }
 */
Characteristic.prototype.setProps = function(props) {
  for (var key in (props || {}))
    if (Object.prototype.hasOwnProperty.call(props, key))
      this.props[key] = props[key];
  return this;
}

Characteristic.prototype.getValue = function(callback, context) {

  if (this.listeners('get').length > 0) {

    // allow a listener to handle the fetching of this value, and wait for completion
    this.emit('get', once(function(err, newValue) {

      if (err) {
        // pass the error along to our callback
        if (callback) callback(err);
      }
      else {
        // getting the value was a success; we can pass it along and also update our cached value
        var oldValue = this.value;
        this.value = newValue;
        if (callback) callback(null, newValue);

        // emit a change event if necessary
        if (oldValue !== newValue)
          this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
      }

    }.bind(this)), context);
  }
  else {

    // no one is listening to the 'get' event, so just return the cached value
    if (callback)
      callback(null, this.value);
  }
}

Characteristic.prototype.setValue = function(newValue, callback, context) {

  if (this.listeners('set').length > 0) {

    // allow a listener to handle the setting of this value, and wait for completion
    this.emit('set', newValue, once(function(err) {

      if (err) {
        // pass the error along to our callback
        if (callback) callback(err);
      }
      else {
        // setting the value was a success; so we can cache it now
        var oldValue = this.value;
        this.value = newValue;
        if (callback) callback();

        // emit a change event if necessary
        if (oldValue !== newValue)
          this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
      }

    }.bind(this)), context);

  }
  else {

    // no one is listening to the 'set' event, so just assign the value blindly
    var oldValue = this.value;
    this.value = newValue;
    if (callback) callback();

    // emit a change event if necessary
    if (oldValue !== newValue)
      this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
  }

  return this; // for chaining
}

Characteristic.prototype.getDefaultValue = function() {
  switch (this.props.format) {
    case Characteristic.Formats.BOOL: return false;
    case Characteristic.Formats.STRING: return null;
    case Characteristic.Formats.ARRAY: return []; // who knows!
    case Characteristic.Formats.DICTIONARY: return {}; // who knows!
    case Characteristic.Formats.DATA: return null; // who knows!
    case Characteristic.Formats.TLV8: return null; // who knows!
    default: return this.props.minValue || 0;
  }
}

Characteristic.prototype._assignID = function(identifierCache, accessoryName, serviceUUID, serviceSubtype) {

  // generate our IID based on our UUID
  this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID);
}

/**
 * Returns a JSON representation of this Accessory suitable for delivering to HAP clients.
 */
Characteristic.prototype.toHAP = function(opt) {

  // ensure our value fits within our constraints if present
  var value = this.value;
  if (this.props.minValue != null && value < this.props.minValue) value = this.props.minValue;
  if (this.props.maxValue != null && value > this.props.maxValue) value = this.props.maxValue;

  var hap = {
    iid: this.iid,
    type: this.UUID,
    perms: this.props.perms,
    format: this.props.format,
    value: value,
    description: this.displayName

    // These properties used to be sent but do not seem to be used:
    //
    // maxLen: this.format === Characteristic.Formats.STRING ? 255 : 1,
    // events: false,
    // bonjour: false
  };

  // extra properties
  if (this.props.unit != null) hap.unit = this.props.unit;
  if (this.props.maxValue != null) hap.maxValue = this.props.maxValue;
  if (this.props.minValue != null) hap.minValue = this.props.minValue;
  if (this.props.minStep != null) hap.minStep = this.props.minStep;

  // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance
  if (this.props.perms.indexOf(Characteristic.Perms.READ) == -1)
    delete hap.value;

  // delete the "value" property anyway if we were asked to
  if (opt && opt.omitValues)
    delete hap.value;

  return hap;
}
<|endoftext|>


<commit_before>'use strict'
/**
 * Copyright (c) 2010-2017 Brian Carlson (brian.m.carlson@gmail.com)
 * All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * README.md file in the root directory of this source tree.
 */

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

var Result = require('./result')
var utils = require('./utils')

var Query = function (config, values, callback) {
  // use of "new" optional
  if (!(this instanceof Query)) { return new Query(config, values, callback) }

  config = utils.normalizeQueryConfig(config, values, callback)

  this.text = config.text
  this.values = config.values
  this.rows = config.rows
  this.types = config.types
  this.name = config.name
  this.binary = config.binary
  this.stream = config.stream
  // use unique portal name each time
  this.portal = config.portal || ''
  this.callback = config.callback
  this._rowMode = config.rowMode
  if (process.domain && config.callback) {
    this.callback = process.domain.bind(config.callback)
  }
  this._result = new Result(this._rowMode, this.types)

  // potential for multiple results
  this._results = this._result
  this.isPreparedStatement = false
  this._canceledDueToError = false
  this._promise = null
  EventEmitter.call(this)
}

util.inherits(Query, EventEmitter)

Query.prototype.requiresPreparation = function () {
  // named queries must always be prepared
  if (this.name) { return true }
  // always prepare if there are max number of rows expected per
  // portal execution
  if (this.rows) { return true }
  // don't prepare empty text queries
  if (!this.text) { return false }
  // prepare if there are values
  if (!this.values) { return false }
  return this.values.length > 0
}

Query.prototype._checkForMultirow = function () {
  // if we already have a result with a command property
  // then we've already executed one query in a multi-statement simple query
  // turn our results into an array of results
  if (this._result.command) {
    if (!Array.isArray(this._results)) {
      this._results = [this._result]
    }
    this._result = new Result(this._rowMode, this.types)
    this._results.push(this._result)
  }
}

// associates row metadata from the supplied
// message with this query object
// metadata used when parsing row results
Query.prototype.handleRowDescription = function (msg) {
  this._checkForMultirow()
  this._result.addFields(msg.fields)
  this._accumulateRows = this.callback || !this.listeners('row').length
}

Query.prototype.handleDataRow = function (msg) {
  var row

  if (this._canceledDueToError) {
    return
  }

  try {
    row = this._result.parseRow(msg.fields)
  } catch (err) {
    this._canceledDueToError = err
    return
  }

  this.emit('row', row, this._result)
  if (this._accumulateRows) {
    this._result.addRow(row)
  }
}

Query.prototype.handleCommandComplete = function (msg, con) {
  this._checkForMultirow()
  this._result.addCommandComplete(msg)
  // need to sync after each command complete of a prepared statement
  if (this.isPreparedStatement) {
    con.sync()
  }
}

// if a named prepared statement is created with empty query text
// the backend will send an emptyQuery message but *not* a command complete message
// execution on the connection will hang until the backend receives a sync message
Query.prototype.handleEmptyQuery = function (con) {
  if (this.isPreparedStatement) {
    con.sync()
  }
}

Query.prototype.handleReadyForQuery = function (con) {
  if (this._canceledDueToError) {
    return this.handleError(this._canceledDueToError, con)
  }
  if (this.callback) {
    this.callback(null, this._results)
  }
  this.emit('end', this._results)
}

Query.prototype.handleError = function (err, connection) {
  // need to sync after error during a prepared statement
  if (this.isPreparedStatement) {
    connection.sync()
  }
  if (this._canceledDueToError) {
    err = this._canceledDueToError
    this._canceledDueToError = false
  }
  // if callback supplied do not emit error event as uncaught error
  // events will bubble up to node process
  if (this.callback) {
    return this.callback(err)
  }
  this.emit('error', err)
}

Query.prototype.submit = function (connection) {
  if (typeof this.text !== 'string' && typeof this.name !== 'string') {
    const err = new Error('A query must have either text or a name. Supplying neither is unsupported.')
    connection.emit('error', err)
    connection.emit('readyForQuery')
    return
  }
  if (this.values && !Array.isArray(this.values)) {
    const err = new Error('Query values must be an array')
    connection.emit('error', err)
    connection.emit('readyForQuery')
    return
  }
  if (this.requiresPreparation()) {
    this.prepare(connection)
  } else {
    connection.query(this.text)
  }
}

Query.prototype.hasBeenParsed = function (connection) {
  return this.name && connection.parsedStatements[this.name]
}

Query.prototype.handlePortalSuspended = function (connection) {
  this._getRows(connection, this.rows)
}

Query.prototype._getRows = function (connection, rows) {
  connection.execute({
    portal: this.portalName,
    rows: rows
  }, true)
  connection.flush()
}

Query.prototype.prepare = function (connection) {
  var self = this
  // prepared statements need sync to be called after each command
  // complete or when an error is encountered
  this.isPreparedStatement = true
  // TODO refactor this poor encapsulation
  if (!this.hasBeenParsed(connection)) {
    connection.parse({
      text: self.text,
      name: self.name,
      types: self.types
    }, true)
  }

  if (self.values) {
    self.values = self.values.map(utils.prepareValue)
  }

  // http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
  connection.bind({
    portal: self.portalName,
    statement: self.name,
    values: self.values,
    binary: self.binary
  }, true)

  connection.describe({
    type: 'P',
    name: self.portalName || ''
  }, true)

  this._getRows(connection, this.rows)
}

Query.prototype.handleCopyInResponse = function (connection) {
  if (this.stream) this.stream.startStreamingToConnection(connection)
  else connection.sendCopyFail('No source stream defined')
}

Query.prototype.handleCopyData = function (msg, connection) {
  var chunk = msg.chunk
  if (this.stream) {
    this.stream.handleChunk(chunk)
  }
  // if there are no stream (for example when copy to query was sent by
  // query method instead of copyTo) error will be handled
  // on copyOutResponse event, so silently ignore this error here
}

Query.prototype.cancel = function () {
  const runningClient = this.client
  if (runningClient.activeQuery === this) {
    // we need a new connection to the server (without StartupMessage)
    // https://www.postgresql.org/docs/9.3/static/protocol-flow.html#AEN99752
    // we will use the connection of a new client...
    const Client = require('./client')
    const cancelingClient = new Client(runningClient.connectionParameters)
    const connection = cancelingClient.connection
    connection.connect(runningClient.port, runningClient.host)
    const self = this
    connection.on('connect', function () {
      connection.cancel(self.client.processID, self.client.secretKey)
    })
  } else if (runningClient.queryQueue.indexOf(this) !== -1) {
    runningClient.queryQueue.splice(runningClient.queryQueue.indexOf(this), 1)
  }
}

module.exports = Query
<commit_msg>callback as arrow function<commit_after>'use strict'
/**
 * Copyright (c) 2010-2017 Brian Carlson (brian.m.carlson@gmail.com)
 * All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * README.md file in the root directory of this source tree.
 */

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

var Result = require('./result')
var utils = require('./utils')

var Query = function (config, values, callback) {
  // use of "new" optional
  if (!(this instanceof Query)) { return new Query(config, values, callback) }

  config = utils.normalizeQueryConfig(config, values, callback)

  this.text = config.text
  this.values = config.values
  this.rows = config.rows
  this.types = config.types
  this.name = config.name
  this.binary = config.binary
  this.stream = config.stream
  // use unique portal name each time
  this.portal = config.portal || ''
  this.callback = config.callback
  this._rowMode = config.rowMode
  if (process.domain && config.callback) {
    this.callback = process.domain.bind(config.callback)
  }
  this._result = new Result(this._rowMode, this.types)

  // potential for multiple results
  this._results = this._result
  this.isPreparedStatement = false
  this._canceledDueToError = false
  this._promise = null
  EventEmitter.call(this)
}

util.inherits(Query, EventEmitter)

Query.prototype.requiresPreparation = function () {
  // named queries must always be prepared
  if (this.name) { return true }
  // always prepare if there are max number of rows expected per
  // portal execution
  if (this.rows) { return true }
  // don't prepare empty text queries
  if (!this.text) { return false }
  // prepare if there are values
  if (!this.values) { return false }
  return this.values.length > 0
}

Query.prototype._checkForMultirow = function () {
  // if we already have a result with a command property
  // then we've already executed one query in a multi-statement simple query
  // turn our results into an array of results
  if (this._result.command) {
    if (!Array.isArray(this._results)) {
      this._results = [this._result]
    }
    this._result = new Result(this._rowMode, this.types)
    this._results.push(this._result)
  }
}

// associates row metadata from the supplied
// message with this query object
// metadata used when parsing row results
Query.prototype.handleRowDescription = function (msg) {
  this._checkForMultirow()
  this._result.addFields(msg.fields)
  this._accumulateRows = this.callback || !this.listeners('row').length
}

Query.prototype.handleDataRow = function (msg) {
  var row

  if (this._canceledDueToError) {
    return
  }

  try {
    row = this._result.parseRow(msg.fields)
  } catch (err) {
    this._canceledDueToError = err
    return
  }

  this.emit('row', row, this._result)
  if (this._accumulateRows) {
    this._result.addRow(row)
  }
}

Query.prototype.handleCommandComplete = function (msg, con) {
  this._checkForMultirow()
  this._result.addCommandComplete(msg)
  // need to sync after each command complete of a prepared statement
  if (this.isPreparedStatement) {
    con.sync()
  }
}

// if a named prepared statement is created with empty query text
// the backend will send an emptyQuery message but *not* a command complete message
// execution on the connection will hang until the backend receives a sync message
Query.prototype.handleEmptyQuery = function (con) {
  if (this.isPreparedStatement) {
    con.sync()
  }
}

Query.prototype.handleReadyForQuery = function (con) {
  if (this._canceledDueToError) {
    return this.handleError(this._canceledDueToError, con)
  }
  if (this.callback) {
    this.callback(null, this._results)
  }
  this.emit('end', this._results)
}

Query.prototype.handleError = function (err, connection) {
  // need to sync after error during a prepared statement
  if (this.isPreparedStatement) {
    connection.sync()
  }
  if (this._canceledDueToError) {
    err = this._canceledDueToError
    this._canceledDueToError = false
  }
  // if callback supplied do not emit error event as uncaught error
  // events will bubble up to node process
  if (this.callback) {
    return this.callback(err)
  }
  this.emit('error', err)
}

Query.prototype.submit = function (connection) {
  if (typeof this.text !== 'string' && typeof this.name !== 'string') {
    const err = new Error('A query must have either text or a name. Supplying neither is unsupported.')
    connection.emit('error', err)
    connection.emit('readyForQuery')
    return
  }
  if (this.values && !Array.isArray(this.values)) {
    const err = new Error('Query values must be an array')
    connection.emit('error', err)
    connection.emit('readyForQuery')
    return
  }
  if (this.requiresPreparation()) {
    this.prepare(connection)
  } else {
    connection.query(this.text)
  }
}

Query.prototype.hasBeenParsed = function (connection) {
  return this.name && connection.parsedStatements[this.name]
}

Query.prototype.handlePortalSuspended = function (connection) {
  this._getRows(connection, this.rows)
}

Query.prototype._getRows = function (connection, rows) {
  connection.execute({
    portal: this.portalName,
    rows: rows
  }, true)
  connection.flush()
}

Query.prototype.prepare = function (connection) {
  var self = this
  // prepared statements need sync to be called after each command
  // complete or when an error is encountered
  this.isPreparedStatement = true
  // TODO refactor this poor encapsulation
  if (!this.hasBeenParsed(connection)) {
    connection.parse({
      text: self.text,
      name: self.name,
      types: self.types
    }, true)
  }

  if (self.values) {
    self.values = self.values.map(utils.prepareValue)
  }

  // http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
  connection.bind({
    portal: self.portalName,
    statement: self.name,
    values: self.values,
    binary: self.binary
  }, true)

  connection.describe({
    type: 'P',
    name: self.portalName || ''
  }, true)

  this._getRows(connection, this.rows)
}

Query.prototype.handleCopyInResponse = function (connection) {
  if (this.stream) this.stream.startStreamingToConnection(connection)
  else connection.sendCopyFail('No source stream defined')
}

Query.prototype.handleCopyData = function (msg, connection) {
  var chunk = msg.chunk
  if (this.stream) {
    this.stream.handleChunk(chunk)
  }
  // if there are no stream (for example when copy to query was sent by
  // query method instead of copyTo) error will be handled
  // on copyOutResponse event, so silently ignore this error here
}

Query.prototype.cancel = function () {
  const runningClient = this.client
  if (runningClient.activeQuery === this) {
    // we need a new connection to the server (without StartupMessage)
    // https://www.postgresql.org/docs/9.3/static/protocol-flow.html#AEN99752
    // we will use the connection of a new client...
    const Client = require('./client')
    const cancelingClient = new Client(runningClient.connectionParameters)
    const connection = cancelingClient.connection
    connection.connect(runningClient.port, runningClient.host)
    connection.on('connect', () => connection.cancel(this.client.processID, this.client.secretKey))
  } else if (runningClient.queryQueue.indexOf(this) !== -1) {
    runningClient.queryQueue.splice(runningClient.queryQueue.indexOf(this), 1)
  }
}

module.exports = Query
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var sys = require('sys');var sys = require('sys');
var Row = require(__dirname + '/row');

var Query = function(config) {
  this.text = config.text;
  this.values = config.values;
  this.rows = config.rows;
  this.types = config.types;
  this.name = config.name;
  //for code clarity purposes we'll declare this here though it's not
  //set or used until a rowDescription message comes in
  this.rowDescription = null;
  this.callback = config.callback;
  EventEmitter.call(this);
};

sys.inherits(Query, EventEmitter);
var p = Query.prototype;

p.requiresPreparation = function() {
  return (this.values || 0).length > 0 || this.name || this.rows;
};

var noParse = function(val) {
  return val;
};

p.submit = function(connection) {
  var self = this;
  if(this.requiresPreparation()) {
    this.prepare(connection);
  } else {
    connection.query(this.text);
  }
  var converters = [];
  var names = [];
  var rows = [];
  var handleRowDescription = function(msg) {
    for(var i = 0; i < msg.fields.length; i++) {
      converters[i] = dataTypeParsers[msg.fields[i].dataTypeID] || noParse;
      names[i] = msg.fields[i].name;
    };
  };
  var handleDatarow = function(msg) {
    var result = new Row();
    for(var i = 0; i < msg.fields.length; i++) {
      var rawValue = msg.fields[i];
      result[names[i]] = rawValue === null ? null : converters[i](rawValue);
    }
    self.emit('row', result);

    //if no reciever, buffer rows
    if(self.callback) {
      rows.push(result);
    }
  };

  var onCommandComplete = function() {
    self.callback(null, {rows: rows});
    rows = [];
  }

  if(self.callback) {
    connection.on('commandComplete', onCommandComplete)
  }

  var onError = function(err) {
    //remove all listeners
    connection.removeListener('rowDescription', handleDatarow);
    connection.removeListener('dataRow', handleDatarow);
    connection.removeListener('error', onError);
    connection.removeListener('readyForQuery', onReadyForQuery);
    if(self.callback) {
      self.callback(err);
      connection.removeListener('commandComplete', onCommandComplete);
    }
    self.emit('error', err);
    self.emit('end');
  };

  var onReadyForQuery = function() {
    //remove all listeners
    connection.removeListener('rowDescription', handleRowDescription);
    connection.removeListener('dataRow', handleDatarow);
    connection.removeListener('readyForQuery', onReadyForQuery);
    connection.removeListener('error', onError);
    if(self.callback) {
      connection.removeListener('commandComplete', onCommandComplete);
    }
    self.emit('end');
  };

  connection.on('rowDescription', handleRowDescription);
  connection.on('dataRow', handleDatarow);
  connection.on('readyForQuery', onReadyForQuery);

  connection.on('error', onError);
};

p.hasBeenParsed = function(connection) {
  return this.name && connection.parsedStatements[this.name];
};

p.prepare = function(connection) {
  var self = this;

  if(!this.hasBeenParsed(connection)) {
    connection.parsedStatements[this.name] = true;
    connection.parse({
      text: self.text,
      name: self.name,
      types: self.types
    });
  }

  //TODO is there some btter way to prepare values for the database?
  if(self.values) {
    self.values = self.values.map(function(val) {
      return (val instanceof Date) ? JSON.stringify(val) : val;
    });
  }

  //http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
  connection.bind({
    portal: self.name,
    statement: self.name,
    values: self.values
  });

  connection.describe({
    type: 'P',
    name: self.name || ""
  });

  var getRows = function() {
    connection.execute({
      portal: self.name,
      rows: self.rows
    });
    connection.flush();
  };

  getRows();

  //TODO support EmptyQueryResponse, ErrorResponse, and PortalSuspended
  var onCommandComplete =  function() {
    connection.removeListener('error', onCommandComplete);
    connection.removeListener('commandComplete', onCommandComplete);
    connection.removeListener('portalSuspended', getRows);
    connection.sync();
  };

  connection.on('portalSuspended', getRows);

  connection.on('commandComplete', onCommandComplete);
  connection.on('error', onCommandComplete);
};

var dateParser = function(isoDate) {
  //TODO find some regexp help
  //this method works but it's ooglay
  //if you wanna contribute...... ;)
  var split = isoDate.split(' ');
  var dateMatcher = /(\d{4})-(\d{2})-(\d{2})/;

  var date = split[0];
  var time = split[1];
  var match = dateMatcher.exec(date);
  var splitDate = date.split('-');
  var year = match[1];
  var month = parseInt(match[2])-1;
  var day = match[3];

  var splitTime = time.split(':');
  var hour = parseInt(splitTime[0]);
  var min = splitTime[1];
  var end = splitTime[2];
  var seconds = /(\d{2})/.exec(end);
  seconds = (seconds ? seconds[1] : 0);
  var mili = /\.(\d{1,})/.exec(end);
  mili = mili ? mili[1].slice(0,3) : 0;
  var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(end);
  //minutes to adjust for timezone
  var tzAdjust = 0;
  if(tZone) {
    var type = tZone[1];
    switch(type) {
    case 'Z': break;
    case '-':
      tzAdjust = -(((parseInt(tZone[2])*60)+(parseInt(tZone[3]||0))));
      break;
    case '+':
      tzAdjust = (((parseInt(tZone[2])*60)+(parseInt(tZone[3]||0))));
      break;
    default:
      throw new Error("Unidentifed tZone part " + type);
    }
  }

  var utcOffset = Date.UTC(year, month, day, hour, min, seconds, mili);

  var date = new Date(utcOffset - (tzAdjust * 60* 1000));
  return date;
};

var dataTypeParsers = {
  20: parseInt,
  21: parseInt,
  23: parseInt,
  26: parseInt,
  1700: parseFloat,
  700: parseFloat,
  701: parseFloat,
  16: function(dbVal) { //boolean
    return dbVal === 't';
  },
  1114: dateParser,
  1184: dateParser
};

module.exports = Query;
<commit_msg>fixed bug related to parseInt bug, leading 0 is interpreated in a fucked upp way<commit_after>var EventEmitter = require('events').EventEmitter;
var sys = require('sys');var sys = require('sys');
var Row = require(__dirname + '/row');

var Query = function(config) {
  this.text = config.text;
  this.values = config.values;
  this.rows = config.rows;
  this.types = config.types;
  this.name = config.name;
  //for code clarity purposes we'll declare this here though it's not
  //set or used until a rowDescription message comes in
  this.rowDescription = null;
  this.callback = config.callback;
  EventEmitter.call(this);
};

sys.inherits(Query, EventEmitter);
var p = Query.prototype;

p.requiresPreparation = function() {
  return (this.values || 0).length > 0 || this.name || this.rows;
};

var noParse = function(val) {
  return val;
};

p.submit = function(connection) {
  var self = this;
  if(this.requiresPreparation()) {
    this.prepare(connection);
  } else {
    connection.query(this.text);
  }
  var converters = [];
  var names = [];
  var rows = [];
  var handleRowDescription = function(msg) {
    for(var i = 0; i < msg.fields.length; i++) {
      converters[i] = dataTypeParsers[msg.fields[i].dataTypeID] || noParse;
      names[i] = msg.fields[i].name;
    };
  };
  var handleDatarow = function(msg) {
    var result = new Row();
    for(var i = 0; i < msg.fields.length; i++) {
      var rawValue = msg.fields[i];
      result[names[i]] = rawValue === null ? null : converters[i](rawValue);
    }
    self.emit('row', result);

    //if no reciever, buffer rows
    if(self.callback) {
      rows.push(result);
    }
  };

  var onCommandComplete = function() {
    self.callback(null, {rows: rows});
    rows = [];
  }

  if(self.callback) {
    connection.on('commandComplete', onCommandComplete)
  }

  var onError = function(err) {
    //remove all listeners
    connection.removeListener('rowDescription', handleDatarow);
    connection.removeListener('dataRow', handleDatarow);
    connection.removeListener('error', onError);
    connection.removeListener('readyForQuery', onReadyForQuery);
    if(self.callback) {
      self.callback(err);
      connection.removeListener('commandComplete', onCommandComplete);
    }
    self.emit('error', err);
    self.emit('end');
  };

  var onReadyForQuery = function() {
    //remove all listeners
    connection.removeListener('rowDescription', handleRowDescription);
    connection.removeListener('dataRow', handleDatarow);
    connection.removeListener('readyForQuery', onReadyForQuery);
    connection.removeListener('error', onError);
    if(self.callback) {
      connection.removeListener('commandComplete', onCommandComplete);
    }
    self.emit('end');
  };

  connection.on('rowDescription', handleRowDescription);
  connection.on('dataRow', handleDatarow);
  connection.on('readyForQuery', onReadyForQuery);

  connection.on('error', onError);
};

p.hasBeenParsed = function(connection) {
  return this.name && connection.parsedStatements[this.name];
};

p.prepare = function(connection) {
  var self = this;

  if(!this.hasBeenParsed(connection)) {
    connection.parsedStatements[this.name] = true;
    connection.parse({
      text: self.text,
      name: self.name,
      types: self.types
    });
  }

  //TODO is there some btter way to prepare values for the database?
  if(self.values) {
    self.values = self.values.map(function(val) {
      return (val instanceof Date) ? JSON.stringify(val) : val;
    });
  }

  //http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
  connection.bind({
    portal: self.name,
    statement: self.name,
    values: self.values
  });

  connection.describe({
    type: 'P',
    name: self.name || ""
  });

  var getRows = function() {
    connection.execute({
      portal: self.name,
      rows: self.rows
    });
    connection.flush();
  };

  getRows();

  //TODO support EmptyQueryResponse, ErrorResponse, and PortalSuspended
  var onCommandComplete =  function() {
    connection.removeListener('error', onCommandComplete);
    connection.removeListener('commandComplete', onCommandComplete);
    connection.removeListener('portalSuspended', getRows);
    connection.sync();
  };

  connection.on('portalSuspended', getRows);

  connection.on('commandComplete', onCommandComplete);
  connection.on('error', onCommandComplete);
};

var dateParser = function(isoDate) {
  //TODO find some regexp help
  //this method works but it's ooglay
  //if you wanna contribute...... ;)
  var split = isoDate.split(' ');
  var dateMatcher = /(\d{4})-(\d{2})-(\d{2})/;

  var date = split[0];
  var time = split[1];
  var match = dateMatcher.exec(date);
  var splitDate = date.split('-');
  var year = match[1];
  var month = parseInt(match[2],10)-1;
  var day = match[3];

  var splitTime = time.split(':');
  var hour = parseInt(splitTime[0],10);
  var min = parseInt(splitTime[1],10);
  var end = splitTime[2];
  var seconds = /(\d{2})/.exec(end);
  seconds = (seconds ? seconds[1] : 0);
	seconds = parseInt(seconds,10);
  var mili = /\.(\d{1,})/.exec(end);
  mili = mili ? mili[1].slice(0,3) : 0;
  var tZone = /([Z|+\-])(\d{2})?(\d{2})?/.exec(end);
  //minutes to adjust for timezone
  var tzAdjust = 0;
  if(tZone) {
    var type = tZone[1];
    switch(type) {
    case 'Z': break;
    case '-':
      tzAdjust = -(((parseInt(tZone[2],10)*60)+(parseInt(tZone[3]||0,10))));
      break;
    case '+':
      tzAdjust = (((parseInt(tZone[2],10)*60)+(parseInt(tZone[3]||0,10))));
      break;
    default:
      throw new Error("Unidentifed tZone part " + type);
    }
  }

  var utcOffset = Date.UTC(year, month, day, hour, min, seconds, mili);

  var date = new Date(utcOffset - (tzAdjust * 60* 1000));
  return date;
};

// To help we test dateParser
Query.dateParser = dateParser;

var dataTypeParsers = {
  20: parseInt,
  21: parseInt,
  23: parseInt,
  26: parseInt,
  1700: parseFloat,
  700: parseFloat,
  701: parseFloat,
  16: function(dbVal) { //boolean
    return dbVal === 't';
  },
  1114: dateParser,
  1184: dateParser
};

module.exports = Query;
<|endoftext|>


<commit_before>var Query = exports.Query = function (){
  this._select = '';
  this._from = [];
  this._where = [];
  this._order = null;
  this._group = null;
  this._having = null;
  this._limit = null;
  this._offset = null;
  this._connectionPool = null;
};

Query.prototype.toSql = function () {
  var sql = "select " + this._select;
  sql += " from " + this._from.map(function(t){return t.table_name;}).join(", ");
  sql += this.whereStatement();

  if(this._group)
    sql += " group by " + this._group;
  if(this._having)
    sql += " order by " + this._having;
  if(this._order)
    sql += " order by " + this._order;
  if(this._limit)
    sql += " limit " + this._limit;
  if(this._offset)
    sql += " offset " + this._offset;

  return sql;
}

Query.prototype.execute = function () {
  var results = this._connectionPool.getConnection().querySync(this.toSql()).fetchAllSync();
  var model = this._from[0];
  if(results && model) {
    return results.map(function(res) {
            var m = new model();
            for(f in res) {m[f] = res[f];}
            return m;
          });
  }
  return results;
}

Query.prototype.where = function (obj) {
  var q = clone(this);
  q._where = q._where.concat(obj);
  return q;
}

Query.prototype.whereStatement = function() {
  if(this._where && this._where.length > 0)
    return " where " + this._where.join(" and ");
  return "";
}

function Clone() { }
function clone(obj) {
    Clone.prototype = obj;
    return new Clone();
}
<commit_msg>more query methods<commit_after>var Query = exports.Query = function (){
  this._select = '';
  this._from = [];
  this._where = [];
  this._order = null;
  this._group = null;
  this._having = null;
  this._limit = null;
  this._offset = null;
  this._connectionPool = null;
};

Query.prototype.toSql = function () {
  var sql = "select " + this._select;
  sql += " from " + this._from.map(function(t){return t.table_name;}).join(", ");
  sql += this.whereStatement();

  if(this._group)
    sql += " group by " + this._group;
  if(this._having)
    sql += " order by " + this._having;
  if(this._order)
    sql += " order by " + this._order;
  if(this._limit)
    sql += " limit " + this._limit;
  if(this._offset)
    sql += " offset " + this._offset;

  return sql;
}

Query.prototype.execute = function () {
  var results = this._connectionPool.getConnection().querySync(this.toSql()).fetchAllSync();
  var model = this._from[0];
  if(results && model) {
    return results.map(function(res) {
            var m = new model();
            for(f in res) {m[f] = res[f];}
            return m;
          });
  }
  return results;
}

Query.prototype.where = function (obj) {
  var q = clone(this);
  q._where = q._where.concat(obj);
  return q;
}

Query.prototype.take = function (obj) {
  var q = clone(this);
  q._limit = obj;
  return q;
}

Query.prototype.group = function (obj) {
  var q = clone(this);
  q._group = obj;
  return q;
}

Query.prototype.having = function (obj) {
  var q = clone(this);
  q._having = obj;
  return q;
}

Query.prototype.skip = function (obj) {
  var q = clone(this);
  q._offset = obj;
  return q;
}

Query.prototype.join = function (other_query) {
  var q = clone(this);
  //TODO
  return q;
}

Query.prototype.whereStatement = function() {
  if(this._where && this._where.length > 0)
    return " where " + this._where.join(" and ");
  return "";
}

function Clone() { }
function clone(obj) {
    Clone.prototype = obj;
    return new Clone();
}
<|endoftext|>


<commit_before>var util = require('util');
var config = require('../config');
var helpers = require('./helpers');
var Job = require('./job');
var Worker = require('./worker');
var debug = require('debug')('convoy:queue');

var prefix = config.keys.prefix;

var Queue = function(name, opts){
  this.name = name;
  this.opts = {
    concurrentWorkers: 1,
    jobTimeout: 5000,
    blockingTimeout: 1
  };

  for(var optionName in opts){
    this.opts[optionName] = opts[optionName];
  }

  this.redisClients = {
    queueClient: opts.redis.createClient(),
    workerClient: opts.redis.createClient()
  };

  // The queue client is used for blocking redis commands
  this.queueClient = this.redisClients.queueClient;
  this.client = this.redisClients.workerClient;
  this.workerClient = this.redisClients.workerClient;

  this.processing = true;
  this.workersRunning = 0;
  this.jobsRunning = 0;
  this.closed = false;
};

module.exports = Queue;

Queue.prototype.addJob = function(job, cb){
  var self = this;
  if(!cb) cb = function(){};

  // Set job in committed list
  this.client.sadd(helpers.key(this.name+':committed'), job.id, function(err, added){
    if(err) return cb(err);

    if(!added){
      // Job is already in the system so we won't try to queue it. It could be queued or processing
      // It's tempting to just zscore the processing list, but that's not atomic and there is a chance
      // that the job is with a worker, but hasn't yet been added to the processing set before we might try queuing it again
      debug('job already committed');
      // We'll still zscore the processing list to find out whether the job's being processed
      return self.client.zscore(helpers.key(self.name+':processing'), job.id, function(err, score){
        var msg = 'committed';
        if(score) msg = 'processing';
        // If the job is in processing state, the client might want to re-queue the job at a later date when it finishes, so call back with a status
        return cb(err, msg);
      });
    }

    // Queue job
    self.client.rpush(helpers.key(self.name+':queued'), job.id, function(err, added){
      if(err) return cb(err);

      return cb(err, added);
    });
  });
};

Queue.prototype.startProcessing = function(cb) {
  var self = this;
  self.processing = true;

  self.processJob(cb);
};

/**
 * Checks if we should fetch a job from the queue based on concurrency settings
 * Fetches the job, spawns a worker and passes the job to the worker
 * @param  {Function} usrCb User function to invoke with job
 */
Queue.prototype.processJob = function(usrCb) {
  var self = this;

  // Only proceed with fetching the job if in processing more and within concurrency limit
  if(!self.processing) return;
  if(self.workersRunning >= self.opts.concurrentWorkers) return;

  self.workersRunning += 1;

  self.fetchJob(function(err, job){
    if(err){
      debug('Failed to fetch job');
      self.endWorker();
      return setTimeout(function(){
        self.processJob(usrCb);
      }, 1000);
    }

    if(!job){
      // BLPOP succeeded but yielded no data (timeout probably reached)
      self.endWorker();
      // Try processing another job
      return self.processJob(usrCb);
    }

    self.spawnWorker(job, function(err, worker){
      // Grab more jobs
      self.processJob(usrCb);

      // Worked failed to start up
      if(err){
        debug('Worker failed to start up');
        self.endWorker(worker);
        // Try processing another job
        return self.processJob(usrCb);
      }

      var usrDone = function(err, workerCb){
        worker.processed(err, workerCb);
        self.endWorker(worker);
        self.processJob(usrCb);
      };

      var jobTimeout;
      var jobTTL = self.opts.jobTimeout;
      var timeoutHit = false;

      if(jobTTL > 0) {
        // Set up a timeout in case the user function never calls back to us
        jobTimeout = setTimeout(function(){
          debug('Warning: Queue processing function did not call done() within %dms. Job is now considered failed.', jobTTL);
          timeoutHit = true;
          return usrDone('timeout');
        }, jobTTL);
      }

      // Pass job to user function + run
      return usrCb(job, function(err, workerCb){
        // If we've already failed this job due to the timeout being reached, and the user function finally
        // calls this job completion callback, then don't continue
        if(!workerCb) workerCb = function(){};
        if(timeoutHit) return workerCb('timeout');
        if(jobTimeout) clearTimeout(jobTimeout);
        return usrDone(err, workerCb);
      });
    });
  });
};

Queue.prototype.spawnWorker = function(job, cb) {
  var self = this;
  var workerTTL = 1000;
  var worker = new Worker(self, job);
  return worker.start(function(err){
    return cb(err, worker);
  });
};

Queue.prototype.endWorker = function() {
  var self = this;
  self.workersRunning -= 1;
};

Queue.prototype.stopProcessing = function() {
  // If blocked, client will still invoke its fetchJob callback with a job
  // once it is released from blpop, but will not go back into blocking state
  // again until startProcessing is called.

  this.processing = false;
};

Queue.prototype.fetchJob = function(cb){
  var self = this;

  var key = helpers.key(this.name+':queued');
  this.blocked = true;
  this.queueClient.blpop(key, self.opts.blockingTimeout, function(err, entry){
    self.blocked = false;
    if(err) return cb(err);

    if(!entry){
      // No jobs to fetch
      return cb();
    }

    var jobID = entry[1];
    var job = new Job(jobID);
    return cb(err, job);
  });
};

Queue.prototype.countQueued = function(cb){
  var key = helpers.key(this.name+':queued');
  this.client.llen(key, cb);
};

Queue.prototype.countCommitted = function(cb){
  var key = helpers.key(this.name+':committed');
  this.client.scard(key, cb);
};

Queue.prototype.countProcessing = function(cb){
  var key = helpers.key(this.name+':processing');
  this.client.zcard(key, cb);
};

Queue.prototype.countFailed = function(cb){
  var key = helpers.key(this.name+':failed');
  this.client.zcard(key, cb);
};

Queue.prototype.jamGuard = function(idleTime, cb) {
  var self = this;
  debug('Checking for jammed jobs...');
  this.clearJammedJobs(idleTime, function(err, jammedJobs){
    self.jamGuardTimeout = setTimeout(self.jamGuard.bind(self, idleTime, cb), idleTime*1000 || 1000);

    if(cb){
      cb(err, jammedJobs);
    }
  });
};

Queue.prototype.clearJammedJobs = function(idleTime, cb) {
  var self = this;

  /**
    Multiple convoys could have set jam guards, so we lock before we clear anything
    to prevent more than one guard from completing the task
  */
  this.setLock('clearJammedJobs', 5, function(err, lockSet, lockKey){
    if(err) return cb(err);

    if(!lockSet){
      debug('another convoy is clearing jammed jobs');
      return cb(null, []);
    }

    self.fetchJammedJobs(idleTime, function(err, members){
      if(err || !members){
        return cb(null, []);
      }

      var jammedJobs = [], pending = 0;

      var iterator = function(){
        if(!--pending){
          self.unlock(lockKey);
        }
      };

      members.forEach(function(jobID){
        pending += 1;
        var job = new Job(jobID);
        jammedJobs.push(job);
        var worker = new Worker(self, job);
        worker.notProcessing(iterator);
      });

      return cb(err, jammedJobs);
    });
  });
};

/*
  Sometimes, jobs can get stuck in processing state.
  This can happen if a worker dies before acking with processing success/failure, or never invokes the processed callback.

  When given a reasonable idle time after which a job can be considered jammed,
  we can fetch a list of jammed jobs.
*/
Queue.prototype.fetchJammedJobs = function(idleTime, cb) {
  var self = this;
  var now = helpers.time();
  var range = Math.round(now - idleTime);

  self.client.zrangebyscore(helpers.key(self.name+':processing'), 0, range, function(err, members){
    if(err) debug(err);

    return cb(err, members);
  });
};

Queue.prototype.setLock = function(name, expire, cb) {
  var expireCommand = 'expire';
  var timestamp = helpers.time();
  timestamp -= timestamp % expire;
  var key = helpers.key(this.name+':lock:'+name+':'+timestamp);
  var rd = this.client.multi();
  rd.setnx(key, timestamp, function(err, lockSet){
    if(err) return cb(err);

    return cb(err, +lockSet, key);
  });
  rd.expire(key, expire);
  rd.exec();
  return key;
};

Queue.prototype.unlock = function(key, cb) {
  this.client.del(key, cb);
};

/*
 * Try to cleanly close clients after all commands are sent
 * When finished closing, the queue object should no longer be used
 *
 * If the queue client is blocked with a pending blpop command, this will not run
 * until the connection is unblocked.
*/
Queue.prototype.close = function(cb) {
  var self = this;
  if(this.closed) return cb();

  this.queueClient.quit(function(err){
    self.closed = true;
    self.processing = false;
    return cb(err);
  });
};

/**
* Forces redis connections to close
*/
Queue.prototype.end = function() {
  for(var i in this.redisClients){
    if(!this.redisClients[i]) continue;

    var client = this.redisClients[i];
    client.quit();
  }
};
<commit_msg>Stop processing when closing<commit_after>var util = require('util');
var config = require('../config');
var helpers = require('./helpers');
var Job = require('./job');
var Worker = require('./worker');
var debug = require('debug')('convoy:queue');

var prefix = config.keys.prefix;

var Queue = function(name, opts){
  this.name = name;
  this.opts = {
    concurrentWorkers: 1,
    jobTimeout: 5000,
    blockingTimeout: 1
  };

  for(var optionName in opts){
    this.opts[optionName] = opts[optionName];
  }

  this.redisClients = {
    queueClient: opts.redis.createClient(),
    workerClient: opts.redis.createClient()
  };

  // The queue client is used for blocking redis commands
  this.queueClient = this.redisClients.queueClient;
  this.client = this.redisClients.workerClient;
  this.workerClient = this.redisClients.workerClient;

  this.processing = true;
  this.workersRunning = 0;
  this.jobsRunning = 0;
  this.closed = false;
};

module.exports = Queue;

Queue.prototype.addJob = function(job, cb){
  var self = this;
  if(!cb) cb = function(){};

  // Set job in committed list
  this.client.sadd(helpers.key(this.name+':committed'), job.id, function(err, added){
    if(err) return cb(err);

    if(!added){
      // Job is already in the system so we won't try to queue it. It could be queued or processing
      // It's tempting to just zscore the processing list, but that's not atomic and there is a chance
      // that the job is with a worker, but hasn't yet been added to the processing set before we might try queuing it again
      debug('job already committed');
      // We'll still zscore the processing list to find out whether the job's being processed
      return self.client.zscore(helpers.key(self.name+':processing'), job.id, function(err, score){
        var msg = 'committed';
        if(score) msg = 'processing';
        // If the job is in processing state, the client might want to re-queue the job at a later date when it finishes, so call back with a status
        return cb(err, msg);
      });
    }

    // Queue job
    self.client.rpush(helpers.key(self.name+':queued'), job.id, function(err, added){
      if(err) return cb(err);

      return cb(err, added);
    });
  });
};

Queue.prototype.startProcessing = function(cb) {
  var self = this;
  self.processing = true;

  self.processJob(cb);
};

/**
 * Checks if we should fetch a job from the queue based on concurrency settings
 * Fetches the job, spawns a worker and passes the job to the worker
 * @param  {Function} usrCb User function to invoke with job
 */
Queue.prototype.processJob = function(usrCb) {
  var self = this;

  // Only proceed with fetching the job if in processing more and within concurrency limit
  if(!self.processing) return;
  if(self.workersRunning >= self.opts.concurrentWorkers) return;

  self.workersRunning += 1;

  self.fetchJob(function(err, job){
    if(err){
      debug('Failed to fetch job');
      self.endWorker();
      return setTimeout(function(){
        self.processJob(usrCb);
      }, 1000);
    }

    if(!job){
      // BLPOP succeeded but yielded no data (timeout probably reached)
      self.endWorker();
      // Try processing another job
      return self.processJob(usrCb);
    }

    self.spawnWorker(job, function(err, worker){
      // Grab more jobs
      self.processJob(usrCb);

      // Worked failed to start up
      if(err){
        debug('Worker failed to start up');
        self.endWorker(worker);
        // Try processing another job
        return self.processJob(usrCb);
      }

      var usrDone = function(err, workerCb){
        worker.processed(err, workerCb);
        self.endWorker(worker);
        self.processJob(usrCb);
      };

      var jobTimeout;
      var jobTTL = self.opts.jobTimeout;
      var timeoutHit = false;

      if(jobTTL > 0) {
        // Set up a timeout in case the user function never calls back to us
        jobTimeout = setTimeout(function(){
          debug('Warning: Queue processing function did not call done() within %dms. Job is now considered failed.', jobTTL);
          timeoutHit = true;
          return usrDone('timeout');
        }, jobTTL);
      }

      // Pass job to user function + run
      return usrCb(job, function(err, workerCb){
        // If we've already failed this job due to the timeout being reached, and the user function finally
        // calls this job completion callback, then don't continue
        if(!workerCb) workerCb = function(){};
        if(timeoutHit) return workerCb('timeout');
        if(jobTimeout) clearTimeout(jobTimeout);
        return usrDone(err, workerCb);
      });
    });
  });
};

Queue.prototype.spawnWorker = function(job, cb) {
  var self = this;
  var workerTTL = 1000;
  var worker = new Worker(self, job);
  return worker.start(function(err){
    return cb(err, worker);
  });
};

Queue.prototype.endWorker = function() {
  var self = this;
  self.workersRunning -= 1;
};

Queue.prototype.stopProcessing = function() {
  // If blocked, client will still invoke its fetchJob callback with a job
  // once it is released from blpop, but will not go back into blocking state
  // again until startProcessing is called.

  this.processing = false;
};

Queue.prototype.fetchJob = function(cb){
  var self = this;

  var key = helpers.key(this.name+':queued');
  this.blocked = true;
  this.queueClient.blpop(key, self.opts.blockingTimeout, function(err, entry){
    self.blocked = false;
    if(err) return cb(err);

    if(!entry){
      // No jobs to fetch
      return cb();
    }

    var jobID = entry[1];
    var job = new Job(jobID);
    return cb(err, job);
  });
};

Queue.prototype.countQueued = function(cb){
  var key = helpers.key(this.name+':queued');
  this.client.llen(key, cb);
};

Queue.prototype.countCommitted = function(cb){
  var key = helpers.key(this.name+':committed');
  this.client.scard(key, cb);
};

Queue.prototype.countProcessing = function(cb){
  var key = helpers.key(this.name+':processing');
  this.client.zcard(key, cb);
};

Queue.prototype.countFailed = function(cb){
  var key = helpers.key(this.name+':failed');
  this.client.zcard(key, cb);
};

Queue.prototype.jamGuard = function(idleTime, cb) {
  var self = this;
  debug('Checking for jammed jobs...');
  this.clearJammedJobs(idleTime, function(err, jammedJobs){
    self.jamGuardTimeout = setTimeout(self.jamGuard.bind(self, idleTime, cb), idleTime*1000 || 1000);

    if(cb){
      cb(err, jammedJobs);
    }
  });
};

Queue.prototype.clearJammedJobs = function(idleTime, cb) {
  var self = this;

  /**
    Multiple convoys could have set jam guards, so we lock before we clear anything
    to prevent more than one guard from completing the task
  */
  this.setLock('clearJammedJobs', 5, function(err, lockSet, lockKey){
    if(err) return cb(err);

    if(!lockSet){
      debug('another convoy is clearing jammed jobs');
      return cb(null, []);
    }

    self.fetchJammedJobs(idleTime, function(err, members){
      if(err || !members){
        return cb(null, []);
      }

      var jammedJobs = [], pending = 0;

      var iterator = function(){
        if(!--pending){
          self.unlock(lockKey);
        }
      };

      members.forEach(function(jobID){
        pending += 1;
        var job = new Job(jobID);
        jammedJobs.push(job);
        var worker = new Worker(self, job);
        worker.notProcessing(iterator);
      });

      return cb(err, jammedJobs);
    });
  });
};

/*
  Sometimes, jobs can get stuck in processing state.
  This can happen if a worker dies before acking with processing success/failure, or never invokes the processed callback.

  When given a reasonable idle time after which a job can be considered jammed,
  we can fetch a list of jammed jobs.
*/
Queue.prototype.fetchJammedJobs = function(idleTime, cb) {
  var self = this;
  var now = helpers.time();
  var range = Math.round(now - idleTime);

  self.client.zrangebyscore(helpers.key(self.name+':processing'), 0, range, function(err, members){
    if(err) debug(err);

    return cb(err, members);
  });
};

Queue.prototype.setLock = function(name, expire, cb) {
  var expireCommand = 'expire';
  var timestamp = helpers.time();
  timestamp -= timestamp % expire;
  var key = helpers.key(this.name+':lock:'+name+':'+timestamp);
  var rd = this.client.multi();
  rd.setnx(key, timestamp, function(err, lockSet){
    if(err) return cb(err);

    return cb(err, +lockSet, key);
  });
  rd.expire(key, expire);
  rd.exec();
  return key;
};

Queue.prototype.unlock = function(key, cb) {
  this.client.del(key, cb);
};

/*
 * Try to cleanly close clients after all commands are sent
 * When finished closing, the queue object should no longer be used
 *
 * If the queue client is blocked with a pending blpop command, this will not run
 * until the connection is unblocked.
*/
Queue.prototype.close = function(cb) {
  var self = this;
  if(self.closed) return cb();

  self.stopProcessing();
  self.queueClient.quit(function(err){
    self.closed = true;
    return cb(err);
  });
};

/**
* Forces redis connections to close
*/
Queue.prototype.end = function() {
  for(var i in this.redisClients){
    if(!this.redisClients[i]) continue;

    var client = this.redisClients[i];
    client.quit();
  }
};
<|endoftext|>


<commit_before>'use strict';

/**
 * This module provides Ravel, a lightweight framework
 * for the rapid creation of MVPs which scale horizontally
 * with ease and support the latest in web technology.
 */
module.exports = function() {
  var Ravel = new (require('events')).EventEmitter();
  Ravel.modules = {};
  Ravel.ApplicationError = require('./util/application_error');

  //current working directory of the app using the
  //ravel library, so that client modules can be
  //loaded with relative paths.
  Ravel.cwd = process.cwd();

  Ravel._moduleFactories = {};
  Ravel._resourceFactories = {};
  Ravel._routesFactories = {};
  Ravel._rooms = {};
  Ravel._knownParameters = {};
  Ravel._params = {};

  //init database provider prototype
  require('./db/database_provider')(Ravel);

  //init authorization provider prototype
  require('./auth/authorization_provider')(Ravel);
  //init parameter system
  require('./core/params')(Ravel, Ravel._knownParameters, Ravel._params);
  //init logging system
  require('./util/log')(Ravel);

  //init dependency injection utility, which is used by most everything else
  var injector = require('./util/injector')(
    Ravel, Ravel._moduleFactories, module.parent);
  Ravel._injector = injector;

  //init module registration (Ravel.module)
  require('./core/module')(Ravel, Ravel._moduleFactories, injector);

  //init routes registration (Ravel.routes)
  require('./core/routes')(Ravel, Ravel._routesFactories, injector);

  //init resource registration (Ravel.resource)
  require('./core/resource')(Ravel, Ravel._resourceFactories, injector);

  //init websocket room registration (Ravel.room)
  require('./core/room')(Ravel, Ravel._rooms);

  //initialize authentication/authorization
  require('./auth/passport_init.js')(Ravel, injector);

  /**
   * Starts the application, when the client is finished
   * supplying parameters and registering modules, resources
   * routes and rooms.
   */
  Ravel.init = function() {
    Ravel._initialized = true;
    Ravel.db = require('./db/database')(Ravel);
    Ravel.kvstore = require('./util/kvstore')('ravel_prefix', Ravel);

    //App dependencies.
    var express = require('express');
    var session = require('express-session');
    var compression = require('compression');
    var favicon = require('serve-favicon');
    var cookieParser = require('cookie-parser');
    var http = require('http'); //https will be provided by reverse proxy
    var path = require('path');
    var Primus = require('primus.io');
    var ExpressRedisStore = require('connect-redis')(session);

    //configure express
    var app = express();
    app.disable('x-powered-by');
    //configure redis session store
    var sessionStoreArgs = {
      host:Ravel.get('redis host'),
      port:Ravel.get('redis port'),
      db:0 //we stick to 0 because clustered redis doesn't support multiple dbs
    };
    if (Ravel.get('redis password')) {
      sessionStoreArgs.pass = Ravel.get('redis password');
    }
    var expressSessionStore = new ExpressRedisStore(sessionStoreArgs);

    app.set('domain', Ravel.get('node domain'));
    app.set('port', Ravel.get('node port'));
    app.set('app domain', Ravel.get('app domain'));
    app.set('app port', Ravel.get('app port'));
    app.enable('trust proxy');
    //configure views
    app.set('views', path.join(Ravel.cwd, Ravel.get('express view directory')));
    app.set('view engine', Ravel.get('express view engine'));
    //app.use(require('morgan')('dev')); //uncomment to see HTTP requests
    app.use(compression());
    if (Ravel.get('express favicon path')) {
      app.use(favicon(path.join(Ravel.cwd, Ravel.get('express favicon path'))));
    }
    app.use(require('body-parser').json());
    app.use(require('method-override')());
    app.use(cookieParser(Ravel.get('express session secret')));
    app.use(session({
      store: expressSessionStore,
      secret:Ravel.get('express session secret'),
      resave:true,
      saveUninitialized:true
    }));
    //cross-site scripting protection, with mobile app support
    app.use(require('./auth/csrf')(Ravel));
    app.use(function(req, res, next){
      if (req.csrfToken) {
        res.locals.token = req.csrfToken();
      }
      next();
    });
    app.use(express.static(path.join(Ravel.cwd, Ravel.get('express public directory'))));
    app.use(require('connect-flash')());

    //basic express configuration is completed
    Ravel.emit('post config express', app);

    //Create ExpressJS server
    Ravel._server = http.createServer(app);

    //Pass server to Primus to get it going on the same port
    //Initialize primus.io with room handling, etc.
    var primus = new Primus(Ravel._server, { transformer: 'websockets', parser: 'JSON' });
    //primus_init produces a configured, cluster-ready broadcasting library
    var broadcast = require('./ws/primus_init.js')(
      Ravel, injector, primus, expressSessionStore, require('./util/websocket_room_resolver')(Ravel._rooms));
    //public version of broadcast, for client use
    Ravel.broadcast = {
      emit: broadcast.emit
    };

    //create registered modules using factories
    for (var moduleName in Ravel._moduleFactories) {
      Ravel._moduleFactories[moduleName]();
    }

    //create registered resources using factories
    for (var resourceName in Ravel._resourceFactories) {
      Ravel._resourceFactories[resourceName](app);
    }

    //create routes using factories
    for (var routesName in Ravel._routesFactories) {
      Ravel._routesFactories[routesName](app);
    }

    //application configuration is completed
    Ravel.emit('post init');
  };

  Ravel.listen = function() {
    if (!Ravel._initialized) {
      throw new Ravel.ApplicationError.NotAllowed('Cannot call Ravel.listen() before Ravel.init()');
    }
    //Start ExpressJS server
    Ravel._server.listen(Ravel.get('node port'), function(){
      Ravel.Log.info('Application server at ' + Ravel.get('node domain') +
        ' listening on port ' + Ravel.get('node port'));
      Ravel.emit('listening');
    });
  };

  Ravel.start = function() {
    Ravel.init();
    Ravel.listen();
  };

  //Register known ravel parameters
  //database providers
  Ravel.registerSimpleParameter('database providers', true);
  Ravel.set('database providers', []);
  //authorization providers
  Ravel.registerSimpleParameter('authorization providers', true);
  Ravel.set('authorization providers', []);
  //redis parameters
  Ravel.registerSimpleParameter('redis host', true);
  Ravel.registerSimpleParameter('redis port', true);
  Ravel.registerSimpleParameter('redis password');
  Ravel.registerSimpleParameter('websocket message cache time');
  //Node/express parameters
  Ravel.registerSimpleParameter('app domain', true);
  Ravel.registerSimpleParameter('app port', true);
  Ravel.registerSimpleParameter('node domain', true);
  Ravel.registerSimpleParameter('node port', true);
  Ravel.registerSimpleParameter('express public directory', true);
  Ravel.registerSimpleParameter('express view directory', true);
  Ravel.registerSimpleParameter('express view engine', true);
  Ravel.registerSimpleParameter('express favicon path');
  Ravel.registerSimpleParameter('express session secret', true);
  Ravel.registerSimpleParameter('disable json vulnerability protection');

  return Ravel;
};
<commit_msg>Added Ravel method for shutting down the server (will be useful for end-to-end testing)<commit_after>'use strict';

/**
 * This module provides Ravel, a lightweight framework
 * for the rapid creation of MVPs which scale horizontally
 * with ease and support the latest in web technology.
 */
module.exports = function() {
  var initialized, listening;

  var Ravel = new (require('events')).EventEmitter();
  Ravel.modules = {};
  Ravel.ApplicationError = require('./util/application_error');

  //current working directory of the app using the
  //ravel library, so that client modules can be
  //loaded with relative paths.
  Ravel.cwd = process.cwd();

  Ravel._moduleFactories = {};
  Ravel._resourceFactories = {};
  Ravel._routesFactories = {};
  Ravel._rooms = {};
  Ravel._knownParameters = {};
  Ravel._params = {};

  //init database provider prototype
  require('./db/database_provider')(Ravel);

  //init authorization provider prototype
  require('./auth/authorization_provider')(Ravel);
  //init parameter system
  require('./core/params')(Ravel, Ravel._knownParameters, Ravel._params);
  //init logging system
  require('./util/log')(Ravel);

  //init dependency injection utility, which is used by most everything else
  var injector = require('./util/injector')(
    Ravel, Ravel._moduleFactories, module.parent);
  Ravel._injector = injector;

  //init module registration (Ravel.module)
  require('./core/module')(Ravel, Ravel._moduleFactories, injector);

  //init routes registration (Ravel.routes)
  require('./core/routes')(Ravel, Ravel._routesFactories, injector);

  //init resource registration (Ravel.resource)
  require('./core/resource')(Ravel, Ravel._resourceFactories, injector);

  //init websocket room registration (Ravel.room)
  require('./core/room')(Ravel, Ravel._rooms);

  //initialize authentication/authorization
  require('./auth/passport_init.js')(Ravel, injector);

  /**
   * Starts the application, when the client is finished
   * supplying parameters and registering modules, resources
   * routes and rooms.
   */
  Ravel.init = function() {
    initialized = true;
    Ravel.db = require('./db/database')(Ravel);
    Ravel.kvstore = require('./util/kvstore')('ravel_prefix', Ravel);

    //App dependencies.
    var express = require('express');
    var session = require('express-session');
    var compression = require('compression');
    var favicon = require('serve-favicon');
    var cookieParser = require('cookie-parser');
    var http = require('http'); //https will be provided by reverse proxy
    var path = require('path');
    var Primus = require('primus.io');
    var ExpressRedisStore = require('connect-redis')(session);

    //configure express
    var app = express();
    app.disable('x-powered-by');
    //configure redis session store
    var sessionStoreArgs = {
      host:Ravel.get('redis host'),
      port:Ravel.get('redis port'),
      db:0 //we stick to 0 because clustered redis doesn't support multiple dbs
    };
    if (Ravel.get('redis password')) {
      sessionStoreArgs.pass = Ravel.get('redis password');
    }
    var expressSessionStore = new ExpressRedisStore(sessionStoreArgs);

    app.set('domain', Ravel.get('node domain'));
    app.set('port', Ravel.get('node port'));
    app.set('app domain', Ravel.get('app domain'));
    app.set('app port', Ravel.get('app port'));
    app.enable('trust proxy');
    //configure views
    app.set('views', path.join(Ravel.cwd, Ravel.get('express view directory')));
    app.set('view engine', Ravel.get('express view engine'));
    //app.use(require('morgan')('dev')); //uncomment to see HTTP requests
    app.use(compression());
    if (Ravel.get('express favicon path')) {
      app.use(favicon(path.join(Ravel.cwd, Ravel.get('express favicon path'))));
    }
    app.use(require('body-parser').json());
    app.use(require('method-override')());
    app.use(cookieParser(Ravel.get('express session secret')));
    app.use(session({
      store: expressSessionStore,
      secret:Ravel.get('express session secret'),
      resave:true,
      saveUninitialized:true
    }));
    //cross-site scripting protection, with mobile app support
    app.use(require('./auth/csrf')(Ravel));
    app.use(function(req, res, next){
      if (req.csrfToken) {
        res.locals.token = req.csrfToken();
      }
      next();
    });
    app.use(express.static(path.join(Ravel.cwd, Ravel.get('express public directory'))));
    app.use(require('connect-flash')());

    //basic express configuration is completed
    Ravel.emit('post config express', app);

    //Create ExpressJS server
    Ravel._server = http.createServer(app);

    //Pass server to Primus to get it going on the same port
    //Initialize primus.io with room handling, etc.
    var primus = new Primus(Ravel._server, { transformer: 'websockets', parser: 'JSON' });
    //primus_init produces a configured, cluster-ready broadcasting library
    var broadcast = require('./ws/primus_init.js')(
      Ravel, injector, primus, expressSessionStore, require('./util/websocket_room_resolver')(Ravel._rooms));
    //public version of broadcast, for client use
    Ravel.broadcast = {
      emit: broadcast.emit
    };

    //create registered modules using factories
    for (var moduleName in Ravel._moduleFactories) {
      Ravel._moduleFactories[moduleName]();
    }

    //create registered resources using factories
    for (var resourceName in Ravel._resourceFactories) {
      Ravel._resourceFactories[resourceName](app);
    }

    //create routes using factories
    for (var routesName in Ravel._routesFactories) {
      Ravel._routesFactories[routesName](app);
    }

    //application configuration is completed
    Ravel.emit('post init');
  };

  Ravel.listen = function() {
    if (!initialized) {
      throw new Ravel.ApplicationError.NotAllowed('Cannot call Ravel.listen() before Ravel.init()');
    }
    //Start ExpressJS server
    Ravel._server.listen(Ravel.get('node port'), function(){
      Ravel.Log.info('Application server at ' + Ravel.get('node domain') +
        ' listening on port ' + Ravel.get('node port'));
      Ravel.emit('listening');
      listening = true;
    });
  };

  Ravel.start = function() {
    Ravel.init();
    Ravel.listen();
  };

  Ravel.close = function() {
    if (!listening) {
      throw new Ravel.ApplicationError.NotAllowed('Cannot call Ravel.close() before Ravel.listen()');
    }
    Ravel._server.close();
    listening = false;
  };

  //Register known ravel parameters
  //database providers
  Ravel.registerSimpleParameter('database providers', true);
  Ravel.set('database providers', []);
  //authorization providers
  Ravel.registerSimpleParameter('authorization providers', true);
  Ravel.set('authorization providers', []);
  //redis parameters
  Ravel.registerSimpleParameter('redis host', true);
  Ravel.registerSimpleParameter('redis port', true);
  Ravel.registerSimpleParameter('redis password');
  Ravel.registerSimpleParameter('websocket message cache time');
  //Node/express parameters
  Ravel.registerSimpleParameter('app domain', true);
  Ravel.registerSimpleParameter('app port', true);
  Ravel.registerSimpleParameter('node domain', true);
  Ravel.registerSimpleParameter('node port', true);
  Ravel.registerSimpleParameter('express public directory', true);
  Ravel.registerSimpleParameter('express view directory', true);
  Ravel.registerSimpleParameter('express view engine', true);
  Ravel.registerSimpleParameter('express favicon path');
  Ravel.registerSimpleParameter('express session secret', true);
  Ravel.registerSimpleParameter('disable json vulnerability protection');

  return Ravel;
};
<|endoftext|>