<commit_before>'use strict';

// MODULES //

var tape = require( 'tape' );
var isnan = require( '@stdlib/math/base/utils/is-nan' );
var abs = require( '@stdlib/math/base/special/abs' );
var pow = require( '@stdlib/math/base/special/pow' );
var LN2 = require( '@stdlib/math/constants/float64-ln2' );
var PI = require( '@stdlib/math/constants/float64-pi' );
var EPS = require( '@stdlib/math/constants/float64-eps' );
var eta = require( './../lib' );

// FIXTURES //

var data = require( './fixtures/julia/results.json' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof eta, 'function', 'main export is a function' );
	t.end();
});

tape( 'if provided `NaN`, the function returns `NaN`', function test( t ) {
	var v = eta( NaN );
	t.equal( isnan( v ), true, 'returns NaN when provided a NaN' );
	t.end();
});

tape( 'the function evaluates the Dirichlet eta function', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;
	var i;

	s = data.x;
	expected = data.expected;
	for ( i = 0; i < s.length; i++ ) {
		v = eta( s[i] );
		delta = abs( v - expected[i] );

		// R: 1.1e5*eps => 2.4424906541753444e-11 => http://finzi.psych.upenn.edu/library/pracma/html/eta.html states that accuracy is 13 digits
		// Julia: 102*eps => 2.2648549702353193e-14
		tol = 102.0 * EPS * abs( expected[i] );
		t.ok( delta <= tol, 'within tolerance. s: '+s[i]+'. v: '+v+'. E: '+expected[i]+'. Δ: '+delta+'. tol: '+tol+'.' );
	}
	t.end();
});

tape( 'if provided `1`, the function returns `ln(2)` (alternating harmonic series)', function test( t ) {
	var v = eta( 1.0 );
	t.equal( v, LN2, 'returns ln(2)' );
	t.end();
});

tape( 'if provided `0`, the function returns `0.5` (Abel sum)', function test( t ) {
	var v = eta( 0.0 );
	t.equal( v, 0.5, 'returns 0.5' );
	t.end();
});

tape( 'if provided `-1`, the function returns `0.25` (Abel sum)', function test( t ) {
	var v = eta( -1.0 );
	t.equal( v, 0.25, 'returns 0.25' );
	t.end();
});

tape( 'if provided `2`, the function returns `π²/12`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	// https://oeis.org/A072691
	expected = 0.822467033424113218236207583323012594609474950603399218867779114685003735201600436916814450309879352652002;

	s = 2.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `4`, the function returns `7π**4/720`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 7.0 * pow( PI, 4 ) / 720.0;

	s = 4.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `6`, the function returns `31π**6/30240`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 31.0 * pow( PI, 6 ) / 30240.0;

	s = 6.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = 2.0 * EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `8`, the function returns `127π**8/1209600`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 127.0 * pow( PI, 8 ) / 1209600.0;

	s = 8.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = 2.0 * EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `10`, the function returns `73π**10/6842880`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 73.0 * pow( PI, 10 ) / 6842880.0;

	s = 10.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `12`, the function returns `1414477π**12/1307674368000`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 1414477.0 * pow( PI, 12 ) / 1307674368000.0;

	s = 12.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = 2.0 * EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});
<commit_msg>Adjust test tolerances<commit_after>'use strict';

// MODULES //

var tape = require( 'tape' );
var isnan = require( '@stdlib/math/base/utils/is-nan' );
var abs = require( '@stdlib/math/base/special/abs' );
var pow = require( '@stdlib/math/base/special/pow' );
var LN2 = require( '@stdlib/math/constants/float64-ln2' );
var PI = require( '@stdlib/math/constants/float64-pi' );
var EPS = require( '@stdlib/math/constants/float64-eps' );
var eta = require( './../lib' );

// FIXTURES //

var data = require( './fixtures/julia/results.json' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof eta, 'function', 'main export is a function' );
	t.end();
});

tape( 'if provided `NaN`, the function returns `NaN`', function test( t ) {
	var v = eta( NaN );
	t.equal( isnan( v ), true, 'returns NaN when provided a NaN' );
	t.end();
});

tape( 'the function evaluates the Dirichlet eta function', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;
	var i;

	s = data.x;
	expected = data.expected;
	for ( i = 0; i < s.length; i++ ) {
		v = eta( s[i] );
		delta = abs( v - expected[i] );

		// R: 1.1e5*eps => 2.4424906541753444e-11 => http://finzi.psych.upenn.edu/library/pracma/html/eta.html states that accuracy is 13 digits
		// Julia: 102*eps => 2.2648549702353193e-14
		tol = 102.0 * EPS * abs( expected[i] );
		t.ok( delta <= tol, 'within tolerance. s: '+s[i]+'. v: '+v+'. E: '+expected[i]+'. Δ: '+delta+'. tol: '+tol+'.' );
	}
	t.end();
});

tape( 'if provided `1`, the function returns `ln(2)` (alternating harmonic series)', function test( t ) {
	var v = eta( 1.0 );
	t.equal( v, LN2, 'returns ln(2)' );
	t.end();
});

tape( 'if provided `0`, the function returns `0.5` (Abel sum)', function test( t ) {
	var v = eta( 0.0 );
	t.equal( v, 0.5, 'returns 0.5' );
	t.end();
});

tape( 'if provided `-1`, the function returns `0.25` (Abel sum)', function test( t ) {
	var v = eta( -1.0 );
	t.equal( v, 0.25, 'returns 0.25' );
	t.end();
});

tape( 'if provided `2`, the function returns `π²/12`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	// https://oeis.org/A072691
	expected = 0.822467033424113218236207583323012594609474950603399218867779114685003735201600436916814450309879352652002;

	s = 2.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `4`, the function returns `7π**4/720`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 7.0 * pow( PI, 4 ) / 720.0;

	s = 4.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `6`, the function returns `31π**6/30240`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 31.0 * pow( PI, 6 ) / 30240.0;

	s = 6.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = 2.0 * EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `8`, the function returns `127π**8/1209600`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 127.0 * pow( PI, 8 ) / 1209600.0;

	s = 8.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = 2.05 * EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `10`, the function returns `73π**10/6842880`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 73.0 * pow( PI, 10 ) / 6842880.0;

	s = 10.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = EPS * 1.55 * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});

tape( 'if provided `12`, the function returns `1414477π**12/1307674368000`', function test( t ) {
	var expected;
	var delta;
	var tol;
	var s;
	var v;

	expected = 1414477.0 * pow( PI, 12 ) / 1307674368000.0;

	s = 12.0;
	v = eta( s );

	delta = abs( v - expected );
	tol = 2.05 * EPS * abs( expected );

	t.ok( delta <= tol, 'within tolerance. s: '+s+'. v: '+v+'. E: '+expected+' Δ: '+delta+'. tol: '+tol );
	t.end();
});
<|endoftext|>


<commit_before>"use strict";
var StepNC = require('../../../../../StepNCNode/build/Release/StepNC');
var file = require('./file');

var app;
var loopStates = {};
let playbackSpeed = 100;

var update = (val) => {
  app.ioServer.emit("nc:state", val);
};

var _updateSpeed = (speed) => {
  app.ioServer.emit("nc:speed", speed);
};

var _getDelta = function(ncId, ms, key, cb) {
  var response = "";
  if (key) {
    response = ms.GetKeystateJSON();
  }
  else {
    response = ms.GetDeltaJSON();
  }
  //app.logger.debug("got " + response);
  cb(response);
};

var _getNext = function(ncId, ms, cb) {
  ms.NextWS();
  //assume switch was successful
  app.logger.debug("Switched!");
  cb();
};

var _getPrev = function(ncId, ms, cb) {
  //ms.PrevWS();
  //assume switch was successful
  app.logger.debug("Switched!");
  cb();
};

var _getToWS = function(ncId, ms, cb) {
  ms.GoToWS(ncId);
  //assume switch was successful
  app.logger.debug("Switched!");
  cb();
};

var _loop = function(ncId, ms, key) {
  if (loopStates[ncId] === true) {
    //app.logger.debug("Loop step " + ncId);
    let rc = ms.AdvanceState();
    if (rc === 0) {  // OK
      //app.logger.debug("OK...");
      _getDelta(ncId, ms, key, function(b) {
        app.ioServer.emit('nc:delta', JSON.parse(b));
        if (playbackSpeed > 0)
            setTimeout(function() { _loop(ncId, ms, false); }, 50 / (playbackSpeed / 200));
        else {
          // app.logger.debug("playback speed is zero, no timeout set");
        }
      });
    }
    else if (rc == 1) {   // SWITCH
      app.logger.debug("SWITCH...");
      _getNext(ncId, ms, function() {
        _loop(ncId, ms, true);
      });
    }
  }
};

var _loopInit = function(req, res) {
  // app.logger.debug("loopstate is " + req.params.loopstate);
  if (req.params.ncId !== undefined) {
    let ncId = req.params.ncId;

    if (req.params.loopstate === undefined) {
      if (loopStates[ncId] === true) {
        res.status(200).send(JSON.stringify({'state': "play", 'speed': playbackSpeed}));
      }
      else {
        res.status(200).send(JSON.stringify({'state': "pause", 'speed': playbackSpeed}));
      }
    }
    else
    {
      let loopstate = req.params.loopstate;
      var ms = file.getMachineState(app, ncId);
      if (typeof(loopStates[ncId]) === 'undefined') {
        loopStates[ncId] = false;
      }

      switch (loopstate) {
        case "start":
          if (loopStates[ncId] === true) {
            res.status(200).send("Already running");
            return;
          }
          app.logger.debug("Looping " + ncId);
          loopStates[ncId] = true;
          res.status(200).send("OK");
          update("play");
          _loop(ncId, ms, false);
          break;
        case "stop":
          if (loopStates[ncId] === false) {
            res.status(200).send("Already stopped");
            return;
          }
          loopStates[ncId] = false;
          update("pause");
          res.status(200).send("OK");
          break;
        default:
          if (!isNaN(parseFloat(loopstate)) && isFinite(loopstate)) {
            let newSpeed = Number(loopstate);
            if (Number(playbackSpeed) === 0 && Number(loopstate) > 0 && loopStates[ncId] === true) {
              // app.logger.debug("Attempting to resume after being 0");
              playbackSpeed = newSpeed;
              _loop(ncId, ms, false);
            }
            playbackSpeed = newSpeed;
            res.status(200).send(JSON.stringify({"state": loopStates[ncId], "speed": playbackSpeed}));
            _updateSpeed(playbackSpeed);
          }
          else {
            // untested case
          }
      }
    }
  }
};

