<commit_before>// Copyright 2014-2017, University of Colorado Boulder

/**
 * Thermometer node, see https://github.com/phetsims/scenery-phet/issues/43
 *
 * @author Aaron Davis
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearFunction = require( 'DOT/LinearFunction' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var ShadedSphereNode = require( 'SCENERY_PHET/ShadedSphereNode' );
  var Shape = require( 'KITE/Shape' );

  // constants
  var FLUID_OVERLAP = 1; // overlap of fluid in tube and bulb, to hide seam
  // center of the bulb is at (0,0), let the client code move to the correct position
  var BULB_CENTER_X = 0;
  var BULB_CENTER_Y = 0;

  /**
   * @param {number} minTemperature
   * @param {number} maxTemperature
   * @param {Property.<number>} temperatureProperty
   * @param {Object} [options]
   * @constructor
   */
  function ThermometerNode( minTemperature, maxTemperature, temperatureProperty, options ) {
    options = _.extend( {
      bulbDiameter: 50,
      tubeWidth: 30,
      tubeHeight: 100,
      lineWidth: 4,
      outlineStroke: 'black',
      tickSpacing: 15,
      majorTickLength: 15,
      minorTickLength: 7.5,
      glassThickness: 2, // space between the thermometer outline and the fluid inside it

      // leave as null to have a transparent background. If a color is given, then an extra Rectangle is created for the background
      backgroundFill: null,

      // all the default colors are shades of red
      fluidMainColor: '#850e0e', // the main color of the bulb fluid, and the left side of the tube gradient
      fluidHighlightColor: '#ff7575', // the highlight color of the bulb fluid and the middle of the tube gradient
      fluidRightSideColor: '#c41515' // the right side of the tube gradient, not used currently
    }, options );

    Node.call( this );

    // Create a shaded sphere to act as the bulb fluid
    var bulbFluidDiameter = options.bulbDiameter - options.lineWidth - options.glassThickness; //TODO should this be options.lineWidth/2 ?
    var bulbFluidNode = new ShadedSphereNode( bulbFluidDiameter, {
      centerX: BULB_CENTER_X,
      centerY: BULB_CENTER_Y,
      mainColor: options.fluidMainColor,
      highlightColor: options.fluidHighlightColor,
      highlightXOffset: -0.2,
      highlightYOffset: 0.2,
      rotation: Math.PI / 2
    } );

    // Angles for the outline of the bulb
    var bulbStartAngle = -Math.acos( options.tubeWidth / options.bulbDiameter );
    var bulbEndAngle = Math.PI - bulbStartAngle;

    // Create the outline for the thermometer, starting with the bulb
    var tubeTopRadius = options.tubeWidth / 2;
    var straightTubeHeight = options.tubeHeight - tubeTopRadius;
    var straightTubeTop = BULB_CENTER_Y - ( options.bulbDiameter / 2 ) - straightTubeHeight;
    var straightTubeLeft = BULB_CENTER_X - ( options.tubeWidth / 2 );

    var outlineShape = new Shape()
      .arc( BULB_CENTER_X, BULB_CENTER_Y, options.bulbDiameter / 2, bulbStartAngle, bulbEndAngle ) // bulb at bottom
      .arc( BULB_CENTER_X, straightTubeTop, tubeTopRadius, Math.PI, 0 ) // rounded top of tube
      .close();
    // tick marks, from top down, alternating major and minor ticks
    var numberOfTicks = Math.floor( straightTubeHeight / options.tickSpacing ) + 1;
    for ( var i = 0; i < numberOfTicks; i++ ) {
      outlineShape.moveTo( straightTubeLeft, straightTubeTop + ( i * options.tickSpacing ) );
      outlineShape.horizontalLineTo( straightTubeLeft + ( ( i % 2 === 0 ) ? options.majorTickLength : options.minorTickLength ) );
    }

    var outlineNode = new Path( outlineShape, {
      stroke: options.outlineStroke,
      lineWidth: options.lineWidth
    } );
    assert && assert( outlineNode.height === options.tubeHeight + options.bulbDiameter + options.lineWidth ); // see scenery-phet#136

    var tubeFluidWidth = options.tubeWidth - options.lineWidth - options.glassThickness; //TODO should this be options.lineWidth/2 ?
    var tubeFluidRadius = tubeFluidWidth / 2;
    var clipBulbRadius = ( options.bulbDiameter - options.lineWidth - options.glassThickness ) / 2; //TODO should this be options.lineWidth/2 ?
    var clipStartAngle = -Math.acos( tubeFluidRadius / clipBulbRadius );
    var clipEndAngle = Math.PI - clipStartAngle;
    var tubeFluidBottom = ( bulbFluidDiameter / 2 ) * Math.sin( clipEndAngle );
    var tubeFluidLeft = -tubeFluidRadius;

    // Clip area for the fluid in the tube, round at the top
    var fluidClipArea = new Shape()
      .moveTo( tubeFluidLeft, tubeFluidBottom + FLUID_OVERLAP )
      .arc( BULB_CENTER_X, straightTubeTop, tubeFluidRadius, Math.PI, 0 ) // round top
      .lineTo( -tubeFluidLeft, tubeFluidBottom + FLUID_OVERLAP)
      .close();

    // Clip the top of the bulb so it's flat where it connects to the tube
    var bulbFluidClipArea = Shape.rectangle( tubeFluidBottom, BULB_CENTER_Y - options.bulbDiameter / 2, options.bulbDiameter, options.bulbDiameter );
    bulbFluidNode.setClipArea( bulbFluidClipArea );

    // Gradient for fluid in tube
    var tubeFluidGradient = new LinearGradient( tubeFluidLeft, 0, tubeFluidLeft + tubeFluidWidth, 0 )
      .addColorStop( 0, options.fluidMainColor )
      .addColorStop( 0.4, options.fluidHighlightColor )
      .addColorStop( 0.5, options.fluidHighlightColor )
      .addColorStop( 1, options.fluidMainColor );

    // Fluid in the tube (correct size set later)
    var tubeFluidNode = new Rectangle( 0, 0, tubeFluidWidth, 0, {
      fill: tubeFluidGradient,
      clipArea: fluidClipArea
    } );

    // Background inside the tube
    if ( options.backgroundFill ) {
      this.addChild( new Path( outlineShape, { fill: options.backgroundFill } ) );
    }

    // Add other nodes after optional background
    this.addChild( tubeFluidNode );
    this.addChild( bulbFluidNode );
    this.addChild( outlineNode );

    // Temperature determines the height of the fluid in the tube
    var maxFluidHeight = new Path( fluidClipArea ).height;
    //TODO this can exceed max/min. should this be clamped? or should it be replaced by dot.Util.linear?
    ivar temperatureLinearFunction = new LinearFunction( minTemperature, maxTemperature, 0, maxFluidHeight );

    var temperaturePropertyObserver = function( temp ) {
      var fluidHeight = temperatureLinearFunction( temp );
      tubeFluidNode.visible = ( fluidHeight > 0 );
      tubeFluidNode.setRect( tubeFluidLeft, tubeFluidBottom - fluidHeight, tubeFluidWidth, fluidHeight + FLUID_OVERLAP );
    };

    temperatureProperty.link( temperaturePropertyObserver );

    this.mutate( options );

    // @private
    this.disposeThermometerNode = function() {
      if ( temperatureProperty.hasListener( temperaturePropertyObserver ) ) {
        temperatureProperty.unlink( temperaturePropertyObserver );
      }
    };
  }

  sceneryPhet.register( 'ThermometerNode', ThermometerNode );

  return inherit( Node, ThermometerNode, {

    /**
     * Ensures that this node is subject to garbage collection
     * @public
     */
    dispose: function() {
      this.disposeThermometerNode();
      Node.prototype.dispose.call( this );
    }
  } );
} );<commit_msg>Fixing regression<commit_after>// Copyright 2014-2017, University of Colorado Boulder

