<commit_before>export { Stage_Params as Params };

import * as Pool from "../../util/Pool.js";
//import * as Recyclable from "../../util/Recyclable.js";
import * as ValueDesc from "../../Unpacker/ValueDesc.js";
import * as ParamDesc from "../../Unpacker/ParamDesc.js";
import * as Weights from "../../Unpacker/Weights.js";
import * as Block from "../Block.js";
import { ParamsBase } from "./Stage_ParamsBase.js";

//!!! ...unfinished... (2022/05/28)
//
// Add parameter bPointwise2SqueezeExcitationAtStageEnd control whether this stage's final block's pointwise2 should have
// squeeze-and-excitation.
//

/**
 * Convolution stage parameters extracted from inputWeightArray.
 *
 */
class Stage_Params extends Weights.Params( ParamsBase ) {

  /**
   * Used as default Stage.Params provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "Stage.Params.Pool", Stage_Params, Stage_Params.setAsConstructor );

  /**
   * If a parameter's value is null, it will be extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {number} sourceHeight
   *   The height of the source image which will be processed by apply(). If null, it will be extracted from
   * inputWeightArray (i.e. by evolution).
   *
   * @param {number} sourceWidth
   *   The width of the source image which will be processed by apply(). If null, it will be extracted from
   * inputWeightArray (i.e. by evolution).
   *
   * @param {number} sourceChannelCount
   *   The depth (channel count) of the source image. It may be the output channel count of the previous convolution stage, so
   * it could be large. If null, it will be extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {number} nConvStageTypeId
   *   The convolution stage type (ValueDesc.ConvStageType.Singleton.Ids.Xxx). If null, it will be extracted from inputWeightArray
   * (i.e. by evolution).
   *

//!!! ...unfinished... (2022/06/25)
// Perhaps, just like the ( height, width ) halving and channel count doubling by evey stage,
// the block count could be doubled by evey stage.

   * @param {number} blockCountRequested
   *   How many blocks inside this stage are wanted.
   *   - If null, it will be extracted from inputWeightArray (i.e. by evolution).
   *   - It must be ( >= 2 ). Because this stage will use one tf.depthwiseConv2d( strides = 2, pad = "same" ) to shrink
   *       (i.e. to halve height x width) and use ( blockCountRequested - 1 ) times tf.depthwiseConv2d( strides = 1, pad = "same" )
   *       until the stage end. These can not be achieved by only one block. So there is at least two blocks.
   *
   * @param {boolean} bPointwise1
   *   If false, there will be no pointwise1 (i.e. the 1st pointwise convolution).If null, it will be extracted from inputWeightArray
   * (i.e. by evolution).
   *
   * @param {number} depthwiseFilterHeight
   *   The height of depthwise convolution's filter. At least 1 (so that 1D data could be processed). If null, it will be extracted
   * from inputWeightArray (i.e. by evolution).
   *
   * @param {number} depthwiseFilterWidth
   *   The width of depthwise convolution's filter. At least 2 (so that meaningless ( 1 * 1 ) could be avoided). If null, it will
   * be extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {boolean} bPointwise2ActivatedAtStageEnd
   *   If true, the blockLast's pointwise2 will have activation function. If false, the blockLast's pointwise2 will have no activation
   * function. If null, it will be extracted from inputWeightArray (i.e. by evolution).
   *
   *   - If there will be next stage after this stage, it usually should be true (i.e. has activation function). The reason is that
   *       this stage's blockLast's pointwise2 (if has no activation function) and the next stage's block0's pointwise1 are essentially
   *       just one pointwise convolution. (Multiple affine transformations can always be combined into just one affine transformation.)
   *
   *     - There is one exception: for MobileNetV2_Xxx, its pointwise2 always has no activation function no matter whether
   *         bPointwise2ActivatedAtStageEnd is true or false. The Reason is that it has add-input-to-output to modify pointwise2
   *         output. So, even if its stage output is not affine transformation (even it has no activation function).
   *
   *   - Only when this stage is the last stage of a neural network, it should be false (i.e. has no activation function). There are
   *       two reasons:
   *
   *     - For ShuffleNetV2_ByMobileNetV1, let it undo activation escaping scales.
   *         In ShuffleNetV2_ByMobileNetV1, if an operation has activation function, the pass-through half part will scale its
   *         convolution filters for escaping the activation function's non-linear parts. This results in its output is wrong
   *         (i.e. different from ShuffleNetV2). In order to resolve this issue, the last operation (i.e. pointwise2) should have
   *         no activation (so it will not scale its convolution filters for escaping the activation function's non-linear parts).
   *
   *     - Even if not ShuffleNetV2_ByMobileNetV1 (i.e. for other ConvStageType), it does have practical advantage in fact. The
   *         output could have any value (i.e. the whole number line). If the last operation (i.e. pointwise2) has activation
   *         function, the output value will be restricted by the activation function (e.g. [ -1, +1 ] for tanh()).
   *
   *
   * @param {number} nSqueezeExcitationChannelCountDivisor
   *   An integer represents the channel count divisor for squeeze-and-excitation's intermediate pointwise convolution channel count.
   * (ValueDesc.SqueezeExcitationChannelCountDivisor.Singleton.Ids.Xx)
   *
   * @param {number} nActivationId
   *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx) after every convolution. If null, it will be
   * extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply() will not dispose inputTensor (i.e. will be kept). If null, it will be extracted from
   * inputWeightArray (i.e. by evolution).
   *
   */
  constructor(
    sourceHeight, sourceWidth, sourceChannelCount,
    nConvStageTypeId,
    blockCountRequested,
    bPointwise1,
    depthwiseFilterHeight, depthwiseFilterWidth,
    bPointwise2ActivatedAtStageEnd,
    nSqueezeExcitationChannelCountDivisor,
    nActivationId,
    bKeepInputTensor
  ) {

    // Q: Why the depthwiseChannelMultiplierBlock0 is not listed as a parameter?
    // A: After considering the following reasons, it is worth to drop this parameter.
    //
    //   - In reality, it is almost no reason to use only avg/max pooling to compose a stage because it keep too little information
    //     for the next stage.
    //
    //   - If depthwiseChannelMultiplierBlock0 is specified as Params.depthwiseChannelMultiplierBlock0.valueDesc.Ids.NONE (0), the input
    //     image will not be shrinked a little (for ( blockCountRequested <= 1 )) or will not be halven (for ( blockCountRequested >= 2 ).
    //     If it is still a parameter it should be forced to 1 at least (always needs depthwise operation) in this case.
    //

    super(
      Stage_Params.SequenceArray,
      sourceHeight, sourceWidth, sourceChannelCount,
      nConvStageTypeId,
      blockCountRequested,
      bPointwise1,
      depthwiseFilterHeight, depthwiseFilterWidth,
      bPointwise2ActivatedAtStageEnd,
      nSqueezeExcitationChannelCountDivisor,
      nActivationId,
      bKeepInputTensor
    );
    Stage_Params.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor(
    sourceHeight, sourceWidth, sourceChannelCount,
    nConvStageTypeId,
    blockCountRequested,
    bPointwise1,
    depthwiseFilterHeight, depthwiseFilterWidth,
    bPointwise2ActivatedAtStageEnd,
    nSqueezeExcitationChannelCountDivisor,
    nActivationId,
    bKeepInputTensor
  ) {
    super.setAsConstructor(
      Stage_Params.SequenceArray,
      sourceHeight, sourceWidth, sourceChannelCount,
      nConvStageTypeId,
      blockCountRequested,
      bPointwise1,
      depthwiseFilterHeight, depthwiseFilterWidth,
      bPointwise2ActivatedAtStageEnd,
      nSqueezeExcitationChannelCountDivisor,
      nActivationId,
      bKeepInputTensor
    );
    Stage_Params.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
    // Do nothing.
  }

  /** @override */
  disposeResources() {
    super.disposeResources();
  }

  /** @override */
  BlockParamsClass_get() {
    return Block.Params;
  }

  /**
   * Extract parameters from inputWeightArray.
   *
   * @return {boolean} Return false, if extraction failed.
   *
   * @override
   */
  init( inputWeightArray, elementOffsetBegin ) {
    let bExtractOk = super.init( inputWeightArray, elementOffsetBegin );
    if ( !bExtractOk )
      return false;

    {
      this.sourceHeight = this.getParamValue_byParamDesc( Stage_Params.sourceHeight );
      this.sourceWidth = this.getParamValue_byParamDesc( Stage_Params.sourceWidth );
      this.sourceChannelCount = this.getParamValue_byParamDesc( Stage_Params.sourceChannelCount );
      this.nConvStageTypeId = this.getParamValue_byParamDesc( Stage_Params.nConvStageTypeId );
      this.blockCountRequested = this.getParamValue_byParamDesc( Stage_Params.blockCountRequested );
      this.bPointwise1 = this.getParamValue_byParamDesc( Stage_Params.bPointwise1 );
      this.depthwiseFilterHeight = this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterHeight );
      this.depthwiseFilterWidth = this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterWidth );
      this.bPointwise2ActivatedAtStageEnd = this.getParamValue_byParamDesc( Stage_Params.bPointwise2ActivatedAtStageEnd );
      this.nSqueezeExcitationChannelCountDivisor = this.getParamValue_byParamDesc( Stage_Params.nSqueezeExcitationChannelCountDivisor );
      this.nActivationId = this.getParamValue_byParamDesc( Stage_Params.nActivationId );
      this.bKeepInputTensor = this.getParamValue_byParamDesc( Stage_Params.bKeepInputTensor );
    }

    this.InferencedParams_create();

    return bExtractOk;
  }

//!!! (2022/07/30 Remarked) Use parent class's data properties instead.
  // get sourceHeight()              { return this.getParamValue_byParamDesc( Stage_Params.sourceHeight ); }
  // get sourceWidth()               { return this.getParamValue_byParamDesc( Stage_Params.sourceWidth ); }
  // get sourceChannelCount()        { return this.getParamValue_byParamDesc( Stage_Params.sourceChannelCount ); }

  // /** @return {number} The number version of nConvStageTypeId. */
  // get nConvStageTypeId()          { return this.getParamValue_byParamDesc( Stage_Params.nConvStageTypeId ); }

  // /** @return {string} The string version of nConvStageTypeId. */
  // get nConvStageTypeName()        { return Stage_Params.nConvStageTypeId.getStringOfValue( this.nConvStageTypeId ); }

  // get blockCountRequested()       { return this.getParamValue_byParamDesc( Stage_Params.blockCountRequested ); }
  // get bPointwise1()               { return this.getParamValue_byParamDesc( Stage_Params.bPointwise1 ); }

  // get depthwiseFilterHeight()     { return this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterHeight ); }
  // get depthwiseFilterWidth()      { return this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterWidth ); }

  // get bPointwise2ActivatedAtStageEnd() { return this.getParamValue_byParamDesc( Stage_Params.bPointwise2ActivatedAtStageEnd ); }

  // get nSqueezeExcitationChannelCountDivisor()     { return this.getParamValue_byParamDesc( Stage_Params.nSqueezeExcitationChannelCountDivisor ); }
  // get nSqueezeExcitationChannelCountDivisorName() {
  //   return Stage_Params.nSqueezeExcitationChannelCountDivisor.getStringOfValue( this.nSqueezeExcitationChannelCountDivisor );
  // }

  // get nActivationId()             { return this.getParamValue_byParamDesc( Stage_Params.nActivationId ); }
  // get nActivationName()           { return Stage_Params.nActivationId.getStringOfValue( this.nActivationId ); }

  // get bKeepInputTensor()          { return this.getParamValue_byParamDesc( Stage_Params.bKeepInputTensor ); }
}