var _wsInit = function(req, res) {
  if (req.params.ncId && req.params.command) {
    let ncId = req.params.ncId;
    let command = req.params.command;
    var ms = file.getMachineState(app, ncId);
    if (typeof(loopStates[ncId]) === 'undefined') {
      loopStates[ncId] = false;
    }

    // load the machine tool using global options
    if (app.machinetool !== "")
      ms.LoadMachine(app.machinetool);

    switch(command) {
      case "next":
        var temp = loopStates[ncId];
        loopStates[ncId] = true;
        if (temp) {
        _getNext(ncId, ms, function() {
        _loop(ncId, ms, true);
        });
        loopStates[ncId] = false;
        update("pause");
        }
        else{
          _loop(ncId,ms,false);
          _getNext(ncId, ms, function() {
          _loop(ncId, ms, true);
          });
          loopStates[ncId] = false;
          update("pause");
        }
        res.status(200).send("OK");
        break;
      case "prev":
        /*var temp = loopStates[ncId];
        loopStates[ncId] = true;
        if (temp) {
        _getPrev(ncId, ms, function() {
        _loop(ncId, ms, true);
        });
        loopStates[ncId] = false;
        update("pause");
        }
        else{
          _loop(ncId,ms,false);
          _getPrev(ncId, ms, function() {
          _loop(ncId, ms, true);
          });
          loopStates[ncId] = false;
          update("pause");
        }
        res.status(200).send("OK");*/
        break;
        default:
          if (!isNaN(parseFloat(loopstate)) && isFinite(loopstate)) {
            let newSpeed = Number(loopstate);
            if (Number(playbackSpeed) === 0 && Number(loopstate) > 0 && loopStates[ncId] === true) {
              // app.logger.debug("Attempting to resume after being 0");
              playbackSpeed = newSpeed;
              _loop(ncId, ms, false);
            }
            playbackSpeed = newSpeed;
            res.status(200).send(JSON.stringify({"state": loopStates[ncId], "speed": playbackSpeed}));
            _updateSpeed(playbackSpeed);
          }
          else {
            // untested case
          }
    }
  }
};

var _getKeyState = function (req, res) {
  //app.logger.debug("KEYSTATE");
  if (req.params.ncId) {
    var ms = file.getMachineState(app, req.params.ncId);
    //FIXME: Needs to be fixed once set function for project name comes out
    var holder = JSON.parse(ms.GetKeystateJSON());
    holder["project"] = req.params.ncId;
    res.status(200).send(ms.GetKeystateJSON());
  }
};

var _getDeltaState = function (req, res) {
  if (req.params.ncId) {
    var ms = file.getMachineState(app, req.params.ncId);
    res.status(200).send(ms.GetDeltaJSON());
  }
};

module.exports = function(globalApp, cb) {
  app = globalApp;
  //app.router.get('/v2/nc/projects/:ncId', _getKeyState);
  app.router.get('/v2/nc/projects/:ncId/state/key', _getKeyState);
  app.router.get('/v2/nc/projects/:ncId/state/delta', _getDeltaState);
  app.router.get('/v2/nc/projects/:ncId/state/loop/:loopstate', _loopInit);
  app.router.get('/v2/nc/projects/:ncId/state/loop/', _loopInit);
  app.router.get('/v2/nc/projects/:ncId/state/ws/:command', _wsInit)
  if (cb) cb();
};
<commit_msg>Fixed it boys<commit_after>"use strict";
var StepNC = require('../../../../../StepNCNode/build/Release/StepNC');
var file = require('./file');

var app;
var loopStates = {};
let playbackSpeed = 100;

var update = (val) => {
  app.ioServer.emit("nc:state", val);
};

var _updateSpeed = (speed) => {
  app.ioServer.emit("nc:speed", speed);
};

//TODO: Get rid of this function and consolidate with endpoint functions if possible
var _getDelta = function(ncId, ms, key, cb) {
  var response = "";
  if (key) {
    var holder = JSON.parse(ms.GetKeystateJSON());
    holder["project"] = ncId;
    response = JSON.stringify(holder);
  }
  else {
    var holder = JSON.parse(ms.GetDeltaJSON());
    holder["project"] = ncId;
    response = JSON.stringify(holder);
  }
  //app.logger.debug("got " + response);
  cb(response);
};

var _getNext = function(ncId, ms, cb) {
  ms.NextWS();
  //assume switch was successful
  app.logger.debug("Switched!");
  cb();
};

var _getPrev = function(ncId, ms, cb) {
  //ms.PrevWS();
  //assume switch was successful
  app.logger.debug("Switched!");
  cb();
};

var _getToWS = function(ncId, ms, cb) {
  ms.GoToWS(ncId);
  //assume switch was successful
  app.logger.debug("Switched!");
  cb();
};

var _loop = function(ncId, ms, key) {
  if (loopStates[ncId] === true) {
    //app.logger.debug("Loop step " + ncId);
    let rc = ms.AdvanceState();
    if (rc === 0) {  // OK
      //app.logger.debug("OK...");
      _getDelta(ncId, ms, key, function(b) {
        app.ioServer.emit('nc:delta', JSON.parse(b));
        if (playbackSpeed > 0)
            setTimeout(function() { _loop(ncId, ms, false); }, 50 / (playbackSpeed / 200));
        else {
          // app.logger.debug("playback speed is zero, no timeout set");
        }
      });
    }
    else if (rc == 1) {   // SWITCH
      app.logger.debug("SWITCH...");
      _getNext(ncId, ms, function() {
        _loop(ncId, ms, true);
      });
    }
  }
};

var _loopInit = function(req, res) {
  // app.logger.debug("loopstate is " + req.params.loopstate);
  if (req.params.ncId !== undefined) {
    let ncId = req.params.ncId;

    if (req.params.loopstate === undefined) {
      if (loopStates[ncId] === true) {
        res.status(200).send(JSON.stringify({'state': "play", 'speed': playbackSpeed}));
      }
      else {
        res.status(200).send(JSON.stringify({'state': "pause", 'speed': playbackSpeed}));
      }
    }
    else
    {
      let loopstate = req.params.loopstate;
      var ms = file.getMachineState(app, ncId);
      if (typeof(loopStates[ncId]) === 'undefined') {
        loopStates[ncId] = false;
      }

      switch (loopstate) {
        case "start":
          if (loopStates[ncId] === true) {
            res.status(200).send("Already running");
            return;
          }
          app.logger.debug("Looping " + ncId);
          loopStates[ncId] = true;
          res.status(200).send("OK");
          update("play");
          _loop(ncId, ms, false);
          break;
        case "stop":
          if (loopStates[ncId] === false) {
            res.status(200).send("Already stopped");
            return;
          }
          loopStates[ncId] = false;
          update("pause");
          res.status(200).send("OK");
          break;
        default:
          if (!isNaN(parseFloat(loopstate)) && isFinite(loopstate)) {
            let newSpeed = Number(loopstate);
            if (Number(playbackSpeed) === 0 && Number(loopstate) > 0 && loopStates[ncId] === true) {
              // app.logger.debug("Attempting to resume after being 0");
              playbackSpeed = newSpeed;
              _loop(ncId, ms, false);
            }
            playbackSpeed = newSpeed;
            res.status(200).send(JSON.stringify({"state": loopStates[ncId], "speed": playbackSpeed}));
            _updateSpeed(playbackSpeed);
          }
          else {
            // untested case
          }
      }
    }
  }
};

var _wsInit = function(req, res) {
  if (req.params.ncId && req.params.command) {
    let ncId = req.params.ncId;
    let command = req.params.command;
    var ms = file.getMachineState(app, ncId);
    if (typeof(loopStates[ncId]) === 'undefined') {
      loopStates[ncId] = false;
    }

    // load the machine tool using global options
    if (app.machinetool !== "")
      ms.LoadMachine(app.machinetool);

    switch(command) {
      case "next":
        var temp = loopStates[ncId];
        loopStates[ncId] = true;
        if (temp) {
        _getNext(ncId, ms, function() {
        _loop(ncId, ms, true);
        });
        loopStates[ncId] = false;
        update("pause");
        }
        else{
          _loop(ncId,ms,false);
          _getNext(ncId, ms, function() {
          _loop(ncId, ms, true);
          });
          loopStates[ncId] = false;
          update("pause");
        }
        res.status(200).send("OK");
        break;
      case "prev":
        /*var temp = loopStates[ncId];
        loopStates[ncId] = true;
        if (temp) {
        _getPrev(ncId, ms, function() {
        _loop(ncId, ms, true);
        });
        loopStates[ncId] = false;
        update("pause");
        }
        else{
          _loop(ncId,ms,false);
          _getPrev(ncId, ms, function() {
          _loop(ncId, ms, true);
          });
          loopStates[ncId] = false;
          update("pause");
        }
        res.status(200).send("OK");*/
        break;
        default:
          if (!isNaN(parseFloat(loopstate)) && isFinite(loopstate)) {
            let newSpeed = Number(loopstate);
            if (Number(playbackSpeed) === 0 && Number(loopstate) > 0 && loopStates[ncId] === true) {
              // app.logger.debug("Attempting to resume after being 0");
              playbackSpeed = newSpeed;
              _loop(ncId, ms, false);
            }
            playbackSpeed = newSpeed;
            res.status(200).send(JSON.stringify({"state": loopStates[ncId], "speed": playbackSpeed}));
            _updateSpeed(playbackSpeed);
          }
          else {
            // untested case
          }
    }
  }
};

var _getKeyState = function (req, res) {
  //app.logger.debug("KEYSTATE");
  if (req.params.ncId) {
    var ms = file.getMachineState(app, req.params.ncId);
    //FIXME: Needs to be fixed once set function for project name comes out
    var holder = JSON.parse(ms.GetKeystateJSON());
    holder["project"] = req.params.ncId;
    res.status(200).send(holder);
  }
};

var _getDeltaState = function (req, res) {
  if (req.params.ncId) {
    var ms = file.getMachineState(app, req.params.ncId);
    var holder = JSON.parse(ms.GetDeltaJSON());
    holder["project"] = req.params.ncId;
    res.status(200).send(holder);
  }
};

module.exports = function(globalApp, cb) {
  app = globalApp;
  //app.router.get('/v2/nc/projects/:ncId', _getKeyState);
  app.router.get('/v2/nc/projects/:ncId/state/key', _getKeyState);
  app.router.get('/v2/nc/projects/:ncId/state/delta', _getDeltaState);
  app.router.get('/v2/nc/projects/:ncId/state/loop/:loopstate', _loopInit);
  app.router.get('/v2/nc/projects/:ncId/state/loop/', _loopInit);
  app.router.get('/v2/nc/projects/:ncId/state/ws/:command', _wsInit)
  if (cb) cb();
};
<|endoftext|>