/**
 * Thermometer node, see https://github.com/phetsims/scenery-phet/issues/43
 *
 * @author Aaron Davis
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearFunction = require( 'DOT/LinearFunction' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var ShadedSphereNode = require( 'SCENERY_PHET/ShadedSphereNode' );
  var Shape = require( 'KITE/Shape' );

  // constants
  var FLUID_OVERLAP = 1; // overlap of fluid in tube and bulb, to hide seam
  // center of the bulb is at (0,0), let the client code move to the correct position
  var BULB_CENTER_X = 0;
  var BULB_CENTER_Y = 0;

  /**
   * @param {number} minTemperature
   * @param {number} maxTemperature
   * @param {Property.<number>} temperatureProperty
   * @param {Object} [options]
   * @constructor
   */
  function ThermometerNode( minTemperature, maxTemperature, temperatureProperty, options ) {
    options = _.extend( {
      bulbDiameter: 50,
      tubeWidth: 30,
      tubeHeight: 100,
      lineWidth: 4,
      outlineStroke: 'black',
      tickSpacing: 15,
      majorTickLength: 15,
      minorTickLength: 7.5,
      glassThickness: 2, // space between the thermometer outline and the fluid inside it

      // leave as null to have a transparent background. If a color is given, then an extra Rectangle is created for the background
      backgroundFill: null,

      // all the default colors are shades of red
      fluidMainColor: '#850e0e', // the main color of the bulb fluid, and the left side of the tube gradient
      fluidHighlightColor: '#ff7575', // the highlight color of the bulb fluid and the middle of the tube gradient
      fluidRightSideColor: '#c41515' // the right side of the tube gradient, not used currently
    }, options );

    Node.call( this );

    // Create a shaded sphere to act as the bulb fluid
    var bulbFluidDiameter = options.bulbDiameter - options.lineWidth - options.glassThickness; //TODO should this be options.lineWidth/2 ?
    var bulbFluidNode = new ShadedSphereNode( bulbFluidDiameter, {
      centerX: BULB_CENTER_X,
      centerY: BULB_CENTER_Y,
      mainColor: options.fluidMainColor,
      highlightColor: options.fluidHighlightColor,
      highlightXOffset: -0.2,
      highlightYOffset: 0.2,
      rotation: Math.PI / 2
    } );

    // Angles for the outline of the bulb
    var bulbStartAngle = -Math.acos( options.tubeWidth / options.bulbDiameter );
    var bulbEndAngle = Math.PI - bulbStartAngle;

    // Create the outline for the thermometer, starting with the bulb
    var tubeTopRadius = options.tubeWidth / 2;
    var straightTubeHeight = options.tubeHeight - tubeTopRadius;
    var straightTubeTop = BULB_CENTER_Y - ( options.bulbDiameter / 2 ) - straightTubeHeight;
    var straightTubeLeft = BULB_CENTER_X - ( options.tubeWidth / 2 );

    var outlineShape = new Shape()
      .arc( BULB_CENTER_X, BULB_CENTER_Y, options.bulbDiameter / 2, bulbStartAngle, bulbEndAngle ) // bulb at bottom
      .arc( BULB_CENTER_X, straightTubeTop, tubeTopRadius, Math.PI, 0 ) // rounded top of tube
      .close();
    // tick marks, from top down, alternating major and minor ticks
    var numberOfTicks = Math.floor( straightTubeHeight / options.tickSpacing ) + 1;
    for ( var i = 0; i < numberOfTicks; i++ ) {
      outlineShape.moveTo( straightTubeLeft, straightTubeTop + ( i * options.tickSpacing ) );
      outlineShape.horizontalLineTo( straightTubeLeft + ( ( i % 2 === 0 ) ? options.majorTickLength : options.minorTickLength ) );
    }

    var outlineNode = new Path( outlineShape, {
      stroke: options.outlineStroke,
      lineWidth: options.lineWidth
    } );
    assert && assert( outlineNode.height === options.tubeHeight + options.bulbDiameter + options.lineWidth ); // see scenery-phet#136

    var tubeFluidWidth = options.tubeWidth - options.lineWidth - options.glassThickness; //TODO should this be options.lineWidth/2 ?
    var tubeFluidRadius = tubeFluidWidth / 2;
    var clipBulbRadius = ( options.bulbDiameter - options.lineWidth - options.glassThickness ) / 2; //TODO should this be options.lineWidth/2 ?
    var clipStartAngle = -Math.acos( tubeFluidRadius / clipBulbRadius );
    var clipEndAngle = Math.PI - clipStartAngle;
    var tubeFluidBottom = ( bulbFluidDiameter / 2 ) * Math.sin( clipEndAngle );
    var tubeFluidLeft = -tubeFluidRadius;

    // Clip area for the fluid in the tube, round at the top
    var fluidClipArea = new Shape()
      .moveTo( tubeFluidLeft, tubeFluidBottom + FLUID_OVERLAP )
      .arc( BULB_CENTER_X, straightTubeTop, tubeFluidRadius, Math.PI, 0 ) // round top
      .lineTo( -tubeFluidLeft, tubeFluidBottom + FLUID_OVERLAP)
      .close();

    // Clip the top of the bulb so it's flat where it connects to the tube
    var bulbFluidClipArea = Shape.rectangle( tubeFluidBottom, BULB_CENTER_Y - options.bulbDiameter / 2, options.bulbDiameter, options.bulbDiameter );
    bulbFluidNode.setClipArea( bulbFluidClipArea );

    // Gradient for fluid in tube
    var tubeFluidGradient = new LinearGradient( tubeFluidLeft, 0, tubeFluidLeft + tubeFluidWidth, 0 )
      .addColorStop( 0, options.fluidMainColor )
      .addColorStop( 0.4, options.fluidHighlightColor )
      .addColorStop( 0.5, options.fluidHighlightColor )
      .addColorStop( 1, options.fluidMainColor );

    // Fluid in the tube (correct size set later)
    var tubeFluidNode = new Rectangle( 0, 0, tubeFluidWidth, 0, {
      fill: tubeFluidGradient,
      clipArea: fluidClipArea
    } );

    // Background inside the tube
    if ( options.backgroundFill ) {
      this.addChild( new Path( outlineShape, { fill: options.backgroundFill } ) );
    }

    // Add other nodes after optional background
    this.addChild( tubeFluidNode );
    this.addChild( bulbFluidNode );
    this.addChild( outlineNode );

    // Temperature determines the height of the fluid in the tube
    var maxFluidHeight = new Path( fluidClipArea ).height;
    //TODO this can exceed max/min. should this be clamped? or should it be replaced by dot.Util.linear?
    var temperatureLinearFunction = new LinearFunction( minTemperature, maxTemperature, 0, maxFluidHeight );

    var temperaturePropertyObserver = function( temp ) {
      var fluidHeight = temperatureLinearFunction( temp );
      tubeFluidNode.visible = ( fluidHeight > 0 );
      tubeFluidNode.setRect( tubeFluidLeft, tubeFluidBottom - fluidHeight, tubeFluidWidth, fluidHeight + FLUID_OVERLAP );
    };

    temperatureProperty.link( temperaturePropertyObserver );

    this.mutate( options );

    // @private
    this.disposeThermometerNode = function() {
      if ( temperatureProperty.hasListener( temperaturePropertyObserver ) ) {
        temperatureProperty.unlink( temperaturePropertyObserver );
      }
    };
  }

  sceneryPhet.register( 'ThermometerNode', ThermometerNode );

  return inherit( Node, ThermometerNode, {

    /**
     * Ensures that this node is subject to garbage collection
     * @public
     */
    dispose: function() {
      this.disposeThermometerNode();
      Node.prototype.dispose.call( this );
    }
  } );
} );<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Checks global references (on the phet object) to verify all modules loaded through require.js that match the usual
 * pattern that would be namespaced. For example, if the sim uses SCENERY_PHET/buttons/ArrowButton, calling this
 * will check for the presence of phet.sceneryPhet.ArrowButton.
 *
 * See https://github.com/phetsims/tasks/issues/378
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  // modules
  var joist = require( 'JOIST/joist' );

  var checkNamespaces = function() {

    // Get a reference to the defined modules. There doesn't seem to be a common way to access this internal
    // information yet in the optimizer (with almond) and with require.js, so we have a fall-back set up.
    var defined = window.requirejs._defined || window.require.s.contexts._.defined;

    /**
     * This function iterates over all defined AMD modules and reports to a problemHandler any problems.
     * @param {function} problemHandler, a function that is called when an error occurs, with an {string} argument that
     * describes the problem
     */
    var visit = function( problemHandler ) {
      for ( var moduleName in defined ) {
        // Skip strings, images, or anything imported with a plugin. Could be added later if needed
        if ( moduleName.indexOf( '!' ) >= 0 ) {
          continue;
        }

        // Skip anything without a slash (the plugins themselves, and the main/config files)
        if ( moduleName.indexOf( '/' ) < 0 ) {
          continue;
        }

        // Skip anything with '..' in the path (chipper imports some things where we can't get the repository prefix)
        if ( moduleName.indexOf( '..' ) >= 0 ) {
          continue;
        }

        var prefix = moduleName.slice( 0, moduleName.indexOf( '/' ) ); // e.g. 'SCENERY_PHET'
        var name = moduleName.slice( moduleName.lastIndexOf( '/' ) + 1 ); // e.g. 'ArrowButton'

        // Convert to camel-case, e.g. 'SCENERY_PHET' to 'sceneryPhet'
        var prefixTokens = prefix.toLowerCase().split( '_' );
        var namespace = [ prefixTokens[ 0 ] ].concat( prefixTokens.slice( 1 ).map( function( token ) {
          return token.charAt( 0 ).toUpperCase() + token.slice( 1 );
        } ) ).join( '' );

        // Skip the module that contains the namespace object (e.g. scenery.js, tandemNamespace.js)
        if ( name === namespace || name === ( namespace + 'Namespace' ) ) {
          continue;
        }

        var namespacedObject = phet[ namespace ] && phet[ namespace ][ name ];

        if ( !namespacedObject ) {
          problemHandler( 'not namespaced: ' + namespace + '.' + name );
        }
        if ( namespacedObject && namespacedObject !== defined[ moduleName ] ) {
          problemHandler( namespace + '.' + name + ' is different than the expected namespaced object' );
        }
      }
    };

    // First pass prints out all issues, to assist developers as we are adding the namespace register calls
    visit( function( errorMessage ) {
      console.log( errorMessage );
    } );

    // Second pass fails with an assertion error.
    visit( function( errorMessage ) {
      // assert && assert( false, errorMessage );
    } );
  };

  joist.register( 'checkNamespaces', checkNamespaces );

  return checkNamespaces;
} );<commit_msg>Added namespaces, see https://github.com/phetsims/phet-io/issues/439<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Checks global references (on the phet object) to verify all modules loaded through require.js that match the usual
 * pattern that would be namespaced. For example, if the sim uses SCENERY_PHET/buttons/ArrowButton, calling this
 * will check for the presence of phet.sceneryPhet.ArrowButton.
 *
 * See https://github.com/phetsims/tasks/issues/378
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  // modules
  var joist = require( 'JOIST/joist' );

  var checkNamespaces = function() {

    // Get a reference to the defined modules. There doesn't seem to be a common way to access this internal
    // information yet in the optimizer (with almond) and with require.js, so we have a fall-back set up.
    var defined = window.requirejs._defined || window.require.s.contexts._.defined;

    /**
     * This function iterates over all defined AMD modules and reports to a problemHandler any problems.
     * @param {function} problemHandler, a function that is called when an error occurs, with an {string} argument that
     * describes the problem
     */
    var visit = function( problemHandler ) {
      for ( var moduleName in defined ) {
        // Skip strings, images, or anything imported with a plugin. Could be added later if needed
        if ( moduleName.indexOf( '!' ) >= 0 ) {
          continue;
        }

        // Skip anything without a slash (the plugins themselves, and the main/config files)
        if ( moduleName.indexOf( '/' ) < 0 ) {
          continue;
        }

        // Skip anything with '..' in the path (chipper imports some things where we can't get the repository prefix)
        if ( moduleName.indexOf( '..' ) >= 0 ) {
          continue;
        }

        var prefix = moduleName.slice( 0, moduleName.indexOf( '/' ) ); // e.g. 'SCENERY_PHET'
        var name = moduleName.slice( moduleName.lastIndexOf( '/' ) + 1 ); // e.g. 'ArrowButton'

        // Convert to camel-case, e.g. 'SCENERY_PHET' to 'sceneryPhet'
        var prefixTokens = prefix.toLowerCase().split( '_' );
        var namespace = [ prefixTokens[ 0 ] ].concat( prefixTokens.slice( 1 ).map( function( token ) {
          return token.charAt( 0 ).toUpperCase() + token.slice( 1 );
        } ) ).join( '' );

        // Skip the module that contains the namespace object (e.g. scenery.js, tandemNamespace.js)
        if ( name === namespace || name === ( namespace + 'Namespace' ) ) {
          continue;
        }

        var namespacedObject = phet[ namespace ] && phet[ namespace ][ name ];

        if ( !namespacedObject ) {
          problemHandler( 'not namespaced: ' + namespace + '.' + name );
        }
        if ( namespacedObject && namespacedObject !== defined[ moduleName ] ) {
          problemHandler( namespace + '.' + name + ' is different than the expected namespaced object' );
        }
      }
    };

    // First pass prints out all issues, to assist developers as we are adding the namespace register calls
    visit( function( errorMessage ) {
      console.log( errorMessage );
    } );

    // Second pass fails with an assertion error.
    visit( function( errorMessage ) {
      assert && assert( false, errorMessage );
    } );
  };

  joist.register( 'checkNamespaces', checkNamespaces );

  return checkNamespaces;
} );<|endoftext|>


