<commit_before>var assert = require('assert') ;
var noop = require('node-noop').noop;
var delegate = require('delegates') ;
var Emitter = require('events').EventEmitter ;
var Conference = require('./conference') ;
var ConferenceConnection = require('./conference-connection') ;
var util = require('util') ;
var only = require('only') ;
var _ = require('lodash') ;
var fs = require('fs') ;
var path = require('path') ;
var async = require('async') ;
var debug = require('debug')('drachtio-fsmrf') ;

var State = {
  NOT_CONNECTED: 1,
  CONNECTED: 2,
  DISCONNECTED: 3
};

/**
 * A media resource on a freeswitch-based MediaServer that is capable of play, record, signal detection, and signal generation
 * @constructor
 * @param {esl.Connection} conn - outbound connection from a media server for one session
 * @param {MediaServer}   ms - MediaServer that contains this Endpoint
 * @param {object}   app - drachtio app
 * @param {Endpoint~createOptions} [opts] configuration options
 * @param {Endpoint~createCallback} cb   callback that is invoked when the endpoint is connected and ready for use
 */
function Endpoint( conn, ms, app, opts, cb ) {
  Emitter.call(this); 

  if( typeof opts === 'function') {
    cb = opts ;
    opts = {} ;
  }

  this._conn = conn ;
  this._ms = ms ;
  this._app = app ;
  this._dialog = null ;
  this._executeCallbacks = {} ;

  this.uuid = conn.getInfo().getHeader('Channel-Unique-ID') ;

  /**
   * defines the local network connection of the Endpoint
   * @type {Endpoint~NetworkConnection}
   */
  this.local = {} ;
  /**
   * defines the remote network connection of the Endpoint
   * @type {Endpoint~NetworkConnection}
   */
  this.remote = {} ;
  /**
   * defines the SIP signaling parameters of the Endpoint
   * @type {Endpoint~SipInfo}
   */
  this.sip = {} ;
  this.state = State.NOT_CONNECTED ;

  if( opts.debugDir ) {
    this.logger = fs.createWriteStream(path.join(opts.debugDir, 'endpoint-' + this.uuid + '.txt' ) ) ;
    var origWrite = conn.socket.write ;
    conn.socket.write = function(data) {
      this.emit.call(this, 'write', data) ;
      return origWrite.apply( this, arguments) ;
    } ;

    conn.socket.on('data', this._onSocketRead.bind( this ) ) ;
    conn.socket.on('write', this._onSocketWrite.bind( this ) ) ;
  }


  this._conn.subscribe('all') ;

  debug("new Endpoint: id: %s, conn: %s", this.uuid, this._conn._id) ;
 
  this._conn.on('esl::event::CHANNEL_ANSWER::*', this._onAnswer.bind(this, cb)) ;
  //this._conn.on('esl::event::CHANNEL_ANSWER::*', function(evt) { return self._onAnswer.bind(self, cb, evt)() ; }) ;
  this._conn.on('esl::event::CHANNEL_EXECUTE::*', this._onChannelExecute.bind(this)) ;
  this._conn.on('esl::event::CHANNEL_HANGUP::*', this._onHangup.bind(this)) ;
  this._conn.on('error', this._onError.bind(this)) ;

  if( opts.codecs ) {
    if( typeof opts.codecs === 'string') { opts.codecs = [opts.codecs]; }

    if( opts.codecs.length > 0 ) {
      this._conn.execute('set', 'codec_string=' + opts.codecs.join(',')) ;
    }
  }

  this._conn.execute('answer') ;

  Object.defineProperty(this, 'dialog', {
    set: function(dialog) {
      this._dialog = dialog ;
      this._dialog.on('destroy', this._onBye.bind( this ) ) ;
    }
  }) ;
  Object.defineProperty(this, 'conn', {
    get: function() {
      return this._conn ;
    }
  }) ;
}
/**
 * Options governing the creation of an Endpoint
 * @typedef {Object} Endpoint~createOptions
 * @property {string} [debugDir] directory into which message trace files; the presence of this param will enable debug tracing
 * @property {string|array} [codecs] preferred codecs; array order indicates order of preference
 * 
 */