<commit_before>// @flow
// All configuration values for timeouts / intervals should go here
export const REPORT_ISSUE_TIME_TRIGGER = 5 * 60; // 5 minutes | unit: seconds
export const ADDRESS_COPY_NOTIFICATION_DURATION = 10; // unit: seconds
export const ADDRESS_COPY_NOTIFICATION_SMALL_DURATION = 3; // unit: seconds
export const DELETE_WALLET_COUNTDOWN = 10; // unit: seconds
export const FORM_VALIDATION_DEBOUNCE_WAIT = 250; // unit: milliseconds
export const NODE_UPDATE_POLL_INTERVAL = 5000; // unit: milliseconds
export const ALLOWED_TIME_DIFFERENCE = 15 * 1000000; // 15 seconds | unit: microseconds
export const MAX_ALLOWED_STALL_DURATION = 2 * 60 * 1000; // 2 minutes | unit: milliseconds
export const NETWORK_STATUS_REQUEST_TIMEOUT = 30 * 1000; // 30 seconds | unit: milliseconds
export const NETWORK_STATUS_POLL_INTERVAL = 2000; // 2 seconds | unit: milliseconds
export const NTP_FORCE_CHECK_POLL_INTERVAL = 30 * 60 * 1000; // 30 minutes | unit: milliseconds
export const NTP_IGNORE_CHECKS_GRACE_PERIOD = 10 * 1000; // 30 seconds | unit: milliseconds
<commit_msg>[DDW-514] fix ntp check timing<commit_after>// @flow
// All configuration values for timeouts / intervals should go here
export const REPORT_ISSUE_TIME_TRIGGER = 5 * 60; // 5 minutes | unit: seconds
export const ADDRESS_COPY_NOTIFICATION_DURATION = 10; // unit: seconds
export const ADDRESS_COPY_NOTIFICATION_SMALL_DURATION = 3; // unit: seconds
export const DELETE_WALLET_COUNTDOWN = 10; // unit: seconds
export const FORM_VALIDATION_DEBOUNCE_WAIT = 250; // unit: milliseconds
export const NODE_UPDATE_POLL_INTERVAL = 5000; // unit: milliseconds
export const ALLOWED_TIME_DIFFERENCE = 15 * 1000000; // 15 seconds | unit: microseconds
export const MAX_ALLOWED_STALL_DURATION = 2 * 60 * 1000; // 2 minutes | unit: milliseconds
export const NETWORK_STATUS_REQUEST_TIMEOUT = 30 * 1000; // 30 seconds | unit: milliseconds
export const NETWORK_STATUS_POLL_INTERVAL = 2000; // 2 seconds | unit: milliseconds
export const NTP_FORCE_CHECK_POLL_INTERVAL = 30 * 60 * 1000; // 30 minutes | unit: milliseconds
export const NTP_IGNORE_CHECKS_GRACE_PERIOD = 30 * 1000; // 30 seconds | unit: milliseconds
<|endoftext|>


<commit_before>// auto-expand.js

(function($){

$(window).bind('resize', fillOutTextArea);
$(document).bind('change', findAutoExpandTextArea);
$(document).bind('keypress', findAutoExpandTextArea);
$(document).bind('input', findAutoExpandTextArea);
$(document).bind('paste', findAutoExpandTextArea);
$(document).bind("DOMNodeInserted", findAutoExpandTextArea);
$(document).ready(function(){setTimeout(fillOutTextArea, 0)});
$(window).load(function(event){
    fillOutTextArea();
});

function fillOutTextArea(){
    findAutoExpandTextAreaIn(document);
}

function findAutoExpandTextArea(event) {
    findAutoExpandTextAreaIn(event.target);
}

function findAutoExpandTextAreaIn(target) {
    var areas = $(".auto-expand", target);
    if ($(target).is(".auto-expand")) {
        areas = areas.add(target);
    }
    for (var i = 0; i < areas.length; i++) {
        expand(areas[i]);
    }
    $(areas).unbind("paste", targetAutoExpandTextArea);
    $(areas).bind("paste", targetAutoExpandTextArea);
}

function targetAutoExpandTextArea(event) {
    if (event.target && event.target.className.match(/\bauto-expand\b/)) {
        setTimeout(function(){
            expand(event.target);
        }, 0);
    }
}

function expand(area) {
    var contentWidth = getAvailableWidth(area);
    var contentHeight = getAvailableHeight(area);
    if ($(area).is(":input")) {
        expandTextArea(area, contentWidth, contentHeight);
    }
}

function expandTextArea(area, contentWidth, innerHeight) {
    var width = area.cols || area.size;
    var height = area.rows;
    var maxCols = Math.min(Math.floor(contentWidth / area.offsetWidth * width - 3), Math.floor(contentWidth / 8 - 3));
    var maxRows = Math.floor(innerHeight / area.offsetHeight * height - 3);
    if (!maxCols || maxCols < 20) {
        maxCols = 96;
    }
    if (!maxRows || maxRows < 3) {
        maxRows = 43;
    }
    var lines = area.value.split("\n");
    var cols = 20;
    var rows = Math.max(1, lines.length);
    for (var i = 0; i < lines.length; i++) {
        var len = lines[i].replace(/\t/g, "        ").length;
        if (cols < len + 1) {
            cols = len + 1;
        }
        rows += Math.floor(len / maxCols);
    }
    if (area.type == "textarea") {
        area.cols = Math.min(maxCols, cols + 1);
        area.rows = Math.min(maxRows, rows + 1);
    } else {
        area.size = Math.min(maxCols, cols + 1);
    }
}

function getAvailableHeight(area) {
    var innerHeight = $(area).height();
    var clientHeight = window.innerHeight || document.documentElement.clientHeight;

    var body = bottom($(area).parents('body>*')) - innerHeight;
    if (body > 0 && body <= clientHeight / 3)
        return clientHeight - body;
    var content = bottom($(area).parents('#content')) - innerHeight;
    if (content > 0 && content <= clientHeight / 3)
        return clientHeight - content;
    var form = bottom($(area).parents('form')) - innerHeight;
    if (form > 0 && form <= clientHeight / 3)
        return clientHeight - form;
    var formHeight = $(area).parents('form').outerHeight(true) - innerHeight;
    if (formHeight > 0 && formHeight <= clientHeight / 3)
        return clientHeight - formHeight;
    return clientHeight;
}

function bottom(element) {
    if ($(element).length)
        return $(element).offset().top + $(element).outerHeight(true);
    return null;
}

function getAvailableWidth(area) {
    var parent = getParentBlock(area);
    var margin = $(area).outerWidth(true) - $(area).width();
    var breakFlag = false;
    $(area).parents().each(function(){
        if (this == parent) {
            breakFlag = true;
        } else if (!breakFlag) {
            margin += $(this).outerWidth(true) - $(this).width();
        }
    });
    var asideLeft = getAsideLeft(area);
    if ($(parent).offset().left + $(parent).outerWidth(true) <= asideLeft)
        return $(parent).width() - margin;
    return $(parent).width() - margin - $(parent).offset().left - $(parent).outerWidth(true) + asideLeft;
}

function getParentBlock(area) {
    var parent = null;
    var parents = $(area).parents();
    for (var i=0;i<parents.length;i++) {
        parent = parents[i];
        var floatStyle = $(parents[i]).css('float');
        if (floatStyle && floatStyle != 'none')
            break;
        if ($(parents[i]).css('display') == 'block')
            break;
    }
    if (parent)
        return parent;
    return $('body')[0];
}

function getAsideLeft(area) {
    var clientWidth = document.documentElement.clientWidth;
    var asideLeft = clientWidth;
    var areaTop = $(area).offset().top;
    $("#sidebar,.aside:visible,aside:visible").filter(function(){
        var top = $(this).offset().top;
        return areaTop < top + $(this).outerHeight(true) && areaTop + $(area).outerHeight(true) > top;
    }).each(function() {
        var left = $(this).offset().left;
        left -= parsePixel($(this).css("margin-left"));
        if (left < asideLeft) {
            asideLeft = left;
        }
    });
    return asideLeft;
}

function parsePixel(str) {
    if (!str)
        return 0;
    if (str.indexOf('px') > 0)
        return parseInt(str.substring(0, str.indexOf('px')));
    if (str == "thin")
        return 1;
    if (str == "medium")
        return 3;
    if (str == "thick")
        return 5;
    return 0;
}

$(window).bind('resize', fillOutFlex);
$(window).load(function(event){
    $('iframe').load(fillOutFlex);
    $('img').load(fillOutFlex);
    fillOutFlex();
});

function fillOutFlex(){
    var areas = $(".flex");
    areas.each(function() {
        flex(this);
    });
}

function flex(area) {
    if ($(area).is(":input")) {
        var contentWidth = getAvailableWidth(area);
        var contentHeight = getAvailableHeight(area);
        flexInput(area, contentWidth, contentHeight);
    } else if (area.nodeName.toLowerCase() == "iframe") {
        var contentWidth = getAvailableWidth(area);
        var contentHeight = getAvailableHeight(area);
        flexIframe(area, contentWidth, contentHeight);
    } else {
        var contentWidth = getAvailableWidth(area);
        var contentHeight = getAvailableHeight(area);
        flexBlock(area, contentWidth, contentHeight);
    }
}

function flexInput(area, contentWidth, innerHeight) {
    $(area).css('width', contentWidth);
    $(area).css('height', innerHeight);
    if (area.scrollHeight > innerHeight && window.parent != window) {
        var clientHeight = window.innerHeight || document.documentElement.clientHeight;
        var height = clientHeight + area.scrollHeight - innerHeight;
        parent.postMessage('PUT height\n\n' + height, '*');
    } else if (area.scrollWidth > contentWidth) {
        var clientWidth = document.documentElement.clientWidth;
        var width = clientWidth + area.scrollWidth - contentWidth;
        parent.postMessage('PUT width\n\n' + width, '*');
    }
}

function flexIframe(iframe, contentWidth, innerHeight) {
    $(iframe).css('width', contentWidth);
    $(iframe).css('height', innerHeight);
}

function flexBlock(area, contentWidth, innerHeight) {
    $(area).css('width', contentWidth);
    $(area).css('max-height', innerHeight);
    $(area).css('overflow', 'auto');
    if (area.scrollHeight > innerHeight && window.parent != window) {
        var clientHeight = window.innerHeight || document.documentElement.clientHeight;
        var height = clientHeight + area.scrollHeight - innerHeight;
        parent.postMessage('PUT height\n\n' + height, '*');
    } else if (area.scrollWidth > contentWidth) {
        var clientWidth = document.documentElement.clientWidth;
        var width = clientWidth + area.scrollWidth - contentWidth;
        parent.postMessage('PUT width\n\n' + width, '*');
    }
}

if (window.parent != window) {
    $(window).bind('load', function() {
        setTimeout(function() {
            var innerHeight = window.innerHeight || document.documentElement.clientHeight;
            if (innerHeight < document.height) {
                parent.postMessage('PUT height\n\n' + document.height, '*');
            } else {
                var maxHeight = innerHeight;
                $('.flex').each(function() {
                    if (this.scrollHeight > this.clientHeight) {
                        var height = document.documentElement.scrollHeight + this.scrollHeight - this.clientHeight;
                        if (height > maxHeight) {
                            maxHeight = height;
                        }
                    }
                });
                if (maxHeight > innerHeight) {
                    parent.postMessage('PUT height\n\n' + maxHeight, '*');
                }
            }
            var clientWidth = document.documentElement.clientWidth;
            if (clientWidth < document.documentElement.scrollWidth) {
                parent.postMessage('PUT width\n\n' + document.documentElement.scrollWidth, '*');
            } else {
                var maxWidth = clientWidth;
                $('.flex').each(function() {
                    if (this.scrollWidth > this.clientWidth) {
                        var width = clientWidth + this.scrollWidth - this.clientWidth;
                        if (width > maxWidth) {
                            maxWidth = width;
                        }
                    }
                });
                if (maxWidth > clientWidth) {
                    parent.postMessage('PUT width\n\n' + maxWidth, '*');
                }
            }
        }, 0);
    });
    $(window).bind('message', function(event) {
        var source = event.originalEvent.source;
        var data = event.originalEvent.data;
        if (data.indexOf('PUT height\n\n') == 0) {
            $('iframe.flex').each(function() {
                if (this.contentWindow == source) {
                    var innerHeight = window.innerHeight || document.documentElement.clientHeight;
                    var height = parseInt(data.substring(data.indexOf('\n\n') + 2));
                    height += innerHeight - $(this).height();
                    parent.postMessage('PUT height\n\n' + height, '*');
                    this.contentWindow.postMessage('OK\n\nPUT height', '*');
                }
            });
        } else if (data.indexOf('PUT width\n\n') == 0) {
            $('iframe.flex').each(function() {
                if (this.contentWindow == source) {
                    var width = parseInt(data.substring(data.indexOf('\n\n') + 2));
                    width += document.documentElement.scrollWidth - $(this).width();
                    parent.postMessage('PUT width\n\n' + width, '*');
                    this.contentWindow.postMessage('OK\n\nPUT width', '*');
                }
            });
        }
    });
}

})(jQuery);