<commit_before>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Colors used throughout this project.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );

  // common colors
  const VERTEX = 'rgb( 128, 0, 128 )'; // purple
  const FOCUS = 'green';

  const GQColors = {

    // interactive curves in each screen
    EXPLORE_INTERACTIVE_CURVE: PhetColorScheme.RED_COLORBLIND,
    STANDARD_FORM_INTERACTIVE_CURVE: PhetColorScheme.RED_COLORBLIND,
    VERTEX_FORM_INTERACTIVE_CURVE: 'black',
    FOCUS_AND_DIRECTRIX_INTERACTIVE_CURVE: 'black',

    // quadratic terms on the Explore screen
    QUADRATIC_TERM: 'blue',
    LINEAR_TERM: 'green',
    CONSTANT_TERM: 'magenta',

    // Coefficients on the Explore screen
    EXPLORE_A: PhetColorScheme.RED_COLORBLIND,
    EXPLORE_B: PhetColorScheme.RED_COLORBLIND,
    EXPLORE_C: PhetColorScheme.RED_COLORBLIND,

    // Coefficients on the Standard Form screen
    STANDARD_FORM_A: PhetColorScheme.RED_COLORBLIND,
    STANDARD_FORM_B: PhetColorScheme.RED_COLORBLIND,
    STANDARD_FORM_C: PhetColorScheme.RED_COLORBLIND,

    // Coefficients on the Vertex Form screen
    VERTEX_FORM_A: 'green', //TODO #29 there is nothing else that is green on the Vertex Form screen
    VERTEX_FORM_H: VERTEX,
    VERTEX_FORM_K: VERTEX,

    // Coefficients on the Focus & Directrix screen
    FOCUS_AND_DIRECTRIX_P: FOCUS,
    FOCUS_AND_DIRECTRIX_H: VERTEX,
    FOCUS_AND_DIRECTRIX_K: VERTEX,

    // common to all screens
    SCREEN_BACKGROUND: 'rgb( 238, 252, 252 )',
    CONTROL_PANEL_BACKGROUND: 'rgb( 238, 238, 238 )',
    SAVED_CURVE: 'rgb( 160, 160, 160 )',
    FOCUS: FOCUS,
    DIRECTRIX: 'green',
    VERTEX: VERTEX,
    AXIS_OF_SYMMETRY: VERTEX,
    ROOTS: PhetColorScheme.RED_COLORBLIND,
    POINT_ON_QUADRATIC: PhetColorScheme.RED_COLORBLIND,
    SEPARATOR: 'rgb( 212, 212, 212 )',

    // transparency of all manipulator halos, which appear on pointer over
    MANIPULATOR_HALO_ALPHA: 0.15
  };

  return graphingQuadratics.register( 'GQColors', GQColors );
} );
<commit_msg>change color for roots, #29<commit_after>// Copyright 2014-2018, University of Colorado Boulder

/**
 * Colors used throughout this project.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const PhetColorScheme = require( 'SCENERY_PHET/PhetColorScheme' );

  // common colors
  const VERTEX = 'rgb( 128, 0, 128 )'; // purple
  const FOCUS = 'green';

  const GQColors = {

    // interactive curves in each screen
    EXPLORE_INTERACTIVE_CURVE: PhetColorScheme.RED_COLORBLIND,
    STANDARD_FORM_INTERACTIVE_CURVE: PhetColorScheme.RED_COLORBLIND,
    VERTEX_FORM_INTERACTIVE_CURVE: 'black',
    FOCUS_AND_DIRECTRIX_INTERACTIVE_CURVE: 'black',

    // quadratic terms on the Explore screen
    QUADRATIC_TERM: 'blue',
    LINEAR_TERM: 'green',
    CONSTANT_TERM: 'magenta',

    // Coefficients on the Explore screen
    EXPLORE_A: PhetColorScheme.RED_COLORBLIND,
    EXPLORE_B: PhetColorScheme.RED_COLORBLIND,
    EXPLORE_C: PhetColorScheme.RED_COLORBLIND,

    // Coefficients on the Standard Form screen
    STANDARD_FORM_A: PhetColorScheme.RED_COLORBLIND,
    STANDARD_FORM_B: PhetColorScheme.RED_COLORBLIND,
    STANDARD_FORM_C: PhetColorScheme.RED_COLORBLIND,

    // Coefficients on the Vertex Form screen
    VERTEX_FORM_A: FOCUS,
    VERTEX_FORM_H: VERTEX,
    VERTEX_FORM_K: VERTEX,

    // Coefficients on the Focus & Directrix screen
    FOCUS_AND_DIRECTRIX_P: FOCUS,
    FOCUS_AND_DIRECTRIX_H: VERTEX,
    FOCUS_AND_DIRECTRIX_K: VERTEX,

    // common to all screens
    SCREEN_BACKGROUND: 'rgb( 238, 252, 252 )',
    CONTROL_PANEL_BACKGROUND: 'rgb( 238, 238, 238 )',
    SAVED_CURVE: 'rgb( 160, 160, 160 )',
    FOCUS: FOCUS,
    DIRECTRIX: 'green',
    VERTEX: VERTEX,
    AXIS_OF_SYMMETRY: VERTEX,
    ROOTS: '#0071bc',
    POINT_ON_QUADRATIC: PhetColorScheme.RED_COLORBLIND,
    SEPARATOR: 'rgb( 212, 212, 212 )',

    // transparency of all manipulator halos, which appear on pointer over
    MANIPULATOR_HALO_ALPHA: 0.15
  };

  return graphingQuadratics.register( 'GQColors', GQColors );
} );
<|endoftext|>


<commit_before>'use strict';

define(['darkwallet', 'bitcoinjs-lib', 'crypto-js'], function(DarkWallet, Bitcoin, CryptoJS) {

var convert = Bitcoin.convert;

/**
 * Class for dark wallet multisig funds
 */
function MultisigFund(multisig) {
    // Multisig here is linked to the model store
    this.multisig = multisig;

    // Indexes we control
    this.me = [];

    // Participants is a list of detected contacts
    this.participants = this.detectParticipants();

    // Tasks is a list of tasks, each linked to the store.
    this.tasks = this.detectTasks();
}

/**
 * Create a profile out of a public key by looking in contacts and wallet.
 */
