<commit_before>/* 16may13abu
 * (c) Software Lab. Alexander Burger
 */

var DrawReq = new XMLHttpRequest();

function drawCanvas(id, dly) {
   if (!DrawReq)
      return true;
   try {
      DrawReq.open("POST",
         document.getElementsByTagName("BASE")[0].href + SesId +
         "!jsDraw?" + id );
      DrawReq.responseType = "arraybuffer";
   }
   catch (e) {return true;}
   DrawReq.onload = function() {
      var lst = plio(new Uint8Array(DrawReq.response));
      var cmd, i;

      if (lst) {
         var ctx = document.getElementById(id).getContext("2d");

         for (i = 0; i < lst.length; ++i) {
            switch ((cmd = lst[i])[0]) {  // In sync with "@lib/canvas.l"
            /*** Functions ***/
            case 1:
               ctx.fillText(cmd[1], cmd[2], cmd[3]);
               break;
            case 2:
               ctx.beginPath();
               ctx.moveTo(cmd[1], cmd[2]);
               ctx.lineTo(cmd[3], cmd[4]);
               ctx.closePath();
               ctx.stroke();
               break;
            case 3:
               ctx.clearRect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 4:
               ctx.strokeRect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 5:
               ctx.fillRect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 6:
               ctx.beginPath();
               break;
            case 7:
               ctx.closePath();
               break;
            case 8:
               ctx.moveTo(cmd[1], cmd[2]);
               break;
            case 9:
               ctx.lineTo(cmd[1], cmd[2]);
               break;
            case 10:
               ctx.bezierCurveTo(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
               break;
            case 11:
               ctx.moveTo(cmd[1], cmd[2]);
               ctx.lineTo(cmd[3], cmd[4]);
               break;
            case 12:
               ctx.rect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 13:
               ctx.arc(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
               break;
            case 14:
               ctx.stroke();
               break;
            case 15:
               ctx.fill();
               break;
            case 16:
               ctx.clip()
               break;
            case 17:
               ctx.drawImage(cmd[1], cmd[2], cmd[3]);
               break;
            case 18:
               ctx.translate(cmd[1], cmd[2]);
               break;
            case 19:
               ctx.rotate(cmd[1]);
               break;
            case 20:
               ctx.scale(cmd[1], cmd[2]);
               break;
            case 21:
               ctx.save();
               break;
            case 22:
               ctx.restore();
               break;
            /*** Variables ***/
            case 23:
               ctx.fillStyle = cmd[1];
               break;
            case 24:
               ctx.strokeStyle = cmd[1];
               break;
            case 25:
               ctx.globalAlpha = cmd[1];
               break;
            case 26:
               ctx.lineWidth = cmd[1];
               break;
            case 27:
               ctx.lineCap = cmd[1];
               break;
            case 28:
               ctx.lineJoin = cmd[1];
               break;
            case 29:
               ctx.miterLimit = cmd[1];
               break;
            case 30:
               ctx.globalCompositeOperation = cmd[1];
               break;
            }
         }
      }
      setTimeout(function() {drawCanvas(id, dly)}, dly);
   }
   try {DrawReq.send(null);}
   catch (e) {
      DrawReq.abort();
      return true;
   }
   return false;
}
<commit_msg>Local request object in Canvas<commit_after>/* 21may13abu
 * (c) Software Lab. Alexander Burger
 */

function drawCanvas(id, dly) {
   var req = new XMLHttpRequest();

   try {
      req.open("POST",
         document.getElementsByTagName("BASE")[0].href + SesId +
         "!jsDraw?" + id );
      req.responseType = "arraybuffer";
   }
   catch (e) {return true;}
   req.onload = function() {
      var lst = plio(new Uint8Array(req.response));
      var cmd, i;

      if (lst) {
         var ctx = document.getElementById(id).getContext("2d");

         for (i = 0; i < lst.length; ++i) {
            switch ((cmd = lst[i])[0]) {  // In sync with "@lib/canvas.l"
            /*** Functions ***/
            case 1:
               ctx.fillText(cmd[1], cmd[2], cmd[3]);
               break;
            case 2:
               ctx.beginPath();
               ctx.moveTo(cmd[1], cmd[2]);
               ctx.lineTo(cmd[3], cmd[4]);
               ctx.closePath();
               ctx.stroke();
               break;
            case 3:
               ctx.clearRect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 4:
               ctx.strokeRect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 5:
               ctx.fillRect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 6:
               ctx.beginPath();
               break;
            case 7:
               ctx.closePath();
               break;
            case 8:
               ctx.moveTo(cmd[1], cmd[2]);
               break;
            case 9:
               ctx.lineTo(cmd[1], cmd[2]);
               break;
            case 10:
               ctx.bezierCurveTo(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
               break;
            case 11:
               ctx.moveTo(cmd[1], cmd[2]);
               ctx.lineTo(cmd[3], cmd[4]);
               break;
            case 12:
               ctx.rect(cmd[1], cmd[2], cmd[3], cmd[4]);
               break;
            case 13:
               ctx.arc(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
               break;
            case 14:
               ctx.stroke();
               break;
            case 15:
               ctx.fill();
               break;
            case 16:
               ctx.clip()
               break;
            case 17:
               ctx.drawImage(cmd[1], cmd[2], cmd[3]);
               break;
            case 18:
               ctx.translate(cmd[1], cmd[2]);
               break;
            case 19:
               ctx.rotate(cmd[1]);
               break;
            case 20:
               ctx.scale(cmd[1], cmd[2]);
               break;
            case 21:
               ctx.save();
               break;
            case 22:
               ctx.restore();
               break;
            /*** Variables ***/
            case 23:
               ctx.fillStyle = cmd[1];
               break;
            case 24:
               ctx.strokeStyle = cmd[1];
               break;
            case 25:
               ctx.globalAlpha = cmd[1];
               break;
            case 26:
               ctx.lineWidth = cmd[1];
               break;
            case 27:
               ctx.lineCap = cmd[1];
               break;
            case 28:
               ctx.lineJoin = cmd[1];
               break;
            case 29:
               ctx.miterLimit = cmd[1];
               break;
            case 30:
               ctx.globalCompositeOperation = cmd[1];
               break;
            }
         }
      }
      setTimeout(function() {drawCanvas(id, dly)}, dly);
   }
   try {req.send(null);}
   catch (e) {
      req.abort();
      return true;
   }
   return false;
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 Malte-Thorben Bruns <skenqbx@googlemail.com>

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// 'Software'), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/* enable ECMA-262 strict mode */
'use strict';

/* load node modules */
var util = require('util');
var events = require('events');
var dgram = require('dgram');

/**
 * @param {Object.<string, *>=} opt_ions Multicast configuration object.
 * @constructor
 * @extends {events.EventEmitter}
 */
function Caster(opt_ions) {
  events.EventEmitter.apply(this);

  opt_ions = opt_ions || {};
  this.multicast = opt_ions.multicast || '224.0.0.42';
  this.address = opt_ions.address || '0.0.0.0';
  this.port = opt_ions.port || null;
  this.addressType = opt_ions.addressType || 4;
  this.loopback = opt_ions.loopback || false;
  this.ttl = opt_ions.ttl || 64;
  this.socket = null;
}
util.inherits(Caster, events.EventEmitter);
module.exports = Caster;

/**
 * Enable the multicast udp socket.
 * @param {function(Error)} opt_callback An optional callback.
 */
Caster.prototype.bind = function(opt_callback) {
  var self = this;
  var socket = dgram.createSocket('udp' + this.addressType);
  opt_callback = opt_callback || function() {};

  function bindHandler(err) {
    if (err) {
      socket.removeListener('listening', bindHandler);
    } else {
      socket.removeListener('error', bindHandler);
      socket.on('message', self._onMessage.bind(self));
      socket.on('error', self._onError.bind(self));
      socket.once('close', self._onClose.bind(self));
      // configure multicast
      socket.setMulticastTTL(self.ttl);
      socket.setMulticastLoopback(self.loopback);
      socket.addMembership(self.multicast);
      // update references
      self.port = socket.address().port;
      self.socket = socket;
    }
    opt_callback(err || null);
  }
  socket.once('listening', bindHandler);
  socket.once('error', bindHandler);

  socket.bind(this.port, this.address);
};

/**
 * @param {Buffer} message The message to be send.
 * @param {function(Error, Number)} opt_callback An optional callback.
 */
Caster.prototype.send = function(message, opt_callback) {
  this.socket.send(message, 0, message.length,
      this.port, this.multicast, opt_callback);
};

/**
 * Close the socket.
 */
Caster.prototype.close = function() {
  if (this.socket) {
    this.socket.close();
  }
};

Caster.prototype._onMessage = function(message, rinfo) {
  this.emit('message', message, rinfo);
};

Caster.prototype._onError = function(err) {
  this.emit('error', err);
};

Caster.prototype._onClose = function() {
  this.close();
  this.emit('close');
};
<commit_msg>optimized event listeners<commit_after>// Copyright (c) 2012 Malte-Thorben Bruns <skenqbx@googlemail.com>

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// 'Software'), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/* enable ECMA-262 strict mode */
'use strict';

/* load node modules */
var util = require('util');
var events = require('events');
var dgram = require('dgram');

/**
 * @param {Object.<string, *>=} opt_ions Multicast configuration object.
 * @constructor
 * @extends {events.EventEmitter}
 */
function Caster(opt_ions) {
  events.EventEmitter.apply(this);

  opt_ions = opt_ions || {};
  this.multicast = opt_ions.multicast || '224.0.0.42';
  this.address = opt_ions.address || '0.0.0.0';
  this.port = opt_ions.port || null;
  this.addressType = opt_ions.addressType || 4;
  this.loopback = opt_ions.loopback || false;
  this.ttl = opt_ions.ttl || 64;
  this.socket = null;
}
util.inherits(Caster, events.EventEmitter);
module.exports = Caster;

/**
 * Enable the multicast udp socket.
 * @param {function(Error)} opt_callback An optional callback.
 */
Caster.prototype.bind = function(opt_callback) {
  var self = this;
  var socket = dgram.createSocket('udp' + this.addressType);
  opt_callback = opt_callback || function() {};

  function bindHandler(err) {
    if (err) {
      socket.removeListener('listening', bindHandler);
    } else {
      socket.removeListener('error', bindHandler);
      socket.on('message', self.emit.bind(self, 'message'));
      socket.on('error', self.emit.bind(self, 'error'));
      socket.once('close', self.emit.bind(self, 'close'));
      // configure multicast
      socket.setMulticastTTL(self.ttl);
      socket.setMulticastLoopback(self.loopback);
      socket.addMembership(self.multicast);
      // update references
      self.port = socket.address().port;
      self.socket = socket;
    }
    opt_callback(err || null);
  }
  socket.once('listening', bindHandler);
  socket.once('error', bindHandler);

  socket.bind(this.port, this.address);
};

/**
 * @param {Buffer} message The message to be send.
 * @param {function(Error, Number)} opt_callback An optional callback.
 */
Caster.prototype.send = function(message, opt_callback) {
  this.socket.send(message, 0, message.length,
      this.port, this.multicast, opt_callback);
};

/**
 * Close the socket.
 */
Caster.prototype.close = function() {
  if (this.socket) {
    this.socket.close();
  }
};
<|endoftext|>


<commit_before>'use strict';

var dgram = require('dgram');

/**
 *  noop
 */

function noop() {};

/**
 *  Expose `Artnet`
 */

module.exports = Artnet;

function Artnet(opts) {
  var opts = opts || {};

  this.host = opts.host || '255.255.255.255';
  this.port = opts.port || 6454;
  this.refresh = opts.refresh || 0;

  this.header =  [65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 0, 0, 1, 0, 2, 0];

  this.socket = null;
  this.interval = null;
  this.timer = null;

  this.data = [];
};

/**
 *  Connect to host
 */

Artnet.prototype.connect = function connect() {
  this.socket = dgram.createSocket('udp4');

  this._startInterval();

  return this;
};

/**
 *  Close a connection
 *
 *  @param {Function} callback - optional
 */

Artnet.prototype.disconnect = function disconnect(callback) {
  callback = callback || noop;

  var self = this;

  this.socket && this.socket.close(function() {
    self.socket = null;
    callback();
  });

  return this;
};

/**
 *  Start the interval
 *
 *  @param {Number} interval
 */

Artnet.prototype._startInterval = function _startInterval(interval) {

  if (interval) this.interval = interval;

  if (!this.interval || this.interval < 50) return null;

  var self = this;

  this.timer = setInterval(function() {
    self._send();
  }, this.interval);

  return this.timer;
}

/**
 *  Stop the interval
 */

Artnet.prototype._stopInterval = function _startInterval() {
  if (!this.timer) return null;

  clearInterval(this.timer);
};

/**
 *  Set a value
 *
 *  @param {Number} universe
 *  @param {Number} channel
 *  @param {Number} value
 */

Artnet.prototype.set = function set(universe, channel, value) {

  if (value < 0) value = 0;
  if (value > 255) value = 255;

  if (channel < 0) channel = 0;
  if (channel > 512) channel = 512;

  if (!this.data[universe]) this.data[universe] = _createUniverse();

  this.data[universe][channel] = value;
};

/**
 *  Transmit the data
 */

Artnet.prototype.send = function send() {

  this.data.forEach(function(universe, uIndex){

    this.header[14] = uIndex;

    var data = this.header.concat(this.data[uIndex]);
    var buff = new Buffer(data);

    this.socket.send(buff, 0, buff.length, this.port, this.host);
  }, this);

};

/**
 *  Create a universe (a 512 member array of zeros)
 */

function _createUniverse() {
  return Array.apply(null, new Array(512)).map(Number.prototype.valueOf, 0);
};
<commit_msg>Added setAll method to send an array of values<commit_after>'use strict';

var dgram = require('dgram');

/**
 *  noop
 */

function noop() {};

/**
 *  Expose `Artnet`
 */

module.exports = Artnet;

function Artnet(opts) {
  var opts = opts || {};

  this.host = opts.host || '255.255.255.255';
  this.port = opts.port || 6454;
  this.refresh = opts.refresh || 0;

  this.header =  [65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 0, 0, 1, 0, 2, 0];

  this.socket = null;
  this.interval = null;
  this.timer = null;

  this.data = [];
};

/**
 *  Connect to host
 */

Artnet.prototype.connect = function connect() {
  this.socket = dgram.createSocket('udp4');

  this._startInterval();

  return this;
};

/**
 *  Close a connection
 *
 *  @param {Function} callback - optional
 */

Artnet.prototype.disconnect = function disconnect(callback) {
  callback = callback || noop;

  var self = this;

  this.socket && this.socket.close(function() {
    self.socket = null;
    callback();
  });

  return this;
};

/**
 *  Start the interval
 *
 *  @param {Number} interval
 */

Artnet.prototype._startInterval = function _startInterval(interval) {

  if (interval) this.interval = interval;

  if (!this.interval || this.interval < 50) return null;

  var self = this;

  this.timer = setInterval(function() {
    self._send();
  }, this.interval);

  return this.timer;
}

/**
 *  Stop the interval
 */

Artnet.prototype._stopInterval = function _startInterval() {
  if (!this.timer) return null;

  clearInterval(this.timer);
};

/**
 *  Set a value
 *
 *  @param {Number} universe
 *  @param {Number} channel
 *  @param {Number} value
 */

Artnet.prototype.set = function set(universe, channel, value) {

  if (value < 0) value = 0;
  if (value > 255) value = 255;

  if (channel < 0) channel = 0;
  if (channel > 512) channel = 512;

  if (!this.data[universe]) this.data[universe] = _createUniverse();

  this.data[universe][channel] = value;
};

Artnet.prototype.setAll = function set(universe, data) {
  if (!this.data[universe]) this.data[universe] = _createUniverse();

  this.data[universe] = data;
};

/**
 *  Transmit the data
 */

Artnet.prototype.send = function send() {

  this.data.forEach(function(universe, uIndex){

    this.header[14] = uIndex;

    var data = this.header.concat(this.data[uIndex]);
    var buff = new Buffer(data);

    this.socket.send(buff, 0, buff.length, this.port, this.host);
  }, this);

};

/**
 *  Create a universe (a 512 member array of zeros)
 */

function _createUniverse() {
  return Array.apply(null, new Array(512)).map(Number.prototype.valueOf, 0);
};
<|endoftext|>


<commit_before>var binary =                    require('binary');
var net =                       require('net');
var EventEmitter =              require('events').EventEmitter;
var util =                      require('util');

var binrpc =                    require('./protocol.js');

/**
 * @class Client
 * @param {object} options containing a host and a port attribute
 */
/** @exports client */
var Client = function (options) {

    var that = this;

    this.reconnectTimeout =     options.reconnectTimeout || 2500;
    this.host =                 options.host || '127.0.0.1';
    this.port =                 options.port;

    /**
     * The request queue. Array elements must be objects with the properties buffer and callback
     * @type {Array}
     */
    this.queue =                [];
    /**
     * Maximum queue length. If queue length is greater than this a methodCall will return error 'You are sending too fast'
     * @type {number}
     */
    this.queueMaxLength =       options.queueMaxLength || 15;
    /**
     * Time in milliseconds. How long to wait for retry if a request is pending
     * @type {number}
     */
    this.queueRetryTimeout =    50;
    /**
     * Indicates if there is a request waiting for its response
     * @type {boolean}
     */
    this.pending =              false;

    /**
     * connect
     * @param {boolean} reconnect optional - defaults to false. Set to true if this is a reconnect
     */
    this.connect = function (reconnect) {

        //console.log(reconnect ? 'client reconnecting' : 'client connecting');
        if (that.socket && typeof that.socket.destroy === 'function') that.socket.destroy();
        reconnect ? that.emit('reconnecting') : that.emit('connecting');
        that.socket = net.createConnection(that.port, that.host);

        that.socket.on('error', function (e) {
             //console.log('socket error', e);
            /**
             * re-emits socket errors
             * @event Client#error
             * @param {Error} e
             */
            that.emit('error', e);

        });

        that.socket.on('timeout', function (e) {
            //console.log('socket timeout', e);
        });

        that.socket.on('close', function (e) {
            //console.log('socket close', e);
            /**
             * emitted when the socket is closed
             * @event Client#close
             */
            that.connected = false;
            that.emit('close');
            setTimeout(function () {
                //that.connect(true);
            }, 3000);
        });

        that.socket.on('connect', function () {
            /**
             * emitted when the socket connection is established
             * @event Client#connect
             */
            //console.log('socket connect');
            that.connected = true;

            that.socket.setKeepAlive(true, 10000);

            setTimeout(function () {
                that.emit('connect');
            }, 200);

        });
    };

    /**
     * Push request to the queue
     * @param {buffer} buf
     * @param {function} cb
     */
    this.queuePush = function (buf, cb) {
        this.queue.push({buf: buf, callback: cb});
        this.queueShift();
    };

    /**
     *  Shift request from the queue and write it to the socket.
     */
    this.queueShift = function () {
        if (that.queue.length) {
            if (that.pending) {
                setTimeout(that.queueShift, that.queueRetryTimeout);
            } else {
                that.pending = true;

                var response = new Buffer(0);
                var chunk = 0;
                var length;

                var obj = that.queue.shift();

                that.socket.on('data', function (data) {
                    if (chunk == 0) {
                        var vars = binary.parse(data)
                            .buffer('head', 3)
                            .word8('msgType')
                            .word32bu('msgSize')
                            .vars
                            ;
                        length = vars.msgSize + 8;
                        response = data;
                        if (vars.head.toString() !== 'Bin') {
                            that.socket.removeAllListeners('data');
                            that.pending = false;
                            if (typeof obj.callback === 'function') obj.callback('malformed response');

                            return;
                        }
                    } else {
                        response = Buffer.concat([response, data]);
                    }
                    chunk = chunk + 1;
                    if (response.length >= length) {
                        that.socket.removeAllListeners('data');
                        that.pending = false;
                        if (typeof obj.callback === 'function') obj.callback(null, response);
                    }
                });
                that.socket.write(obj.buf);
            }
        }
    };

    /**
     * methodCall
     * @param {string} method
     * @param {Array} params
     * @param {function} callback optional - if omitted an empty string will be send as response
     */
    this.methodCall = function (method, params, callback) {

        if (!that.connected) {
            if (typeof callback === 'function') callback(new Error('not connected'));
            return;
        }

        if (this.pending && this.queue.length > this.queueMaxLength) {
            if (typeof callback === 'function') callback(new Error('You are sending too fast'));
        } else {
            this.queuePush(binrpc.encodeRequest(method, params), function (err, res) {
                if (typeof callback === 'function') callback(err, binrpc.decodeResponse(res));
            });
        }

    };

    this.connect();

};

util.inherits(Client, EventEmitter);

module.exports = Client;
<commit_msg>remove console.log<commit_after>var binary =                    require('binary');
var net =                       require('net');
var EventEmitter =              require('events').EventEmitter;
var util =                      require('util');

var binrpc =                    require('./protocol.js');

/**
 * @class Client
 * @param {object} options containing a host and a port attribute
 */
/** @exports client */
var Client = function (options) {

    var that = this;

    this.reconnectTimeout =     options.reconnectTimeout || 2500;
    this.host =                 options.host || '127.0.0.1';
    this.port =                 options.port;

    /**
     * The request queue. Array elements must be objects with the properties buffer and callback
     * @type {Array}
     */
    this.queue =                [];
    /**
     * Maximum queue length. If queue length is greater than this a methodCall will return error 'You are sending too fast'
     * @type {number}
     */
    this.queueMaxLength =       options.queueMaxLength || 15;
    /**
     * Time in milliseconds. How long to wait for retry if a request is pending
     * @type {number}
     */
    this.queueRetryTimeout =    50;
    /**
     * Indicates if there is a request waiting for its response
     * @type {boolean}
     */
    this.pending =              false;

    /**
     * connect
     * @param {boolean} reconnect optional - defaults to false. Set to true if this is a reconnect
     */
    this.connect = function (reconnect) {

        if (that.socket && typeof that.socket.destroy === 'function') that.socket.destroy();
        reconnect ? that.emit('reconnecting') : that.emit('connecting');
        that.socket = net.createConnection(that.port, that.host);

        that.socket.on('error', function (e) {
            /**
             * re-emits socket errors
             * @event Client#error
             * @param {Error} e
             */
            that.emit('error', e);

        });

        that.socket.on('timeout', function (e) {
            /**
             * @event Client#timeout
             */
            that.emit('timeout');
        });

        that.socket.on('close', function (e) {
            that.connected = false;
            /**
             * emitted when the socket is closed
             * @event Client#close
             */
            that.emit('close');
            //setTimeout(function () {
                //that.connect(true);
            //}, 3000);
        });

        that.socket.on('connect', function () {
            that.connected = true;
            that.socket.setKeepAlive(true, 10000);
            setTimeout(function () {
                /**
                 * emitted when the socket connection is established
                 * @event Client#connect
                 */
                that.emit('connect');
            }, 200);
        });
    };

    /**
     * Push request to the queue
     * @param {buffer} buf
     * @param {function} cb
     */
    this.queuePush = function (buf, cb) {
        this.queue.push({buf: buf, callback: cb});
        this.queueShift();
    };

    /**
     *  Shift request from the queue and write it to the socket.
     */
    this.queueShift = function () {
        if (that.queue.length) {
            if (that.pending) {
                setTimeout(that.queueShift, that.queueRetryTimeout);
            } else {
                that.pending = true;

                var response = new Buffer(0);
                var chunk = 0;
                var length;

                var obj = that.queue.shift();

                that.socket.on('data', function (data) {
                    if (chunk == 0) {
                        var vars = binary.parse(data)
                            .buffer('head', 3)
                            .word8('msgType')
                            .word32bu('msgSize')
                            .vars
                            ;
                        length = vars.msgSize + 8;
                        response = data;
                        if (vars.head.toString() !== 'Bin') {
                            that.socket.removeAllListeners('data');
                            that.pending = false;
                            if (typeof obj.callback === 'function') obj.callback('malformed response');

                            return;
                        }
                    } else {
                        response = Buffer.concat([response, data]);
                    }
                    chunk = chunk + 1;
                    if (response.length >= length) {
                        that.socket.removeAllListeners('data');
                        that.pending = false;
                        if (typeof obj.callback === 'function') obj.callback(null, response);
                    }
                });
                that.socket.write(obj.buf);
            }
        }
    };

    /**
     * methodCall
     * @param {string} method
     * @param {Array} params
     * @param {function} callback optional - if omitted an empty string will be send as response
     */
    this.methodCall = function (method, params, callback) {

        if (!that.connected) {
            if (typeof callback === 'function') callback(new Error('not connected'));
            return;
        }

        if (this.pending && this.queue.length > this.queueMaxLength) {
            if (typeof callback === 'function') callback(new Error('You are sending too fast'));
        } else {
            this.queuePush(binrpc.encodeRequest(method, params), function (err, res) {
                if (typeof callback === 'function') callback(err, binrpc.decodeResponse(res));
            });
        }

    };

    this.connect();

};

util.inherits(Client, EventEmitter);

module.exports = Client;
<|endoftext|>


<commit_before>'use strict';

// Dependencies
//
var  request = require('request');

// This is a boolean flag that determines if
// the worker is busy or free
//
var idle = true;

// Used to hold the report error function
//
var reportError = null;

// Used to hold the report progress function
//
var reportProgress = null;

// Used to track the current job, so that it can be
// referenced in other places
//
var currentJob = null;

// Used to track the jobQueueUrl
//
var jobQueueUrl = 'http://localhost:3800';

// Sets the job queue url to a new value
//
// @url   {String}    The url to set the job queue url to
// @cb    {Function}  The function to execute once finished
//
function setJobQueueUrl (url, cb) {
  jobQueueUrl = url;
  if(cb) { cb(); }
}

// Gets the job queue url
//
// @cb    {Function}  The function to execute once finished
//
function getJobQueueUrl (cb) {
  if(cb) {
    cb(null);
  }
  return jobQueueUrl;
}

// This defines the function for reporting progress
//
// @job     {Object}    The job record
//
function defineProgress (job) {
  reportProgress = function (progress) {
    var url = job.webhooks.processing[0].url;
    request.put({url: url, body: {progress: progress}, json: true}, function (err) {
      if (err) {
        reportError(err);
      }
    });
  };
  return reportProgress;
}

// This defines the function for reporting an error
//
// @job     {Object}    The job record
//
function defineError (job, cb) {
  reportError = function (err) {
    job.metadata.dataToSendWhenFailed.progress.data = err;
    request.put({url: jobQueueUrl + '/jobs/' + job._id + '/fail', body: {job: job}, json: true}, function () {
      request.put({url: jobQueueUrl + '/jobs/' + job._id + '/release', json: true}, function () {
        if (cb && typeof cb === 'function') {
          cb(err);
        } else {
          console.log(err);
          process.exit(1);
        }
      });
    });
  };

  return reportError;
}

// Finds any jobs on the queue that are available
//
// @cb    {Function}  The function to execute once finished
//
function findAvailableJob (jobType, cb) {
  var url, query;

  if (jobType) {
    url     = jobQueueUrl + '/jobs/search';
    query   = {status: 'new', type: jobType};
  } else {
    url     = jobQueueUrl + '/jobs/new';
  }

  // for now, we assume that all jobs are Process PDF
  // once we support upload to S3 jobs, then we'll need to inspect the job's type
  // in the list.
  //
  var conditions = {url: url, json: true};
  if (query) {conditions.qs = query;}

  request(conditions, function (err, res, body) {
    if (err) {
      cb(err, null);
    } else {
      if (body && body.length > 0) {
        cb(err, body[0]);
      } else {
        cb(err, null);
      }
    }
  });
}

// Takes a job from the queue
//
// @job   {Object}    The job record
// @cb    {Function}  The function to execute once finished
//
function takeJob (job, cb) {
  request.put({url: jobQueueUrl + '/jobs/' + job._id + '/take', json: true}, function (err, res, body) {
    if (err) {
      cb(err);
    } else {
      if (res.statusCode !== 201) {
        cb(new Error(body));
      } else {
        defineError(job);
        defineProgress(job);
        currentJob = job;
        cb(null);
      }
    }
  });
}

// Completes the job
//
// @job   {Object}    The job record
// @cb    {Function}  The function to execute once finished
//
function completeJob (job, cb) {
  request.put({url: jobQueueUrl + '/jobs/' + job._id + '/complete', body: {job: job}, json: true}, function (err, res, body) {
    if (err) {
      cb(err);
    } else {
      if (res.statusCode !== 201) {
        cb(new Error(body));
      } else {
        cb(null);
      }
    }
  });
}

// Gets a job from the job queue server
//
// @jobType     {String}    The job type to fetch
// @cb          {Function}  The function to execute once finished
//
function getJobFromQueue (jobType, cb) {

  if (idle) {
    findAvailableJob(jobType, function(err, job) {
      if (err === null && job !== null) {
        takeJob(job, function (err) {
          if (err === null) {
            idle = false;
            cb(job);
          }
        });
      }
    });
  }

}

var Job = function(jobData) {
  var jobDataAttributes = Object.keys(jobData);
  for (var i = 0; i < jobDataAttributes.length; i++) {
    this[jobDataAttributes[i]] = jobData[jobDataAttributes[i]];
  }
  return this;
};

Job.prototype.progress = function(current, total) {
  defineProgress(this)((total) ? current / total * 100 : current);
};

Job.prototype.error = function(err, cb) {
  defineError(this)(err, cb);
};

Job.prototype.complete = function(cb) {
  completeJob(this, cb);
};

function waitForJob(jobType, workerFunction) {
  function pollQueue() {
    getJobFromQueue(jobType, function(jobData) {
      var job = new Job(jobData);

      workerFunction(job, function(err, cb) {
        if(err instanceof Function) {
          cb = err;
          err = undefined;
        }
        if(err) {
          return job.error(err, cb);
        } else {
          return job.complete(cb);
        }
      });
    });
  }

  pollQueue();

  var interval = setInterval(pollQueue, 2000);

  function stop () {
    clearInterval(interval);
  }

  return {
    interval: interval,
    pollQueue: pollQueue,
    stop: stop
  };

}

// Frees the worker
//
function free () {
  idle = true;
}

// Returns true/false depending on idle
//
function isFree () {
  return idle;
}

//
// Create a job in queue
//
// @param  {Object}   jobData
// @param  {Function} callback (err)
//
// Example:
//
//  client.createJob({
//    name                : 'Process PDF for ' + customer.identifier,
//    type                : 'process_pdf',
//    metadata            : {
//      url: 'http://mys3store/uploadedpdf_asd23'
//    },
//    webhooks            : {
//      processing        : [{method: 'PUT', url: 'http://logserver/api/log/process_pdf/processing', data: 'dataToSendWhenProcessing'}],
//      completed         : [
//          {method: 'PUT', url: 'http://logserver/api/log/completed'  data: 'dataToSend'},
//          {method: 'PUT', url: 'http://myapp/api/v1/pdf/' + pdf.id + '/progress', data: {event: 'complete', data: {}}}
//      ],
//      failed            : [
//          {method: 'PUT', url: http://logserver/api/log/progress', data: { event: 'failed' }}
//      ]
//    }
//  }, function(err) {
//    if(err) return console.log('OH NO AN ERROR HAS ARRIVED AT THE PARTY:', err);
//    console.log('Job Created');
//  });
//
function createJob (jobData, callback) {
  request.post({
    url: jobQueueUrl + '/jobs',
    body: { job: jobData },
    json: true
  }, callback);
}

// Retries a job
//
//
// TODO - retryJob
// request.put({url: config.jobQueueUrl + '/jobs/' + currentJob._id + '/retry', json: true});

// Expose the public API
//
module.exports = {
  setJobQueueUrl  : setJobQueueUrl,
  getJobQueueUrl  : getJobQueueUrl,
  completeJob     : completeJob,
  getJobFromQueue : getJobFromQueue,
  defineError     : defineError,
  defineProgress  : defineProgress,
  free            : free,
  isFree          : isFree,
  waitForJob      : waitForJob,
  createJob       : createJob
};<commit_msg>Free worker after waitForJob has run<commit_after>'use strict';

// Dependencies
//
var  request = require('request');

// This is a boolean flag that determines if
// the worker is busy or free
//
var idle = true;

// Used to hold the report error function
//
var reportError = null;

// Used to hold the report progress function
//
var reportProgress = null;

// Used to track the current job, so that it can be
// referenced in other places
//
var currentJob = null;

// Used to track the jobQueueUrl
//
var jobQueueUrl = 'http://localhost:3800';

// Sets the job queue url to a new value
//
// @url   {String}    The url to set the job queue url to
// @cb    {Function}  The function to execute once finished
//
function setJobQueueUrl (url, cb) {
  jobQueueUrl = url;
  if(cb) { cb(); }
}

// Gets the job queue url
//
// @cb    {Function}  The function to execute once finished
//
function getJobQueueUrl (cb) {
  if(cb) {
    cb(null);
  }
  return jobQueueUrl;
}

// This defines the function for reporting progress
//
// @job     {Object}    The job record
//
function defineProgress (job) {
  reportProgress = function (progress) {
    var url = job.webhooks.processing[0].url;
    request.put({url: url, body: {progress: progress}, json: true}, function (err) {
      if (err) {
        reportError(err);
      }
    });
  };
  return reportProgress;
}

// This defines the function for reporting an error
//
// @job     {Object}    The job record
//
function defineError (job, cb) {
  reportError = function (err) {
    job.metadata.dataToSendWhenFailed.progress.data = err;
    request.put({url: jobQueueUrl + '/jobs/' + job._id + '/fail', body: {job: job}, json: true}, function () {
      request.put({url: jobQueueUrl + '/jobs/' + job._id + '/release', json: true}, function () {
        if (cb && typeof cb === 'function') {
          cb(err);
        } else {
          console.log(err);
          process.exit(1);
        }
      });
    });
  };

  return reportError;
}

// Finds any jobs on the queue that are available
//
// @cb    {Function}  The function to execute once finished
//
function findAvailableJob (jobType, cb) {
  var url, query;

  if (jobType) {
    url     = jobQueueUrl + '/jobs/search';
    query   = {status: 'new', type: jobType};
  } else {
    url     = jobQueueUrl + '/jobs/new';
  }

  // for now, we assume that all jobs are Process PDF
  // once we support upload to S3 jobs, then we'll need to inspect the job's type
  // in the list.
  //
  var conditions = {url: url, json: true};
  if (query) {conditions.qs = query;}

  request(conditions, function (err, res, body) {
    if (err) {
      cb(err, null);
    } else {
      if (body && body.length > 0) {
        cb(err, body[0]);
      } else {
        cb(err, null);
      }
    }
  });
}

// Takes a job from the queue
//
// @job   {Object}    The job record
// @cb    {Function}  The function to execute once finished
//
function takeJob (job, cb) {
  request.put({url: jobQueueUrl + '/jobs/' + job._id + '/take', json: true}, function (err, res, body) {
    if (err) {
      cb(err);
    } else {
      if (res.statusCode !== 201) {
        cb(new Error(body));
      } else {
        defineError(job);
        defineProgress(job);
        currentJob = job;
        cb(null);
      }
    }
  });
}

// Completes the job
//
// @job   {Object}    The job record
// @cb    {Function}  The function to execute once finished
//
function completeJob (job, cb) {
  request.put({url: jobQueueUrl + '/jobs/' + job._id + '/complete', body: {job: job}, json: true}, function (err, res, body) {
    if (err) {
      cb(err);
    } else {
      if (res.statusCode !== 201) {
        cb(new Error(body));
      } else {
        cb(null);
      }
    }
  });
}

// Gets a job from the job queue server
//
// @jobType     {String}    The job type to fetch
// @cb          {Function}  The function to execute once finished
//
function getJobFromQueue (jobType, cb) {

  if (idle) {
    findAvailableJob(jobType, function(err, job) {
      if (err === null && job !== null) {
        takeJob(job, function (err) {
          if (err === null) {
            idle = false;
            cb(job);
          }
        });
      }
    });
  }

}

var Job = function(jobData) {
  var jobDataAttributes = Object.keys(jobData);
  for (var i = 0; i < jobDataAttributes.length; i++) {
    this[jobDataAttributes[i]] = jobData[jobDataAttributes[i]];
  }
  return this;
};

Job.prototype.progress = function(current, total) {
  defineProgress(this)((total) ? current / total * 100 : current);
};

Job.prototype.error = function(err, cb) {
  defineError(this)(err, cb);
};

Job.prototype.complete = function(cb) {
  completeJob(this, cb);
};

function waitForJob(jobType, workerFunction) {
  function pollQueue() {
    getJobFromQueue(jobType, function(jobData) {
      var job = new Job(jobData);

      workerFunction(job, function(err, cb) {
        if(err instanceof Function) {
          cb = err;
          err = undefined;
        }
        if(err) {
          job.error(err, cb);
          free();
        } else {
          job.complete(cb);
          free();
        }
      });
    });
  }

  pollQueue();

  var interval = setInterval(pollQueue, 2000);

  function stop () {
    clearInterval(interval);
  }

  return {
    interval: interval,
    pollQueue: pollQueue,
    stop: stop
  };

}

// Frees the worker
//
function free () {
  idle = true;
}

// Returns true/false depending on idle
//
function isFree () {
  return idle;
}

//
// Create a job in queue
//
// @param  {Object}   jobData
// @param  {Function} callback (err)
//
// Example:
//
//  client.createJob({
//    name                : 'Process PDF for ' + customer.identifier,
//    type                : 'process_pdf',
//    metadata            : {
//      url: 'http://mys3store/uploadedpdf_asd23'
//    },
//    webhooks            : {
//      processing        : [{method: 'PUT', url: 'http://logserver/api/log/process_pdf/processing', data: 'dataToSendWhenProcessing'}],
//      completed         : [
//          {method: 'PUT', url: 'http://logserver/api/log/completed'  data: 'dataToSend'},
//          {method: 'PUT', url: 'http://myapp/api/v1/pdf/' + pdf.id + '/progress', data: {event: 'complete', data: {}}}
//      ],
//      failed            : [
//          {method: 'PUT', url: http://logserver/api/log/progress', data: { event: 'failed' }}
//      ]
//    }
//  }, function(err) {
//    if(err) return console.log('OH NO AN ERROR HAS ARRIVED AT THE PARTY:', err);
//    console.log('Job Created');
//  });
//
function createJob (jobData, callback) {
  request.post({
    url: jobQueueUrl + '/jobs',
    body: { job: jobData },
    json: true
  }, callback);
}

// Retries a job
//
//
// TODO - retryJob
// request.put({url: config.jobQueueUrl + '/jobs/' + currentJob._id + '/retry', json: true});

// Expose the public API
//
module.exports = {
  setJobQueueUrl  : setJobQueueUrl,
  getJobQueueUrl  : getJobQueueUrl,
  completeJob     : completeJob,
  getJobFromQueue : getJobFromQueue,
  defineError     : defineError,
  defineProgress  : defineProgress,
  free            : free,
  isFree          : isFree,
  waitForJob      : waitForJob,
  createJob       : createJob
};<|endoftext|>


<commit_before>/**
 * Module dependencies
 */
var Connection = require('./connection')
  , events = require('events')
  , util = require('util')
  , crypto = require('crypto');

/**
 * Default options
 */
var defaultConnectOptions = {
  keepalive: 60,
  protocolId: 'MQIsdp',
  protocolVersion: 3,
  reconnectPeriod: 1000,
  clean: true,
  encoding: 'utf8'
};

var defaultId = function() {
  return 'mqttjs_' + crypto.randomBytes(8).toString('hex');
};

var nop = function(){};

/**
 * MqttClient constructor
 *
 * @param {Stream} stream - stream
 * @param {Object} [options] - connection options
 * (see Connection#connect)
 */
var MqttClient = module.exports =
function MqttClient(streamBuilder, options) {
  var that = this;

  if (!this instanceof MqttClient) {
    return new MqttClient(stream, options);
  }

  this.options = options || {};

  // Defaults
  for(var k in defaultConnectOptions) {
    if ('undefined' === typeof this.options[k]) {
      this.options[k] = defaultConnectOptions[k];
    } else {
      this.options[k] = options[k];
    }
  }

  this.options.clientId = this.options.clientId || defaultId();

  this.streamBuilder = streamBuilder;

  this._setupStream();

  // Ping timer, setup in _setupPingTimer
  this.pingTimer = null;
  // Is the client connected?
  this.connected = false;
  // Packet queue
  this.queue = [];
  // Are we intentionally disconnecting?
  this.disconnecting = false;
  // Reconnect timer
  this.reconnectTimer = null;

  // Inflight messages
  this.inflight = {
    puback: {},
    pubrec: {},
    pubcomp: {},
    suback: {},
    unsuback: {}
  };

  // Incoming messages
  this.incoming = {
    pubrel: {}
  };

  // Mark connected on connect
  this.on('connect', function() {
    this.connected = true;
  });

  // Mark disconnected on stream close
  this.on('close', function() {
    this.connected = false;
  });

  // Setup ping timer
  this.on('connect', this._setupPingTimer);

  // Send queued packets
  this.on('connect', function() {
    var queue = that.queue
      , length = queue.length;

    for (var i = 0; i < length; i += 1) {
      that._sendPacket(
        queue[i].type,
        queue[i].packet,
        queue[i].cb
      );
    }
    that.queue = [];
  });

  // Clear ping timer
  this.on('close', function () {
    if (that.pingTimer !== null) {
      clearInterval(that.pingTimer);
      that.pingTimer = null;
    }
  });

  // Setup reconnect timer on disconnect
  this.on('close', function() {
    that._setupReconnect();
  });

  // Clear reconnect timer on connect
  this.on('connect', function() {
    that._clearReconnect();
  });

  events.EventEmitter.call(this);
};
util.inherits(MqttClient, events.EventEmitter);

/**
 * setup the event handlers in the inner stream.
 *
 * @api private
 */
MqttClient.prototype._setupStream = function() {
  var that = this;

  this.stream = this.streamBuilder();

  // MqttConnection
  this.conn = this.stream.pipe(new Connection());

  // Set encoding of incoming publish payloads
  if (this.options.encoding) {
    this.conn.setPacketEncoding(this.options.encoding);
  }

  // Suppress connection errors
  this.stream.on('error', nop);

  // Echo stream close
  this.stream.on('close', this.emit.bind(this, 'close'));

  // Send a connect packet on stream connect
  this.stream.on('connect', function () {
    that.conn.connect(that.options);
  });

  this.stream.on('secureConnect', function () {
    that.conn.connect(that.options);
  });

  // Handle incoming publish
  this.conn.on('publish', function (packet) {
    that._handlePublish(packet);
  });

  // one single handleAck function
  var handleAck = function (packet) {
    that._handleAck(packet);
  };

  // Handle incoming acks
  var acks = ['puback', 'pubrec', 'pubcomp', 'suback', 'unsuback'];

  acks.forEach(function (event) {
    that.conn.on(event, handleAck);
  });

  // Handle outgoing acks
  this.conn.on('pubrel', function (packet) {
    that._handlePubrel(packet);
  });

  // Handle connack
  this.conn.on('connack', function (packet) {
    that._handleConnack(packet);
  });

  // Echo connection errors
  this.conn.on('error', this.emit.bind(this, 'error'));
};

/**
 * publish - publish <message> to <topic>
 *
 * @param {String} topic - topic to publish to
 * @param {String, Buffer} message - message to publish
 * @param {Object} [opts] - publish options, includes:
 *    {Number} qos - qos level to publish on
 *    {Boolean} retain - whether or not to retain the message
 * @param {Function} [callback] - function(err){}
 *    called when publish succeeds or fails
 * @returns {MqttClient} this - for chaining
 * @api public
 *
 * @example client.publish('topic', 'message');
 * @example
 *     client.publish('topic', 'message', {qos: 1, retain: true});
 * @example client.publish('topic', 'message', console.log);
 */
MqttClient.prototype.publish =
function(topic, message, opts, callback) {
  var packet;

  // .publish(topic, payload, cb);
  if ('function' === typeof opts) {
    callback = opts;
    opts = null;
  }

  // Default opts
  if(!opts) opts = {qos: 0, retain: false};

  callback = callback || nop;

  packet = {
    topic: topic,
    payload: message,
    qos: opts.qos,
    retain: opts.retain,
    messageId: this._nextId()
  }

  this._sendPacket('publish', packet, function () {
    switch (opts.qos) {
      case 0:
        // Immediately callback
        callback();
        break;
      case 1:
        // Add to puback callbacks
        this.inflight.puback[packet.messageId] = callback;
        break;
      case 2:
        // Add to pubrec callbacks
        this.inflight.pubrec[packet.messageId] = callback;
        break
      default:
        break;
    }
  });

  return this;
};

/**
 * subscribe - subscribe to <topic>
 *
 * @param {String, Array} topic - topic(s) to subscribe to
 * @param {Object} [opts] - subscription options, includes:
 *    {Number} qos - subscribe qos level
 * @param {Function} [callback] - function(err, granted){} where:
 *    {Error} err - subscription error (none at the moment!)
 *    {Array} granted - array of {topic: 't', qos: 0}
 * @returns {MqttClient} this - for chaining
 * @api public
 * @example client.subscribe('topic');
 * @example client.subscribe('topic', {qos: 1});
 * @example client.subscribe('topic', console.log);
 */
MqttClient.prototype.subscribe =
function(topic, opts, callback) {
  var subs = [];

  // .subscribe('topic', callback)
  if ('function' === typeof opts) {
    callback = opts;
    opts = null;
  }

  // Defaults
  opts = opts || {qos: 0};
  callback = callback || nop;

  if ('string' === typeof topic) {
    subs.push({topic: topic, qos: opts.qos});
  } else if ('object' === typeof topic) {
    // TODO: harder array check
    for (var i = 0; i < topic.length; i += 1) {
      var t = topic[i];
      subs.push({topic: t, qos: opts.qos});
    }
  } else {
    // Error!
  }

  var packet = {
    subscriptions: subs,
    qos: 1,
    retain: false,
    dup: false,
    messageId: this._nextId()
  };

  this._sendPacket('subscribe', packet, function () {
    this.inflight.suback[packet.messageId] = {
      callback: callback,
      packet: packet
    };
  });

  return this;
};

/**
 * unsubscribe - unsubscribe from topic(s)
 *
 * @param {String, Array} topic - topics to unsubscribe from
 * @param {Function} [callback] - callback fired on unsuback
 * @returns {MqttClient} this - for chaining
 * @api public
 * @example client.unsubscribe('topic');
 * @example client.unsubscribe('topic', console.log);
 */
MqttClient.prototype.unsubscribe = function(topic, callback) {
  callback = callback || nop;
  var packet = {messageId: this._nextId()};

  if ('string' === typeof topic) {
    packet.unsubscriptions = [topic];
  } else if ('object' === typeof topic && topic.length) {
    packet.unsubscriptions = topic;
  }

  this._sendPacket('unsubscribe', packet, function () {
    this.inflight.unsuback[packet.messageId] = callback;
  });

  return this;
};

/**
 * end - close connection
 *
 * @returns {MqttClient} this - for chaining
 * @api public
 */
MqttClient.prototype.end = function() {
  this.disconnecting = true;
  if (!this.connected) {
    this.once('connect', this._cleanUp.bind(this));
  } else {
    this._cleanUp();
  }

  return this;
};

/**
 * destroy - close connection and destroy
 *
 * @returns {MqttClient} this - for chaining
 * @api public
 */
MqttClient.prototype.destroy = function() {
    this._cleanUp();
    this._clearReconnect();

    this.stream.destroy();

    if (this.conn) {
      this.conn.removeAllListeners();
      delete this.conn;
      this.conn = null;
    }

    return this;
};

/**
 * _reconnect - implement reconnection
 * @api privateish
 */
MqttClient.prototype._reconnect = function() {

  if (this.conn) {
    this.conn.removeAllListeners();
    delete this.conn;
  }

  this._setupStream();
};

/**
 * _setupReconnect - setup reconnect timer
 */
MqttClient.prototype._setupReconnect = function() {
  var that = this;

  if (!that.disconnecting && !that.reconnectTimer) {
    if (0 === that.options.reconnectPeriod) { // Do not use reconnecting
      return;
    }

    that.reconnectTimer = setInterval(function () {
      that._reconnect();
    }, that.options.reconnectPeriod);
  }
};

/**
 * _clearReconnect - clear the reconnect timer
 */
MqttClient.prototype._clearReconnect = function() {
  if (this.reconnectTimer) {
    clearInterval(this.reconnectTimer);
    this.reconnectTimer = false;
  }
};

/**
 * _cleanUp - clean up on connection end
 * @api private
 */
MqttClient.prototype._cleanUp = function() {
  this.conn.disconnect();
  this.stream.end();
  if (this.pingTimer !== null) {
    clearInterval(this.pingTimer);
    this.pingTimer = null;
  }
};

/**
 * _sendPacket - send or queue a packet
 * @param {String} type - packet type (see `protocol`)
 * @param {Object} packet - packet options
 * @param {Function} cb - callback when the packet is sent
 * @api private
 */

MqttClient.prototype._sendPacket = function(type, packet, cb) {
  if (this.connected) {
    this.conn[type](packet);
    if (cb) cb.call(this);
  } else {
    this.queue.push({type: type, packet: packet, cb: cb});
  }
};

/**
 * _setupPingTimer - setup the ping timer
 *
 * @api private
 */
MqttClient.prototype._setupPingTimer = function() {
  var that = this;

  if (!this.pingTimer && this.options.keepalive) {
    this.pingTimer = setInterval((function () {
      that.conn.pingreq();
    }), this.options.keepalive * 600);
  }
};

/**
 * _handleConnack
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handleConnack = function(packet) {
  var rc = packet.returnCode;

  // TODO: move to protocol
  var errors = [
    '',
    'Unacceptable protocol version',
    'Identifier rejected',
    'Server unavailable',
    'Bad username or password',
    'Not authorized'
  ];

  if (rc === 0) {
    this.emit('connect');
  } else if (rc > 0) {
    this.emit('error',
        new Error('Connection refused: ' + errors[rc]));
  }
};

/**
 * _handlePublish
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handlePublish = function(packet) {
  var topic = packet.topic
    , message = packet.payload
    , qos = packet.qos
    , mid = packet.messageId
    , retain = packet.retain;

  switch (qos) {
    case 0:
      this.emit('message', topic, message, packet);
      break;
    case 1:
      this.conn.puback({messageId: mid});
      this.emit('message', topic, message, packet);
      break;
    case 2:
      this.conn.pubrec({messageId: mid});
      this.incoming.pubrel[mid] = packet;
      break;
    default:
      break;
  }
};

/**
 * _handleAck
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handleAck = function(packet) {
  var mid = packet.messageId
    , type = packet.cmd
    , cb = this.inflight[type][mid];

  if (!cb) {
    // Server sent an ack in error, ignore it.
    return;
  }

  // Process
  switch (type) {
    case 'puback':
      // Callback - we're done
      cb();
      break;
    case 'pubrec':
      // Pubrel and add to pubcomp list
      this.conn.pubrel(packet);
      this.inflight.pubcomp[mid] = cb;
      break;
    case 'pubcomp':
      // Callback - we're done
      cb();
      break;
    case 'suback':
      // TODO: RIDICULOUS HACK, PLEASE FIX
      var origSubs = cb.packet.subscriptions
        , cb = cb.callback
        , granted = packet.granted;

      for (var i = 0; i < granted.length; i += 1) {
        origSubs[i].qos = granted[i];
      }
      cb(null, origSubs);
      break;
    case 'unsuback':
      cb();
      break;
    default:
      // code
  }

  // Remove from queue
  delete this.inflight[type][mid];
};

/**
 * _handlePubrel
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handlePubrel = function(packet) {
  var mid = packet.messageId
    , pub = this.incoming.pubrel[mid];

  if (!pub) this.emit('error', new Error('Unknown message id'));
  this.conn.pubcomp({messageId: mid});
  this.emit('message', pub.topic, pub.payload, pub);
};

/**
 * _nextId
 */
MqttClient.prototype._nextId = function() {
  return Math.floor(Math.random() * 65535);
};
<commit_msg>enhancing mqtt.js: puback by app and ping - client to send ack for a received message. - keep connection with mqtt.ping(handling ping_ack)<commit_after>'use strict';
/**
 * Module dependencies
 */
var Connection = require('./connection')
  , events = require('events')
  , util = require('util')
  , crypto = require('crypto');

/**
 * Default options
 */
var defaultConnectOptions = {
  keepalive: 60,
  protocolId: 'MQIsdp',
  protocolVersion: 3,
  reconnectPeriod: 1000,
  clean: true,
  encoding: 'utf8',
  logger: console,
  autopuback: true
};

var defaultId = function() {
  return 'mqttjs_' + crypto.randomBytes(8).toString('hex');
};

var nop = function(){};

var _lastMsgRcvdTime = -1,
    logger = console;
/**
 * MqttClient constructor
 *
 * @param {Stream} stream - stream
 * @param {Object} [options] - connection options
 * (see Connection#connect)
 */
var MqttClient = module.exports =
function MqttClient(streamBuilder, options) {
  var that = this;

  if (!this instanceof MqttClient) {
    return new MqttClient(stream, options);
  }

  this.options = options || {};

  // Defaults
  for(var k in defaultConnectOptions) {
    if ('undefined' === typeof this.options[k]) {
      this.options[k] = defaultConnectOptions[k];
    } else {
      this.options[k] = options[k];
    }
  }

  logger = this.options.logger;

  this.options.clientId = this.options.clientId || defaultId();

  this.streamBuilder = streamBuilder;

  this._setupStream();

  // Ping timer, setup in _setupPingTimer
  this.pingTimer = null;
  // Is the client connected?
  this.connected = false;
  // Packet queue
  this.queue = [];
  // Are we intentionally disconnecting?
  this.disconnecting = false;
  // Reconnect timer
  this.reconnectTimer = null;

  // Inflight messages
  this.inflight = {
    puback: {},
    pubrec: {},
    pubcomp: {},
    suback: {},
    unsuback: {}
  };

  // Incoming messages
  this.incoming = {
    pubrel: {}
  };

  // Mark connected on connect
  this.on('connect', function() {
    this.connected = true;
  });

  // Mark disconnected on stream close
  this.on('close', function() {
    this.connected = false;
  });

  // Setup ping timer
  this.on('connect', this._setupPingTimer);

  // Send queued packets
  this.on('connect', function() {
    var queue = that.queue
      , length = queue.length;

    for (var i = 0; i < length; i += 1) {
      that._sendPacket(
        queue[i].type,
        queue[i].packet,
        queue[i].cb
      );
    }
    that.queue = [];
  });

  // Clear ping timer
  this.on('close', function () {
    if (that.pingTimer !== null) {
      clearInterval(that.pingTimer);
      that.pingTimer = null;
    }
  });

  // Setup reconnect timer on disconnect
  this.on('close', function() {
    that._setupReconnect();
  });

  // Clear reconnect timer on connect
  this.on('connect', function() {
    that._clearReconnect();
  });

  events.EventEmitter.call(this);
};
util.inherits(MqttClient, events.EventEmitter);

/**
 * setup the event handlers in the inner stream.
 *
 * @api private
 */
MqttClient.prototype._setupStream = function() {
  var that = this;

  this.stream = this.streamBuilder();

  // MqttConnection
  this.conn = this.stream.pipe(new Connection());

  // Set encoding of incoming publish payloads
  if (this.options.encoding) {
    this.conn.setPacketEncoding(this.options.encoding);
  }

  // Suppress connection errors
  this.stream.on('error', nop);

  // Echo stream close
  this.stream.on('close', this.emit.bind(this, 'close'));

  // Send a connect packet on stream connect
  this.stream.on('connect', function () {
    that.conn.connect(that.options);
  });

  this.stream.on('secureConnect', function () {
    that.conn.connect(that.options);
  });

  // Handle incoming publish
  this.conn.on('publish', function (packet) {
    _lastMsgRcvdTime = Date.now();
    that._handlePublish(packet);
  });

  // one single handleAck function
  var handleAck = function (packet) {
    _lastMsgRcvdTime = Date.now();
    that._handleAck(packet);
  };

  // Handle incoming acks
  var acks = ['puback', 'pubrec', 'pubcomp', 'suback', 'unsuback'];

  acks.forEach(function (event) {
    that.conn.on(event, handleAck);
  });

  // Handle outgoing acks
  this.conn.on('pubrel', function (packet) {
    _lastMsgRcvdTime = Date.now();
    that._handlePubrel(packet);
  });

  // Handle connack
  this.conn.on('connack', function (packet) {
    that._handleConnack(packet);
  });

  // Echo connection errors
  this.conn.on('error', this.emit.bind(this, 'error'));
};

/**
 * publish - publish <message> to <topic>
 *
 * @param {String} topic - topic to publish to
 * @param {String, Buffer} message - message to publish
 * @param {Object} [opts] - publish options, includes:
 *    {Number} qos - qos level to publish on
 *    {Boolean} retain - whether or not to retain the message
 * @param {Function} [callback] - function(err){}
 *    called when publish succeeds or fails
 * @returns {MqttClient} this - for chaining
 * @api public
 *
 * @example client.publish('topic', 'message');
 * @example
 *     client.publish('topic', 'message', {qos: 1, retain: true});
 * @example client.publish('topic', 'message', console.log);
 */
MqttClient.prototype.publish =
function(topic, message, opts, callback) {
  var packet;

  // .publish(topic, payload, cb);
  if ('function' === typeof opts) {
    callback = opts;
    opts = null;
  }

  // Default opts
  if(!opts) opts = {qos: 0, retain: false};

  callback = callback || nop;

  packet = {
    topic: topic,
    payload: message,
    qos: opts.qos,
    retain: opts.retain,
    messageId: this._nextId()
  }

  this._sendPacket('publish', packet, function () {
    switch (opts.qos) {
      case 0:
        // Immediately callback
        callback();
        break;
      case 1:
        // Add to puback callbacks
        this.inflight.puback[packet.messageId] = callback;
        break;
      case 2:
        // Add to pubrec callbacks
        this.inflight.pubrec[packet.messageId] = callback;
        break
      default:
        break;
    }
  });

  return this;
};

/**
 * subscribe - subscribe to <topic>
 *
 * @param {String, Array} topic - topic(s) to subscribe to
 * @param {Object} [opts] - subscription options, includes:
 *    {Number} qos - subscribe qos level
 * @param {Function} [callback] - function(err, granted){} where:
 *    {Error} err - subscription error (none at the moment!)
 *    {Array} granted - array of {topic: 't', qos: 0}
 * @returns {MqttClient} this - for chaining
 * @api public
 * @example client.subscribe('topic');
 * @example client.subscribe('topic', {qos: 1});
 * @example client.subscribe('topic', console.log);
 */
MqttClient.prototype.subscribe =
function(topic, opts, callback) {
  var subs = [];

  // .subscribe('topic', callback)
  if ('function' === typeof opts) {
    callback = opts;
    opts = null;
  }

  // Defaults
  opts = opts || {qos: 0};
  callback = callback || nop;

  if ('string' === typeof topic) {
    subs.push({topic: topic, qos: opts.qos});
  } else if ('object' === typeof topic) {
    // TODO: harder array check
    for (var i = 0; i < topic.length; i += 1) {
      var t = topic[i];
      subs.push({topic: t, qos: opts.qos});
    }
  } else {
    // Error!
  }

  var packet = {
    subscriptions: subs,
    qos: 1,
    retain: false,
    dup: false,
    messageId: this._nextId()
  };

  this._sendPacket('subscribe', packet, function () {
    this.inflight.suback[packet.messageId] = {
      callback: callback,
      packet: packet
    };
  });

  return this;
};

/**
 * unsubscribe - unsubscribe from topic(s)
 *
 * @param {String, Array} topic - topics to unsubscribe from
 * @param {Function} [callback] - callback fired on unsuback
 * @returns {MqttClient} this - for chaining
 * @api public
 * @example client.unsubscribe('topic');
 * @example client.unsubscribe('topic', console.log);
 */
MqttClient.prototype.unsubscribe = function(topic, callback) {
  callback = callback || nop;
  var packet = {messageId: this._nextId()};

  if ('string' === typeof topic) {
    packet.unsubscriptions = [topic];
  } else if ('object' === typeof topic && topic.length) {
    packet.unsubscriptions = topic;
  }

  this._sendPacket('unsubscribe', packet, function () {
    this.inflight.unsuback[packet.messageId] = callback;
  });

  return this;
};

/**
 * end - close connection
 *
 * @returns {MqttClient} this - for chaining
 * @api public
 */
MqttClient.prototype.end = function() {
  this.disconnecting = true;
  if (!this.connected) {
    this.once('connect', this._cleanUp.bind(this));
  } else {
    this._cleanUp();
  }

  return this;
};

/**
 * destroy - close connection and destroy
 *
 * @returns {MqttClient} this - for chaining
 * @api public
 */
MqttClient.prototype.destroy = function() {
    this._cleanUp();
    this._clearReconnect();

    this.stream.destroy();

    if (this.conn) {
      this.conn.removeAllListeners();
      delete this.conn;
      this.conn = null;
    }

    return this;
};

/**
 * _reconnect - implement reconnection
 * @api privateish
 */
MqttClient.prototype._reconnect = function() {

  if (this.conn) {
    this.conn.removeAllListeners();
    delete this.conn;
  }

  this._setupStream();
};

/**
 * _setupReconnect - setup reconnect timer
 */
MqttClient.prototype._setupReconnect = function() {
  var that = this;

  if (!that.disconnecting && !that.reconnectTimer) {
    if (0 === that.options.reconnectPeriod) { // Do not use reconnecting
      return;
    }

    that.reconnectTimer = setInterval(function () {
      that._reconnect();
    }, that.options.reconnectPeriod);
  }
};

/**
 * _clearReconnect - clear the reconnect timer
 */
MqttClient.prototype._clearReconnect = function() {
  if (this.reconnectTimer) {
    clearInterval(this.reconnectTimer);
    this.reconnectTimer = false;
  }
};

/**
 * _cleanUp - clean up on connection end
 * @api private
 */
MqttClient.prototype._cleanUp = function() {
  this.conn.disconnect();
  this.stream.end();
  if (this.pingTimer !== null) {
    clearInterval(this.pingTimer);
    this.pingTimer = null;
  }
};

/**
 * _sendPacket - send or queue a packet
 * @param {String} type - packet type (see `protocol`)
 * @param {Object} packet - packet options
 * @param {Function} cb - callback when the packet is sent
 * @api private
 */

MqttClient.prototype._sendPacket = function(type, packet, cb) {
  if (this.connected) {
    this.conn[type](packet);
    if (cb) cb.call(this);
  } else {
    this.queue.push({type: type, packet: packet, cb: cb});
  }
};

/**
 * _setupPingTimer - setup the ping timer
 *
 *  see mqtt v3r1 - http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#keep-alive-timer
 * @api private
 */
MqttClient.prototype._setupPingTimer = function() {
  var that = this,
    _pingResp = true,
    ka =  this.options.keepalive * 1000;

  logger.info('[PingTimer] keepalive check every ' + (ka/3000) + ', pingresp interval <' + ka*2/3000);
  function _ping2keepAlive() {
    var now = Date.now();
    if (!_pingResp) {
      logger.warn('destory due to no pingresp');
      that.destroy();     // close if the last ping is failed
      return;
    }

    if ((_lastMsgRcvdTime + ka*2/3) < now ) {
      _pingResp = false;  // reset before ping
      that.conn.pingreq();
      logger.debug('PINGREQ @ ', now);
      that.conn.once('pingresp', function () {
        _pingResp = true;
        _lastMsgRcvdTime = Date.now();
        logger.debug('PINGRESP @ ', _lastMsgRcvdTime);
      });
    }
  }
  if (!this.pingTimer && this.options.keepalive) {
    _ping2keepAlive();
    this.pingTimer = setInterval(_ping2keepAlive, ka/3); // every 3rd of ka
  }
};

/**
 * _handleConnack
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handleConnack = function(packet) {
  var rc = packet.returnCode;

  // TODO: move to protocol
  var errors = [
    '',
    'Unacceptable protocol version',
    'Identifier rejected',
    'Server unavailable',
    'Bad username or password',
    'Not authorized'
  ];

  if (rc === 0) {
    this.emit('connect');
  } else if (rc > 0) {
    this.emit('error',
        new Error('Connection refused: ' + errors[rc]));
  }
};

/**
 * _handlePublish
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handlePublish = function(packet) {
  var topic = packet.topic
    , message = packet.payload
    , qos = packet.qos
    , mid = packet.messageId
    , retain = packet.retain;

  switch (qos) {
    case 0:
      this.emit('message', topic, message, packet);
      break;
    case 1:
      if (this.options.autopuback) {
        this.conn.puback({messageId: mid});
        this.emit('message', topic, message, packet);
      } else {
        this.emit('message', topic, message, packet, this.conn.puback.bind(this.conn, {messageId: mid}));
      }
      break;
    case 2:
      this.conn.pubrec({messageId: mid});
      this.incoming.pubrel[mid] = packet;
      break;
    default:
      break;
  }
};

/**
 * _handleAck
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handleAck = function(packet) {
  var mid = packet.messageId
    , type = packet.cmd
    , cb = this.inflight[type][mid];

  if (!cb) {
    // Server sent an ack in error, ignore it.
    return;
  }

  // Process
  switch (type) {
    case 'puback':
      // Callback - we're done
      cb();
      break;
    case 'pubrec':
      // Pubrel and add to pubcomp list
      this.conn.pubrel(packet);
      this.inflight.pubcomp[mid] = cb;
      break;
    case 'pubcomp':
      // Callback - we're done
      cb();
      break;
    case 'suback':
      // TODO: RIDICULOUS HACK, PLEASE FIX
      var origSubs = cb.packet.subscriptions
        , cb = cb.callback
        , granted = packet.granted;

      for (var i = 0; i < granted.length; i += 1) {
        origSubs[i].qos = granted[i];
      }
      cb(null, origSubs);
      break;
    case 'unsuback':
      cb();
      break;
    default:
      // code
  }

  // Remove from queue
  delete this.inflight[type][mid];
};

/**
 * _handlePubrel
 *
 * @param {Object} packet
 * @api private
 */

MqttClient.prototype._handlePubrel = function(packet) {
  var mid = packet.messageId
    , pub = this.incoming.pubrel[mid];

  if (!pub) this.emit('error', new Error('Unknown message id'));
  if (this.options.autopuback) {
    this.conn.pubcomp({messageId: mid});
    this.emit('message', pub.topic, pub.payload, pub);
  } else {
    this.emit('message', pub.topic, pub.payload, pub, this.conn.pubcomp.bind(this.conn, {messageId: mid}));
  }
};

/**
 * _nextId
 */
MqttClient.prototype._nextId = function() {
  return Math.floor(Math.random() * 65535);
};
<|endoftext|>


<commit_before>/*
Copyright 2011 Timothy J Fontaine <tjfontaine@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

*/

"use strict";

var dgram = require('dgram'),
  ipaddr = require('ipaddr.js'),
  net = require('net'),
  consts = require('./consts'),
  types = require('./types'),
  Question = require('./question'),
  Response = require('./response');

var random_integer = function () {
  return Math.floor(Math.random() * 50000 + 1);
};

var PendingRequests = function () {
  this.active_ = {};
  this.active_.count = 0;

  var socket, self = this;

  this.platform = require('./platform');
  this.platform.on('ready', function () {
    self.sendQueued();
  });
  if (this.platform.ready) {
    this.sendQueued();
  }

  Object.defineProperty(this, 'socket', {
    get: function () {
      if (!socket) {
        socket = dgram.createSocket('udp4');
        socket.bind();
        socket.on('message', function (msg, remote) {
          self.handleMessage(msg, remote);
        });
      }
      return socket;
    },
    set: function (value) {
      if (value === undefined && socket) {
        socket.close();
      }
      socket = value;
    },
  });
};

PendingRequests.prototype.sendQueued = function () {
  var i, request;

  for (i in this.active_) {
    if (this.active_.hasOwnProperty(i) && i !== 'count') {
      request = this.active_[i];
      if (!request.started) {
        request.start();
      }
    }
  }
};

PendingRequests.prototype.add = function (request) {
  var id = random_integer();
  while (this.active_[id] !== undefined) {
    id = random_integer();
  }
  request.id = id;
  this.active_[id] = request;
  this.active_.count++;

  if (this.platform.ready) {
    request.start();
  }
};

PendingRequests.prototype.remove = function (request) {
  delete this.active_[request.id];
  this.active_.count--;

  if (this.active_.count === 0) {
    this.socket = undefined;
  }
};

PendingRequests.prototype.handleMessage = function (msg, remote) {
  var err, request,
    answer = new Response(this.socket, remote);

  answer.unpack(msg);
  if (this.active_[answer.header.id]) {
    request = this.active_[answer.header.id];
    request.handle(err, answer);
  }
};

var IN_FLIGHT = new PendingRequests();

var Request = function (domain, rrtype, check_hosts, callback) {
  this.retries_ = undefined;
  this.domain = domain;
  this.rrtype = rrtype;
  this.check_hosts = check_hosts;
  this.callback = callback;

  this.started = false;

  this.buildQuestion(domain);

  IN_FLIGHT.add(this);
};

Request.prototype.remove = function () {
  clearTimeout(this.timer_);
  IN_FLIGHT.remove(this);
};

Request.prototype.cancel = function () {
  this.remove();
  console.log("Request ID:", this.id, "cancelled");
};

Request.prototype.buildQuestion = function (name) {
  this.question = new Question();
  this.question.type = this.rrtype;
  this.question.class = consts.NAME_TO_QCLASS.IN;
  this.question.name = name;
};

Request.prototype.isInHosts = function () {
  var results;
  if (IN_FLIGHT.platform.hosts[this.question.name]) {
    results = IN_FLIGHT.platform.hosts[this.question.name];
    this.callback(null, results);
    return true;
  } else {
    return false;
  }
}

Request.prototype.start = function () {
  var response, self = this;

  if (!this.started) {
    this.started = true;
    this.retries_ = IN_FLIGHT.platform.attempts;
    this.search_path = IN_FLIGHT.platform.search_path.slice(0);
  }

  if (this.timer_) {
    clearTimeout(this.timer_);
  }

  this.timer_ = setTimeout(function () {
    self.handleTimeout();
  }, IN_FLIGHT.platform.timeout);

  if (this.check_hosts && this.isInHosts()) {
    this.remove();
    return;
  }

  this.retries_--;
  this.message = new Response(IN_FLIGHT.socket, this.getServer());
  this.message.header.id = this.id;
  this.message.header.rd = 1;
  this.message.question.push(this.question);
  this.message.send();
};

Request.prototype.handle = function (err, answer) {
  var rcode, errno;

  if (answer) {
    rcode = answer.header.rcode;
  }

  switch (rcode) {
  case consts.NAME_TO_RCODE.NOERROR:
    break;
  case consts.NAME_TO_RCODE.NOTFOUND:
    if (this.retries_ > 0 && this.search_path.length > 0) {
      this.buildQuestion([this.domain, this.search_path.pop()].join('.'));
    } else {
      err = new Error('getHostByName ' + consts.NOTFOUND);
      err.errno = consts.NOTFOUND;
    }
    answer = undefined;
    break;
  default:
    errno = consts.RCODE_TO_NAME[rcode];
    err = new Error('getHostByName ' + errno);
    err.errno = consts[errno];
    answer = undefined;
    break;
  }

  if (err || answer) {
    this.remove();
    this.callback(err, answer);
  } else {
    this.start();
  }
};

Request.prototype.handleTimeout = function () {
  var err;

  if (this.retries_ === 0) {
    this.remove();
    err = new Error('getHostByName ' + consts.TIMEOUT);
    err.errno = consts.TIMEOUT;
    this.callback(err, undefined);
  } else {
    this.start();
  }
};

Request.prototype.getServer = function () {
  var server = IN_FLIGHT.platform.name_servers[0];
  return {
    address: server,
    port: 53,
  };
};

var resolve = function (domain) {
  var rrtype = consts.NAME_TO_QTYPE.A,
    callback = arguments[arguments.length - 1];

  if (arguments.length === 3) {
    rrtype = consts.NAME_TO_QTYPE[arguments['1']];
  }

  return new Request(domain, rrtype, false, function (err, response) {
    var ret = [], i, a;

    if (err) {
      callback(err, response);
      return;
    }

    for (i = 0; i < response.answer.length; i++) {
      a = response.answer[i];
      if (a.type === rrtype) {
        a = a.promote();
        switch (rrtype) {
        case consts.NAME_TO_QTYPE.A:
        case consts.NAME_TO_QTYPE.AAAA:
          ret.push(a.address);
          break;
        case consts.NAME_TO_QTYPE.MX:
          ret.push({
            priority: a.priority,
            exchange: a.exchange,
          });
          break;
        case consts.NAME_TO_QTYPE.TXT:
        case consts.NAME_TO_QTYPE.NS:
        case consts.NAME_TO_QTYPE.CNAME:
        case consts.NAME_TO_QTYPE.PTR:
          ret.push(a.data);
          break;
        case consts.NAME_TO_QTYPE.SRV:
          ret.push({
            priority: a.priority,
            weight: a.weight,
            port: a.port,
            name: a.target,
          });
          break;
        }
      }
    }

    if (ret.length === 0) {
      err = consts.NODATA;
      ret = undefined;
    }
    callback(err, ret);
  });
};
exports.resolve = resolve;

var resolve4 = function (domain, callback) {
  return resolve(domain, 'A', function (err, results) {
    callback(err, results);
  });
};
exports.resolve4 = resolve4;

var resolve6 = function (domain, callback) {
  return resolve(domain, 'AAAA', function (err, results) {
    callback(err, results);
  });
};
exports.resolve6 = resolve6;

var resolveMx = function (domain, callback) {
  return resolve(domain, 'MX', function (err, results) {
    callback(err, results);
  });
};
exports.resolveMx = resolveMx;

var resolveTxt = function (domain, callback) {
  return resolve(domain, 'TXT', function (err, results) {
    callback(err, results);
  });
};
exports.resolveTxt = resolveTxt;

var resolveSrv = function (domain, callback) {
  return resolve(domain, 'SRV', function (err, results) {
    callback(err, results);
  });
};
exports.resolveSrv = resolveSrv;

var resolveNs = function (domain, callback) {
  return resolve(domain, 'NS', function (err, results) {
    callback(err, results);
  });
};
exports.resolveNs = resolveNs;

var resolveCname = function (domain, callback) {
  return resolve(domain, 'CNAME', function (err, results) {
    callback(err, results);
  });
};
exports.resolveCname = resolveCname;

var reverse = function (ip, callback) {
  var reverseip, address, parts, kind, error;

  if (!net.isIP(ip)) {
    error = new Error("getHostByAddr ENOTIMP");
    error.errno = 'ENOTIMP';
    throw error;
  }

  address = ipaddr.parse(ip);
  kind = address.kind();

  switch (kind) {
  case 'ipv4':
    address = address.toByteArray();
    address.reverse();
    reverseip = address.join('.') + '.IN-ADDR.ARPA';
    break;
  case 'ipv6':
    parts = [];
    address.toNormalizedString().split(':').forEach(function (part) {
      var i, pad = 4 - part.length;
      for (i = 0; i < pad; i++) {
        part = '0' + part;
      }
      part.split('').forEach(function (p) {
        parts.push(p);
      });
    });
    parts.reverse();
    reverseip = parts.join('.') + '.IP6.ARPA';
    break;
  }

  return resolve(reverseip, 'PTR', function (err, results) {
    callback(err, results);
  });
};
exports.reverse = reverse;

var lookup = function (domain) {
  var callback = arguments[arguments.length - 1],
    family,
    rrtype;

  family = net.isIP(domain);

  if (family === 4 || family === 6) {
    callback(null, domain, family);
    return {};
  }

  family = 4;

  if (domain === null) {
    callback(null, null, family);
    return {};
  }

  if (arguments.length === 3) {
    family = arguments['1'];
    rrtype = consts.FAMILY_TO_QTYPE[family];
  } else {
    rrtype = consts.NAME_TO_QTYPE.ANY;
  }

  return new Request(domain, rrtype, true, function (err, response) {
    var i, afamily, address, a;

    if (err) {
      callback(err, null, 4);
      return;
    }

    if (response instanceof Response) {
      for (i = 0; i < response.answer.length; i++) {
        a = response.answer[i];
        if (afamily && address) {
          break;
        }
        switch (a.type) {
        case consts.NAME_TO_QTYPE.A:
        case consts.NAME_TO_QTYPE.AAAA:
          afamily = consts.QTYPE_TO_FAMILY[a.type];
          address = a.promote().address;
          break;
        }
      }
    } else {
      afamily = net.isIP(response[0]);
      address = response[0];
    }
    callback(err, address, afamily);
  });
};
exports.lookup = lookup;
<commit_msg>lint free<commit_after>/*
Copyright 2011 Timothy J Fontaine <tjfontaine@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

*/

"use strict";

var dgram = require('dgram'),
  ipaddr = require('ipaddr.js'),
  net = require('net'),
  consts = require('./consts'),
  types = require('./types'),
  Question = require('./question'),
  Response = require('./response');

var random_integer = function () {
  return Math.floor(Math.random() * 50000 + 1);
};

var PendingRequests = function () {
  this.active_ = {};
  this.active_.count = 0;

  var socket, self = this;

  this.platform = require('./platform');
  this.platform.on('ready', function () {
    self.sendQueued();
  });
  if (this.platform.ready) {
    this.sendQueued();
  }

  Object.defineProperty(this, 'socket', {
    get: function () {
      if (!socket) {
        socket = dgram.createSocket('udp4');
        socket.bind();
        socket.on('message', function (msg, remote) {
          self.handleMessage(msg, remote);
        });
      }
      return socket;
    },
    set: function (value) {
      if (value === undefined && socket) {
        socket.close();
      }
      socket = value;
    },
  });
};

PendingRequests.prototype.sendQueued = function () {
  var i, request;

  for (i in this.active_) {
    if (this.active_.hasOwnProperty(i) && i !== 'count') {
      request = this.active_[i];
      if (!request.started) {
        request.start();
      }
    }
  }
};

PendingRequests.prototype.add = function (request) {
  var id = random_integer();
  while (this.active_[id] !== undefined) {
    id = random_integer();
  }
  request.id = id;
  this.active_[id] = request;
  this.active_.count++;

  if (this.platform.ready) {
    request.start();
  }
};

PendingRequests.prototype.remove = function (request) {
  delete this.active_[request.id];
  this.active_.count--;

  if (this.active_.count === 0) {
    this.socket = undefined;
  }
};

PendingRequests.prototype.handleMessage = function (msg, remote) {
  var err, request,
    answer = new Response(this.socket, remote);

  answer.unpack(msg);
  if (this.active_[answer.header.id]) {
    request = this.active_[answer.header.id];
    request.handle(err, answer);
  }
};

var IN_FLIGHT = new PendingRequests();

var Request = function (domain, rrtype, check_hosts, callback) {
  this.retries_ = undefined;
  this.domain = domain;
  this.rrtype = rrtype;
  this.check_hosts = check_hosts;
  this.callback = callback;

  this.started = false;

  this.buildQuestion(domain);

  IN_FLIGHT.add(this);
};

Request.prototype.remove = function () {
  clearTimeout(this.timer_);
  IN_FLIGHT.remove(this);
};

Request.prototype.cancel = function () {
  this.remove();
  console.log("Request ID:", this.id, "cancelled");
};

Request.prototype.buildQuestion = function (name) {
  this.question = new Question();
  this.question.type = this.rrtype;
  this.question.class = consts.NAME_TO_QCLASS.IN;
  this.question.name = name;
};

Request.prototype.isInHosts = function () {
  var results;
  if (IN_FLIGHT.platform.hosts[this.question.name]) {
    results = IN_FLIGHT.platform.hosts[this.question.name];
    this.callback(null, results);
    return true;
  } else {
    return false;
  }
};

Request.prototype.start = function () {
  var self = this;

  if (!this.started) {
    this.started = true;
    this.retries_ = IN_FLIGHT.platform.attempts;
    this.search_path = IN_FLIGHT.platform.search_path.slice(0);
  }

  if (this.timer_) {
    clearTimeout(this.timer_);
  }

  this.timer_ = setTimeout(function () {
    self.handleTimeout();
  }, IN_FLIGHT.platform.timeout);

  if (this.check_hosts && this.isInHosts()) {
    this.remove();
    return;
  }

  this.retries_--;
  this.message = new Response(IN_FLIGHT.socket, this.getServer());
  this.message.header.id = this.id;
  this.message.header.rd = 1;
  this.message.question.push(this.question);
  this.message.send();
};

Request.prototype.handle = function (err, answer) {
  var rcode, errno;

  if (answer) {
    rcode = answer.header.rcode;
  }

  switch (rcode) {
  case consts.NAME_TO_RCODE.NOERROR:
    break;
  case consts.NAME_TO_RCODE.NOTFOUND:
    if (this.retries_ > 0 && this.search_path.length > 0) {
      this.buildQuestion([this.domain, this.search_path.pop()].join('.'));
    } else {
      err = new Error('getHostByName ' + consts.NOTFOUND);
      err.errno = consts.NOTFOUND;
    }
    answer = undefined;
    break;
  default:
    errno = consts.RCODE_TO_NAME[rcode];
    err = new Error('getHostByName ' + errno);
    err.errno = consts[errno];
    answer = undefined;
    break;
  }

  if (err || answer) {
    this.remove();
    this.callback(err, answer);
  } else {
    this.start();
  }
};

Request.prototype.handleTimeout = function () {
  var err;

  if (this.retries_ === 0) {
    this.remove();
    err = new Error('getHostByName ' + consts.TIMEOUT);
    err.errno = consts.TIMEOUT;
    this.callback(err, undefined);
  } else {
    this.start();
  }
};

Request.prototype.getServer = function () {
  var server = IN_FLIGHT.platform.name_servers[0];
  return {
    address: server,
    port: 53,
  };
};

var resolve = function (domain) {
  var rrtype = consts.NAME_TO_QTYPE.A,
    callback = arguments[arguments.length - 1];

  if (arguments.length === 3) {
    rrtype = consts.NAME_TO_QTYPE[arguments['1']];
  }

  return new Request(domain, rrtype, false, function (err, response) {
    var ret = [], i, a;

    if (err) {
      callback(err, response);
      return;
    }

    for (i = 0; i < response.answer.length; i++) {
      a = response.answer[i];
      if (a.type === rrtype) {
        a = a.promote();
        switch (rrtype) {
        case consts.NAME_TO_QTYPE.A:
        case consts.NAME_TO_QTYPE.AAAA:
          ret.push(a.address);
          break;
        case consts.NAME_TO_QTYPE.MX:
          ret.push({
            priority: a.priority,
            exchange: a.exchange,
          });
          break;
        case consts.NAME_TO_QTYPE.TXT:
        case consts.NAME_TO_QTYPE.NS:
        case consts.NAME_TO_QTYPE.CNAME:
        case consts.NAME_TO_QTYPE.PTR:
          ret.push(a.data);
          break;
        case consts.NAME_TO_QTYPE.SRV:
          ret.push({
            priority: a.priority,
            weight: a.weight,
            port: a.port,
            name: a.target,
          });
          break;
        }
      }
    }

    if (ret.length === 0) {
      err = consts.NODATA;
      ret = undefined;
    }
    callback(err, ret);
  });
};
exports.resolve = resolve;

var resolve4 = function (domain, callback) {
  return resolve(domain, 'A', function (err, results) {
    callback(err, results);
  });
};
exports.resolve4 = resolve4;

var resolve6 = function (domain, callback) {
  return resolve(domain, 'AAAA', function (err, results) {
    callback(err, results);
  });
};
exports.resolve6 = resolve6;

var resolveMx = function (domain, callback) {
  return resolve(domain, 'MX', function (err, results) {
    callback(err, results);
  });
};
exports.resolveMx = resolveMx;

var resolveTxt = function (domain, callback) {
  return resolve(domain, 'TXT', function (err, results) {
    callback(err, results);
  });
};
exports.resolveTxt = resolveTxt;

var resolveSrv = function (domain, callback) {
  return resolve(domain, 'SRV', function (err, results) {
    callback(err, results);
  });
};
exports.resolveSrv = resolveSrv;

var resolveNs = function (domain, callback) {
  return resolve(domain, 'NS', function (err, results) {
    callback(err, results);
  });
};
exports.resolveNs = resolveNs;

var resolveCname = function (domain, callback) {
  return resolve(domain, 'CNAME', function (err, results) {
    callback(err, results);
  });
};
exports.resolveCname = resolveCname;

var reverse = function (ip, callback) {
  var reverseip, address, parts, kind, error;

  if (!net.isIP(ip)) {
    error = new Error("getHostByAddr ENOTIMP");
    error.errno = 'ENOTIMP';
    throw error;
  }

  address = ipaddr.parse(ip);
  kind = address.kind();

  switch (kind) {
  case 'ipv4':
    address = address.toByteArray();
    address.reverse();
    reverseip = address.join('.') + '.IN-ADDR.ARPA';
    break;
  case 'ipv6':
    parts = [];
    address.toNormalizedString().split(':').forEach(function (part) {
      var i, pad = 4 - part.length;
      for (i = 0; i < pad; i++) {
        part = '0' + part;
      }
      part.split('').forEach(function (p) {
        parts.push(p);
      });
    });
    parts.reverse();
    reverseip = parts.join('.') + '.IP6.ARPA';
    break;
  }

  return resolve(reverseip, 'PTR', function (err, results) {
    callback(err, results);
  });
};
exports.reverse = reverse;

var lookup = function (domain) {
  var callback = arguments[arguments.length - 1],
    family,
    rrtype;

  family = net.isIP(domain);

  if (family === 4 || family === 6) {
    callback(null, domain, family);
    return {};
  }

  family = 4;

  if (domain === null) {
    callback(null, null, family);
    return {};
  }

  if (arguments.length === 3) {
    family = arguments['1'];
    rrtype = consts.FAMILY_TO_QTYPE[family];
  } else {
    rrtype = consts.NAME_TO_QTYPE.ANY;
  }

  return new Request(domain, rrtype, true, function (err, response) {
    var i, afamily, address, a;

    if (err) {
      callback(err, null, 4);
      return;
    }

    if (response instanceof Response) {
      for (i = 0; i < response.answer.length; i++) {
        a = response.answer[i];
        if (afamily && address) {
          break;
        }
        switch (a.type) {
        case consts.NAME_TO_QTYPE.A:
        case consts.NAME_TO_QTYPE.AAAA:
          afamily = consts.QTYPE_TO_FAMILY[a.type];
          address = a.promote().address;
          break;
        }
      }
    } else {
      afamily = net.isIP(response[0]);
      address = response[0];
    }
    callback(err, address, afamily);
  });
};
exports.lookup = lookup;
<|endoftext|>


<commit_before>var util = require('util');
var EventEmitter = require('events').EventEmitter;
var r = require('rethinkdb');
var pipeEvent = require('pipe-event');

/**
 * Expose module.
 */

module.exports = Client;

/**
 * Create a new client.
 *
 * @param {object} [options]
 * @see avalaible options http://rethinkdb.com/api/javascript/connect/
 */

function Client(options) {
  EventEmitter.call(this);
  var client = this;

  this.r = r;
  this.connDeferred = r.connect(options);
  this.conn = null;

  this.connDeferred.then(function onConnect(conn) {
    if (client.conn) return ;

    client.conn = conn;
    pipeEvent(['connect', 'close', 'error'], conn, client);
  });
}

util.inherits(Client, EventEmitter);

/**
 * Run a command on the current connection.
 *
 * @param {TermBase} term
 * @param {function} [cb]
 */

Client.prototype.run = function (term, cb) {
  var client = this;

  return this.connDeferred
  .then(function onConnect(conn) {
    if (! client.conn) {
      client.conn = conn;
      pipeEvent(['connect', 'close', 'error'], conn, client);
    }

    return term.run(conn);
  })
  .nodeify(cb);
};

/**
 * Close the connection.
 *
 * @see http://rethinkdb.com/api/javascript/close/
 */

Client.prototype.close = function () {
  var args = arguments;
  var client = this;

  this.connDeferred.then(function closeConnection(conn) {
    conn.close.apply(conn, args);
    client.conn = null;
  });
};<commit_msg>refactor(clean): clean code<commit_after>var util = require('util');
var EventEmitter = require('events').EventEmitter;
var r = require('rethinkdb');
var pipeEvent = require('pipe-event');

/**
 * Expose module.
 */

module.exports = Client;

/**
 * Create a new client.
 *
 * @param {object} [options]
 * @see avalaible options http://rethinkdb.com/api/javascript/connect/
 */

function Client(options) {
  EventEmitter.call(this);
  var client = this;

  this.r = r;
  this.connDeferred = r.connect(options);
  this.conn = null;

  this.connDeferred.then(function onConnect(conn) {
    if (client.conn) return ;

    client.conn = conn;
    pipeEvent(['connect', 'close', 'error'], conn, client);
  });
}

util.inherits(Client, EventEmitter);

/**
 * Run a command on the current connection.
 *
 * @param {TermBase} term
 * @param {function} [cb]
 */

Client.prototype.run = function (term, cb) {
  return this.connDeferred
  .then(function onConnect(conn) {
    return term.run(conn);
  })
  .nodeify(cb);
};

/**
 * Close the connection.
 *
 * @see http://rethinkdb.com/api/javascript/close/
 */

Client.prototype.close = function () {
  var args = arguments;
  var client = this;

  this.connDeferred.then(function closeConnection(conn) {
    conn.close.apply(conn, args);
    client.conn = null;
  });
};<|endoftext|>