<commit_msg>Check size of #content before wrapper in auto-expand<commit_after>// auto-expand.js

(function($){

$(window).bind('resize', fillOutTextArea);
$(document).bind('change', findAutoExpandTextArea);
$(document).bind('keypress', findAutoExpandTextArea);
$(document).bind('input', findAutoExpandTextArea);
$(document).bind('paste', findAutoExpandTextArea);
$(document).bind("DOMNodeInserted", findAutoExpandTextArea);
$(document).ready(function(){setTimeout(fillOutTextArea, 0)});
$(window).load(function(event){
    fillOutTextArea();
});

function fillOutTextArea(){
    findAutoExpandTextAreaIn(document);
}

function findAutoExpandTextArea(event) {
    findAutoExpandTextAreaIn(event.target);
}

function findAutoExpandTextAreaIn(target) {
    var areas = $(".auto-expand", target);
    if ($(target).is(".auto-expand")) {
        areas = areas.add(target);
    }
    for (var i = 0; i < areas.length; i++) {
        expand(areas[i]);
    }
    $(areas).unbind("paste", targetAutoExpandTextArea);
    $(areas).bind("paste", targetAutoExpandTextArea);
}

function targetAutoExpandTextArea(event) {
    if (event.target && event.target.className.match(/\bauto-expand\b/)) {
        setTimeout(function(){
            expand(event.target);
        }, 0);
    }
}

function expand(area) {
    var contentWidth = getAvailableWidth(area);
    var contentHeight = getAvailableHeight(area);
    if ($(area).is(":input")) {
        expandTextArea(area, contentWidth, contentHeight);
    }
}

function expandTextArea(area, contentWidth, innerHeight) {
    var width = area.cols || area.size;
    var height = area.rows;
    var maxCols = Math.min(Math.floor(contentWidth / area.offsetWidth * width - 3), Math.floor(contentWidth / 8 - 3));
    var maxRows = Math.floor(innerHeight / area.offsetHeight * height - 3);
    if (!maxCols || maxCols < 20) {
        maxCols = 96;
    }
    if (!maxRows || maxRows < 3) {
        maxRows = 43;
    }
    var lines = area.value.split("\n");
    var cols = 20;
    var rows = Math.max(1, lines.length);
    for (var i = 0; i < lines.length; i++) {
        var len = lines[i].replace(/\t/g, "        ").length;
        if (cols < len + 1) {
            cols = len + 1;
        }
        rows += Math.floor(len / maxCols);
    }
    if (area.type == "textarea") {
        area.cols = Math.min(maxCols, cols + 1);
        area.rows = Math.min(maxRows, rows + 1);
    } else {
        area.size = Math.min(maxCols, cols + 1);
    }
}

function getAvailableHeight(area) {
    var innerHeight = $(area).height();
    var clientHeight = window.innerHeight || document.documentElement.clientHeight;

    var container = $(area).parents('form');
    if (!container.length) {
        container = $(area).parents('#content');
        if (!container.length) {
            container = $(area).parents('body>*');
        }
    }
    var form = bottom(container) - innerHeight;
    if (form > 0 && form <= clientHeight / 3)
        return clientHeight - form;
    var formHeight = container.outerHeight(true) - innerHeight;
    if (formHeight > 0 && formHeight <= clientHeight / 3)
        return clientHeight - formHeight;
    return clientHeight;
}

function bottom(element) {
    if ($(element).length)
        return $(element).offset().top + $(element).outerHeight(true);
    return null;
}

function getAvailableWidth(area) {
    var parent = getParentBlock(area);
    var margin = $(area).outerWidth(true) - $(area).width();
    var breakFlag = false;
    $(area).parents().each(function(){
        if (this == parent) {
            breakFlag = true;
        } else if (!breakFlag) {
            margin += $(this).outerWidth(true) - $(this).width();
        }
    });
    var asideLeft = getAsideLeft(area);
    if ($(parent).offset().left + $(parent).outerWidth(true) <= asideLeft)
        return $(parent).width() - margin;
    return $(parent).width() - margin - $(parent).offset().left - $(parent).outerWidth(true) + asideLeft;
}

function getParentBlock(area) {
    var parent = null;
    var parents = $(area).parents();
    for (var i=0;i<parents.length;i++) {
        parent = parents[i];
        var floatStyle = $(parents[i]).css('float');
        if (floatStyle && floatStyle != 'none')
            break;
        if ($(parents[i]).css('display') == 'block')
            break;
    }
    if (parent)
        return parent;
    return $('body')[0];
}

function getAsideLeft(area) {
    var clientWidth = document.documentElement.clientWidth;
    var asideLeft = clientWidth;
    var areaTop = $(area).offset().top;
    $("#sidebar,.aside:visible,aside:visible").filter(function(){
        var top = $(this).offset().top;
        return areaTop < top + $(this).outerHeight(true) && areaTop + $(area).outerHeight(true) > top;
    }).each(function() {
        var left = $(this).offset().left;
        left -= parsePixel($(this).css("margin-left"));
        if (left < asideLeft) {
            asideLeft = left;
        }
    });
    return asideLeft;
}

function parsePixel(str) {
    if (!str)
        return 0;
    if (str.indexOf('px') > 0)
        return parseInt(str.substring(0, str.indexOf('px')));
    if (str == "thin")
        return 1;
    if (str == "medium")
        return 3;
    if (str == "thick")
        return 5;
    return 0;
}

$(window).bind('resize', fillOutFlex);
$(window).load(function(event){
    $('iframe').load(fillOutFlex);
    $('img').load(fillOutFlex);
    fillOutFlex();
});

function fillOutFlex(){
    var areas = $(".flex");
    areas.each(function() {
        flex(this);
    });
}

function flex(area) {
    if ($(area).is(":input")) {
        var contentWidth = getAvailableWidth(area);
        var contentHeight = getAvailableHeight(area);
        flexInput(area, contentWidth, contentHeight);
    } else if (area.nodeName.toLowerCase() == "iframe") {
        var contentWidth = getAvailableWidth(area);
        var contentHeight = getAvailableHeight(area);
        flexIframe(area, contentWidth, contentHeight);
    } else {
        var contentWidth = getAvailableWidth(area);
        var contentHeight = getAvailableHeight(area);
        flexBlock(area, contentWidth, contentHeight);
    }
}

function flexInput(area, contentWidth, innerHeight) {
    $(area).css('width', contentWidth);
    $(area).css('height', innerHeight);
    if (area.scrollHeight > innerHeight && window.parent != window) {
        var clientHeight = window.innerHeight || document.documentElement.clientHeight;
        var height = clientHeight + area.scrollHeight - innerHeight;
        parent.postMessage('PUT height\n\n' + height, '*');
    } else if (area.scrollWidth > contentWidth) {
        var clientWidth = document.documentElement.clientWidth;
        var width = clientWidth + area.scrollWidth - contentWidth;
        parent.postMessage('PUT width\n\n' + width, '*');
    }
}

function flexIframe(iframe, contentWidth, innerHeight) {
    $(iframe).css('width', contentWidth);
    $(iframe).css('height', innerHeight);
}

function flexBlock(area, contentWidth, innerHeight) {
    $(area).css('width', contentWidth);
    $(area).css('max-height', innerHeight);
    $(area).css('overflow', 'auto');
    if (area.scrollHeight > innerHeight && window.parent != window) {
        var clientHeight = window.innerHeight || document.documentElement.clientHeight;
        var height = clientHeight + area.scrollHeight - innerHeight;
        parent.postMessage('PUT height\n\n' + height, '*');
    } else if (area.scrollWidth > contentWidth) {
        var clientWidth = document.documentElement.clientWidth;
        var width = clientWidth + area.scrollWidth - contentWidth;
        parent.postMessage('PUT width\n\n' + width, '*');
    }
}

if (window.parent != window) {
    $(window).bind('load', function() {
        setTimeout(function() {
            var innerHeight = window.innerHeight || document.documentElement.clientHeight;
            if (innerHeight < document.height) {
                parent.postMessage('PUT height\n\n' + document.height, '*');
            } else {
                var maxHeight = innerHeight;
                $('.flex').each(function() {
                    if (this.scrollHeight > this.clientHeight) {
                        var height = document.documentElement.scrollHeight + this.scrollHeight - this.clientHeight;
                        if (height > maxHeight) {
                            maxHeight = height;
                        }
                    }
                });
                if (maxHeight > innerHeight) {
                    parent.postMessage('PUT height\n\n' + maxHeight, '*');
                }
            }
            var clientWidth = document.documentElement.clientWidth;
            if (clientWidth < document.documentElement.scrollWidth) {
                parent.postMessage('PUT width\n\n' + document.documentElement.scrollWidth, '*');
            } else {
                var maxWidth = clientWidth;
                $('.flex').each(function() {
                    if (this.scrollWidth > this.clientWidth) {
                        var width = clientWidth + this.scrollWidth - this.clientWidth;
                        if (width > maxWidth) {
                            maxWidth = width;
                        }
                    }
                });
                if (maxWidth > clientWidth) {
                    parent.postMessage('PUT width\n\n' + maxWidth, '*');
                }
            }
        }, 0);
    });
    $(window).bind('message', function(event) {
        var source = event.originalEvent.source;
        var data = event.originalEvent.data;
        if (data.indexOf('PUT height\n\n') == 0) {
            $('iframe.flex').each(function() {
                if (this.contentWindow == source) {
                    var innerHeight = window.innerHeight || document.documentElement.clientHeight;
                    var height = parseInt(data.substring(data.indexOf('\n\n') + 2));
                    height += innerHeight - $(this).height();
                    parent.postMessage('PUT height\n\n' + height, '*');
                    this.contentWindow.postMessage('OK\n\nPUT height', '*');
                }
            });
        } else if (data.indexOf('PUT width\n\n') == 0) {
            $('iframe.flex').each(function() {
                if (this.contentWindow == source) {
                    var width = parseInt(data.substring(data.indexOf('\n\n') + 2));
                    width += document.documentElement.scrollWidth - $(this).width();
                    parent.postMessage('PUT width\n\n' + width, '*');
                    this.contentWindow.postMessage('OK\n\nPUT width', '*');
                }
            });
        }
    });
}

})(jQuery);