MultisigFund.prototype.detectParticipant = function(pubKeyBytes, i) {
    var identity = DarkWallet.getIdentity();

    // Ensure we check the compressed version for my address
    var myPubKey = Bitcoin.ECPubKey.fromBytes(pubKeyBytes, true);
    var myAddress = myPubKey.getAddress(Bitcoin.networks[identity.wallet.network]);

    var participant = { pubKey: pubKeyBytes };

    var walletAddress = identity.wallet.getWalletAddress(myAddress);
    if (walletAddress) {
        // Current identity
        participant.type = 'me';
        this.me.push(i);
        participant.name = identity.name;
        participant.address = walletAddress;
        // In some cases would not be the stealth identifier.
        // Also, doing it like this so it would show the same as in contacts..
        participant.hash = CryptoJS.SHA256(walletAddress.stealth).toString();
    } else {
        // Check if it's a contact
        var contact = identity.contacts.findByPubKey(pubKeyBytes);
        if (contact) {
            participant.name = contact.data.name;
            participant.hash = contact.data.hash;
            participant.type = 'contact';
            if (contact.findIdentityKey()) {
                participant.paired = true;
            }
        } else {
            // Just set some values
            var compressed = (pubKeyBytes.length === 33);

            var contactAddress = Bitcoin.ECPubKey.fromBytes(pubKeyBytes, compressed);
            participant.name = contactAddress.toString();
            participant.hash = contactAddress.toHex();
        }
    }
    return participant;
};

/**
 * Detect participants for a fund from contacts and wallet.
 */
MultisigFund.prototype.detectParticipants = function() {
    // TODO: Not very efficient, should keep track in some way
    var self = this;
    var participants = [];

    this.multisig.pubKeys.forEach(function(pubKey, i) {
        participants.push(self.detectParticipant(pubKey, i));
    });

    return participants;
}

/**
 * Check tasks and put some info in the pocket
 */
MultisigFund.prototype.detectTasks = function() {
    var self = this;
    var fund = this.multisig;
    var identity = DarkWallet.getIdentity();
    var res = [];
    // Check pending tasks for fund
    var tasks = identity.tasks.tasks.multisig;
    if (tasks) {
        tasks.forEach(function(task) {
            var addresses = task.pending.map(function(p){ return p.address; });

            if (addresses.indexOf(fund.address) > -1) {
                var canSign = self.me.slice();
                // See if our indexes are signed
                if (self.me.length > 0) {
                    task.pending.forEach(function(p){
                        var signed = Object.keys(p.signatures);
                        canSign = canSign.filter(function(i) {return (signed.indexOf(""+i) === -1)});
                    });
                }

                var tx = Bitcoin.Transaction.fromHex(task.tx);
                res.push({tx: tx, task: task, canSign: canSign});

            }
        });
    }
    return res;
};

/**
 * Get the fundTask for a model task.
 */
MultisigFund.prototype.findFundTask = function(task) {
    for(var i=0; i<this.tasks.length; i++) {
        if (this.tasks[i].task === task) {
            return this.tasks[i];
        }
    }
}

/**
 * Order signatures for this multisig
 */
MultisigFund.prototype.organizeSignatures = function(hexSigs) {
    var signatures = [];
    var multisig = this.multisig;
    multisig.participants.forEach(function(participant, i) {
        if (hexSigs.hasOwnProperty(i)) {
            signatures.push(hexSigs[i]);
        } else {
            // do nothing
        }
    });
    return signatures;
}

/**
 * Check if we have enough signatures and put them into the transaction
 */
MultisigFund.prototype.finishTransaction = function(spend) {
    var self = this;
    var multisig = this.multisig;
    var script = Bitcoin.Script.fromHex(multisig.script);

    var finished = true;
    spend.task.pending.forEach(function(input) {
        var hexSigs = self.organizeSignatures(input.signatures);

        if (Object.keys(input.signatures).length >= multisig.m) {
            // convert inputs to buffers
            var sigs = hexSigs.map(function(sig) {return new Bitcoin.Buffer(sig, 'hex');});
            // apply multisigs
            spend.tx.ins[input.index].script = Bitcoin.scripts.multisigInput(sigs, script);
        } else {
            finished = false;
        }
    });
    if (finished) {
        spend.task.tx = spend.tx.toHex();
        return spend.tx;
    }
};

/**
 * Import a signature
 */
MultisigFund.prototype.importSignature = function(sigHex, spend) {
    var added = false;
    var multisig = this.multisig;

    var sig;
    try {
       sig = new Bitcoin.Buffer(sigHex, 'hex');
    } catch(e) {
       throw new Error('Malformed signature');
    }

    // Check where this signature goes
    var script = Bitcoin.Script.fromHex(multisig.script);
    spend.task.pending.forEach(function(input) {
        var txHashBuf = spend.tx.hashForSignature(input.index, script, 1);
        multisig.pubKeys.forEach(function(pubKeyBytes, pIdx) {
            var pubKey = Bitcoin.ECPubKey.fromBytes(pubKeyBytes);
            if (pubKey.verify(txHashBuf, sig)) {
                input.signatures[pIdx] = sigHex;
                added = true;
            }
        });
    });
    return added;
};

/**
 * Get a spend task from the given tx hash
 */
MultisigFund.prototype.getSpend = function(txHash) {
    for(var i=0; i<this.tasks.length; i++) {
        var spend = this.tasks[i];
        var hash = spend.tx.getId();
        if (hash === txHash) {
            return spend;
        }
    }
};

/**
 * Import a partial transaction into the fund
 */
MultisigFund.prototype.importTransaction = function(serializedTx) {
    // import transaction here
    var identity = DarkWallet.getIdentity();

    // we import the tx
    var tx;
    try {
        tx = Bitcoin.Transaction.fromHex(serializedTx);
    } catch(e) {
        throw new Error('Malformed transaction');
    }
    // Find our inputs
    var inputs = this.getValidInputs(tx);

    if (inputs.length === 0) {
        throw new Error('Transaction is not for this multisig');
    }

    // Check if we have the tx in the identity store
    var tasks = identity.tasks.getTasks('multisig');
    tasks.forEach(function(task) {
        if (task.tx === serializedTx) {
            throw new Error('Already have this transaction!');
        }
    });

    // Now create the task
    var pending = [];
    // inputs format here comes from getValidInputs not directly from tx
    inputs.forEach(function(input) {
        var out = Bitcoin.bufferutils.reverse(input.outpoint.hash).toString('hex')+':'+input.outpoint.index;
        //var address = Bitcoin.Address.fromInputScript(input.script, Bitcoin.networks[identity.wallet.versions.network]).toString();
        pending.push({output: out, address: input.address, index: input.index, signatures: {}, type: 'multisig'});
    });
    var task = {tx: serializedTx, 'pending': pending, stealth: false};
    var spend = {tx: tx, task: task};

    // add as task in the store
    identity.tasks.addTask('multisig', task);
    // Maybe should be imported here but now it's done on the angular controller..
    this.tasks.push(spend)
    return spend;
};

/**
 * Continue signing after getting the password
 */
MultisigFund.prototype.signTransaction = function(password, spend, inputs) {
    var self = this;
    var identity = DarkWallet.getIdentity();
    var script = Bitcoin.Script.fromHex(this.multisig.script);

    var signed = false;

    var builder = Bitcoin.TransactionBuilder.fromTransaction(spend.tx);
    // find key
    this.participants.forEach(function(participant, pIdx) {
        if (participant.type === 'me') {
            var seq = participant.address.index;
            identity.wallet.getPrivateKey(seq, password, function(privKey) {
                inputs.forEach(function(input, i) {
                    builder.sign(input.index, privKey, script, 1);
                    var sig = builder.signatures[input.index].signatures.slice(-1);
                    var hexSig = sig.toDER().toString('hex');
                    spend.task.pending[i].signatures[pIdx] = hexSig
                    // propagate transaction
                    DarkWallet.service.multisigTrack.sign(self.multisig, spend.tx, hexSig);
                    signed = true;
                });
                // TODO: may want to remove just this participant here
                spend.canSign = [];
                spend.task.signed = true;
            });
        }
    });
    if (signed) {
        spend.tx = builder.buildIncomplete();
    }
    return signed;
};

/**
 * Sign a transaction with our keys
 */
MultisigFund.prototype.signTxForeign = function(foreignKey, spend) {
    var identity = DarkWallet.getIdentity();
    var multisig = this.multisig;

    var inputs = this.getValidInputs(spend.tx);
    if (inputs.length === 0) {
         // Shouldn't happen
         throw new Error('Transaction is not for this multisig');
    }

    var script = Bitcoin.Script.fromHex(this.multisig.script);
    var privKey = Bitcoin.ECKey.fromWIF(foreignKey);
    privKey.pub.compressed = true;
    var signingAddress = privKey.pub.getAddress(Bitcoin.networks[identity.wallet.network]).toString();

    var signed = false;
    var builder = Bitcoin.TransactionBuilder.fromTransaction(spend.tx);

    // Check each participant
    this.participants.forEach(function(participant, pIdx) {
        if (participant.type !== 'me') {     // can't be me if we're importing the key
            var pubKey = Bitcoin.ECPubKey.fromBytes(participant.pubKey, true);

            if (pubKey.getAddress(Bitcoin.networks[identity.wallet.network]).toString() === signingAddress) {
                // It's this position, so sign all inputs
                inputs.forEach(function(input, i) {
                    builder.sign(input.index, privKey, script, 1);
                    var sig = builder.signatures[input.index].signatures.slice(-1);
                    var hexSig = sig.toDER().toString('hex');
                    spend.task.pending[i].signatures[pIdx] = hexSig;
                    DarkWallet.service.multisigTrack.sign(multisig, spend.tx, hexSig);
                    signed = true;
                });
            }
        }
    });
    if (signed) {
        spend.tx = builder.buildIncomplete();
    }
    return signed;
}

/**
 * Get valid inputs for this transaction
 * @returns [{output: utxo.receive, address: utxo.address, index: idx, signatures: {}, type: outAddress?outAddress.type:'signature'}, ...]
 */