/**
 * This callback is invoked when an endpoint has been created and is ready for commands.
 * @callback Endpoint~createCallback
 * @param {Error} error 
 * @param {Endpoint} ep the Endpoint
 */
util.inherits(Endpoint, Emitter) ;

module.exports = exports = Endpoint ;


/**
 * return the MediaServer associated with this Endpoint
 * @return {MediaServer} MediaServer 
 */
Endpoint.prototype.getMediaServer = function() {
    return this._ms ;
} ;

/**
 * play an audio file on the endpoint
 * @param  {string|Array}   file file (or array of files) to play 
 * @param  {Endpoint~playOperationCallback} cb   callback function invoked when operation completes
 */
Endpoint.prototype.play = function( file, cb ) {
  assert.ok( 'string' === typeof file || _.isArray( file ), 'file param is required and must be a string or array') ;

  var self = this ;
  cb = cb || noop ;
  var files = _.isArray( file ) ? file : [file] ;

  async.waterfall([
    function setDelimiter(callback) {
      if( 1 === files.length ) { 
        return callback(null); 
      }
      self._conn.execute('set', 'playback_delimiter=!', function(evt){
        debug('playback_delimiter response: ', evt) ;
        callback(null); 
      }) ;
    }, 
    function sendPlay(callback) {
      self._conn.execute('playback', files.join('!'), function(evt) {
        var result = {
          playbackSeconds: evt.getHeader('variable_playback_seconds'),
          playbackMilliseconds: evt.getHeader('variable_playback_ms'),
        } ;
        callback( null, result ) ;
      }) ;
    }
    ], 
    function(err, result){
      cb(err, result) ;
    }
  ) ;
} ;
/**
 * This callback is invoked when a media operation has completed
 * @callback Endpoint~playOperationCallback
 * @param {Error} err - error returned from play request
 * @param {object} results - results of the operation
 * @param {String} results.playbackSeconds - number of seconds of audio played
 * @param {String} results.playbackMilliseconds - number of fractional milliseconds of audio played
 */

/**
 * play an audio file and collect digits
 * @param  {Endpoint~playCollectOptions}   opts - playcollect options
 * @param  {Endpoint~playCollectOperationCallback} cb - callback function invoked when operation completes
 */
Endpoint.prototype.playCollect = function( opts, cb) {
  assert(typeof opts, 'object', '\'opts\' param is required') ;
  assert(typeof opts.file, 'string', '\'opts.file\' param is required') ;

  opts.min = opts.min || 0 ;
  opts.max = opts.max || 128 ;
  opts.tries = opts.tries || 1 ;
  opts.timeout = opts.timeout || 120000 ;
  opts.terminators = opts.terminators || '#' ;
  opts.invalidFile = opts.invalidFile || 'silence_stream://250' ;
  opts.varName = 'myDigitBuffer' ;
  opts.regexp = opts.regexp || '^\\d+[' + opts.terminators + ']' ;
  opts.digitTimeout = opts.digitTimeout || 8000 ;

  var args = [] ;
  ['min', 'max', 'tries', 'timeout', 'terminators', 'file', 'invalidFile', 'regexp','varName','digitTimeout']
  .forEach(function(prop) { 
    args.push( opts[prop] ) ; 
  }) ;

  this._conn.execute('play_and_get_digits', args.join(' '), function(evt){
    if('play_and_get_digits' !== evt.getHeader('variable_current_application')) {
      console.log('expected response to play_and_get_digits but got %s', evt.getHeader('variable_current_application')) ;
      return ;
    }
    var result = {
      digits: evt.getHeader('variable_myDigitBuffer'),
      terminatorUsed: evt.getHeader('variable_read_terminator_used'),
      playbackSeconds: evt.getHeader('variable_playback_seconds'),
      playbackMilliseconds: evt.getHeader('variable_playback_ms'),
    } ;
    cb(null, result) ;
  }) ;
} ;<commit_msg>endpoint should listen only for events with a uuid specific to that endpoint<commit_after>var assert = require('assert') ;
var noop = require('node-noop').noop;
var delegate = require('delegates') ;
var Emitter = require('events').EventEmitter ;
var Conference = require('./conference') ;
var ConferenceConnection = require('./conference-connection') ;
var util = require('util') ;
var only = require('only') ;
var _ = require('lodash') ;
var fs = require('fs') ;
var path = require('path') ;
var async = require('async') ;
var debug = require('debug')('drachtio-fsmrf') ;