<|endoftext|>


<commit_before>module.exports = {
    title: 'PhotosynQ Documentation',
    description: 'Documentation on how to use the PhotosynQ platform, including the web, mobile and desktop applications as well as the instruments.',
    head: [
        ['link', { rel: 'icon', href: '/icons/favicon-32x32.png' }],
        ['link', { rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.css' }],
        ['link', { rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.css' }],
        ['script', {src: 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.0.0/mermaid.min.js' }]

    ],
    base: '/',
    home: true,
    locales: {
        '/': {
            lang: 'en-US',
            title: 'PhotosynQ Documentation',
            description: 'Documentation on how to use the PhotosynQ platform, including the web, mobile and desktop applications as well as the instruments.'
        }
    },
    markdown: {
        lineNumbers: true,
        externalLinks: { target: '_blank', rel: 'noopener noreferrer' },
        toc: { includeLevel: [2, 3] },
        extendMarkdown: md => {
            md.set({ breaks: true });
            md.use(require('markdown-it-katex'));
            md.use(require('markdown-it-mermaid').default);
        }
    },
    extend: '@vuepress/theme-default',
    themeConfig: {
        logo: '/logo_menu.svg',
        nav: [
            {
                text: 'Documents',
                items: [
                    {
                        text: 'Documents (PDF)',
                        items: [
                            { text: 'Help Manual', link: 'https://help.photosynq.org/downloads/PhotosynQ-Help-Manual.pdf' },
                            { text: 'Tutorials', link: 'https://help.photosynq.org/downloads/PhotosynQ-Getting-Started.pdf' },
                            { text: 'MultispeQ v1.0', link: 'https://help.photosynq.org/downloads/PhotosynQ-MultispeQ-v1.0.pdf' },
                            { text: 'MultispeQ v2.0', link: 'https://help.photosynq.org/downloads/PhotosynQ-MultispeQ-v2.0.pdf' },
                            { text: 'Firmware', link: 'https://help.photosynq.org/downloads/PhotosynQ-Firmware.pdf' }
                        ]
                    },
                    {
                        text: 'E-Books (epub)',
                        items: [
                            { text: 'Full Documentation', link: 'https://help.photosynq.org/downloads/PhotosynQ-Documentation.epub' }
                        ]
                    }
                ]
            },
            {
                text: 'More',
                items: [
                    {
                        text: 'Resources',
                        items: [
                            { text: 'Forums', link: 'https://photosynq.org/forums' },
                            { text: 'Blog', link: 'https://help.photosynq.org' }
                        ]
                    },
                    {
                        text: 'Firmware',
                        items: [
                            { text: 'Latest Firmware', link: 'https://github.com/Photosynq/MultispeQ-Firmware/releases/latest' },
                            { text: 'Changelog', link: 'https://github.com/Photosynq/MultispeQ-Firmware/releases' }
                        ]
                    },
                    {
                        text: 'Documentation',
                        items: [
                            { text: 'Contribute', link: 'https://github.com/Photosynq/PhotosynQ-Documentation/blob/master/contributing.md' },
                            { text: 'Changelog', link: 'https://github.com/photosynq/PhotosynQ-Documentation/releases' }
                        ]
                    }
                ]
            },
            { text: 'PhotosynQ', link: 'https://photosynq.org' }
        ],
        sidebar: [
            '/',
            {
                title: 'Account',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'account/create-an-account',
                    'account/edit-your-profile',
                    'account/manage-your-password',
                    'account/sign-in-to-photosynq',
                    'account/change-your-email-address',
                    'account/your-subscriptions',
                    'account/project-invitations',
                    'account/sign-out-of-photosynq',
                    'account/deactivate-an-account',
                    'account/photosynq-glossary'
                ]
            },
            {
                title: 'Mobile Application',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'mobile-application/photosynq-for-android',
                    'mobile-application/installation',
                    'mobile-application/general',
                    'mobile-application/connect-an-instrument',
                    'mobile-application/settings',
                    'mobile-application/projects',
                    'mobile-application/discover',
                    'mobile-application/quick-measurements',
                    'mobile-application/measurements',
                    'mobile-application/adding-notes-and-pictures'
                ]
            },
            {
                title: 'Desktop Application',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'desktop-application/installation',
                    'desktop-application/general',
                    'desktop-application/connect-an-instrument',
                    'desktop-application/settings',
                    'desktop-application/projects',
                    'desktop-application/adding-notes-and-pictures',
                    'desktop-application/notebook',
                    'desktop-application/protocols',
                    'desktop-application/macros',
                    'desktop-application/protocol-editor',
                    'desktop-application/macro-editor',
                    'desktop-application/console'
                ]
            },
            {
                title: 'Projects',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'projects/what-are-projects',
                    'projects/join-a-project',
                    'projects/create-a-new-project',
                    'projects/select-a-protocol',
                    'projects/adding-project-questions',
                    'projects/project-locations',
                    'projects/adding-a-project-description',
                    'projects/editing-a-project',
                    'projects/managing-project-settings',
                    'projects/project-collaborators',
                    'projects/import-custom-data',
                    'projects/barcodes',
                    'projects/large-scale-projects'
                ]
            },
            {
                title: 'View & Analyze Data',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'view-and-analyze-data/view-your-project-data',
                    'view-and-analyze-data/the-dashboard',
                    'view-and-analyze-data/plot-data',
                    'view-and-analyze-data/the-map',
                    'view-and-analyze-data/the-spreadsheet',
                    'view-and-analyze-data/statistics',
                    'view-and-analyze-data/series-data-subsets',
                    'view-and-analyze-data/filter-data',
                    'view-and-analyze-data/set-thresholds',
                    'view-and-analyze-data/viewing-notes-and-pictures',
                    'view-and-analyze-data/advanced-parameters',
                    'view-and-analyze-data/data-issues',
                    'view-and-analyze-data/flag-measurements',
                    'view-and-analyze-data/sessions',
                    'view-and-analyze-data/download-data',
                    'view-and-analyze-data/external-libraries',
                    'view-and-analyze-data/how-to-cite'
                ]
            },
            {
                title: 'Protocols',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'protocols/protocol-basics',
                    'protocols/run-a-protocol',
                    'protocols/create-edit-protocol',
                    'protocols/repeats',
                    'protocols/variables',
                    'protocols/commands'
                ]
            },
            {
                title: 'Macros',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'macros/macro-basics',
                    'macros/create-edit-a-macro',
                    'macros/coding-and-functions',
                    'macros/provided-functions'
                ]
            },
            {
                title: 'Instruments',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'instruments/multispeq-v2.0',
                    'instruments/multispeq-v2.0-configuration',
                    'instruments/multispeq-v1.0-configuration',
                    'instruments/what-does-the-multispeq-measure',
                    'instruments/instrument-settings',
                    'instruments/instrument-calibrations',
                    'instruments/firmware-updates',
                    'instruments/console-commands',
                    'instruments/light-guide-mask'
                ]
            },
            {
                title: 'Tutorials',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'tutorials/getting-started',
                    'tutorials/creating-a-project',
                    'tutorials/data-collection',
                    'tutorials/data-viewing',
                    'tutorials/data-quality',
                    'tutorials/data-analysis',
                    'tutorials/building-a-protocol',
                    'tutorials/building-an-advanced-protocol',
                    'tutorials/building-a-macro',
                    'tutorials/building-an-advanced-macro'
                ]
            }
        ],
        lastUpdated: 'Last Updated',
        repo: 'photosynq/PhotosynQ-Documentation',
        // repoLabel: 'Contribute!',
        docsRepo: 'photosynq/PhotosynQ-Documentation',
        docsDir: 'docs',
        docsBranch: 'development',
        editLinks: true,
        editLinkText: 'Help us improve this page!',
        algolia: {
            apiKey: (require('./.env.json')['algolia-key'] || ''),
            indexName: (require('./.env.json')['algolia-idxName'] || '')
        }
    },
    plugins: [
        ['@vuepress/back-to-top', true ],
        ['@vuepress/google-analytics', {
            'ga': (require('./.env.json')['ga-key'] || '')
        }],
        ['@vuepress/pwa', {
            serviceWorker: true,
            updatePopup: true
        }],
        ['sitemap', {
            hostname: 'https://help.photosynq.org',
            outFile: 'sitemap.xml'
        }]
        // ,
        // ['@vuepress/medium-zoom', {
        //     selector: 'img.zoom-custom-imgs',
        //     // See: https://github.com/francoischalifour/medium-zoom#options
        //     options: {
        //         margin: 16
        //     }
        // }],
        // ['vuepress-plugin-reading-time'],
        // ['vuepress-plugin-element-tabs']
    ]
};<commit_msg>Fix: Add font awesome CDN to header<commit_after>module.exports = {
    title: 'PhotosynQ Documentation',
    description: 'Documentation on how to use the PhotosynQ platform, including the web, mobile and desktop applications as well as the instruments.',
    head: [
        ['link', { rel: 'icon', href: '/icons/favicon-32x32.png' }],
        ['link', { rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css' }],
        ['link', { rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.css' }],
        ['link', { rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.css' }],
        ['script', {src: 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.0.0/mermaid.min.js' }]

    ],
    base: '/',
    home: true,
    locales: {
        '/': {
            lang: 'en-US',
            title: 'PhotosynQ Documentation',
            description: 'Documentation on how to use the PhotosynQ platform, including the web, mobile and desktop applications as well as the instruments.'
        }
    },
    markdown: {
        lineNumbers: true,
        externalLinks: { target: '_blank', rel: 'noopener noreferrer' },
        toc: { includeLevel: [2, 3] },
        extendMarkdown: md => {
            md.set({ breaks: true });
            md.use(require('markdown-it-katex'));
            md.use(require('markdown-it-mermaid').default);
        }
    },
    extend: '@vuepress/theme-default',
    themeConfig: {
        logo: '/logo_menu.svg',
        nav: [
            {
                text: 'Documents',
                items: [
                    {
                        text: 'Documents (PDF)',
                        items: [
                            { text: 'Help Manual', link: 'https://help.photosynq.org/downloads/PhotosynQ-Help-Manual.pdf' },
                            { text: 'Tutorials', link: 'https://help.photosynq.org/downloads/PhotosynQ-Getting-Started.pdf' },
                            { text: 'MultispeQ v1.0', link: 'https://help.photosynq.org/downloads/PhotosynQ-MultispeQ-v1.0.pdf' },
                            { text: 'MultispeQ v2.0', link: 'https://help.photosynq.org/downloads/PhotosynQ-MultispeQ-v2.0.pdf' },
                            { text: 'Firmware', link: 'https://help.photosynq.org/downloads/PhotosynQ-Firmware.pdf' }
                        ]
                    },
                    {
                        text: 'E-Books (epub)',
                        items: [
                            { text: 'Full Documentation', link: 'https://help.photosynq.org/downloads/PhotosynQ-Documentation.epub' }
                        ]
                    }
                ]
            },
            {
                text: 'More',
                items: [
                    {
                        text: 'Resources',
                        items: [
                            { text: 'Forums', link: 'https://photosynq.org/forums' },
                            { text: 'Blog', link: 'https://help.photosynq.org' }
                        ]
                    },
                    {
                        text: 'Firmware',
                        items: [
                            { text: 'Latest Firmware', link: 'https://github.com/Photosynq/MultispeQ-Firmware/releases/latest' },
                            { text: 'Changelog', link: 'https://github.com/Photosynq/MultispeQ-Firmware/releases' }
                        ]
                    },
                    {
                        text: 'Documentation',
                        items: [
                            { text: 'Contribute', link: 'https://github.com/Photosynq/PhotosynQ-Documentation/blob/master/contributing.md' },
                            { text: 'Changelog', link: 'https://github.com/photosynq/PhotosynQ-Documentation/releases' }
                        ]
                    }
                ]
            },
            { text: 'PhotosynQ', link: 'https://photosynq.org' }
        ],
        sidebar: [
            '/',
            {
                title: 'Account',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'account/create-an-account',
                    'account/edit-your-profile',
                    'account/manage-your-password',
                    'account/sign-in-to-photosynq',
                    'account/change-your-email-address',
                    'account/your-subscriptions',
                    'account/project-invitations',
                    'account/sign-out-of-photosynq',
                    'account/deactivate-an-account',
                    'account/photosynq-glossary'
                ]
            },
            {
                title: 'Mobile Application',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'mobile-application/photosynq-for-android',
                    'mobile-application/installation',
                    'mobile-application/general',
                    'mobile-application/connect-an-instrument',
                    'mobile-application/settings',
                    'mobile-application/projects',
                    'mobile-application/discover',
                    'mobile-application/quick-measurements',
                    'mobile-application/measurements',
                    'mobile-application/adding-notes-and-pictures'
                ]
            },
            {
                title: 'Desktop Application',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'desktop-application/installation',
                    'desktop-application/general',
                    'desktop-application/connect-an-instrument',
                    'desktop-application/settings',
                    'desktop-application/projects',
                    'desktop-application/adding-notes-and-pictures',
                    'desktop-application/notebook',
                    'desktop-application/protocols',
                    'desktop-application/macros',
                    'desktop-application/protocol-editor',
                    'desktop-application/macro-editor',
                    'desktop-application/console'
                ]
            },
            {
                title: 'Projects',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'projects/what-are-projects',
                    'projects/join-a-project',
                    'projects/create-a-new-project',
                    'projects/select-a-protocol',
                    'projects/adding-project-questions',
                    'projects/project-locations',
                    'projects/adding-a-project-description',
                    'projects/editing-a-project',
                    'projects/managing-project-settings',
                    'projects/project-collaborators',
                    'projects/import-custom-data',
                    'projects/barcodes',
                    'projects/large-scale-projects'
                ]
            },
            {
                title: 'View & Analyze Data',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'view-and-analyze-data/view-your-project-data',
                    'view-and-analyze-data/the-dashboard',
                    'view-and-analyze-data/plot-data',
                    'view-and-analyze-data/the-map',
                    'view-and-analyze-data/the-spreadsheet',
                    'view-and-analyze-data/statistics',
                    'view-and-analyze-data/series-data-subsets',
                    'view-and-analyze-data/filter-data',
                    'view-and-analyze-data/set-thresholds',
                    'view-and-analyze-data/viewing-notes-and-pictures',
                    'view-and-analyze-data/advanced-parameters',
                    'view-and-analyze-data/data-issues',
                    'view-and-analyze-data/flag-measurements',
                    'view-and-analyze-data/sessions',
                    'view-and-analyze-data/download-data',
                    'view-and-analyze-data/external-libraries',
                    'view-and-analyze-data/how-to-cite'
                ]
            },
            {
                title: 'Protocols',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'protocols/protocol-basics',
                    'protocols/run-a-protocol',
                    'protocols/create-edit-protocol',
                    'protocols/repeats',
                    'protocols/variables',
                    'protocols/commands'
                ]
            },
            {
                title: 'Macros',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'macros/macro-basics',
                    'macros/create-edit-a-macro',
                    'macros/coding-and-functions',
                    'macros/provided-functions'
                ]
            },
            {
                title: 'Instruments',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'instruments/multispeq-v2.0',
                    'instruments/multispeq-v2.0-configuration',
                    'instruments/multispeq-v1.0-configuration',
                    'instruments/what-does-the-multispeq-measure',
                    'instruments/instrument-settings',
                    'instruments/instrument-calibrations',
                    'instruments/firmware-updates',
                    'instruments/console-commands',
                    'instruments/light-guide-mask'
                ]
            },
            {
                title: 'Tutorials',
                collapsable: true,
                sidebarDepth: 1,
                children: [
                    'tutorials/getting-started',
                    'tutorials/creating-a-project',
                    'tutorials/data-collection',
                    'tutorials/data-viewing',
                    'tutorials/data-quality',
                    'tutorials/data-analysis',
                    'tutorials/building-a-protocol',
                    'tutorials/building-an-advanced-protocol',
                    'tutorials/building-a-macro',
                    'tutorials/building-an-advanced-macro'
                ]
            }
        ],
        lastUpdated: 'Last Updated',
        repo: 'photosynq/PhotosynQ-Documentation',
        // repoLabel: 'Contribute!',
        docsRepo: 'photosynq/PhotosynQ-Documentation',
        docsDir: 'docs',
        docsBranch: 'development',
        editLinks: true,
        editLinkText: 'Help us improve this page!',
        algolia: {
            apiKey: (require('./.env.json')['algolia-key'] || ''),
            indexName: (require('./.env.json')['algolia-idxName'] || '')
        }
    },
    plugins: [
        ['@vuepress/back-to-top', true ],
        ['@vuepress/google-analytics', {
            'ga': (require('./.env.json')['ga-key'] || '')
        }],
        ['@vuepress/pwa', {
            serviceWorker: true,
            updatePopup: true
        }],
        ['sitemap', {
            hostname: 'https://help.photosynq.org',
            outFile: 'sitemap.xml'
        }]
        // ,
        // ['@vuepress/medium-zoom', {
        //     selector: 'img.zoom-custom-imgs',
        //     // See: https://github.com/francoischalifour/medium-zoom#options
        //     options: {
        //         margin: 16
        //     }
        // }],
        // ['vuepress-plugin-reading-time'],
        // ['vuepress-plugin-element-tabs']
    ]
};<|endoftext|>