MultisigFund.prototype.getValidInputs = function(tx) {
    // import transaction here
    var identity = DarkWallet.getIdentity();
    var multisig = this.multisig;
    var walletAddress = identity.wallet.getWalletAddress(multisig.address);

    if (walletAddress) {
        // we import the tx
        var inputs = identity.tx.forAddress(walletAddress, tx);
        return inputs;
    }
    return [];
};

return MultisigFund;

});
<commit_msg>Allow multisig importing signatures from serialized transactions and serializing transactions with just some signatures. Fixes #147<commit_after>'use strict';

define(['darkwallet', 'bitcoinjs-lib', 'crypto-js'], function(DarkWallet, Bitcoin, CryptoJS) {

var convert = Bitcoin.convert;

/**
 * Class for dark wallet multisig funds
 */
function MultisigFund(multisig) {
    // Multisig here is linked to the model store
    this.multisig = multisig;

    // Indexes we control
    this.me = [];

    // Participants is a list of detected contacts
    this.participants = this.detectParticipants();

    // Tasks is a list of tasks, each linked to the store.
    this.tasks = this.detectTasks();
}

/**
 * Create a profile out of a public key by looking in contacts and wallet.
 */
MultisigFund.prototype.detectParticipant = function(pubKeyBytes, i) {
    var identity = DarkWallet.getIdentity();

    // Ensure we check the compressed version for my address
    var myPubKey = Bitcoin.ECPubKey.fromBytes(pubKeyBytes, true);
    var myAddress = myPubKey.getAddress(Bitcoin.networks[identity.wallet.network]);

    var participant = { pubKey: pubKeyBytes };

    var walletAddress = identity.wallet.getWalletAddress(myAddress);
    if (walletAddress) {
        // Current identity
        participant.type = 'me';
        this.me.push(i);
        participant.name = identity.name;
        participant.address = walletAddress;
        // In some cases would not be the stealth identifier.
        // Also, doing it like this so it would show the same as in contacts..
        participant.hash = CryptoJS.SHA256(walletAddress.stealth).toString();
    } else {
        // Check if it's a contact
        var contact = identity.contacts.findByPubKey(pubKeyBytes);
        if (contact) {
            participant.name = contact.data.name;
            participant.hash = contact.data.hash;
            participant.type = 'contact';
            if (contact.findIdentityKey()) {
                participant.paired = true;
            }
        } else {
            // Just set some values
            var compressed = (pubKeyBytes.length === 33);

            var contactAddress = Bitcoin.ECPubKey.fromBytes(pubKeyBytes, compressed);
            participant.name = contactAddress.toString();
            participant.hash = contactAddress.toHex();
        }
    }
    return participant;
};

/**
 * Detect participants for a fund from contacts and wallet.
 */
MultisigFund.prototype.detectParticipants = function() {
    // TODO: Not very efficient, should keep track in some way
    var self = this;
    var participants = [];

    this.multisig.pubKeys.forEach(function(pubKey, i) {
        participants.push(self.detectParticipant(pubKey, i));
    });

    return participants;
}

/**
 * Check tasks and put some info in the pocket
 */
MultisigFund.prototype.detectTasks = function() {
    var self = this;
    var fund = this.multisig;
    var identity = DarkWallet.getIdentity();
    var res = [];
    // Check pending tasks for fund
    var tasks = identity.tasks.tasks.multisig;
    if (tasks) {
        tasks.forEach(function(task) {
            var addresses = task.pending.map(function(p){ return p.address; });

            if (addresses.indexOf(fund.address) > -1) {
                var canSign = self.me.slice();
                // See if our indexes are signed
                if (self.me.length > 0) {
                    task.pending.forEach(function(p){
                        var signed = Object.keys(p.signatures);
                        canSign = canSign.filter(function(i) {return (signed.indexOf(""+i) === -1)});
                    });
                }

                var tx = Bitcoin.Transaction.fromHex(task.tx);
                res.push({tx: tx, task: task, canSign: canSign});

            }
        });
    }
    return res;
};

/**
 * Get the fundTask for a model task.
 */
MultisigFund.prototype.findFundTask = function(task) {
    for(var i=0; i<this.tasks.length; i++) {
        if (this.tasks[i].task === task) {
            return this.tasks[i];
        }
    }
}

/**
 * Order signatures for this multisig
 */
MultisigFund.prototype.organizeSignatures = function(hexSigs) {
    var signatures = [];
    var multisig = this.multisig;
    multisig.participants.forEach(function(participant, i) {
        if (hexSigs.hasOwnProperty(i)) {
            signatures.push(hexSigs[i]);
        } else {
            // do nothing
        }
    });
    return signatures;
}

/**
 * Check if we have enough signatures and put them into the transaction
 */
MultisigFund.prototype.finishTransaction = function(spend, incomplete) {
    var self = this;
    var multisig = this.multisig;
    var script = Bitcoin.Script.fromHex(multisig.script);

    var finished = true;
    var builder = Bitcoin.TransactionBuilder.fromTransaction(spend.tx);
    spend.task.pending.forEach(function(input) {
        var hexSigs = self.organizeSignatures(input.signatures);

        if (Object.keys(input.signatures).length >= multisig.m || incomplete) {
            // convert inputs to signatures
            var sigs = hexSigs.map(function(sig) {return Bitcoin.ECSignature.fromDER(new Bitcoin.Buffer(sig, 'hex'));});
            // add signatures to the builder
            if (!builder.signatures[input.index]) {
                builder.signatures[input.index] = {hashType: Bitcoin.Transaction.SIGHASH_ALL, pubKeys: [], redeemScript: script, scriptType: "multisig", signatures: []};
            }
            builder.signatures[input.index].signatures = sigs;
        } else {
            finished = false;
        }
    });
    if (finished) {
        spend.tx = builder.buildIncomplete();
        spend.task.tx = spend.tx.toHex();
        return spend.tx;
    }
};

MultisigFund.prototype.importInputSignature = function(tx, sig, input, script) {
    var added;
    var txHashBuf = tx.hashForSignature(input.index, script, 1);
    this.multisig.pubKeys.forEach(function(pubKeyBytes, pIdx) {
        var pubKey = Bitcoin.ECPubKey.fromBytes(pubKeyBytes);
        if (pubKey.verify(txHashBuf, sig)) {
            input.signatures[pIdx] = sig.toDER().toString('hex');
            added = true;
        }
    });
    return added;
}

/**
 * Import a signature
 */
MultisigFund.prototype.importSignature = function(sigHex, spend) {
    var self = this;
    var added = false;
    var multisig = this.multisig;

    var sig;
    try {
       sig = Bitcoin.ECSignature.fromDER(new Bitcoin.Buffer(sigHex, 'hex'));
    } catch(e) {
       throw new Error('Malformed signature');
    }

    // Check where this signature goes
    var script = Bitcoin.Script.fromHex(multisig.script);
    spend.task.pending.forEach(function(input) {
        if (self.importInputSignature(spend.tx, sig, input, script)) {
            added = true;
        }
    });
    if (added) {
        this.finishTransaction(spend, true);
    }
    return added;
};

/**
 * Get a spend task from the given tx hash
 */
MultisigFund.prototype.getSpend = function(txHash) {
    for(var i=0; i<this.tasks.length; i++) {
        var spend = this.tasks[i];
        var hash = spend.tx.getId();
        if (hash === txHash) {
            return spend;
        }
    }
};

/**
 * Import a partial transaction into the fund
 */
MultisigFund.prototype.importTransaction = function(serializedTx) {
    var self = this;
    // import transaction here
    var identity = DarkWallet.getIdentity();

    // we import the tx
    var tx;
    try {
        tx = Bitcoin.Transaction.fromHex(serializedTx);
    } catch(e) {
        throw new Error('Malformed transaction');
    }
    // Find our inputs
    var inputs = this.getValidInputs(tx);

    if (inputs.length === 0) {
        throw new Error('Transaction is not for this multisig');
    }

    // Check if we have the tx in the identity store
    var found = false;
    var tasks = identity.tasks.getTasks('multisig');
    tasks.forEach(function(task) {
        if (Bitcoin.Transaction.fromHex(task.tx).getId() === tx.getId()) {
            found = task;
        }
    });

    var script = Bitcoin.Script.fromHex(this.multisig.script);

    // Now create the task
    var pending = found ? found.pending : [];
    // inputs format here comes from getValidInputs not directly from tx
    inputs.forEach(function(input) {
        if (!found) {
            var out = Bitcoin.bufferutils.reverse(input.outpoint.hash).toString('hex')+':'+input.outpoint.index;
            pending.push({output: out, address: input.address, index: input.index, signatures: {}, type: 'multisig'});
        }

        // Import signatures
        var txIn = tx.ins[input.index];
        if (txIn.script.chunks.length > 2) {
            txIn.script.chunks.slice(1,-1).forEach(function(sigBuf) {
                sigBuf = Bitcoin.ECSignature.parseScriptSignature(sigBuf).signature;
                self.importInputSignature(tx, sigBuf, pending[pending.length-1], script);
            });
        }
    });
    if (found) {
        var spends = this.tasks.filter(function(spend) {return spend.task === found})
        this.finishTransaction(spends[0], true);
        return spends[0];
    }
    else {
        var task = {tx: serializedTx, 'pending': pending, stealth: false};
        var spend = {tx: tx, task: task};

        // add as task in the store
        identity.tasks.addTask('multisig', task);
        // Maybe should be imported here but now it's done on the angular controller..
        this.tasks.push(spend)
        return spend;
    }
};

/**
 * Continue signing after getting the password
 */
MultisigFund.prototype.signTransaction = function(password, spend, inputs) {
    var self = this;
    var identity = DarkWallet.getIdentity();
    var script = Bitcoin.Script.fromHex(this.multisig.script);

    var signed = false;

    var builder = Bitcoin.TransactionBuilder.fromTransaction(spend.tx);
    // find key
    this.participants.forEach(function(participant, pIdx) {
        if (participant.type === 'me') {
            var seq = participant.address.index;
            identity.wallet.getPrivateKey(seq, password, function(privKey) {
                inputs.forEach(function(input, i) {
                    builder.sign(input.index, privKey, script, 1);
                    var sig = builder.signatures[input.index].signatures.slice(-1)[0];
                    var hexSig = sig.toDER().toString('hex');
                    spend.task.pending[i].signatures[pIdx] = hexSig
                    // propagate transaction
                    DarkWallet.service.multisigTrack.sign(self.multisig, spend.tx, hexSig);
                    signed = true;
                });
                // TODO: may want to remove just this participant here
                spend.canSign = [];
                spend.task.signed = true;
            });
        }
    });
    if (signed) {
        this.finishTransaction(spend, true);
    }
    return signed;
};

/**
 * Sign a transaction with our keys
 */
MultisigFund.prototype.signTxForeign = function(foreignKey, spend) {
    var identity = DarkWallet.getIdentity();
    var multisig = this.multisig;

    var inputs = this.getValidInputs(spend.tx);
    if (inputs.length === 0) {
         // Shouldn't happen
         throw new Error('Transaction is not for this multisig');
    }

    var script = Bitcoin.Script.fromHex(this.multisig.script);
    var privKey = Bitcoin.ECKey.fromWIF(foreignKey);
    privKey.pub.compressed = true;
    var signingAddress = privKey.pub.getAddress(Bitcoin.networks[identity.wallet.network]).toString();

    var signed = false;
    var builder = Bitcoin.TransactionBuilder.fromTransaction(spend.tx);

    // Check each participant
    this.participants.forEach(function(participant, pIdx) {
        if (participant.type !== 'me') {     // can't be me if we're importing the key
            var pubKey = Bitcoin.ECPubKey.fromBytes(participant.pubKey, true);

            if (pubKey.getAddress(Bitcoin.networks[identity.wallet.network]).toString() === signingAddress) {
                // It's this position, so sign all inputs
                inputs.forEach(function(input, i) {
                    builder.sign(input.index, privKey, script, 1);
                    var sig = builder.signatures[input.index].signatures.slice(-1)[0];
                    var hexSig = sig.toDER().toString('hex');
                    spend.task.pending[i].signatures[pIdx] = hexSig;
                    DarkWallet.service.multisigTrack.sign(multisig, spend.tx, hexSig);
                    signed = true;
                });
            }
        }
    });
    if (signed) {
        this.finishTransaction(spend, true);
    }
    return signed;
}

/**
 * Get valid inputs for this transaction
 * @returns [{output: utxo.receive, address: utxo.address, index: idx, signatures: {}, type: outAddress?outAddress.type:'signature'}, ...]
 */
MultisigFund.prototype.getValidInputs = function(tx) {
    // import transaction here
    var identity = DarkWallet.getIdentity();
    var multisig = this.multisig;
    var walletAddress = identity.wallet.getWalletAddress(multisig.address);

    if (walletAddress) {
        // we import the tx
        var inputs = identity.tx.forAddress(walletAddress, tx);
        return inputs;
    }
    return [];
};

return MultisigFund;

});
<|endoftext|>