var State = {
  NOT_CONNECTED: 1,
  CONNECTED: 2,
  DISCONNECTED: 3
};

/**
 * A media resource on a freeswitch-based MediaServer that is capable of play, record, signal detection, and signal generation
 * @constructor
 * @param {esl.Connection} conn - outbound connection from a media server for one session
 * @param {MediaServer}   ms - MediaServer that contains this Endpoint
 * @param {object}   app - drachtio app
 * @param {Endpoint~createOptions} [opts] configuration options
 * @param {Endpoint~createCallback} cb   callback that is invoked when the endpoint is connected and ready for use
 */
function Endpoint( conn, ms, app, opts, cb ) {
  Emitter.call(this); 

  if( typeof opts === 'function') {
    cb = opts ;
    opts = {} ;
  }

  this._conn = conn ;
  this._ms = ms ;
  this._app = app ;
  this._dialog = null ;
  this._executeCallbacks = {} ;

  this.uuid = conn.getInfo().getHeader('Channel-Unique-ID') ;

  /**
   * defines the local network connection of the Endpoint
   * @type {Endpoint~NetworkConnection}
   */
  this.local = {} ;
  /**
   * defines the remote network connection of the Endpoint
   * @type {Endpoint~NetworkConnection}
   */
  this.remote = {} ;
  /**
   * defines the SIP signaling parameters of the Endpoint
   * @type {Endpoint~SipInfo}
   */
  this.sip = {} ;
  this.state = State.NOT_CONNECTED ;

  if( opts.debugDir ) {
    this.logger = fs.createWriteStream(path.join(opts.debugDir, 'endpoint-' + this.uuid + '.txt' ) ) ;
    var origWrite = conn.socket.write ;
    conn.socket.write = function(data) {
      this.emit.call(this, 'write', data) ;
      return origWrite.apply( this, arguments) ;
    } ;

    conn.socket.on('data', this._onSocketRead.bind( this ) ) ;
    conn.socket.on('write', this._onSocketWrite.bind( this ) ) ;
  }


  this._conn.subscribe('all') ;

  debug("new Endpoint: id: %s, conn: %s", this.uuid, this._conn._id) ;
 
  this._conn.on('esl::event::CHANNEL_ANSWER::' + this.uuid, this._onAnswer.bind(this, cb)) ;
  //this._conn.on('esl::event::CHANNEL_ANSWER::*', function(evt) { return self._onAnswer.bind(self, cb, evt)() ; }) ;
  this._conn.on('esl::event::CHANNEL_EXECUTE::'+ this.uuid, this._onChannelExecute.bind(this)) ;
  this._conn.on('esl::event::CHANNEL_HANGUP::'+ this.uuid, this._onHangup.bind(this)) ;
  this._conn.on('error', this._onError.bind(this)) ;

  if( opts.codecs ) {
    if( typeof opts.codecs === 'string') { opts.codecs = [opts.codecs]; }

    if( opts.codecs.length > 0 ) {
      this._conn.execute('set', 'codec_string=' + opts.codecs.join(',')) ;
    }
  }

  this._conn.execute('answer') ;

  Object.defineProperty(this, 'dialog', {
    set: function(dialog) {
      this._dialog = dialog ;
      this._dialog.on('destroy', this._onBye.bind( this ) ) ;
    }
  }) ;
  Object.defineProperty(this, 'conn', {
    get: function() {
      return this._conn ;
    }
  }) ;
}
/**
 * Options governing the creation of an Endpoint
 * @typedef {Object} Endpoint~createOptions
 * @property {string} [debugDir] directory into which message trace files; the presence of this param will enable debug tracing
 * @property {string|array} [codecs] preferred codecs; array order indicates order of preference
 * 
 */