// Define parameter descriptions.
Stage_Params.sourceHeight =                   new ParamDesc.Int(                "sourceHeight",               1, ( 10 * 1024 ) );
Stage_Params.sourceWidth =                    new ParamDesc.Int(                "sourceWidth",                1, ( 10 * 1024 ) );
Stage_Params.sourceChannelCount =             new ParamDesc.Int(                "sourceChannelCount",         1, ( 10 * 1024 ) );

Stage_Params.nConvStageTypeId =               new ParamDesc.ConvStageType(      "nConvStageTypeId" );

Stage_Params.blockCountRequested =            new ParamDesc.Int(                "blockCountRequested",        2, (  1 * 1024 ) );
Stage_Params.bPointwise1 =                    new ParamDesc.Bool(               "bPointwise1" );
Stage_Params.depthwiseFilterHeight =          new ParamDesc.Int(                "depthwiseFilterHeight",      1, ( 10 * 1024 ) );
Stage_Params.depthwiseFilterWidth =           new ParamDesc.Int(                "depthwiseFilterWidth",       2, ( 10 * 1024 ) );
Stage_Params.bPointwise2ActivatedAtStageEnd = new ParamDesc.Bool(               "bPointwise2ActivatedAtStageEnd" );

Stage_Params.nSqueezeExcitationChannelCountDivisor = new ParamDesc.SqueezeExcitationChannelCountDivisor( "nSqueezeExcitationChannelCountDivisor" );
//Stage_Params.bSqueezeExcitationPrefix =       new ParamDesc.Bool(               "bSqueezeExcitationPrefix" );

Stage_Params.nActivationId =                  new ParamDesc.ActivationFunction( "nActivationId" );

Stage_Params.bKeepInputTensor =               new ParamDesc.Bool(               "bKeepInputTensor" );

/**
 * Define the order of these parameters. (Fills ParamDesc.Xxx.seqId according to this array's order.)
 */
Stage_Params.SequenceArray = new ParamDesc.SequenceArray( [
  Stage_Params.sourceHeight,
  Stage_Params.sourceWidth,
  Stage_Params.sourceChannelCount,
  Stage_Params.nConvStageTypeId,
  Stage_Params.blockCountRequested,
  Stage_Params.bPointwise1,
  Stage_Params.depthwiseFilterHeight,
  Stage_Params.depthwiseFilterWidth,
  Stage_Params.bPointwise2ActivatedAtStageEnd,
  Stage_Params.nSqueezeExcitationChannelCountDivisor,
  Stage_Params.nActivationId,
  Stage_Params.bKeepInputTensor,
] );

<commit_msg>InferencedParams_create<commit_after>export { Stage_Params as Params };

import * as Pool from "../../util/Pool.js";
//import * as Recyclable from "../../util/Recyclable.js";
import * as ValueDesc from "../../Unpacker/ValueDesc.js";
import * as ParamDesc from "../../Unpacker/ParamDesc.js";
import * as Weights from "../../Unpacker/Weights.js";
import * as Block from "../Block.js";
import { ParamsBase } from "./Stage_ParamsBase.js";

//!!! ...unfinished... (2022/05/28)
//
// Add parameter bPointwise2SqueezeExcitationAtStageEnd control whether this stage's final block's pointwise2 should have
// squeeze-and-excitation.
//

/**
 * Convolution stage parameters extracted from inputWeightArray.
 *
 */
class Stage_Params extends Weights.Params( ParamsBase ) {

  /**
   * Used as default Stage.Params provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "Stage.Params.Pool", Stage_Params, Stage_Params.setAsConstructor );

  /**
   * If a parameter's value is null, it will be extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {number} sourceHeight
   *   The height of the source image which will be processed by apply(). If null, it will be extracted from
   * inputWeightArray (i.e. by evolution).
   *
   * @param {number} sourceWidth
   *   The width of the source image which will be processed by apply(). If null, it will be extracted from
   * inputWeightArray (i.e. by evolution).
   *
   * @param {number} sourceChannelCount
   *   The depth (channel count) of the source image. It may be the output channel count of the previous convolution stage, so
   * it could be large. If null, it will be extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {number} nConvStageTypeId
   *   The convolution stage type (ValueDesc.ConvStageType.Singleton.Ids.Xxx). If null, it will be extracted from inputWeightArray
   * (i.e. by evolution).
   *

//!!! ...unfinished... (2022/06/25)
// Perhaps, just like the ( height, width ) halving and channel count doubling by evey stage,
// the block count could be doubled by evey stage.

   * @param {number} blockCountRequested
   *   How many blocks inside this stage are wanted.
   *   - If null, it will be extracted from inputWeightArray (i.e. by evolution).
   *   - It must be ( >= 2 ). Because this stage will use one tf.depthwiseConv2d( strides = 2, pad = "same" ) to shrink
   *       (i.e. to halve height x width) and use ( blockCountRequested - 1 ) times tf.depthwiseConv2d( strides = 1, pad = "same" )
   *       until the stage end. These can not be achieved by only one block. So there is at least two blocks.
   *
   * @param {boolean} bPointwise1
   *   If false, there will be no pointwise1 (i.e. the 1st pointwise convolution).If null, it will be extracted from inputWeightArray
   * (i.e. by evolution).
   *
   * @param {number} depthwiseFilterHeight
   *   The height of depthwise convolution's filter. At least 1 (so that 1D data could be processed). If null, it will be extracted
   * from inputWeightArray (i.e. by evolution).
   *
   * @param {number} depthwiseFilterWidth
   *   The width of depthwise convolution's filter. At least 2 (so that meaningless ( 1 * 1 ) could be avoided). If null, it will
   * be extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {boolean} bPointwise2ActivatedAtStageEnd
   *   If true, the blockLast's pointwise2 will have activation function. If false, the blockLast's pointwise2 will have no activation
   * function. If null, it will be extracted from inputWeightArray (i.e. by evolution).
   *
   *   - If there will be next stage after this stage, it usually should be true (i.e. has activation function). The reason is that
   *       this stage's blockLast's pointwise2 (if has no activation function) and the next stage's block0's pointwise1 are essentially
   *       just one pointwise convolution. (Multiple affine transformations can always be combined into just one affine transformation.)
   *
   *     - There is one exception: for MobileNetV2_Xxx, its pointwise2 always has no activation function no matter whether
   *         bPointwise2ActivatedAtStageEnd is true or false. The Reason is that it has add-input-to-output to modify pointwise2
   *         output. So, even if its stage output is not affine transformation (even it has no activation function).
   *
   *   - Only when this stage is the last stage of a neural network, it should be false (i.e. has no activation function). There are
   *       two reasons:
   *
   *     - For ShuffleNetV2_ByMobileNetV1, let it undo activation escaping scales.
   *         In ShuffleNetV2_ByMobileNetV1, if an operation has activation function, the pass-through half part will scale its
   *         convolution filters for escaping the activation function's non-linear parts. This results in its output is wrong
   *         (i.e. different from ShuffleNetV2). In order to resolve this issue, the last operation (i.e. pointwise2) should have
   *         no activation (so it will not scale its convolution filters for escaping the activation function's non-linear parts).
   *
   *     - Even if not ShuffleNetV2_ByMobileNetV1 (i.e. for other ConvStageType), it does have practical advantage in fact. The
   *         output could have any value (i.e. the whole number line). If the last operation (i.e. pointwise2) has activation
   *         function, the output value will be restricted by the activation function (e.g. [ -1, +1 ] for tanh()).
   *
   *
   * @param {number} nSqueezeExcitationChannelCountDivisor
   *   An integer represents the channel count divisor for squeeze-and-excitation's intermediate pointwise convolution channel count.
   * (ValueDesc.SqueezeExcitationChannelCountDivisor.Singleton.Ids.Xx)
   *
   * @param {number} nActivationId
   *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx) after every convolution. If null, it will be
   * extracted from inputWeightArray (i.e. by evolution).
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply() will not dispose inputTensor (i.e. will be kept). If null, it will be extracted from
   * inputWeightArray (i.e. by evolution).
   *
   */
  constructor(
    sourceHeight, sourceWidth, sourceChannelCount,
    nConvStageTypeId,
    blockCountRequested,
    bPointwise1,
    depthwiseFilterHeight, depthwiseFilterWidth,
    bPointwise2ActivatedAtStageEnd,
    nSqueezeExcitationChannelCountDivisor,
    nActivationId,
    bKeepInputTensor
  ) {

    // Q: Why the depthwiseChannelMultiplierBlock0 is not listed as a parameter?
    // A: After considering the following reasons, it is worth to drop this parameter.
    //
    //   - In reality, it is almost no reason to use only avg/max pooling to compose a stage because it keep too little information
    //     for the next stage.
    //
    //   - If depthwiseChannelMultiplierBlock0 is specified as Params.depthwiseChannelMultiplierBlock0.valueDesc.Ids.NONE (0), the input
    //     image will not be shrinked a little (for ( blockCountRequested <= 1 )) or will not be halven (for ( blockCountRequested >= 2 ).
    //     If it is still a parameter it should be forced to 1 at least (always needs depthwise operation) in this case.
    //

    super(
      Stage_Params.SequenceArray,
      sourceHeight, sourceWidth, sourceChannelCount,
      nConvStageTypeId,
      blockCountRequested,
      bPointwise1,
      depthwiseFilterHeight, depthwiseFilterWidth,
      bPointwise2ActivatedAtStageEnd,
      nSqueezeExcitationChannelCountDivisor,
      nActivationId,
      bKeepInputTensor
    );
    Stage_Params.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor(
    sourceHeight, sourceWidth, sourceChannelCount,
    nConvStageTypeId,
    blockCountRequested,
    bPointwise1,
    depthwiseFilterHeight, depthwiseFilterWidth,
    bPointwise2ActivatedAtStageEnd,
    nSqueezeExcitationChannelCountDivisor,
    nActivationId,
    bKeepInputTensor
  ) {
    super.setAsConstructor(
      Stage_Params.SequenceArray,
      sourceHeight, sourceWidth, sourceChannelCount,
      nConvStageTypeId,
      blockCountRequested,
      bPointwise1,
      depthwiseFilterHeight, depthwiseFilterWidth,
      bPointwise2ActivatedAtStageEnd,
      nSqueezeExcitationChannelCountDivisor,
      nActivationId,
      bKeepInputTensor
    );
    Stage_Params.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
    // Do nothing.
  }

  /** @override */
  disposeResources() {
    super.disposeResources();
  }

  /** @override */
  BlockParamsClass_get() {
    return Block.Params;
  }

  /**
   * Extract parameters from inputWeightArray.
   *
   * @return {boolean} Return false, if extraction failed.
   *
   * @override
   */
  init( inputWeightArray, elementOffsetBegin ) {
    let bExtractOk = super.init( inputWeightArray, elementOffsetBegin );
    if ( !bExtractOk )
      return false;

    {
      this.sourceHeight = this.getParamValue_byParamDesc( Stage_Params.sourceHeight );
      this.sourceWidth = this.getParamValue_byParamDesc( Stage_Params.sourceWidth );
      this.sourceChannelCount = this.getParamValue_byParamDesc( Stage_Params.sourceChannelCount );
      this.nConvStageTypeId = this.getParamValue_byParamDesc( Stage_Params.nConvStageTypeId );
      this.blockCountRequested = this.getParamValue_byParamDesc( Stage_Params.blockCountRequested );
      this.bPointwise1 = this.getParamValue_byParamDesc( Stage_Params.bPointwise1 );
      this.depthwiseFilterHeight = this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterHeight );
      this.depthwiseFilterWidth = this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterWidth );
      this.bPointwise2ActivatedAtStageEnd = this.getParamValue_byParamDesc( Stage_Params.bPointwise2ActivatedAtStageEnd );
      this.nSqueezeExcitationChannelCountDivisor = this.getParamValue_byParamDesc( Stage_Params.nSqueezeExcitationChannelCountDivisor );
      this.nActivationId = this.getParamValue_byParamDesc( Stage_Params.nActivationId );
      this.bKeepInputTensor = this.getParamValue_byParamDesc( Stage_Params.bKeepInputTensor );
    }