<commit_before>'use strict';

var express = require('express');
var router = express.Router();
var util = require('util');
var request = require('request');
var jsonfile = require('jsonfile');
var lxc = require('../lxc');
var doapi = require('../doapi')();

var label2runner = {};

var workers = (function(){
	var workers = [];
	workers.settings = require('./workers.json');

	workers.currentCreating = 0;

	workers.create = function(){
		if(workers.currentCreating > workers.settings.max ) return false;
		workers.currentCreating++;
		doapi.dropletToActive({
			name: 'clw'+workers.settings.version+'-'+(Math.random()*100).toString().slice(-4),
			image: workers.settings.image,
			size: workers.settings.size,
			onCreate: function(data){
				console.log(data)
				doapi.dropletSetTag('clwV'+workers.settings.version, data.droplet.id);
			},
			onActive: function(worker, args){
				workers.startRunners({
					worker: workers.makeWorkerObj(worker),
					onStart: function(runner, args){
						workers.push(args.worker);
						doapi.domianAddRecord({
							domain: "codeland.us",
							type: "A",
							name: "*."+worker.name+".workers",
							data: worker.publicIP
						});
						args.onStart = function(){};
					},
					onDone: function(args){
						console.log("done with workers");
					}
				});
				workers.currentCreating--;
			}
		});

	};

	workers.makeWorkerObj = function(worker){
		worker.networks.v4.forEach(function(value){
			worker[value.type+'IP'] = value.ip_address;
		});

		worker.availrunners = [];
		worker.ip = worker.publicIP;
		worker.usedrunner = 0;
		worker.index = workers.length;

		worker.getRunner = function(){
			if(this.availrunners.length === 0) return false;
			// console.log('getting runner from ', worker.name, ' avail length ', this.availrunners.length);
			var runner = this.availrunners.pop();
			this.usedrunner++;
			runnerTimeout(runner);

			return runner;
		}

		return worker;
	};

	workers.__workersId = function(argument){
		return workers.map(function(item){
			return item.id;
		});

	};

	workers.destroy = function(worker){
		var worker = worker || workers.pop();
		return doapi.dropletDestroy(worker.id, function(body) {
			console.log('body of destroy', body);
		});
	};

	workers.destroyOld = function(tag){
		tag = tag || 'clwV'+workers.settings.version;
		var currentIDs = workers.__workersId();
		var deleteDroplets = function(droplets){
			if(droplets.length === 0) return true;
			var droplet = droplets.pop();
			if(~currentIDs.indexOf(droplet.id)) return deleteDroplets(droplets);

			doapi.dropletDestroy(droplet.id, function(body){
				setTimeout(deleteDroplets, 1000, droplets);
			});
		}

		doapi.dropletsByTag(tag, function(data){
			data = JSON.parse(data);
			console.log('current worker ids', currentIDs);
			console.log('do droplets', data['droplets'].length, data['droplets'].map(function(item){
				return item.name+' | '+item.id;
			}));

			deleteDroplets(data['droplets']);
		});
	};

	workers.startRunners = function(args){
		// console.log('starting runners on', args.worker.name, args.worker.ip)
		if(!args.worker || workers.settings.image > args.worker.image.id){
			console.log('blocked outdated worker', workers.settings.image, args.worker.image.id)
			return ;
		}
		args.stopPercent = args.stopPercent || 80;
		args.onStart = args.onStart || function(){};
		args.onDone = args.onDone || function(){};

		ramPercentUsed(args.worker.ip, function(usedMemPercent){
			if(usedMemPercent > args.stopPercent ){
				console.log('using', String(usedMemPercent).trim(),
					'percent memory, stopping runner creation!', args.worker.availrunners.length,
					'created on ', args.worker.name
				);
				args.onDone(args);
				return ;
			}

			var name = 'crunner-'+(Math.random()*100).toString().slice(-4);
			// console.log('Free ram check passed!')
			lxc.startEphemeral(name, 'crunner0', args.worker.ip, function(data){
				if(!data.ip) return setTimeout(workers.startRunners, 0, args);
				// console.log('started runner on', args.worker.name)

				var runner = {
					ip: data.ip,
					name: name,
					worker: args.worker,
					label: args.worker.name + ':' + name
				};
				args.onStart(runner, args);

				args.worker.availrunners.push(runner);

				setTimeout(workers.startRunners, 0, args);
			});
		});
	};

	workers.checkBalance = function(){
		console.log('checking balance');

		if(workers.length < workers.settings.min){
			console.log('less then '+ workers.settings.min +' workers, starting a droplet');
			for(var i=workers.settings.min-workers.length; i--;) workers.create();
			return ;
		}

		for(let worker of workers){
			console.log("checking", worker.name, "if zombie");
			if(worker.availrunners.length === 0 && worker.usedrunner === 0){
				workers.splice(workers.indexOf(worker), 1);
				console.log('found zombie worker, destroying');
				workers.destroy(worker);
			}
		}

		if(workers.currentCreating) return ;

		var lastMinAval = 0;
		for(let worker of workers.slice(-workers.settings.minAvail)){
			if(worker.usedrunner !== 0){
				console.log('last 3 workers have no free runners, starting droplet');
				return workers.create();
			}
			if(workers.length > workers.settings.min && workers.usedrunner === 0 ){
				lastMinAval++;
			}
		}
		if(lastMinAval == workers.settings.minAvail){
			console.log('Last 3 runners not used, killing last runner', workers.length);
			return workers.destroy();
		}

	};
	workers.settingsSave = function(){
		jsonfile.writeFile('./workers.json', workers.settings, {spaces: 2}, function(err) {
			console.error(err);
		});
	};

	workers.add = function(newWorkers){
		newWorkers.forEach(function(worker){
			workers.push(worker);
		});
	};

	doapi.tagCreate('clwV'+workers.settings.version);
	return workers;

})();