/**
 * This callback is invoked when an endpoint has been created and is ready for commands.
 * @callback Endpoint~createCallback
 * @param {Error} error 
 * @param {Endpoint} ep the Endpoint
 */
util.inherits(Endpoint, Emitter) ;

module.exports = exports = Endpoint ;


/**
 * return the MediaServer associated with this Endpoint
 * @return {MediaServer} MediaServer 
 */
Endpoint.prototype.getMediaServer = function() {
    return this._ms ;
} ;

/**
 * play an audio file on the endpoint
 * @param  {string|Array}   file file (or array of files) to play 
 * @param  {Endpoint~playOperationCallback} cb   callback function invoked when operation completes
 */
Endpoint.prototype.play = function( file, cb ) {
  assert.ok( 'string' === typeof file || _.isArray( file ), 'file param is required and must be a string or array') ;

  var self = this ;
  cb = cb || noop ;
  var files = _.isArray( file ) ? file : [file] ;

  async.waterfall([
    function setDelimiter(callback) {
      if( 1 === files.length ) { 
        return callback(null); 
      }
      self._conn.execute('set', 'playback_delimiter=!', function(evt){
        debug('playback_delimiter response: ', evt) ;
        callback(null); 
      }) ;
    }, 
    function sendPlay(callback) {
      self._conn.execute('playback', files.join('!'), function(evt) {
        var result = {
          playbackSeconds: evt.getHeader('variable_playback_seconds'),
          playbackMilliseconds: evt.getHeader('variable_playback_ms'),
        } ;
        callback( null, result ) ;
      }) ;
    }
    ], 
    function(err, result){
      cb(err, result) ;
    }
  ) ;
} ;
/**
 * This callback is invoked when a media operation has completed
 * @callback Endpoint~playOperationCallback
 * @param {Error} err - error returned from play request
 * @param {object} results - results of the operation
 * @param {String} results.playbackSeconds - number of seconds of audio played
 * @param {String} results.playbackMilliseconds - number of fractional milliseconds of audio played
 */

/**
 * play an audio file and collect digits
 * @param  {Endpoint~playCollectOptions}   opts - playcollect options
 * @param  {Endpoint~playCollectOperationCallback} cb - callback function invoked when operation completes
 */
Endpoint.prototype.playCollect = function( opts, cb) {
  assert(typeof opts, 'object', '\'opts\' param is required') ;
  assert(typeof opts.file, 'string', '\'opts.file\' param is required') ;

  opts.min = opts.min || 0 ;
  opts.max = opts.max || 128 ;
  opts.tries = opts.tries || 1 ;
  opts.timeout = opts.timeout || 120000 ;
  opts.terminators = opts.terminators || '#' ;
  opts.invalidFile = opts.invalidFile || 'silence_stream://250' ;
  opts.varName = 'myDigitBuffer' ;
  opts.regexp = opts.regexp || '\\d+' ;
  opts.digitTimeout = opts.digitTimeout || 8000 ;

  var args = [] ;
  ['min', 'max', 'tries', 'timeout', 'terminators', 'file', 'invalidFile', 'regexp','varName','digitTimeout']
  .forEach(function(prop) { 
    args.push( opts[prop] ) ; 
  }) ;

  this._conn.execute('play_and_get_digits', args.join(' '), function(evt){
    if('play_and_get_digits' !== evt.getHeader('variable_current_application')) {
      console.log('expected response to play_and_get_digits but got %s', evt.getHeader('variable_current_application')) ;
      return ;
    }
    var result = {
      digits: evt.getHeader('variable_myDigitBuffer'),
      terminatorUsed: evt.getHeader('variable_read_terminator_used'),
      playbackSeconds: evt.getHeader('variable_playback_seconds'),
      playbackMilliseconds: evt.getHeader('variable_playback_ms'),
    } ;
    cb(null, result) ;
  }) ;
} ;
{'fraction_non_alphanumeric': 0.14558117519778213, 'fraction_numerical': 0.003110419906687403, 'mean_word_length': 3.5945945945945947, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '755451', 'n_tokens_mistral': 4658, 'n_tokens_neox': 4548, 'n_words': 1166}