    // (2022/07/31 Remarked)
    //
    // Stage.Params needs not and can not generate Block.Params by itself. Only
    // Stage.Base.initer() could do that.
    //
    // The reason is Stage.BlockParamsCreator needs input_height and input_width
    // of previous block. And these could only be available from previous Block.Base
    // which should be created by Stage.Base.initer().
    //
    //
    //this.InferencedParams_create();

    return bExtractOk;
  }

//!!! (2022/07/30 Remarked) Use parent class's data properties instead.
  // get sourceHeight()              { return this.getParamValue_byParamDesc( Stage_Params.sourceHeight ); }
  // get sourceWidth()               { return this.getParamValue_byParamDesc( Stage_Params.sourceWidth ); }
  // get sourceChannelCount()        { return this.getParamValue_byParamDesc( Stage_Params.sourceChannelCount ); }

  // /** @return {number} The number version of nConvStageTypeId. */
  // get nConvStageTypeId()          { return this.getParamValue_byParamDesc( Stage_Params.nConvStageTypeId ); }

  // /** @return {string} The string version of nConvStageTypeId. */
  // get nConvStageTypeName()        { return Stage_Params.nConvStageTypeId.getStringOfValue( this.nConvStageTypeId ); }

  // get blockCountRequested()       { return this.getParamValue_byParamDesc( Stage_Params.blockCountRequested ); }
  // get bPointwise1()               { return this.getParamValue_byParamDesc( Stage_Params.bPointwise1 ); }

  // get depthwiseFilterHeight()     { return this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterHeight ); }
  // get depthwiseFilterWidth()      { return this.getParamValue_byParamDesc( Stage_Params.depthwiseFilterWidth ); }

  // get bPointwise2ActivatedAtStageEnd() { return this.getParamValue_byParamDesc( Stage_Params.bPointwise2ActivatedAtStageEnd ); }

  // get nSqueezeExcitationChannelCountDivisor()     { return this.getParamValue_byParamDesc( Stage_Params.nSqueezeExcitationChannelCountDivisor ); }
  // get nSqueezeExcitationChannelCountDivisorName() {
  //   return Stage_Params.nSqueezeExcitationChannelCountDivisor.getStringOfValue( this.nSqueezeExcitationChannelCountDivisor );
  // }

  // get nActivationId()             { return this.getParamValue_byParamDesc( Stage_Params.nActivationId ); }
  // get nActivationName()           { return Stage_Params.nActivationId.getStringOfValue( this.nActivationId ); }

  // get bKeepInputTensor()          { return this.getParamValue_byParamDesc( Stage_Params.bKeepInputTensor ); }
}

// Define parameter descriptions.
Stage_Params.sourceHeight =                   new ParamDesc.Int(                "sourceHeight",               1, ( 10 * 1024 ) );
Stage_Params.sourceWidth =                    new ParamDesc.Int(                "sourceWidth",                1, ( 10 * 1024 ) );
Stage_Params.sourceChannelCount =             new ParamDesc.Int(                "sourceChannelCount",         1, ( 10 * 1024 ) );

Stage_Params.nConvStageTypeId =               new ParamDesc.ConvStageType(      "nConvStageTypeId" );

Stage_Params.blockCountRequested =            new ParamDesc.Int(                "blockCountRequested",        2, (  1 * 1024 ) );
Stage_Params.bPointwise1 =                    new ParamDesc.Bool(               "bPointwise1" );
Stage_Params.depthwiseFilterHeight =          new ParamDesc.Int(                "depthwiseFilterHeight",      1, ( 10 * 1024 ) );
Stage_Params.depthwiseFilterWidth =           new ParamDesc.Int(                "depthwiseFilterWidth",       2, ( 10 * 1024 ) );
Stage_Params.bPointwise2ActivatedAtStageEnd = new ParamDesc.Bool(               "bPointwise2ActivatedAtStageEnd" );

Stage_Params.nSqueezeExcitationChannelCountDivisor = new ParamDesc.SqueezeExcitationChannelCountDivisor( "nSqueezeExcitationChannelCountDivisor" );
//Stage_Params.bSqueezeExcitationPrefix =       new ParamDesc.Bool(               "bSqueezeExcitationPrefix" );

Stage_Params.nActivationId =                  new ParamDesc.ActivationFunction( "nActivationId" );

Stage_Params.bKeepInputTensor =               new ParamDesc.Bool(               "bKeepInputTensor" );

/**
 * Define the order of these parameters. (Fills ParamDesc.Xxx.seqId according to this array's order.)
 */
Stage_Params.SequenceArray = new ParamDesc.SequenceArray( [
  Stage_Params.sourceHeight,
  Stage_Params.sourceWidth,
  Stage_Params.sourceChannelCount,
  Stage_Params.nConvStageTypeId,
  Stage_Params.blockCountRequested,
  Stage_Params.bPointwise1,
  Stage_Params.depthwiseFilterHeight,
  Stage_Params.depthwiseFilterWidth,
  Stage_Params.bPointwise2ActivatedAtStageEnd,
  Stage_Params.nSqueezeExcitationChannelCountDivisor,
  Stage_Params.nActivationId,
  Stage_Params.bKeepInputTensor,
] );

<|endoftext|>


<commit_before>export { testCorrectness };

//import * as RandTools from "../util/RandTools.js";
import * as TensorPlaceholder from "../Conv/TensorPlaceholder.js";
import * as Operation from "../Conv/Operation.js";

/**
 * For testing Operation.Base.
 *
 */
class Case {

  /**
   * @param {number} caseId  The id of this test case.
   */
  constructor( caseId, bInput0, bInput1, outputTensorCount, bKeepInputTensor0, bKeepInputTensor1 ) {
    this.caseId = caseId;

    let input0;
    if ( bInput0 )
      input0 = new TensorPlaceholder.Base();

    let input1;
    if ( bInput1 )
      input1 = new TensorPlaceholder.Base();

    this.operation = new Operation.Base( input0, input1, outputTensorCount );
    this.operation.setKeepInputTensor( bKeepInputTensor0, bKeepInputTensor1 );

    tf.tidy( () => {

      let memoryInfo_apply_before = tf.memory(); // Test memory leakage of .apply.

      let numTensors_delta = 0;
      if ( input0 ) {
        input0.realTensor = tf.randomNormal( Case.testTensorShape );
        ++numTensors_delta;
      }

      if ( input1 ) {
        input1.realTensor = tf.randomNormal( Case.testTensorShape );
        ++numTensors_delta;
      }

      this.operation.apply();

//!!! ...unfinished... (2022/06/02)

      let memoryInfo_apply_after = tf.memory();

      let numTensors_result = ( memoryInfo_apply_before.numTensors + numTensors_delta );
      tf.util.assert( ( memoryInfo_apply_after.numTensors == ( memoryInfo_apply_before.numTensors + numTensors_delta ) ),
        `jsPerf_Operation.Case() memory leak. `
          + `( this.caseId == ${this.caseId} ). `
          + `result tensor count (${memoryInfo_apply_after.numTensors}) `
          + `should be ( ${numTensors_result} ) = ( ${memoryInfo_apply_before.numTensors} + ${numTensors_delta} ).`
       );
    });

//!!! ...unfinished... (2022/06/02)
}

Case.testTensorShape = [ 1 ];

function testCorrectness() {

//!!! ...unfinished... (2022/06/02)
//   let casesArray = [
//     new Case(  0, [  1,  2 ], [  3,  4 ],  5, [  3,  3 ], [  4,  6 ], [  3,  8 ], [   5,  10 ] ),
/
//   ];

  let bInput0, bInput1, outputTensorCount, bKeepInputTensor0, bKeepInputTensor1;
  let caseId = 0;
  for ( let nInput0 = 0; nInput0 <= 1; ++nInput0 ) {
    for ( let nInput1 = 0; nInput1 <= 1; ++nInput1 ) {
      for ( let outputTensorCount = 0; outputTensorCount <= 2; ++outputTensorCount ) {
        for ( let nKeepInputTensor0 = 0; nKeepInputTensor0 <= 1; ++nKeepInputTensor0 ) {
          for ( let nKeepInputTensor1 = 0; nKeepInputTensor1 <= 1; ++nKeepInputTensor1 ) {

            bInput0 = ( nInput0 != 0 );
            bInput1 = ( nInput1 != 0 );
            bKeepInputTensor0 = ( nKeepInputTensor0 != 0 );
            bKeepInputTensor1 = ( nKeepInputTensor1 != 0 );

            let testCase = new Case( caseId, bInput0, bInput1, outputTensorCount, bKeepInputTensor0, bKeepInputTensor1 );

            ++caseId;
          }
        }
      }
    }
  }
}
<commit_msg>Update jsPerf_Operation.js<commit_after>export { testCorrectness };

//import * as RandTools from "../util/RandTools.js";
import * as TensorPlaceholder from "../Conv/TensorPlaceholder.js";
import * as Operation from "../Conv/Operation.js";

/**
 * For testing Operation.Base.
 *
 */
class Case {