<commit_before>/*
IRA - Interactive Relational Algebra Tool
Copyright (C) 2010-2012 Henrik Mühe

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
function Join(input1, input2) {
    this.leftOuter = false;
    this.rightOuter = false;

    this.input1 = input1;
    this.input2 = input2;

    this.setChildren([this.input1, this.input2]);

    this.getInput1 = function() {
        return this.input1;
    };

    this.getInput2 = function() {
        return this.input2;
    };

    this.getName = function() {
        return this.input1.getName() + "_" + this.input2.getName();
    };
    this.setName = null;

    this.moo = function() {
        alert('moo');
    };

    this.getColumns = function() {
        var columns = this.input1.getColumns().clone();
        this.input2.getColumns().each(function(c) {
            if (columns.indexOf(c) < 0) {
                columns.push(c);
            }
        });
        return columns;
    };
    this.setColumns = null;

    this.getResult = function() {
        var rel1 = this.input1.getResult();
        var rel2 = this.input2.getResult();
        var col1 = this.input1.getColumns();
        var col2 = this.input2.getColumns();
        var result = [];

        // find natural join columns
        var joincolumns = [];
        col1.each(function(c1) {
            col2.each(function(c2) {
                if (c1 == c2) {
                    joincolumns.push(c1);
                }
            });
        });

        // build result
        var rights_added = new Hash();
        var left_outer = this.leftOuter;
        rel1.each(function(row1) {
            var left_added = false;
            rel2.each(function(row2, nr) {
                var good = true;
                joincolumns.each(function(c) {
                    if (row1[col1.indexOf(c)] != row2[col2.indexOf(c)]) {
                        good = false;
                    }
                });

                if (good) {
                    var newrow = row1.clone();
                    col2.each(function(c, nr) {
                        if (joincolumns.indexOf(c) < 0) {
                            newrow.push(row2[nr]);
                        }
                    });
                    result.push(newrow);

                    rights_added.set(nr, true);
                    left_added = true;
                }
            });

            // left outer join addon
            if (left_outer && !left_added) {
                var newrow = row1.clone();
                for (i = 0; i < col2.length - joincolumns.length; i++) {
                    newrow.push(null);
                }
                result.push(newrow);
            }
        });

        // right outer join addon
		var columns = this.getColumns();
        if (this.rightOuter) {
            rel2.each(function(row2, nr) {
                if (!rights_added.get(nr)) {
                    var newrow = [];
					// add left-hand columns
					for (i = 0; i < col1.length; i++) {
						if (joincolumns.indexOf(col1[i]) < 0) {
                            newrow.push(null);
                        } else {
							newrow.push(row2[col2.indexOf(col1[i])]);
						}
                    }
					// add right-hand columns
					for (i = 0; i < col2.length; i++) {
						console.log(col2[i] + " " + joincolumns.indexOf(col2[i]));
						if (joincolumns.indexOf(col2[i]) < 0) {
							newrow.push(row2[i]);
                        } else {
							// already added
						}
                    }
                    /*for (i = 0; i < col1.length - joincolumns.length; i++) {
                        newrow.unshift(null);
                    }*/
                    result.push(newrow);
                }
            });
        }

        return result;
    };

    this.copy = function() {
        return new Join(this.input1.copy(), this.input2.copy());
    };

    this.toHTML = function(options) {
        var display = '';
        display += '(' + this.input1.toHTML(options) + " " + latex("\\bowtie") + " " + this.input2.toHTML(options) + ")";
        return display;
    };

    this.toLatex = function(options) {
        return "(" + this.input1.toLatex(options) + "\\bowtie " + this.input2.toLatex(options) + ")";
    };
}
Join.prototype = new Relation();
<commit_msg>Fixing joins + rename errors<commit_after>/*
IRA - Interactive Relational Algebra Tool
Copyright (C) 2010-2012 Henrik Mühe

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
function Join(input1, input2) {
    this.leftOuter = false;
    this.rightOuter = false;

    this.input1 = input1;
    this.input2 = input2;

    this.setChildren([this.input1, this.input2]);

    this.getInput1 = function() {
        return this.input1;
    };

    this.getInput2 = function() {
        return this.input2;
    };

    this.getName = function() {
        return this.input1.getName() + "_" + this.input2.getName();
    };
    this.setName = null;

    this.moo = function() {
        alert('moo');
    };

    this.getColumns = function() {
        var columns = this.input1.getColumns().clone();
        var isInJoin = function(c) {
          var data2 = c.split(".");
          return columns.reduce(function(r,n) {
            var data1 = n.split(".");
            return r || data1[data1.length-1] == data2[data2.length-1];
          }, false);
        };
        return columns.concat(this.input2.getColumns().filter(function(c) {
          return !isInJoin(c);
        }));
    };
    this.setColumns = null;

    this.getResult = function() {
        var rel1 = this.input1.getResult();
        var rel2 = this.input2.getResult();
        var col1 = this.input1.getColumns();
        var col2 = this.input2.getColumns();
        var result = [];

        // find natural join columns
        var joincolumns = [];
        col1.each(function(c1) {
            var data1 = c1.split(".");
            col2.each(function(c2) {
                var data2 = c2.split(".");
                if (data1[data1.length-1] == data2[data2.length-1]) {
                    joincolumns.push([c1,c2]);
                }
            });
        });

        var isInJoin = function(c) {
          return joincolumns.reduce(function(r,n) {
            return r || n[0] == c || n[1] == c;
          }, false);
        };

        // build result
        var rights_added = new Hash();
        var left_outer = this.leftOuter;
        rel1.each(function(row1) {
            var left_added = false;
            rel2.each(function(row2, nr) {
                var good = true;
                joincolumns.each(function(c) {
                    if (row1[col1.indexOf(c[0])] != row2[col2.indexOf(c[1])]) {
                        good = false;
                    }
                });

                if (good) {
                    var newrow = row1.clone();
                    col2.each(function(c, nr) {
                        if (!isInJoin(c)) {
                            newrow.push(row2[nr]);
                        }
                    });
                    result.push(newrow);

                    rights_added.set(nr, true);
                    left_added = true;
                }
            });

            // left outer join addon
            if (left_outer && !left_added) {
                var newrow = row1.clone();
                for (i = 0; i < col2.length - joincolumns.length; i++) {
                    newrow.push(null);
                }
                result.push(newrow);
            }
        });

        // right outer join addon
		var columns = this.getColumns();
        if (this.rightOuter) {
            rel2.each(function(row2, nr) {
                if (!rights_added.get(nr)) {
                    var newrow = [];
					// add left-hand columns
					for (i = 0; i < col1.length; i++) {
						if (joincolumns.indexOf(col1[i]) < 0) {
                            newrow.push(null);
                        } else {
							newrow.push(row2[col2.indexOf(col1[i])]);
						}
                    }
					// add right-hand columns
					for (i = 0; i < col2.length; i++) {
						console.log(col2[i] + " " + joincolumns.indexOf(col2[i]));
						if (joincolumns.indexOf(col2[i]) < 0) {
							newrow.push(row2[i]);
                        } else {
							// already added
						}
                    }
                    /*for (i = 0; i < col1.length - joincolumns.length; i++) {
                        newrow.unshift(null);
                    }*/
                    result.push(newrow);
                }
            });
        }

        return result;
    };

    this.copy = function() {
        return new Join(this.input1.copy(), this.input2.copy());
    };

    this.toHTML = function(options) {
        var display = '';
        display += '(' + this.input1.toHTML(options) + " " + latex("\\bowtie") + " " + this.input2.toHTML(options) + ")";
        return display;
    };

    this.toLatex = function(options) {
        return "(" + this.input1.toLatex(options) + "\\bowtie " + this.input2.toLatex(options) + ")";
    };
}
Join.prototype = new Relation();
<|endoftext|>