var ramPercentUsed = function(ip, callback){

	return lxc.exec(
		"python3 -c \"a=`head /proc/meminfo|grep MemAvail|grep -Po '\\d+'`;t=`head /proc/meminfo|grep MemTotal|grep -Po '\\d+'`;print(round(((t-a)/t)*100, 2))\"",
		ip,
		callback
	);
};

var runnerTimeout = function(runner, time){
	time = time || 60000; // 1 minutes

	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}

	return runner.timeout = setTimeout(runnerFree, time, runner);
};

var runnerFree = function(runner){
	lxc.stop(runner.name, runner.worker.ip);
	runner.worker.usedrunner--;
	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}
	delete label2runner[runner.label];

	workers.startRunners({worker: runner.worker});
};

var getAvailrunner = function(runner){
	for(let worker of workers){
		if(worker.availrunners.length === 0) continue;
		if(runner && runner.worker.index <= worker.index) break;
		if(runner) runnerFree(runner);

		return worker.getRunner();
	}

	if(runner) return runner;
};

var run = function(req, res, runner, count){
	count = count || 0;
	console.log('run start', count);

	if(!runner){
		console.log('no runner');
		res.status(503);
		return res.json({error: 'No runners, try again soon.'});
	}

	if(count > 2){
		console.log('to many reties on runner');
		return res.status(400).json({error: 'Runner restarted to many times'});
	}

	var httpOptions = {
		url: 'http://' + runner.worker.ip,
		headers: {
			Host: runner.name
		},
		body: JSON.stringify({
			code: req.body.code
		})
	};

	return request.post(httpOptions, function(error, response, body){
		// console.log('runner response:', arguments)
		if(error || response.statusCode !== 200) return run(req, res, getAvailrunner(), ++count);
		body = JSON.parse(body);

		if(req.query.once){
			res.json(body);
			return runnerFree(runner, 0);
		}

		label2runner[runner.label] = runner;
		body['ip'] = runner.label;
		body['rname'] = runner.name;
		body['wname'] = runner.worker.name;
		res.json(body);

		runnerTimeout(runner);
	});
};

setTimeout(function(){
	// console.log('Starting balance checking in 30 seconds')
	setInterval(workers.checkBalance, 15000);
}, 600000);

workers.destroyOld();
workers.checkBalance();

router.get('/stop/:name', function(req, res, next){
	return lxc.stop(req.params.name, function(data){
		console.log('stop', arguments);
		if(data){
			return res.json({status: 500, name: req.params.name, message: data});
		}else{
			return res.json({status: 200});
		}
	});
});
router.get('/destroyOld', function(req, res, next) {
	workers.destroyOld();
	res.send('?');
});

router.post('/updateID', function(req, res, next){
	var newWorkers = {
		workers: [],
		image: req.query.image,
		target: req.query.target || workers.length,
		size: req.query.size || workers.settings.size,
		version: workers.settings.version+1,
		min: req.query.min || workers.settings,
		minAvail: req.query.minAvail || workers.settings
	};

	doapi.tagCreate('clwV'+newWorkers.version);
	workers.destroyOld('clwV'+newWorkers.version);

	for(var i=0; i<newWorkers.target; i++){

		doapi.dropletToActive({
			name: 'clw'+newWorkers.version+'-'+(Math.random()*100).toString().slice(-4),
			newWorkers: newWorkers,
			image: newWorkers.image,
			size: newWorkers.size,
			onCreate: function(data, args){
				doapi.dropletSetTag('clwV'+args.newWorkers.version, data.droplet.id);
			},
			onActive: function(droplet, args){
				workers.startRunners({
					worker: workers.makeWorkerObj(droplet),
					newWorkers: args.newWorkers,
					onStart: function(runner, args){
						args.newWorkers.workers.push(args.worker);
						console.log('onStart', args.worker.name);
						args.onStart = function(){};
					},
					onDone: function(args){
						console.log('new workers:', args.newWorkers.workers.length);
						doapi.domianAddRecord({
							domain: "codeland.us",
							type: "A",
							name: "*."+args.worker.name+".workers",
							data: args.worker.publicIP
						});

						if(args.newWorkers.workers.length >= args.newWorkers.target){
							console.log('upgrade complete!')
							workers.settings.image = args.newWorkers.image;
							workers.settings.size = args.newWorkers.size;
							workers.settings.min = args.newWorkers.min;
							workers.settings.minAvail = args.newWorkers.minAvail;

							workers.forEach(function(worker){
								worker.availrunners.forEach(function(runner){
									lxc.stop(runner.name, runner.worker.ip);
								});
								worker.availrunners = [];
							});

							workers.add(args.newWorkers.workers);
							workers.settingsSave();
							workers.checkBalance();
						}
					}

				});
			}
		});
	}
	res.json({status: "maybe?"});
});