  /**
   * @param {number} caseId  The id of this test case.
   */
  constructor( caseId, bInput0, bInput1, outputTensorCount, bKeepInputTensor0, bKeepInputTensor1 ) {
    this.caseId = caseId;

    let input0;
    if ( bInput0 )
      input0 = new TensorPlaceholder.Base();

    let input1;
    if ( bInput1 )
      input1 = new TensorPlaceholder.Base();

    this.operation = new Operation.Base( input0, input1, outputTensorCount );
    this.operation.setKeepInputTensor( bKeepInputTensor0, bKeepInputTensor1 );

    tf.tidy( () => {

      let memoryInfo_apply_before = tf.memory(); // Test memory leakage of .apply.

      let numTensors_delta = 0;
      if ( input0 ) {
        input0.realTensor = tf.randomNormal( Case.testTensorShape );
        ++numTensors_delta;
      }

      if ( input1 ) {
        input1.realTensor = tf.randomNormal( Case.testTensorShape );
        ++numTensors_delta;
      }

      this.operation.apply();

//!!! ...unfinished... (2022/06/02)

      let memoryInfo_apply_after = tf.memory();

      let numTensors_result = ( memoryInfo_apply_before.numTensors + numTensors_delta );
      tf.util.assert( ( memoryInfo_apply_after.numTensors == ( memoryInfo_apply_before.numTensors + numTensors_delta ) ),
        `jsPerf_Operation.Case() memory leak. `
          + `( this.caseId == ${this.caseId} ). `
          + `result tensor count (${memoryInfo_apply_after.numTensors}) `
          + `should be ( ${numTensors_result} ) = ( ${memoryInfo_apply_before.numTensors} + ${numTensors_delta} ).`
       );
    });

//!!! ...unfinished... (2022/06/02)
}

Case.testTensorShape = [ 1 ];

function testCorrectness() {

//!!! ...unfinished... (2022/06/02)
//   let casesArray = [
//     new Case(  0, [  1,  2 ], [  3,  4 ],  5, [  3,  3 ], [  4,  6 ], [  3,  8 ], [   5,  10 ] ),
/
//   ];

  let caseId = -1;
  let bInput0, bInput1, outputTensorCount, bKeepInputTensor0, bKeepInputTensor1;

  for ( let nInput0 = 0; nInput0 <= 1; ++nInput0 ) {
    for ( let nInput1 = 0; nInput1 <= 1; ++nInput1 ) {
      for ( let outputTensorCount = 0; outputTensorCount <= 2; ++outputTensorCount ) {
        for ( let nKeepInputTensor0 = 0; nKeepInputTensor0 <= 1; ++nKeepInputTensor0 ) {
          for ( let nKeepInputTensor1 = 0; nKeepInputTensor1 <= 1; ++nKeepInputTensor1 ) {

            ++caseId;

            bInput0 = ( nInput0 != 0 );
            bInput1 = ( nInput1 != 0 );

            if ( !bInput0 && !bInput1 )
              continue; // Operation should have at least one input.

            bKeepInputTensor0 = ( nKeepInputTensor0 != 0 );
            bKeepInputTensor1 = ( nKeepInputTensor1 != 0 );

            let testCase = new Case( caseId, bInput0, bInput1, outputTensorCount, bKeepInputTensor0, bKeepInputTensor1 );
          }
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>/**
 * A Bot for Slack!
 */

/**
 * Define a function for initiating a conversation on installation
 * With custom integrations, we don't have a way to find out who installed us, so we can't message them :(
 */

function onInstallation(bot, installer) {
    if (installer) {
        bot.startPrivateConversation({user: installer}, function (err, convo) {
            if (err) {
                console.log(err);
            } else {
                convo.say("Hi there, I'm Taylor. I'm a bot that has just joined your team.");
                convo.say('You can now /invite me to a channel so that I can be of use!');
            }
        });
    }
}

// Create a fake http server to bind port
var PORT = (process.env.PORT || 5000)
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Server is running\n');
}).listen(PORT, function(){
    //Callback triggered when server is successfully listening. Hurray!
    console.log("Server listening on: http://localhost:%s", PORT);
});

/**
 * Configure the persistence options
 */

var config = {};
if (process.env.MONGOLAB_URI) {
    var BotkitStorage = require('botkit-storage-mongo');
    config = {
        storage: BotkitStorage({mongoUri: process.env.MONGOLAB_URI}),
    };
} else {
    config = {
        json_file_store: ((process.env.TOKEN)?'./db_taylor_ci/':'./db_slack_bot_a/'), //use a different name if an app or CI
    };
}

/**
 * Are being run as an app or a custom integration? The initialization will differ, depending
 */

if (process.env.TOKEN || process.env.SLACK_TOKEN) {
    //Treat this as a custom integration
    var customIntegration = require('./lib/custom_integrations');
    var token = (process.env.TOKEN) ? process.env.TOKEN : process.env.SLACK_TOKEN;
    var controller = customIntegration.configure(token, config, onInstallation);
} else if (process.env.CLIENT_ID && process.env.CLIENT_SECRET && process.env.PORT) {
    //Treat this as an app
    var app = require('./lib/apps');
    var controller = app.configure(process.env.PORT, process.env.CLIENT_ID, process.env.CLIENT_SECRET, config, onInstallation);
} else {
    console.log('Error: If this is a custom integration, please specify TOKEN in the environment. \
                  If this is an app, please specify CLIENTID, CLIENTSECRET, and PORT in the environment');
    process.exit(1);
}

/**
 * A demonstration for how to handle websocket events. In this case, just log when we have and have not
 * been disconnected from the websocket. In the future, it would be super awesome to be able to specify
 * a reconnect policy, and do reconnections automatically. In the meantime, we aren't going to attempt reconnects,
 * WHICH IS A B0RKED WAY TO HANDLE BEING DISCONNECTED. So we need to fix this.
 *
 * TODO: fixed b0rked reconnect behavior
 */
// Handle events related to the websocket connection to Slack
controller.on('rtm_open', function (bot) {
    console.log('** The RTM api just connected!');
});

controller.on('rtm_close', function (bot) {
    console.log('** The RTM api just closed');
    // you may want to attempt to re-open
});

/**
 * Core bot logic goes here!
 */
// BEGIN EDITING HERE!

controller.on('bot_channel_join', function (bot, message) {
    bot.reply(message, "I'm here!")
});

controller.hears(['hello','hi'],'direct_message,direct_mention,mention',function(bot, message) {

    bot.api.reactions.add({
        timestamp: message.ts,
        channel: message.channel,
        name: 'robot_face',
    },function(err, res) {
        if (err) {
            bot.botkit.log('Failed to add emoji reaction :(',err);
        }
    });

    controller.storage.users.get(message.user,function(err, user) {
        if (user && user.name) {
            bot.reply(message,'Hello ' + user.name + '!!');
        } else {
            bot.reply(message,'Hello.');
        }
    });
});

controller.hears(['call me (.*)'],'direct_message,direct_mention,mention',function(bot, message) {
    var matches = message.text.match(/call me (.*)/i);
    var name = matches[1];
    controller.storage.users.get(message.user,function(err, user) {
        if (!user) {
            user = {
                id: message.user,
            };
        }
        user.name = name;
        controller.storage.users.save(user,function(err, id) {
            bot.reply(message,'Got it. I will call you ' + user.name + ' from now on.');
        });
    });
});

controller.hears(['what is my name','who am i'],'direct_message,direct_mention,mention',function(bot, message) {

    controller.storage.users.get(message.user,function(err, user) {
        if (user && user.name) {
            bot.reply(message,'Your name is ' + user.name);
        } else {
            bot.reply(message,'I don\'t know yet!');
        }
    });
});

controller.hears(['shutdown'],'direct_message,direct_mention,mention',function(bot, message) {

    bot.startConversation(message,function(err, convo) {

        convo.ask('Are you sure you want me to shutdown?',[
            {
                pattern: bot.utterances.yes,
                callback: function(response, convo) {
                    convo.say('Bye!');
                    convo.next();
                    setTimeout(function() {
                        process.exit();
                    },3000);
                }
            },
        {
            pattern: bot.utterances.no,
            default: true,
            callback: function(response, convo) {
                convo.say('*Phew!*');
                convo.next();
            }
        }
        ]);
    });
});

controller.hears(['uptime','identify yourself',
                  'who are you','what is your name'],
                  'direct_message,direct_mention,mention',function(bot, message) {
    var uptime = formatUptime(process.uptime());
    bot.reply(message,':robot_face: I am <@' + bot.identity.name + '>. I have been running for ' + uptime + '.');
});

function formatUptime(uptime) {
    var unit = 'second';
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'minute';
    }
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'hour';
    }
    if (uptime != 1) {
        unit = unit + 's';
    }

    uptime = uptime + ' ' + unit;
    return uptime;
}

/**
API.ai integration for default case.
*/

var apiai = require('apiai');
var apiai_app = apiai(
  process.env.APIAI_ACC, process.env.APIAI_SUB
);

controller.hears(['(.*)'],'direct_message,direct_mention,mention',function(bot, message) {
    var apiai_request = apiai_app.textRequest(message.text);
    console.log(message.text)
    apiai_request.on('response', function(response) {
        console.log(response)
        bot.reply(response);
    });
    apiai_request.on('error', function(error) {
      controller.storage.users.get(message.user,function(err, user) {
          if (user && user.name) {
              bot.reply(message,'Sorry, I don\'t understand that yet ' + user.name +'.');
          } else {
              bot.reply(message,'Sorry, I don\'t understand that yet.');
          }
      });
    });
    apiai_request.end();
});
<commit_msg>Basic api.ai integration<commit_after>/**
 * A Bot for Slack!
 */

/**
 * Define a function for initiating a conversation on installation
 * With custom integrations, we don't have a way to find out who installed us, so we can't message them :(
 */

function onInstallation(bot, installer) {
    if (installer) {
        bot.startPrivateConversation({user: installer}, function (err, convo) {
            if (err) {
                console.log(err);
            } else {
                convo.say("Hi there, I'm Taylor. I'm a bot that has just joined your team.");
                convo.say('You can now /invite me to a channel so that I can be of use!');
            }
        });
    }
}

// Create a fake http server to bind port
var PORT = (process.env.PORT || 5000)
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Server is running\n');
}).listen(PORT, function(){
    //Callback triggered when server is successfully listening. Hurray!
    console.log("Server listening on: http://localhost:%s", PORT);
});

/**
 * Configure the persistence options
 */

var config = {};
if (process.env.MONGOLAB_URI) {
    var BotkitStorage = require('botkit-storage-mongo');
    config = {
        storage: BotkitStorage({mongoUri: process.env.MONGOLAB_URI}),
    };
} else {
    config = {
        json_file_store: ((process.env.TOKEN)?'./db_taylor_ci/':'./db_slack_bot_a/'), //use a different name if an app or CI
    };
}

/**
 * Are being run as an app or a custom integration? The initialization will differ, depending
 */

if (process.env.TOKEN || process.env.SLACK_TOKEN) {
    //Treat this as a custom integration
    var customIntegration = require('./lib/custom_integrations');
    var token = (process.env.TOKEN) ? process.env.TOKEN : process.env.SLACK_TOKEN;
    var controller = customIntegration.configure(token, config, onInstallation);
} else if (process.env.CLIENT_ID && process.env.CLIENT_SECRET && process.env.PORT) {
    //Treat this as an app
    var app = require('./lib/apps');
    var controller = app.configure(process.env.PORT, process.env.CLIENT_ID, process.env.CLIENT_SECRET, config, onInstallation);
} else {
    console.log('Error: If this is a custom integration, please specify TOKEN in the environment. \
                  If this is an app, please specify CLIENTID, CLIENTSECRET, and PORT in the environment');
    process.exit(1);
}

/**
 * A demonstration for how to handle websocket events. In this case, just log when we have and have not
 * been disconnected from the websocket. In the future, it would be super awesome to be able to specify
 * a reconnect policy, and do reconnections automatically. In the meantime, we aren't going to attempt reconnects,
 * WHICH IS A B0RKED WAY TO HANDLE BEING DISCONNECTED. So we need to fix this.
 *
 * TODO: fixed b0rked reconnect behavior
 */
// Handle events related to the websocket connection to Slack
controller.on('rtm_open', function (bot) {
    console.log('** The RTM api just connected!');
});

controller.on('rtm_close', function (bot) {
    console.log('** The RTM api just closed');
    // you may want to attempt to re-open
});

/**
 * Core bot logic goes here!
 */
// BEGIN EDITING HERE!

controller.on('bot_channel_join', function (bot, message) {
    bot.reply(message, "I'm here!")
});

controller.hears(['hello','hi'],'direct_message,direct_mention,mention',function(bot, message) {

    bot.api.reactions.add({
        timestamp: message.ts,
        channel: message.channel,
        name: 'robot_face',
    },function(err, res) {
        if (err) {
            bot.botkit.log('Failed to add emoji reaction :(',err);
        }
    });

    controller.storage.users.get(message.user,function(err, user) {
        if (user && user.name) {
            bot.reply(message,'Hello ' + user.name + '!!');
        } else {
            bot.reply(message,'Hello.');
        }
    });
});

controller.hears(['call me (.*)'],'direct_message,direct_mention,mention',function(bot, message) {
    var matches = message.text.match(/call me (.*)/i);
    var name = matches[1];
    controller.storage.users.get(message.user,function(err, user) {
        if (!user) {
            user = {
                id: message.user,
            };
        }
        user.name = name;
        controller.storage.users.save(user,function(err, id) {
            bot.reply(message,'Got it. I will call you ' + user.name + ' from now on.');
        });
    });
});

controller.hears(['what is my name','who am i'],'direct_message,direct_mention,mention',function(bot, message) {

    controller.storage.users.get(message.user,function(err, user) {
        if (user && user.name) {
            bot.reply(message,'Your name is ' + user.name);
        } else {
            bot.reply(message,'I don\'t know yet!');
        }
    });
});

controller.hears(['shutdown'],'direct_message,direct_mention,mention',function(bot, message) {

    bot.startConversation(message,function(err, convo) {

        convo.ask('Are you sure you want me to shutdown?',[
            {
                pattern: bot.utterances.yes,
                callback: function(response, convo) {
                    convo.say('Bye!');
                    convo.next();
                    setTimeout(function() {
                        process.exit();
                    },3000);
                }
            },
        {
            pattern: bot.utterances.no,
            default: true,
            callback: function(response, convo) {
                convo.say('*Phew!*');
                convo.next();
            }
        }
        ]);
    });
});

controller.hears(['uptime','identify yourself',
                  'who are you','what is your name'],
                  'direct_message,direct_mention,mention',function(bot, message) {
    var uptime = formatUptime(process.uptime());
    bot.reply(message,':robot_face: I am <@' + bot.identity.name + '>. I have been running for ' + uptime + '.');
});

function formatUptime(uptime) {
    var unit = 'second';
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'minute';
    }
    if (uptime > 60) {
        uptime = uptime / 60;
        unit = 'hour';
    }
    if (uptime != 1) {
        unit = unit + 's';
    }