<commit_before>var child_process = require('child_process');
var should = require("should");
var serialport;

try {
    serialport = require("serialport");
} catch (e) {
    serialport = null; // failover
}

module.exports.FireStepDriver = (function() {
    function reset_serialDriver(that) {
        console.log("TTY\t: FireStepDriver reset_serialDriver()");
        that.model.available = false;
        that.model.initialized = false;
        that.serialQueue = [];
    }

    function send_startup(that) {
        that.model.available = true;
        if (that.serialQueue.length > 0) {
            console.log("TTY\t: FireStepDriver send_startup() clearing queue items:", that.serialQueue.length);
            that.serialQueue = [];
        }
        that.model.initialized = false;
        that.serialInProgress = false;
        that.onStartup();
        that.processQueue();
    }

    function open_serialport(that, options) {
        console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") opening serialport");
        that.serial = new serialport.SerialPort(that.model.rest.serialPath, {
            buffersize: options.buffersize,
            parser: serialport.parsers.readline('\n'),
            baudrate: options.baudrate
        }, false);
        that.serial.on("data", function(data) {
            that.onSerialData(data);
        });
        that.serialInProgress = true;
        that.model.driver = "serialport";
        that.serial.open(function(error) {
            that.error = error;
            if (error) {
                console.log("TTY\t: FireStepDriver.open(" + that.model.rest.serialPath + ") FAILED:" + error);
                reset_serialDriver(that);
            } else {
                that.model.available = true;
                console.log("TTY\t: FireStepDriver() SerialPort.open(" + that.model.rest.serialPath + ") ready...");
                reset_serialDriver(that);
                send_startup(that);
            }
        });
    }

    function open_firestep(that, options) {
        try {
            that.firestep_proc = {}; // mark intent (actual value is set async)
            that.model.driver = "firestep";

            function onOpenSuccess(that, stdout, attempts) {
                console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") firestep reset successful. attempts:" + attempts + " stdout:" + stdout);
                that.firestep_proc = child_process.spawn('firestep', ['-d', that.model.rest.serialPath]);
                console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") firestep cli pid:" + that.firestep_proc.pid);
                that.firestep_proc.on('close', function(code) {
                    if (code) {
                        console.log("TTY\t: firestep cli ERROR:" + code);
                    } else {
                        console.log("TTY\t: firestep cli ended normally");
                    }
                });
                that.firestep_proc.stdout.on('data', function(buffer) {
                    var data = buffer.toString();
                    data = data.substr(0, data.length - 1); // chop LF to match serialport
                    //console.log("TTY\t: firestep stdout:" + data);
                    that.onSerialData(data);
                });
                that.firestep_proc.stderr.on('data', function(data) {
                    console.warn("STDERR\t: firestep => " + data);
                    reset_serialDriver(that);
                });
                send_startup(that);
            }
            var cmd = 'firestep -d ' + that.model.rest.serialPath + ' -r';
            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") " + cmd);
            var child1 = child_process.exec(cmd, function(error, stdout, stdin) {
                if (error instanceof Error) {
                    console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") attempt #1:" + error);
                    var child2 = child_process.exec(cmd, function(error, stdout, stdin) {
                        if (error instanceof Error) {
                            reset_serialDriver(that);
                            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") RETRY #1:" + error);
                        } else {
                            onOpenSuccess(that, stdout, 2);
                        }
                    });
                } else {
                    onOpenSuccess(that, stdout, 1);
                }
            });
        } catch (e) {
            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") UNAVAILABLE:" + e);
            reset_serialDriver(that);
        }
    }

    function close_serialport(that) {
        console.log("ERROR\t: close_serialport() not implemented");
    }

    function close_firestep(that) {
        if (that.isAvailable() && that.firestep_proc != null) {
            console.log("INFO\t: shutting down FireStep");
            that.firestep_proc.kill('SIGTERM');
        }
    }

    ////////////////// constructor
    function FireStepDriver(model, options) {
        var that = this;
        should.exist(model);
        options = options || {};
        options.buffersize = options.buffersize || 255;
        options.baudrate = options.baudrate || 19200;
        options.maxHistory = options.maxHistory || 50;
        options.msLaunchTimeout = options.msLaunchTimeout || 3000; // allow EEPROM commands to complete
        that.maxHistory = options.maxHistory;
        that.serialQueue = [];
        that.onIdle = options.onIdle || function(){}; // called whenever all serial data has been processed
        that.onStartup = options.onStartup || function(){} ; // called when serial connection is established
        that.onResponse = options.onResponse || function(response){}; // called for each full serial response
        that.serialInProgress = false;
        that.serialHistory = [];
        that.msLaunchTimeout = options.msLaunchTimeout;
        that.model = model;

        return that;
    }

    FireStepDriver.prototype.open = function(options) {
        var that = this;
        if (serialport) {
            open_serialport(that, options);
        } else {
            open_firestep(that, options);
        }
        return that;
    }
    FireStepDriver.prototype.close = function(options) {
        var that = this;
        if (serialport) {
            close_serialport(that);
        } else {
            close_firestep(that);
        }
        return that;
    }
    FireStepDriver.prototype.write = function(cmd) {
        var that = this;
        that.model.writes++;
        console.log("TTY\t: WRITE(" + that.model.writes + ") " + cmd + "\\n");
        try {
            if (that.serial) {
                that.serial.write(cmd);
                that.serial.write("\n");
            } else if (that.firestep_proc) {
                if (that.firestep_proc.pid) {
                    that.firestep_proc.stdin.write(cmd);
                    that.firestep_proc.stdin.write("\n");
                } else {
                    setTimeout(function() {
                        if (that.firestep_proc.pid) {
                            that.firestep_proc.stdin.write(cmd);
                            that.firestep_proc.stdin.write("\n");
                        } else {
                            // FireStep spawn failed
                            console.log("TTY\t: firestep response TIMEOUT:" + that.msLaunchTimeout + "ms");
                            reset_serialDriver(that);
                        }
                    }, that.msLaunchTimeout);
                }
            } else {
                throw new Error("no serial driver");
            }
        } catch (e) {
            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") UNAVAILABLE:" + e);
            reset_serialDriver(that);
        }
    }
    FireStepDriver.prototype.processQueue = function() {
        var that = this;
        if (that.serialQueue.length <= 0) {
            //console.log("TTY\t: FireStepDriver.processQueue(empty) ");
        } else if (!that.model.available) {
            console.log("TTY\t: FireStepDriver.processQueue(unavailable) ", that.serialQueue.length,
                " items");
        } else if (that.serialInProgress) {
            //console.log("TTY\t: FireStepDriver.processQueue(busy) ", that.serialQueue.length, " items");
        } else {
            that.serialInProgress = true;
            var request = that.serialQueue.shift();
            that.serialHistory.splice(0, 0, request);
            that.serialHistory.splice(that.maxHistory);
            var cmd = JSON.stringify(request.cmd);
            that.write(cmd);
        }
    };

    FireStepDriver.prototype.onSerialData = function(data) {
        var that = this;
        that.model.reads++;
        console.log("TTY\t: READ(" + that.model.reads + ") " + data + "\\n");
        if (typeof data !== 'string') {
            throw new Error("expected Javascript string for serial data return");
        }
        var response;
        if (data.indexOf('{') === 0) { // success
            try {
                response = JSON.parse(data);
                if (response.s < 0) {
                    console.log("TTY\t: FireStep COMMAND FAILED:" + data);
                    console.log("TTY\t: FireStepDriver() COMMAND QUEUE CLEARED " + that.serialQueue.length + " ITEMS");
                    that.serialQueue = [];
                }
            } catch(e) {
                response = new Error("unexpected FireStep response:" + data);
            }
            that.onResponse(response);
        }

        if (that.serialInProgress && data[data.length - 1] === ' ') { // FireStep idle is SPACE-LF
            that.serialInProgress = false;
            var request = that.serialHistory.length > 0 ? that.serialHistory[0] : {};
            try {
                request.resp = JSON.parse(data);
            } catch (e) {
                console.log("TTY\t: ERROR(INVALID JSON): " + data, e);
            }
            try {
                request.onDone && originalRequest.onDone(originalRequest.resp);
            } catch (e) {
                console.log("TTY\t: ERROR(response handler failed):" + data, e);
            }
            that.processQueue();
            if (that.serialQueue.length == 0) {
                that.onIdle();
            }
        }

        return that;
    };
    FireStepDriver.prototype.history = function() {
        var that = this;
        return that.serialHistory;
    }
    FireStepDriver.prototype.pushQueue = function(cmd, onDone) {
        var that = this;
        that.serialQueue.push({
            "cmd": cmd,
            "onDone": onDone
        });
    }
    return FireStepDriver;
})();
<commit_msg>firestep-refactor<commit_after>var child_process = require('child_process');
var should = require("should");
var serialport;