router.get('/liststuff', function(req, res, next){
	var obj = util.inspect(workers, {depth: 4});
	res.send("
Workers

"+obj+"

label2runner

"+util.inspect(label2runner)+'

DO calls
'+doapi.calls);
});

router.get('/ping/:runner', function(req, res, next){
	var runner = label2runner[req.params.runner];
	runnerTimeout(runner);
	res.json({res:''});
});

router.post('/run/:runner?', function (req, res, next){
	console.log('hit runner route');
	var runner = getAvailrunner(label2runner[req.params.runner]);
	return run(req, res, runner);
});

module.exports = router;
<commit_msg>docs and fixe<commit_after>'use strict';

var express = require('express');
var router = express.Router();
var util = require('util');
var request = require('request');
var jsonfile = require('jsonfile');
var lxc = require('../lxc');
var doapi = require('../doapi')();
var settings = require('./workers.json');

// mapping of current used runners for quick loop up based on runner label
var label2runner = {};

var workers = (function(){
	// works array constructor. This will hold the works(order by creation) and all
	// the methods interacting with the workers.

	// base array that will be the workers objects.
	var workers = [];

	// persistent settings object
	// .image is the currently used Digital Ocean snap shot ID
	// .lastSnapShotId is the previous ID used Digital Ocean snap shot
	// .version is the current worker version
	// .size is the base Droplet size for worker creation
	// .min is the minimum amount of workers that should exist
	// .max is the maximum amount of works that ca exist
	// .minAvail is the amount of empty workers there should be
	workers.settings = settings;

	// How many droplets are currently in the process of being created. It takes
	// about 3 minutes to create a worker.
	workers.currentCreating = 0;

	workers.create = function(){
		// manages the creation of a work from first call to all runners seeded

		// dont create more workers then the settings file allows
		if(workers.currentCreating > workers.settings.max ) return false;
		workers.currentCreating++;

		doapi.dropletToActive({
			name: 'clw'+workers.settings.version+'-'+(Math.random()*100).toString().slice(-4),
			image: workers.settings.image,
			size: workers.settings.size,
			onCreate: function(data){
				console.log(data)
				doapi.dropletSetTag('clwV'+workers.settings.version, data.droplet.id);
			},
			onActive: function(worker, args){
				workers.startRunners({
					worker: workers.makeWorkerObj(worker),
					onStart: function(runner, args){
						workers.push(args.worker);
						doapi.domianAddRecord({
							domain: "codeland.us",
							type: "A",
							name: "*."+worker.name+".workers",
							data: worker.publicIP
						});
						args.onStart = function(){};
					},
					onDone: function(args){
						console.log("done with workers");
					}
				});
				workers.currentCreating--;
			}
		});

	};

	workers.makeWorkerObj = function(worker){
		// Create object for each worker.

		worker.networks.v4.forEach(function(value){
			worker[value.type+'IP'] = value.ip_address;
		});

		worker.availrunners = [];
		worker.ip = worker.publicIP;
		worker.usedrunners = 0;
		worker.index = workers.length;

		worker.getRunner = function(){
			if(this.availrunners.length === 0) return false;
			// console.log('getting runner from ', worker.name, ' avail length ', this.availrunners.length);
			var runner = this.availrunners.pop();
			this.usedrunners++;
			runnerTimeout(runner);

			return runner;
		}

		return worker;
	};

	workers.__workersId = function(argument){
		// create array of all current worker Digital Ocean ID
		return workers.map(function(item){
			return item.id;
		});

	};

	workers.destroy = function(worker){
		// todo: If worker is passed, check for it in the workers array and
		// remove it if found.

		var worker = worker || workers.pop();
		return doapi.dropletDestroy(worker.id, function(body) {
			console.log('body of destroy', body);
		});
	};

	workers.destroyByTag = function(tag){
		// Delete works that with

		tag = tag || 'clwV' + workers.settings.version;
		var currentIDs = workers.__workersId();

		var deleteDroplets = function(droplets){
			if(droplets.length === 0) return true;
			var droplet = droplets.pop();
			if(~currentIDs.indexOf(droplet.id)) return deleteDroplets(droplets);

			doapi.dropletDestroy(droplet.id, function(body){
				setTimeout(deleteDroplets, 1000, droplets);
			});
		}

		doapi.dropletsByTag(tag, function(data){
			data = JSON.parse(data);
			console.log('current worker ids', currentIDs);
			console.log('do droplets', data['droplets'].length, data['droplets'].map(function(item){
				return item.name+' | '+item.id;
			}));

			deleteDroplets(data['droplets']);
		});
	};

	workers.startRunners = function(args){
		// console.log('starting runners on', args.worker.name, args.worker.ip)

		// dont make runners on out dated workers
		if(!args.worker || workers.settings.image > args.worker.image.id){
			console.log('blocked outdated worker', workers.settings.image, args.worker.image.id)
			return ;
		}

		// percent of used RAM to stop runner creation
		args.stopPercent = args.stopPercent || 80;
		args.onStart = args.onStart || function(){};
		args.onDone = args.onDone || function(){};

		ramPercentUsed(args.worker.ip, function(usedMemPercent){
			if(usedMemPercent > args.stopPercent ){
				console.log('using', String(usedMemPercent).trim(),
					'percent memory, stopping runner creation!', args.worker.availrunners.length,
					'created on ', args.worker.name
				);
				args.onDone(args);
				return ;
			}

			var name = 'crunner-'+(Math.random()*100).toString().slice(-4);
			// console.log('Free ram check passed!')
			lxc.startEphemeral(name, 'crunner0', args.worker.ip, function(data){
				if(!data.ip) return setTimeout(workers.startRunners, 0, args);
				// console.log('started runner on', args.worker.name)

				var runner = {
					ip: data.ip,
					name: name,
					worker: args.worker,
					label: args.worker.name + ':' + name
				};
				args.onStart(runner, args);

				args.worker.availrunners.push(runner);

				setTimeout(workers.startRunners, 0, args);
			});
		});
	};

	workers.checkForZombies = function(){
		// check to make sure all works are used or usable.

		let zombies = 0;

		for(let worker of workers){
			console.log("checking", worker.name, "if zombie");
			// if a runner has no available runners and no used runners, its a
			// zombie. This should happen when a newer image ID has been added
			// and old workers slowly lose there usefulness.
			if(worker.availrunners.length === 0 && worker.usedrunners === 0){
				workers.splice(workers.indexOf(worker), 1);
				console.log('found zombie worker, destroying');
				workers.destroy(worker);
				zombie++;
			}
		}

		return zombie;
	};

	workers.checkBalance = function(){
		console.log('checking balance');

		workers.checkForZombies();

		// if there are workers being created, stop scale up and down check
		if(workers.currentCreating) return ;

		// scale up and down check

		// hold amount of workers with no used runners
		var lastMinAval = 0;

		// check to make sure the `workers.settings.minAvail` have free runners
		for(let worker of workers.slice(-workers.settings.minAvail)){
			if(worker.usedrunners === 0){
				lastMinAval++;
			}else{
				// no need to keep counting, workers need to be created
				break;
			}
		}

		if(lastMinAval > workers.settings.minAvail){
			// Remove workers if there are more then the settings states
			console.log('Last 3 runners not used, killing last runner', workers.length);

			return workers.destroy();

		}else(lastMinAval < workers.settings.minAvail){
			// creates workers if the settings file demands it
			console.log('last 3 workers have no free runners, starting droplet');

			return workers.create();

		}

	};
	workers.settingsSave = function(){
		// save the live settings file to disk

		jsonfile.writeFile('./workers.json', workers.settings, {spaces: 2}, function(err) {
			console.error(err);
		});
	};

	workers.add = function(newWorkers){
		newWorkers.forEach(function(worker){
			workers.push(worker);
		});
	};

	// make sure Digital Ocean has a tag for the current worker version
	doapi.tagCreate('clwV'+workers.settings.version);

	return workers;

})();

var ramPercentUsed = function(ip, callback){
	// checks the percent of ram used on a worker.

	return lxc.exec(
		"python3 -c \"a=`head /proc/meminfo|grep MemAvail|grep -Po '\\d+'`;t=`head /proc/meminfo|grep MemTotal|grep -Po '\\d+'`;print(round(((t-a)/t)*100, 2))\"",
		ip,
		callback
	);
};

var runnerTimeout = function(runner, time){
	time = time || 60000; // 1 minutes

	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}

	return runner.timeout = setTimeout(runnerFree, time, runner);
};

var runnerFree = function(runner){
	lxc.stop(runner.name, runner.worker.ip);
	runner.worker.usedrunners--;
	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}
	delete label2runner[runner.label];

	workers.startRunners({worker: runner.worker});
};

var getAvailrunner = function(runner){
	for(let worker of workers){
		if(worker.availrunners.length === 0) continue;
		if(runner && runner.worker.index <= worker.index) break;
		if(runner) runnerFree(runner);

		return worker.getRunner();
	}

	if(runner) return runner;
};

var run = function(req, res, runner, count){
	count = count || 0;
	console.log('run start', count);

	if(!runner){
		console.log('no runner');
		res.status(503);
		return res.json({error: 'No runners, try again soon.'});
	}

	if(count > 2){
		console.log('to many reties on runner');
		return res.status(400).json({error: 'Runner restarted to many times'});
	}

	var httpOptions = {
		url: 'http://' + runner.worker.ip,
		headers: {
			Host: runner.name
		},
		body: JSON.stringify({
			code: req.body.code
		})
	};

	return request.post(httpOptions, function(error, response, body){
		// console.log('runner response:', arguments)
		if(error || response.statusCode !== 200) return run(req, res, getAvailrunner(), ++count);
		body = JSON.parse(body);

		if(req.query.once){
			res.json(body);
			return runnerFree(runner, 0);
		}

		label2runner[runner.label] = runner;
		body['ip'] = runner.label;
		body['rname'] = runner.name;
		body['wname'] = runner.worker.name;
		res.json(body);

		runnerTimeout(runner);
	});
};

setTimeout(function(){
	// console.log('Starting balance checking in 30 seconds')
	setInterval(workers.checkBalance, 15000);
}, 600000);

workers.destroyByTag();
workers.checkBalance();

router.get('/stop/:name', function(req, res, next){
	return lxc.stop(req.params.name, function(data){
		console.log('stop', arguments);
		if(data){
			return res.json({status: 500, name: req.params.name, message: data});
		}else{
			return res.json({status: 200});
		}
	});
});
router.get('/destroyByTag', function(req, res, next) {
	workers.destroyByTag();
	res.send('?');
});

router.post('/updateID', function(req, res, next){
	var newWorkers = {
		workers: [],
		image: req.query.image,
		target: req.query.target || workers.length,
		size: req.query.size || workers.settings.size,
		version: workers.settings.version+1,
		min: req.query.min || workers.settings,
		minAvail: req.query.minAvail || workers.settings
	};

	doapi.tagCreate('clwV'+newWorkers.version);
	workers.destroyByTag('clwV'+newWorkers.version);

	for(var i=0; i<newWorkers.target; i++){

		doapi.dropletToActive({
			name: 'clw'+newWorkers.version+'-'+(Math.random()*100).toString().slice(-4),
			newWorkers: newWorkers,
			image: newWorkers.image,
			size: newWorkers.size,
			onCreate: function(data, args){
				doapi.dropletSetTag('clwV'+args.newWorkers.version, data.droplet.id);
			},
			onActive: function(droplet, args){
				workers.startRunners({
					worker: workers.makeWorkerObj(droplet),
					newWorkers: args.newWorkers,
					onStart: function(runner, args){
						args.newWorkers.workers.push(args.worker);
						console.log('onStart', args.worker.name);
						args.onStart = function(){};
					},
					onDone: function(args){
						console.log('new workers:', args.newWorkers.workers.length);
						doapi.domianAddRecord({
							domain: "codeland.us",
							type: "A",
							name: "*."+args.worker.name+".workers",
							data: args.worker.publicIP
						});

						if(args.newWorkers.workers.length >= args.newWorkers.target){
							console.log('upgrade complete!')
							workers.settings.image = args.newWorkers.image;
							workers.settings.size = args.newWorkers.size;
							workers.settings.min = args.newWorkers.min;
							workers.settings.minAvail = args.newWorkers.minAvail;

							workers.forEach(function(worker){
								worker.availrunners.forEach(function(runner){
									lxc.stop(runner.name, runner.worker.ip);
								});
								worker.availrunners = [];
							});

							workers.add(args.newWorkers.workers);
							workers.settingsSave();
							workers.checkBalance();
						}
					}

				});
			}
		});
	}
	res.json({status: "maybe?"});
});

router.get('/liststuff', function(req, res, next){
	var obj = util.inspect(workers, {depth: 4});
	res.send("
Workers

"+obj+"

label2runner

"+util.inspect(label2runner)+'

DO calls
'+doapi.calls);
});

router.get('/ping/:runner', function(req, res, next){
	var runner = label2runner[req.params.runner];
	runnerTimeout(runner);
	res.json({res:''});
});

router.post('/run/:runner?', function (req, res, next){
	console.log('hit runner route');
	var runner = getAvailrunner(label2runner[req.params.runner]);
	return run(req, res, runner);
});

module.exports = router;
<|endoftext|>