    uptime = uptime + ' ' + unit;
    return uptime;
}

/**
API.ai integration for default case.
*/

var apiai = require('apiai');
var apiai_app = apiai(
  process.env.APIAI_ACC, process.env.APIAI_SUB
);

controller.hears(['(.*)'],'direct_message,direct_mention,mention',function(bot, message) {
    var apiai_request = apiai_app.textRequest(message.text);
    console.log(message.text)
    apiai_request.on('response', function(response) {
        console.log(response.result.fulfillment.speech)
        bot.reply(response.result.fulfillment.speech);
    });
    apiai_request.on('error', function(error) {
      controller.storage.users.get(message.user,function(err, user) {
          if (user && user.name) {
              bot.reply(message,'Sorry, I don\'t understand that yet ' + user.name +'.');
          } else {
              bot.reply(message,'Sorry, I don\'t understand that yet.');
          }
      });
    });
    apiai_request.end();
});
<|endoftext|>


<commit_before>/*

 This file is part of the Sonata package.

 (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>

 For the full copyright and license information, please view the LICENSE
 file that was distributed with this source code.

 */

jQuery(document).ready(function() {
    jQuery('html').removeClass('no-js');
    if (window.SONATA_CONFIG && window.SONATA_CONFIG.CONFIRM_EXIT) {
        jQuery('.sonata-ba-form form').confirmExit();
    }

    Admin.setup_per_page_switcher(document);

    Admin.shared_setup(document);
});

jQuery(document).on('sonata-admin-append-form-element', function(e) {
    Admin.setup_select2(e.target);
    Admin.setup_icheck(e.target);
});