try {
    serialport = require("serialport");
} catch (e) {
    serialport = null; // failover
}

module.exports.FireStepDriver = (function() {
    function reset_serialDriver(that) {
        console.log("TTY\t: FireStepDriver reset_serialDriver()");
        that.model.available = false;
        that.model.initialized = false;
        that.serialQueue = [];
    }

    function send_startup(that) {
        that.model.available = true;
        if (that.serialQueue.length > 0) {
            console.log("TTY\t: FireStepDriver send_startup() clearing queue items:", that.serialQueue.length);
            that.serialQueue = [];
        }
        that.model.initialized = false;
        that.serialInProgress = false;
        that.onStartup();
        that.processQueue();
    }

    function open_serialport(that, options) {
        console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") opening serialport");
        that.serial = new serialport.SerialPort(that.model.rest.serialPath, {
            buffersize: options.buffersize,
            parser: serialport.parsers.readline('\n'),
            baudrate: options.baudrate
        }, false);
        that.serial.on("data", function(data) {
            that.onSerialData(data);
        });
        that.serialInProgress = true;
        that.model.driver = "serialport";
        that.serial.open(function(error) {
            that.error = error;
            if (error) {
                console.log("TTY\t: FireStepDriver.open(" + that.model.rest.serialPath + ") FAILED:" + error);
                reset_serialDriver(that);
            } else {
                that.model.available = true;
                console.log("TTY\t: FireStepDriver() SerialPort.open(" + that.model.rest.serialPath + ") ready...");
                reset_serialDriver(that);
                send_startup(that);
            }
        });
    }

    function open_firestep(that, options) {
        try {
            that.firestep_proc = {}; // mark intent (actual value is set async)
            that.model.driver = "firestep";

            function onOpenSuccess(that, stdout, attempts) {
                console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") firestep reset successful. attempts:" + attempts + " stdout:" + stdout);
                that.firestep_proc = child_process.spawn('firestep', ['-d', that.model.rest.serialPath]);
                console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") firestep cli pid:" + that.firestep_proc.pid);
                that.firestep_proc.on('close', function(code) {
                    if (code) {
                        console.log("TTY\t: firestep cli ERROR:" + code);
                    } else {
                        console.log("TTY\t: firestep cli ended normally");
                    }
                });
                that.firestep_proc.stdout.on('data', function(buffer) {
                    var data = buffer.toString();
                    data = data.substr(0, data.length - 1); // chop LF to match serialport
                    //console.log("TTY\t: firestep stdout:" + data);
                    that.onSerialData(data);
                });
                that.firestep_proc.stderr.on('data', function(data) {
                    console.warn("STDERR\t: firestep => " + data);
                    reset_serialDriver(that);
                });
                send_startup(that);
            }
            var cmd = 'firestep -d ' + that.model.rest.serialPath + ' -r';
            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") " + cmd);
            var child1 = child_process.exec(cmd, function(error, stdout, stdin) {
                if (error instanceof Error) {
                    console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") attempt #1:" + error);
                    var child2 = child_process.exec(cmd, function(error, stdout, stdin) {
                        if (error instanceof Error) {
                            reset_serialDriver(that);
                            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") RETRY #1:" + error);
                        } else {
                            onOpenSuccess(that, stdout, 2);
                        }
                    });
                } else {
                    onOpenSuccess(that, stdout, 1);
                }
            });
        } catch (e) {
            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") UNAVAILABLE:" + e);
            reset_serialDriver(that);
        }
    }

    function close_serialport(that) {
        console.log("ERROR\t: close_serialport() not implemented");
    }

    function close_firestep(that) {
        if (that.isAvailable() && that.firestep_proc != null) {
            console.log("INFO\t: shutting down FireStep");
            that.firestep_proc.kill('SIGTERM');
        }
    }

    ////////////////// constructor
    function FireStepDriver(model, options) {
        var that = this;
        should.exist(model);
        options = options || {};
        options.buffersize = options.buffersize || 255;
        options.baudrate = options.baudrate || 19200;
        options.maxHistory = options.maxHistory || 50;
        options.msLaunchTimeout = options.msLaunchTimeout || 3000; // allow EEPROM commands to complete
        that.maxHistory = options.maxHistory;
        that.serialQueue = [];
        that.onIdle = options.onIdle || function(){}; // called whenever all serial data has been processed
        that.onStartup = options.onStartup || function(){} ; // called when serial connection is established
        that.onResponse = options.onResponse || function(response){}; // called for each full serial response
        that.serialInProgress = false;
        that.serialHistory = [];
        that.msLaunchTimeout = options.msLaunchTimeout;
        that.model = model;

        return that;
    }

    FireStepDriver.prototype.open = function(options) {
        var that = this;
        if (serialport) {
            open_serialport(that, options);
        } else {
            open_firestep(that, options);
        }
        return that;
    }
    FireStepDriver.prototype.close = function(options) {
        var that = this;
        if (serialport) {
            close_serialport(that);
        } else {
            close_firestep(that);
        }
        return that;
    }
    FireStepDriver.prototype.write = function(cmd) {
        var that = this;
        that.model.writes++;
        console.log("TTY\t: WRITE(" + that.model.writes + ") " + cmd + "\\n");
        try {
            if (that.serial) {
                that.serial.write(cmd);
                that.serial.write("\n");
            } else if (that.firestep_proc) {
                if (that.firestep_proc.pid) {
                    that.firestep_proc.stdin.write(cmd);
                    that.firestep_proc.stdin.write("\n");
                } else {
                    setTimeout(function() {
                        if (that.firestep_proc.pid) {
                            that.firestep_proc.stdin.write(cmd);
                            that.firestep_proc.stdin.write("\n");
                        } else {
                            // FireStep spawn failed
                            console.log("TTY\t: firestep response TIMEOUT:" + that.msLaunchTimeout + "ms");
                            reset_serialDriver(that);
                        }
                    }, that.msLaunchTimeout);
                }
            } else {
                throw new Error("no serial driver");
            }
        } catch (e) {
            console.log("TTY\t: FireStepDriver(" + that.model.rest.serialPath + ") UNAVAILABLE:" + e);
            reset_serialDriver(that);
        }
    }
    FireStepDriver.prototype.processQueue = function() {
        var that = this;
        if (that.serialQueue.length <= 0) {
            //console.log("TTY\t: FireStepDriver.processQueue(empty) ");
        } else if (!that.model.available) {
            console.log("TTY\t: FireStepDriver.processQueue(unavailable) ", that.serialQueue.length,
                " items");
        } else if (that.serialInProgress) {
            //console.log("TTY\t: FireStepDriver.processQueue(busy) ", that.serialQueue.length, " items");
        } else {
            that.serialInProgress = true;
            var request = that.serialQueue.shift();
            that.serialHistory.splice(0, 0, request);
            that.serialHistory.splice(that.maxHistory);
            var cmd = JSON.stringify(request.cmd);
            that.write(cmd);
        }
    };

    FireStepDriver.prototype.onSerialData = function(data) {
        var that = this;
        that.model.reads++;
        console.log("TTY\t: READ(" + that.model.reads + ") " + data + "\\n");
        if (typeof data !== 'string') {
            throw new Error("expected Javascript string for serial data return");
        }
        var response;
        if (data.indexOf('{') === 0) { // success
            try {
                response = JSON.parse(data);
                if (response.s < 0) {
                    console.log("TTY\t: FireStep COMMAND FAILED:" + data);
                    console.log("TTY\t: FireStepDriver() COMMAND QUEUE CLEARED " + that.serialQueue.length + " ITEMS");
                    that.serialQueue = [];
                }
            } catch(e) {
                response = new Error("unexpected FireStep response:" + data);
            }
            that.onResponse(response);
        }

        if (that.serialInProgress && data[data.length - 1] === ' ') { // FireStep idle is SPACE-LF
            that.serialInProgress = false;
            var request = that.serialHistory.length > 0 ? that.serialHistory[0] : {};
            try {
                request.resp = JSON.parse(data);
            } catch (e) {
                console.log("TTY\t: ERROR(INVALID JSON): " + data, e);
            }
            try {
                request.onDone && request.onDone(request.resp);
            } catch (e) {
                console.log("TTY\t: ERROR(response handler failed):" + data, e);
            }
            that.processQueue();
            if (that.serialQueue.length == 0) {
                that.onIdle();
            }
        }

        return that;
    };
    FireStepDriver.prototype.history = function() {
        var that = this;
        return that.serialHistory;
    }
    FireStepDriver.prototype.pushQueue = function(cmd, onDone) {
        var that = this;
        that.serialQueue.push({
            "cmd": cmd,
            "onDone": onDone
        });
    }
    return FireStepDriver;
})();
<|endoftext|>