var Admin = {

    /**
     * This function must called when a ajax call is done, to ensure
     * retrieve html is properly setup
     *
     * @param subject
     */
    shared_setup: function(subject) {
        Admin.log("[Admin] apply shared_setup");
        Admin.setup_collection_buttons(subject);
        Admin.set_object_field_value(subject);
        Admin.setup_select2(subject);
        Admin.setup_icheck(subject);
        Admin.add_filters(subject);
        Admin.setup_xeditable(subject);
        Admin.add_pretty_errors(subject);
        Admin.setup_form_tabs_for_errors(subject);
        Admin.setup_inline_form_errors(subject);
//        Admin.setup_list_modal(subject);
    },
    setup_list_modal: function(modal) {
        // this will force relation modal to open list of entity in a wider modal
        // to improve readability
        jQuery('div.modal-dialog', modal).css({
            width:  '90%', //choose your width
            height: '85%',
            padding: 0
        });
        jQuery('div.modal-content', modal).css({
            'border-radius':'0',
            height:   '100%',
            padding: 0
        });
        jQuery('.modal-body', modal).css({
            width:    'auto',
            height:   '90%',
            padding: 5,
            overflow: 'scroll'
        });
    },
    setup_select2: function(subject) {
        if (window.SONATA_CONFIG && window.SONATA_CONFIG.USE_SELECT2 && window.Select2) {

            jQuery('select:not([data-sonata-select2="false"])', subject).each(function() {
                var select = $(this);

                var allowClearEnabled = false;

                if (select.find('option[value=""]').length) {
                    allowClearEnabled = true;
                }

                if (select.attr('data-sonata-select2-allow-clear')==='true') {
                    allowClearEnabled = true;
                } else if (select.attr('data-sonata-select2-allow-clear')==='false') {
                    allowClearEnabled = false;
                }

                select.select2({
                    width: 'resolve',
                    minimumResultsForSearch: 10,
                    allowClear: allowClearEnabled
                });

                var popover = select.data('popover');

                if (undefined !== popover) {
                    select
                        .select2('container')
                        .popover(popover.options)
                    ;
                }
            });
        }
    },
    setup_icheck: function(subject) {
        if (window.SONATA_CONFIG && window.SONATA_CONFIG.USE_ICHECK) {
            jQuery("input[type='checkbox'], input[type='radio']", subject).iCheck({
                checkboxClass: 'icheckbox_minimal',
                radioClass: 'iradio_minimal'
            });
        }
    },

    setup_xeditable: function(subject) {
        jQuery('.x-editable', subject).editable({
            emptyclass: 'editable-empty btn btn-sm',
            emptytext: '',
            success: function(response) {
                if('KO' === response.status) {
                    return response.message;
                }

                var html = jQuery(response.content);
                Admin.setup_xeditable(html);

                jQuery(this)
                    .closest('td')
                    .replaceWith(html)
                ;
            }
        });
    },

    /**
     * render log message
     * @param mixed
     */
    log: function() {
        var msg = '[Sonata.Admin] ' + Array.prototype.join.call(arguments,', ');
        if (window.console && window.console.log) {
            window.console.log(msg);
        } else if (window.opera && window.opera.postError) {
            window.opera.postError(msg);
        }
    },

    /**
     * display related errors messages
     *
     * @param subject
     */
    add_pretty_errors: function(subject) {
        jQuery('div.sonata-ba-field-error', subject).each(function(index, element) {
            var input = jQuery(':input', element);

            if (!input.length) {
                return;
            }

            var message = jQuery('div.sonata-ba-field-error-messages', element).html();
            jQuery('div.sonata-ba-field-error-messages', element).remove();

            if (!message || message.length == 0) {
                return;
            }

            var target = input,
                fieldShortDescription = input.closest('.field-container').find('.field-short-description'),
                select2 = input.closest('.select2-container')
                ;

            if (fieldShortDescription.length) {
                target = fieldShortDescription;
            } else if (select2.length) {
                target= select2;
            }

            target.popover({
                content: message,
                trigger: 'hover',
                html: true,
                placement: 'top',
                template: '

'
            });

        });
    },

    stopEvent: function(event) {
        // https://github.com/sonata-project/SonataAdminBundle/issues/151
        //if it is a standard browser use preventDefault otherwise it is IE then return false
        if(event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }

        //if it is a standard browser get target otherwise it is IE then adapt syntax and get target
        if (typeof event.target != 'undefined') {
            targetElement = event.target;
        } else {
            targetElement = event.srcElement;
        }

        return targetElement;
    },

    add_filters: function(subject) {
        jQuery('div.filter_container .sonata-filter-option', subject).hide();
        jQuery('h4.filter_legend', subject).click(function(event) {
            jQuery('div.filter_container .sonata-filter-option').toggle();
        });
    },

    /**
     * Change object field value
     * @param subject
     */
    set_object_field_value: function(subject) {

        this.log(jQuery('a.sonata-ba-edit-inline', subject));
        jQuery('a.sonata-ba-edit-inline', subject).click(function(event) {
            Admin.stopEvent(event);

            var subject = jQuery(this);
            jQuery.ajax({
                url: subject.attr('href'),
                type: 'POST',
                success: function(json) {
                    if(json.status === "OK") {
                        var elm = jQuery(subject).parent();
                        elm.children().remove();
                        // fix issue with html comment ...
                        elm.html(jQuery(json.content.replace(//g, "")).html());
                        elm.effect("highlight", {'color' : '#57A957'}, 2000);
                        Admin.set_object_field_value(elm);
                    } else {
                        jQuery(subject).parent().effect("highlight", {'color' : '#C43C35'}, 2000);
                    }
                }
            });
        });
    },

    setup_collection_buttons: function(subject) {

        jQuery(subject).on('click', '.sonata-collection-add', function(event) {
            Admin.stopEvent(event);

            var container = jQuery(this).closest('[data-prototype]');
            var proto = container.attr('data-prototype');
            var protoName = container.attr('data-prototype-name') || '__name__';
            // Set field id
            var idRegexp = new RegExp(container.attr('id')+'_'+protoName,'g');
            proto = proto.replace(idRegexp, container.attr('id')+'_'+(container.children().length - 1));

            // Set field name
            var parts = container.attr('id').split('_');
            var nameRegexp = new RegExp(parts[parts.length-1]+'\\]\\['+protoName,'g');
            proto = proto.replace(nameRegexp, parts[parts.length-1]+']['+(container.children().length - 1));
            jQuery(proto)
                .insertBefore(jQuery(this).parent())
                .trigger('sonata-admin-append-form-element')
            ;

            jQuery(this).trigger('sonata-collection-item-added');
        });

        jQuery(subject).on('click', '.sonata-collection-delete', function(event) {
            Admin.stopEvent(event);

            jQuery(this).trigger('sonata-collection-item-deleted');

            jQuery(this).closest('.sonata-collection-row').remove();
        });
    },

    setup_per_page_switcher: function(subject) {
        jQuery('select.per-page').change(function(event) {
            jQuery('input[type=submit]').hide();

            window.top.location.href=this.options[this.selectedIndex].value;
        });
    },

    setup_form_tabs_for_errors: function(subject) {
        // Switch to first tab with server side validation errors on page load
        jQuery('form', subject).each(function() {
            Admin.show_form_first_tab_with_errors(jQuery(this), '.sonata-ba-field-error');
        });

        // Switch to first tab with HTML5 errors on form submit
        jQuery(subject)
            .on('click', 'form [type="submit"]', function() {
                Admin.show_form_first_tab_with_errors(jQuery(this).closest('form'), ':invalid');
            })
            .on('keypress', 'form [type="text"]', function(e) {
                if (13 === e.which) {
                    Admin.show_form_first_tab_with_errors(jQuery(this), ':invalid');
                }
            })
        ;
    },

    show_form_first_tab_with_errors: function(form, errorSelector) {
        var tabs = form.find('.nav-tabs a'),
            firstTabWithErrors;

        tabs.each(function() {
            var id = jQuery(this).attr('href'),
                tab = jQuery(this),
                icon = tab.find('.has-errors');

            if (jQuery(id).find(errorSelector).length > 0) {
                // Only show first tab with errors
                if (!firstTabWithErrors) {
                    tab.tab('show');
                    firstTabWithErrors = tab;
                }

                icon.removeClass('hide');
            } else {
                icon.addClass('hide');
            }
        });
    },

    setup_inline_form_errors: function(subject) {
        var deleteCheckboxSelector = '.sonata-ba-field-inline-table [id$="_delete"][type="checkbox"]';

        jQuery(deleteCheckboxSelector, subject).each(function() {
            Admin.switch_inline_form_errors(jQuery(this));
        });

        $(subject).on('change', deleteCheckboxSelector, function() {
            Admin.switch_inline_form_errors(jQuery(this));
        });
    },

    /**
     * Disable inline form errors when the row is marked for deletion
     */
    switch_inline_form_errors: function(deleteCheckbox) {
        var row = deleteCheckbox.closest('.sonata-ba-field-inline-table'),
            errors = row.find('.sonata-ba-field-error-messages')
        ;

        if (deleteCheckbox.is(':checked')) {
            row
                .find('[required]')
                .removeAttr('required')
                .attr('data-required', 'required')
            ;

            errors.hide();
        } else {
            row
                .find('[data-required]')
                .attr('required', 'required')
            ;

            errors.show();
        }
    }
};
<commit_msg>confirm exit did not work correctly for multiple forms on one page because the serialized data are generated over an collection and not over the single forms<commit_after>/*

 This file is part of the Sonata package.

 (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>

 For the full copyright and license information, please view the LICENSE
 file that was distributed with this source code.

 */

jQuery(document).ready(function() {
    jQuery('html').removeClass('no-js');
    if (window.SONATA_CONFIG && window.SONATA_CONFIG.CONFIRM_EXIT) {
        jQuery('.sonata-ba-form form').each( function () { $(this).confirmExit(); } );
    }

    Admin.setup_per_page_switcher(document);

    Admin.shared_setup(document);
});

jQuery(document).on('sonata-admin-append-form-element', function(e) {
    Admin.setup_select2(e.target);
    Admin.setup_icheck(e.target);
});

var Admin = {

    /**
     * This function must called when a ajax call is done, to ensure
     * retrieve html is properly setup
     *
     * @param subject
     */
    shared_setup: function(subject) {
        Admin.log("[Admin] apply shared_setup");
        Admin.setup_collection_buttons(subject);
        Admin.set_object_field_value(subject);
        Admin.setup_select2(subject);
        Admin.setup_icheck(subject);
        Admin.add_filters(subject);
        Admin.setup_xeditable(subject);
        Admin.add_pretty_errors(subject);
        Admin.setup_form_tabs_for_errors(subject);
        Admin.setup_inline_form_errors(subject);
//        Admin.setup_list_modal(subject);
    },
    setup_list_modal: function(modal) {
        // this will force relation modal to open list of entity in a wider modal
        // to improve readability
        jQuery('div.modal-dialog', modal).css({
            width:  '90%', //choose your width
            height: '85%',
            padding: 0
        });
        jQuery('div.modal-content', modal).css({
            'border-radius':'0',
            height:   '100%',
            padding: 0
        });
        jQuery('.modal-body', modal).css({
            width:    'auto',
            height:   '90%',
            padding: 5,
            overflow: 'scroll'
        });
    },
    setup_select2: function(subject) {
        if (window.SONATA_CONFIG && window.SONATA_CONFIG.USE_SELECT2 && window.Select2) {

            jQuery('select:not([data-sonata-select2="false"])', subject).each(function() {
                var select = $(this);

                var allowClearEnabled = false;

                if (select.find('option[value=""]').length) {
                    allowClearEnabled = true;
                }

                if (select.attr('data-sonata-select2-allow-clear')==='true') {
                    allowClearEnabled = true;
                } else if (select.attr('data-sonata-select2-allow-clear')==='false') {
                    allowClearEnabled = false;
                }

                select.select2({
                    width: 'resolve',
                    minimumResultsForSearch: 10,
                    allowClear: allowClearEnabled
                });

                var popover = select.data('popover');

                if (undefined !== popover) {
                    select
                        .select2('container')
                        .popover(popover.options)
                    ;
                }
            });
        }
    },
    setup_icheck: function(subject) {
        if (window.SONATA_CONFIG && window.SONATA_CONFIG.USE_ICHECK) {
            jQuery("input[type='checkbox'], input[type='radio']", subject).iCheck({
                checkboxClass: 'icheckbox_minimal',
                radioClass: 'iradio_minimal'
            });
        }
    },

    setup_xeditable: function(subject) {
        jQuery('.x-editable', subject).editable({
            emptyclass: 'editable-empty btn btn-sm',
            emptytext: '',
            success: function(response) {
                if('KO' === response.status) {
                    return response.message;
                }

                var html = jQuery(response.content);
                Admin.setup_xeditable(html);

                jQuery(this)
                    .closest('td')
                    .replaceWith(html)
                ;
            }
        });
    },

    /**
     * render log message
     * @param mixed
     */
    log: function() {
        var msg = '[Sonata.Admin] ' + Array.prototype.join.call(arguments,', ');
        if (window.console && window.console.log) {
            window.console.log(msg);
        } else if (window.opera && window.opera.postError) {
            window.opera.postError(msg);
        }
    },

    /**
     * display related errors messages
     *
     * @param subject
     */
    add_pretty_errors: function(subject) {
        jQuery('div.sonata-ba-field-error', subject).each(function(index, element) {
            var input = jQuery(':input', element);

            if (!input.length) {
                return;
            }

            var message = jQuery('div.sonata-ba-field-error-messages', element).html();
            jQuery('div.sonata-ba-field-error-messages', element).remove();

            if (!message || message.length == 0) {
                return;
            }

            var target = input,
                fieldShortDescription = input.closest('.field-container').find('.field-short-description'),
                select2 = input.closest('.select2-container')
                ;

            if (fieldShortDescription.length) {
                target = fieldShortDescription;
            } else if (select2.length) {
                target= select2;
            }

            target.popover({
                content: message,
                trigger: 'hover',
                html: true,
                placement: 'top',
                template: '

'
            });

        });
    },

    stopEvent: function(event) {
        // https://github.com/sonata-project/SonataAdminBundle/issues/151
        //if it is a standard browser use preventDefault otherwise it is IE then return false
        if(event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }

        //if it is a standard browser get target otherwise it is IE then adapt syntax and get target
        if (typeof event.target != 'undefined') {
            targetElement = event.target;
        } else {
            targetElement = event.srcElement;
        }

        return targetElement;
    },

    add_filters: function(subject) {
        jQuery('div.filter_container .sonata-filter-option', subject).hide();
        jQuery('h4.filter_legend', subject).click(function(event) {
            jQuery('div.filter_container .sonata-filter-option').toggle();
        });
    },

    /**
     * Change object field value
     * @param subject
     */
    set_object_field_value: function(subject) {

        this.log(jQuery('a.sonata-ba-edit-inline', subject));
        jQuery('a.sonata-ba-edit-inline', subject).click(function(event) {
            Admin.stopEvent(event);

            var subject = jQuery(this);
            jQuery.ajax({
                url: subject.attr('href'),
                type: 'POST',
                success: function(json) {
                    if(json.status === "OK") {
                        var elm = jQuery(subject).parent();
                        elm.children().remove();
                        // fix issue with html comment ...
                        elm.html(jQuery(json.content.replace(//g, "")).html());
                        elm.effect("highlight", {'color' : '#57A957'}, 2000);
                        Admin.set_object_field_value(elm);
                    } else {
                        jQuery(subject).parent().effect("highlight", {'color' : '#C43C35'}, 2000);
                    }
                }
            });
        });
    },

    setup_collection_buttons: function(subject) {

        jQuery(subject).on('click', '.sonata-collection-add', function(event) {
            Admin.stopEvent(event);

            var container = jQuery(this).closest('[data-prototype]');
            var proto = container.attr('data-prototype');
            var protoName = container.attr('data-prototype-name') || '__name__';
            // Set field id
            var idRegexp = new RegExp(container.attr('id')+'_'+protoName,'g');
            proto = proto.replace(idRegexp, container.attr('id')+'_'+(container.children().length - 1));

            // Set field name
            var parts = container.attr('id').split('_');
            var nameRegexp = new RegExp(parts[parts.length-1]+'\\]\\['+protoName,'g');
            proto = proto.replace(nameRegexp, parts[parts.length-1]+']['+(container.children().length - 1));
            jQuery(proto)
                .insertBefore(jQuery(this).parent())
                .trigger('sonata-admin-append-form-element')
            ;

            jQuery(this).trigger('sonata-collection-item-added');
        });

        jQuery(subject).on('click', '.sonata-collection-delete', function(event) {
            Admin.stopEvent(event);

            jQuery(this).trigger('sonata-collection-item-deleted');

            jQuery(this).closest('.sonata-collection-row').remove();
        });
    },

    setup_per_page_switcher: function(subject) {
        jQuery('select.per-page').change(function(event) {
            jQuery('input[type=submit]').hide();

            window.top.location.href=this.options[this.selectedIndex].value;
        });
    },

    setup_form_tabs_for_errors: function(subject) {
        // Switch to first tab with server side validation errors on page load
        jQuery('form', subject).each(function() {
            Admin.show_form_first_tab_with_errors(jQuery(this), '.sonata-ba-field-error');
        });

        // Switch to first tab with HTML5 errors on form submit
        jQuery(subject)
            .on('click', 'form [type="submit"]', function() {
                Admin.show_form_first_tab_with_errors(jQuery(this).closest('form'), ':invalid');
            })
            .on('keypress', 'form [type="text"]', function(e) {
                if (13 === e.which) {
                    Admin.show_form_first_tab_with_errors(jQuery(this), ':invalid');
                }
            })
        ;
    },

    show_form_first_tab_with_errors: function(form, errorSelector) {
        var tabs = form.find('.nav-tabs a'),
            firstTabWithErrors;

        tabs.each(function() {
            var id = jQuery(this).attr('href'),
                tab = jQuery(this),
                icon = tab.find('.has-errors');

            if (jQuery(id).find(errorSelector).length > 0) {
                // Only show first tab with errors
                if (!firstTabWithErrors) {
                    tab.tab('show');
                    firstTabWithErrors = tab;
                }

                icon.removeClass('hide');
            } else {
                icon.addClass('hide');
            }
        });
    },

    setup_inline_form_errors: function(subject) {
        var deleteCheckboxSelector = '.sonata-ba-field-inline-table [id$="_delete"][type="checkbox"]';

        jQuery(deleteCheckboxSelector, subject).each(function() {
            Admin.switch_inline_form_errors(jQuery(this));
        });

        $(subject).on('change', deleteCheckboxSelector, function() {
            Admin.switch_inline_form_errors(jQuery(this));
        });
    },

    /**
     * Disable inline form errors when the row is marked for deletion
     */
    switch_inline_form_errors: function(deleteCheckbox) {
        var row = deleteCheckbox.closest('.sonata-ba-field-inline-table'),
            errors = row.find('.sonata-ba-field-error-messages')
        ;

        if (deleteCheckbox.is(':checked')) {
            row
                .find('[required]')
                .removeAttr('required')
                .attr('data-required', 'required')
            ;

            errors.hide();
        } else {
            row
                .find('[data-required]')
                .attr('required', 'required')
            ;

            errors.show();
        }
    }
};
<|endoftext|>


<commit_before>
var os = require('os');
var net = require('net');
var dns = require('dns');
var util = require('util');
var mdns = require('mdns');
var isme = require('isme');
var gmid = require('gmid');
var async = require('async');
var events = require('events');

var IS_HEX_STRING = /^[0123456789ABCDEF]+$/;

function compareHex(hexA, hexB) {
  for (var i = 0, l = hexA.length; i < l; i++) {
    // if hex digits are the same nothing can be concluded
    if (hexA[i] === hexB[i]) continue;

    // At this point the hex digits arn't the same, compare the size
    // and return true if hexA was greater than hexB
    return (parseInt(hexA[i], 16) > parseInt(hexB[i], 16));
  }

  // If they where totally equal, something went wrong and
  // it should be ignored, thats a `return true`.
  return true;
}

function isObject(value) {
  return (typeof value === 'object' && value !== null);
}

function Service(settings, connectionHandler) {
  if (!(this instanceof Service)) {
    return new Service(settings, connectionHandler);
  }

  var self = this;

  // Check that settings is of correct type
  if (typeof settings !== 'string' && !isObject(settings)) {
    throw new TypeError('first argument must be a string or an object');
  }

  // Create settings object
  if (typeof settings === 'string') {
    settings = {
      name: settings
    };
  }
  if (settings.hasOwnProperty('uuid') === false) {
    settings.uuid = gmid();
  }

  // Check that the service name property is of correct type
  if (typeof settings.name !== 'string') {
    throw new TypeError('service name must be a string type');
  }

  // Check that the service uuid property is of correct type
  if (typeof settings.uuid !== 'string') {
    throw new TypeError('service uuid must be a string type');
  }
  settings.uuid = settings.uuid.toUpperCase();

  // Check that service uuid is a hex string
  if (IS_HEX_STRING.test(settings.uuid) === false) {
    throw new TypeError('service uuid must be a hex string');
  }

  // helps handling fast close
  this._closed = false;

  // Collection of online sockets
  this.connections = [];

  // Add connection handler if given
  if (connectionHandler) this.on('connection', connectionHandler);

  // Contains unique ID and a service key (given by service name)
  this._uuid = settings.uuid;
  this._key = mdns.tcp(settings.name);

  // Collection of services
  this._services = [];
  this._serviceBuffer = [];

  // Keeps addresses and port
  this._address = {
    addresses: null,
    port: null
  };

  // do the service listen on localhost
  this._internalAllowed = false;

  this._relayError = function (err) {
    self.emit('error', err);
  };

  // Keeps main objects, unfortunately the announce object
  // can't be created before the service server is online
  this._server = net.createServer();
  this._server.on('error', this._relayError);

  this._discover = null;
  this._announce = null;
}
util.inherits(Service, events.EventEmitter);
module.exports = Service;

Service.prototype.listen = function () {
  var self = this;
  var args = Array.prototype.slice.call(arguments);

  var port, address;

  // Extract callback
  if (typeof args[args.length - 1] === 'function') {
    this.once('listening', args.pop());
  }

  // Extract address
  if (args.length === 2) {
    address = args.pop() || "0.0.0.0";
  } else {
    address = "0.0.0.0";
  }
  if (typeof address !== 'string') {
    throw new TypeError('address must be a string');
  }

  // Extract port
  port = args.pop() || 0;
  if (typeof port !== 'number') {
    throw new TypeError('port must be a number');
  }

  // Transform hostname/address to an IP address
  dns.lookup(address, function (err, address) {
    if (self._closed) return;
    if (err) return self.emit('error', err);

    // Start service server
    self._server.listen(port, address);

    // Server is online
    self._server.once('listening', function () {
      if (self._closed) return;

      self._address.port = self._server.address().port;

      // Start announceing and discovering
      self._startService(address);
      self._discover.on('serviceUp', offline);
    });

    // Got connection start handshake
    self._server.on('connection', function (socket) {
      // Relay errors to the service object, when initializing is done the
      // error handler is removed.
      socket.on('error', self._relayError);

      self._addSocket(socket);
    });
  });

  // Used when this service is online but an announcement was made
  function offline(service) {

    // Use self announcement to catch broadcasted public ip
    if (self._selfAnnouncement(service)) {
      self._address.addresses = self._getAddresses(service);

      // Switch to online mode
      self._discover.removeListener('serviceUp', offline);
      self._discover.on('serviceUp', online);

      // Emit listening
      self.emit('listening');

      // Connect to buffered services
      self._serviceBuffer.forEach(self._createConnection.bind(self));
      self._serviceBuffer = [];

      return;
    }

    // When offline, then store service in buffer
    self._serviceBuffer.push(service);
  }

  // Used when this service is online
  function online(service) {
    // Skip self announcement
    if (self._selfAnnouncement(service)) return;

    self._createConnection(service);
  }
};

Service.prototype._createConnection = function (service) {
  var self = this;

  // Create remote connection object
  var remote = service.name;

  // The service worker with the highest number gets the pleasure of
  // initiating the TPC connection.
  if (compareHex(this._uuid, remote)) return;

  // Don't allow multiply connection to same service worker
  if (this._services.indexOf(remote) !== -1) return;
  this._services.push(remote);

  // Connect to remote and start handshake
  var addresses = this._getAddresses(service);
  var socket = net.connect({ port: service.port, host: addresses[0] });

  // Relay errors to the service object, when initializing is done the
  // error handler is removed.
  socket.on('error', this._relayError);

  // remove the uuid safe guard once the socket is closed
  socket.once('close', function () {
    var index = self._services.indexOf(remote);
    if (index === -1) return;
    self._services.splice(index, 1);
  });

  socket.once('connect', function () {
    self._addSocket(socket);
  });
};

Service.prototype._addSocket = function (socket) {
  var self = this;

  // Add socket to connections list now
  this.connections.push(socket);

  // Remove socket from connection list once closed
  socket.once('close', function () {
    self._removeSocket(socket);
  });

  // Remove initializing error handler, its up to the user to
  // handle errors now.
  socket.removeListener('error', self._relayError);

  // Done, emit connection event
  this.emit('connection', socket);
};

// Remove socket from connections list
Service.prototype._removeSocket = function (socket) {
    var index = this.connections.indexOf(socket);
    if (index === -1) return;

    this.connections.splice(index, 1);
};

// returns an object containing sorted internal IPv4 and IPv6 addresses
function getInternalAddresses() {
  var result = {
    IPv4: [],
    IPv6: []
  };

  var interfaces = os.networkInterfaces();
  for (var name in interfaces) {
    if (interfaces.hasOwnProperty(name) === false) continue;

    var addresses = interfaces[name];
    for (var i = 0; i < addresses.length; i++) {
      if (addresses[i].internal === false) continue;

      result[ addresses[i].family ].push(addresses[i].address);
    }
  }

  return result;
}

Service.prototype._getAddresses = function (service) {
  var addresses = {
    IPv4: service.addresses.filter(net.isIPv4.bind(net)),
    IPv6: service.addresses.filter(net.isIPv6.bind(net))
  };

  // Add localhost addresses as a connection optimization and
  // a necessity when listening only on localhost
  if (this._internalAllowed &&
     (service.addresses.length === 0 || service.addresses.some(isme))) {

    var internals = getInternalAddresses();
    addresses.IPv4 = [].concat(addresses.IPv4, internals.IPv4);
    addresses.IPv6 = [].concat(addresses.IPv6, internals.IPv6);
  }

  // the addresses is sorted in IPv4 first and IPv6 first,
  // if localhost is allowed, it will be the first item in
  // the list.
  return [].concat(addresses.IPv4, addresses.IPv6);
};

Service.prototype._selfAnnouncement = function (service) {
  return (service.name === this._uuid);
};

Service.prototype._startService = function (address) {
  // TODO: swtch this out with a C call to if_nametoindex once the mdns bug
  // is fixed.
  // ref: https://github.com/agnat/node_mdns/issues/55
  var index;
  if (isme(address, 'any')) {
    this._internalAllowed = true;
    index = 0;
  } else if (isme(address, 'local')) {
    this._internalAllowed = true;
    index = -1;
  } else {
    this.emit('error',
      new Error('specfic none internal addresses are not yet supported' +
                ' try 0.0.0.0 insted'));
    return;
  }

  this._discover = mdns.createBrowser(this._key, {
    interfaceIndex: index
  });

  this._discover.on('error', this._relayError);

  this._discover.start();

  this._announce = mdns.createAdvertisement(
    this._key, this._address.port, {
      // workaround for some wird bug
      // ref: https://github.com/agnat/node_mdns/issues/51
      name: this._uuid,

      interfaceIndex: index
    }
  );

  this._announce.on('error', this._relayError);

  this._announce.start();
};

// Stop announceing and discovering
Service.prototype._stopService = function () {
  if (this._announce) this._announce.stop();
  if (this._discover) this._discover.stop();
};

Service.prototype.address = function () {
  // addresses is the last property there will be set
  if (this._address.addresses === null) {
    return null;
  }

  return this._address;
};

Service.prototype.close = function (callback) {
  var self = this;

  this._closed = true;

  // reset address info
  this._address = {
    port: null,
    addresses: null
  };

  // Callback is just a close event handler
  if (callback) this.once('close', callback),

  // Stop announceing and discovering
  this._stopService();

  async.parallel([

    // Close all sockets
    function (done) {
      async.forEach(self.connections.slice(0), function(socket, eachDone) {
        socket.end();
        socket.once('close', function () { eachDone(null); });
      }, done);
    },

    // Close connection server, note the server.close callback
    // won't be executed before all connected sockets are closed.
    function (done) {
      if (self._server.address() === null) return done(null);
      self._server.close(done);
    }
  ], function () {
    self.emit('close');
  });
};
<commit_msg>[feature] support all ip addresses<commit_after>
var os = require('os');
var net = require('net');
var dns = require('dns');
var util = require('util');
var mdns = require('mdns');
var isme = require('isme');
var gmid = require('gmid');
var async = require('async');
var events = require('events');
var ifname = require('interface-name');

var IS_HEX_STRING = /^[0123456789ABCDEF]+$/;
var LOOPBACK_NAME = ifname('127.0.0.1');

function compareHex(hexA, hexB) {
  for (var i = 0, l = hexA.length; i < l; i++) {
    // if hex digits are the same nothing can be concluded
    if (hexA[i] === hexB[i]) continue;

    // At this point the hex digits arn't the same, compare the size
    // and return true if hexA was greater than hexB
    return (parseInt(hexA[i], 16) > parseInt(hexB[i], 16));
  }

  // If they where totally equal, something went wrong and
  // it should be ignored, thats a `return true`.
  return true;
}

function isObject(value) {
  return (typeof value === 'object' && value !== null);
}

function Service(settings, connectionHandler) {
  if (!(this instanceof Service)) {
    return new Service(settings, connectionHandler);
  }

  var self = this;

  // Check that settings is of correct type
  if (typeof settings !== 'string' && !isObject(settings)) {
    throw new TypeError('first argument must be a string or an object');
  }

  // Create settings object
  if (typeof settings === 'string') {
    settings = {
      name: settings
    };
  }
  if (settings.hasOwnProperty('uuid') === false) {
    settings.uuid = gmid();
  }

  // Check that the service name property is of correct type
  if (typeof settings.name !== 'string') {
    throw new TypeError('service name must be a string type');
  }

  // Check that the service uuid property is of correct type
  if (typeof settings.uuid !== 'string') {
    throw new TypeError('service uuid must be a string type');
  }
  settings.uuid = settings.uuid.toUpperCase();

  // Check that service uuid is a hex string
  if (IS_HEX_STRING.test(settings.uuid) === false) {
    throw new TypeError('service uuid must be a hex string');
  }

  // helps handling fast close
  this._closed = false;

  // Collection of online sockets
  this.connections = [];

  // Add connection handler if given
  if (connectionHandler) this.on('connection', connectionHandler);

  // Contains unique ID and a service key (given by service name)
  this._uuid = settings.uuid;
  this._key = mdns.tcp(settings.name);

  // Collection of services
  this._services = [];
  this._serviceBuffer = [];

  // Keeps addresses and port
  this._address = {
    addresses: null,
    port: null
  };

  // do the service listen on localhost
  this._internalAllowed = false;

  this._relayError = function (err) {
    self.emit('error', err);
  };

  // Keeps main objects, unfortunately the announce object
  // can't be created before the service server is online
  this._server = net.createServer();
  this._server.on('error', this._relayError);

  this._discover = null;
  this._announce = null;
}
util.inherits(Service, events.EventEmitter);
module.exports = Service;

Service.prototype.listen = function () {
  var self = this;
  var args = Array.prototype.slice.call(arguments);

  var port, address;

  // Extract callback
  if (typeof args[args.length - 1] === 'function') {
    this.once('listening', args.pop());
  }

  // Extract address
  if (args.length === 2) {
    address = args.pop() || "0.0.0.0";
  } else {
    address = "0.0.0.0";
  }
  if (typeof address !== 'string') {
    throw new TypeError('address must be a string');
  }

  // Extract port
  port = args.pop() || 0;
  if (typeof port !== 'number') {
    throw new TypeError('port must be a number');
  }

  // Transform hostname/address to an IP address
  dns.lookup(address, function (err, address) {
    if (self._closed) return;
    if (err) return self.emit('error', err);

    // Start service server
    self._server.listen(port, address);

    // Server is online
    self._server.once('listening', function () {
      if (self._closed) return;

      self._address.port = self._server.address().port;

      // Start announceing and discovering
      self._startService(address);
      self._discover.on('serviceUp', offline);
    });

    // Got connection start handshake
    self._server.on('connection', function (socket) {
      // Relay errors to the service object, when initializing is done the
      // error handler is removed.
      socket.on('error', self._relayError);

      self._addSocket(socket);
    });
  });

  // Used when this service is online but an announcement was made
  function offline(service) {

    // Use self announcement to catch broadcasted public ip
    if (self._selfAnnouncement(service)) {
      self._address.addresses = self._getAddresses(service);

      // Switch to online mode
      self._discover.removeListener('serviceUp', offline);
      self._discover.on('serviceUp', online);

      // Emit listening
      self.emit('listening');

      // Connect to buffered services
      self._serviceBuffer.forEach(self._createConnection.bind(self));
      self._serviceBuffer = [];

      return;
    }

    // When offline, then store service in buffer
    self._serviceBuffer.push(service);
  }

  // Used when this service is online
  function online(service) {
    // Skip self announcement
    if (self._selfAnnouncement(service)) return;

    self._createConnection(service);
  }
};

Service.prototype._createConnection = function (service) {
  var self = this;

  // Create remote connection object
  var remote = service.name;

  // The service worker with the highest number gets the pleasure of
  // initiating the TPC connection.
  if (compareHex(this._uuid, remote)) return;

  // Don't allow multiply connection to same service worker
  if (this._services.indexOf(remote) !== -1) return;
  this._services.push(remote);

  // Connect to remote and start handshake
  var addresses = this._getAddresses(service);
  var socket = net.connect({ port: service.port, host: addresses[0] });

  // Relay errors to the service object, when initializing is done the
  // error handler is removed.
  socket.on('error', this._relayError);

  // remove the uuid safe guard once the socket is closed
  socket.once('close', function () {
    var index = self._services.indexOf(remote);
    if (index === -1) return;
    self._services.splice(index, 1);
  });

  socket.once('connect', function () {
    self._addSocket(socket);
  });
};

Service.prototype._addSocket = function (socket) {
  var self = this;

  // Add socket to connections list now
  this.connections.push(socket);

  // Remove socket from connection list once closed
  socket.once('close', function () {
    self._removeSocket(socket);
  });

  // Remove initializing error handler, its up to the user to
  // handle errors now.
  socket.removeListener('error', self._relayError);

  // Done, emit connection event
  this.emit('connection', socket);
};

// Remove socket from connections list
Service.prototype._removeSocket = function (socket) {
    var index = this.connections.indexOf(socket);
    if (index === -1) return;

    this.connections.splice(index, 1);
};

// returns an object containing sorted internal IPv4 and IPv6 addresses
function getLoopbackAddresses() {
  var result = {
    IPv4: [],
    IPv6: []
  };

  var interfaces = os.networkInterfaces();

  var addresses = interfaces[LOOPBACK_NAME];
  for (var i = 0; i < addresses.length; i++) {
    result[ addresses[i].family ].push(addresses[i].address);
  }

  return result;
}

Service.prototype._getAddresses = function (service) {
  var addresses = {
    IPv4: service.addresses.filter(net.isIPv4.bind(net)),
    IPv6: service.addresses.filter(net.isIPv6.bind(net))
  };

  // Add localhost addresses as a connection optimization and
  // a necessity when listening only on localhost
  if (this._loopbackAllowed &&
     (service.addresses.length === 0 || service.addresses.some(isme))) {

    var loopback = getLoopbackAddresses();
    addresses.IPv4 = [].concat(loopback.IPv4, addresses.IPv4);
    addresses.IPv6 = [].concat(loopback.IPv6, addresses.IPv6);
  }

  // the addresses is sorted in IPv4 first and IPv6 first,
  // if localhost is allowed, it will be the first item in
  // the list.
  return [].concat(addresses.IPv4, addresses.IPv6);
};

Service.prototype._selfAnnouncement = function (service) {
  return (service.name === this._uuid);
};

Service.prototype._startService = function (address) {
  var networkName = ifname(address);
  var network = networkName;
  var isAny = isme(address, 'any');

  // If it listen to loopback interface set flag
  if (isAny || name === LOOPBACK_NAME) {
    this._loopbackAllowed = true;
  }

  // Special case, where loopback interface is used
  if (networkName === LOOPBACK_NAME) {

    // There is a linux-avahi exception when dealling with a loopback interface
    if (mdns.isAvahi) {
      return this.emit('Error',
        new Error('loopback address is not supported on linux-avahi platform'));
    }

    // Use specical key
    network = mdns.loopbackInterface();
  } else if (isAny) {
    network = undefined;
  }

  this._discover = mdns.createBrowser(this._key, {
    networkInterface: network
  });

  this._discover.on('error', this._relayError);

  this._discover.start();

  this._announce = mdns.createAdvertisement(
    this._key, this._address.port, {
      // workaround for some wird bug
      // ref: https://github.com/agnat/node_mdns/issues/51
      name: this._uuid,

      networkInterface: network
    }
  );

  this._announce.on('error', this._relayError);

  this._announce.start();
};

// Stop announceing and discovering
Service.prototype._stopService = function () {
  if (this._announce) this._announce.stop();
  if (this._discover) this._discover.stop();
};

Service.prototype.address = function () {
  // addresses is the last property there will be set
  if (this._address.addresses === null) {
    return null;
  }

  return this._address;
};

Service.prototype.close = function (callback) {
  var self = this;

  this._closed = true;

  // reset address info
  this._address = {
    port: null,
    addresses: null
  };

  // Callback is just a close event handler
  if (callback) this.once('close', callback),

  // Stop announceing and discovering
  this._stopService();

  async.parallel([

    // Close all sockets
    function (done) {
      async.forEach(self.connections.slice(0), function(socket, eachDone) {
        socket.end();
        socket.once('close', function () { eachDone(null); });
      }, done);
    },

    // Close connection server, note the server.close callback
    // won't be executed before all connected sockets are closed.
    function (done) {
      if (self._server.address() === null) return done(null);
      self._server.close(done);
    }
  ], function () {
    self.emit('close');
  });
};
<|endoftext|>