define(function (require) {
  var woodman = require('woodman');
  var logger = woodman.getLogger('AbstractSyncClock');

  var EventTarget = require('event-target');

  /**
   * Default constructor for a synchronized clock
   *
   * @class
   * @param {Number} initialSkew The initial clock skew
   * @param {Number} initialDelta The initial static delta
   */
  var SyncClock = function (initialSkew, initialDelta) {
    var self = this;

    /**
     * The current estimation of the skew with the reference clock, in ms
     */
    var skew = initialSkew || 0.0;

    /**
     * Some agreed fixed delta delay, in ms.
     */
    var delta = initialDelta || 0.0;

    /**
     * The ready state of the synchronized clock
     */
    var readyState = 'connecting';

    /**
     * Define the "readyState", "skew" and "delta" properties. Note that
     * setting these properties may trigger "readystatechange" and "change"
     * events.
     */
    Object.defineProperties(this, {
      readyState: {
        get: function () {
          return readyState;
        },
        set: function (state) {
          if (state !== readyState) {
            readyState = state;
            logger.log('ready state updated, dispatch "readystatechange" event');
            // Dispatch the event on next loop to give code that wants to
            // listen to the initial change to "open" time to attach an event
            // listener (locally synchronized clocks typically set the
            // readyState property to "open" directly within the constructor)
            setTimeout(function () {
              self.dispatchEvent({
                type: 'readystatechange',
                value: state
              });
            }, 0);
          }
        }
      },
      delta: {
        get: function () {
          return delta;
        },
        set: function (value) {
          var previousDelta = delta;
          delta = value;
          if (previousDelta === delta) {
            logger.log('delta updated, same as before');
          }
          else {
            logger.log('delta updated, dispatch "change" event');
            self.dispatchEvent({
              type: 'change'
            });
          }
        }
      },
      skew: {
        get: function () {
          return skew;
        },
        set: function (value) {
          var previousSkew = skew;
          skew = value;
          if (readyState !== 'open') {
            logger.log('skew updated, clock not open');
          }
          else if (previousSkew === skew) {
            logger.log('skew updated, same as before');
          }
          else {
            logger.log('skew updated, dispatch "change" event');
            self.dispatchEvent({
              type: 'change'
            });
          }
        }
      }
    });
  };


  // Synchronized clocks implement EventTarget
  SyncClock.prototype.addEventListener = EventTarget.addEventListener;
  SyncClock.prototype.removeEventListener = EventTarget.removeEventListener;
  SyncClock.prototype.dispatchEvent = EventTarget.dispatchEvent;


  /**
   * Returns the time at the reference clock that corresponds to the local
   * time provided (both in milliseconds since 1 January 1970 00:00:00 UTC)
   *
   * @function
   * @param {Number} localTime The local time in milliseconds
   * @returns {Number} The corresponding time on the reference clock
   */
  SyncClock.prototype.getTime = function (localTime) {
    return localTime + this.skew - this.delta;
  };


  /**
   * Returns the number of milliseconds elapsed since
   * 1 January 1970 00:00:00 UTC on the reference clock
   *
   * @function
   * @returns {Number} The current timestamp 
   */
  SyncClock.prototype.now = function () {
    return this.getTime(Date.now());
  };

  /**
   * Stops synchronization with the reference clock.
   *
   * In derived classes, this should typically be used to stop background
   * synchronization mechanisms.
   *
   * @function
   */
  SyncClock.prototype.close = function () {
    if ((this.readyState === 'closing') ||
        (this.readyState === 'closed')) {
      return;
    }
    this.readyState = 'closing';
    this.readyState = 'closed';
  };

  // Expose the class to the outer world
  return SyncClock;
});
示例#2
0
    {
      level: (argv.verbose ? 'log' : 'error'),
      appenders: [
        {
          type: 'Console',
          name: 'console',
          layout: {
            type: 'pattern',
            pattern: '%d{HH:mm:ss} [%level] %message%n'
          }
        }
      ]
    }
  ]
});
var logger = woodman.getLogger('runner');


/**
 * Display usage information if so requested
 */
if (argv.help) {
  optimist.showHelp();
  process.exit(0);
}


/**
 * Expand path of custom before/after JS files if they were provided
 */
if (argv.before) {
var domainMiddleware = require('express-domain-middleware');
app.use(domainMiddleware);

app.use(bodyParser.json({ type: 'application/vnd.api+json' }));

if (node_env === 'sandbox') {
    var serveIndex = require('serve-index');
    var localPath = __dirname + '/provision/logs';
    app.use('/logs', serveIndex(localPath));
    app.use('/logs', express.static(localPath));

}

woodman.load('console %domain - %message');
var logger = woodman.getLogger('app');

morgan.token('domain', function (req, res) {
    return process.domain.id;
});
app.use(morgan({ immediate: true, format: ':domain - :method :url' }));

app.get('/', function(req, res){
    res.send('hello world !');
});

app.use(function (err, req, res, next) {

    var message = err.stack;
    logger.error(message);
    if (node_env === "sandbox") {
示例#4
0
define(function (require) {
  var woodman = require('woodman');
  var logger = woodman.getLogger('SocketSyncClock');

  var AbstractSyncClock = require('./AbstractSyncClock');
  var isNumber = require('./utils').isNumber;
  var stringify = require('./utils').stringify;

  // Web Sockets ready state constants
  var CONNECTING = 0;
  var OPEN = 1;
  var CLOSING = 2;
  var CLOSED = 3;

  // Number of exchanges to make with the server to compute the first skew
  var initialAttempts = 10;

  // Interval between two exchanges during initialization (in ms)
  var initialInterval = 10;

  // Maximum number of attempts before giving up
  var maxAttempts = 10;

  // Interval between two attempts when the clock is open (in ms)
  var attemptInterval = 500;

  // Interval between two synchronization batches (in ms)
  var batchInterval = 10000;

  // Minimum roundtrip threshold (in ms)
  var minRoundtripThreshold = 5;


  /**
   * Creates a Socket synchronization clock
   *
   * @class
   * @param {String} url The URL of the remote timing object for which we
   *   want to synchronize the clock (only used to check permissions)
   * @param {WebSocket} socket A Web socket to use as communication channel.
   */
  var SocketSyncClock = function (url, socket) {
    // Initialize the base class with default data
    AbstractSyncClock.call(this);

    var self = this;


    /**
     * The Web Socket that will be used to exchange sync information with
     * the online server
     */
    this.socket = socket;


    /**
     * Minimum round trip detected so far (in ms)
     */
    var roundtripMin = 1000;


    /**
     * Current round trip threshold above which the "sync"
     * request is considered to be a failure (in ms)
     *
     * NB: this threshold must always be higher than the minimum round trip
     */
    var roundtripThreshold = 1000;


    /**
     * Number of "sync" attempts in the current batch so far.
     * The clock will attempt up to maxAttempts attempts in a row
     * each time it wants to synchronize
     */
    var attempts = 0;


    /**
     * Valid responses received from the server for the current batch
     */
    var initialSyncMessages = [];


    /**
     * ID of the attempt response we are currently waiting for
     */
    var attemptId = null;


    /**
     * The attempt timeout
     */
    var attemptTimeout = null;


    /**
     * Timeout to detect when the server fails to respond in time
     */
    var timeoutTimeout = null;


    if (socket.readyState === OPEN) {
      logger.info('WebSocket already opened');
      sendSyncRequest();
    }
    else if (socket.readyState === CLOSED) {
      logger.log('WebSocket closed');
      this.readyState = 'closed';
    }

    var errorHandler = function (err) {
      logger.warn('WebSocket error', err);
      // TODO: properly deal with network errors
      return true;
    };

    var openHandler = function () {
      logger.info('WebSocket client connected');
      sendSyncRequest();
      return true;
    };

    var closeHandler = function () {
      logger.log('WebSocket closed');
      self.close();
      return true;
    };

    var messageHandler = function (evt) {
      var msg = null;
      var received = Date.now();
      var skew = 0;

      if (typeof evt.data !== 'string') {
        logger.log('message from server is not a string, pass on');
        return true;
      }

      try {
        msg = JSON.parse(evt.data) || {};
      }
      catch (err) {
        logger.warn('message from server is not JSON, pass on');
        return true;
      }

      if (msg.type !== 'sync') {
        logger.log('message from server is not a sync message, pass on');
        return true;
      }

      if (!msg.client || !msg.server ||
          !isNumber(msg.client.sent) ||
          !isNumber(msg.server.received) ||
          !isNumber(msg.server.sent)) {
        logger.log('sync message is incomplete, ignore');
        return true;
      }

      if (msg.id !== attemptId) {
        logger.log('sync message is not the expected one, ignore');
        return true;
      }

      // Message is for us
      attempts += 1;

      // Compute round trip duration
      var roundtripDuration = received - msg.client.sent;

      // Check round trip duration
      if ((self.readyState !== 'connecting') &&
          (roundtripDuration > roundtripThreshold)) {
        logger.log('sync message took too long, ignore');
        return false;
      }

      if (timeoutTimeout) {
        // Cancel the timeout set to detect server timeouts.
        clearTimeout(timeoutTimeout);
        timeoutTimeout = null;
      }
      else {
        // A timeout already occurred
        // (should have normally be trapped by the check on round trip
        // duration, but timeout scheduling and the event loop are not
        // an exact science)
        logger.log('sync message took too long, ignore');
        return false;
      }

      // During initialization, simply store the response,
      // we'll process things afterwards
      if (self.readyState === 'connecting') {
        logger.log('sync message during initialization, store');
        initialSyncMessages.push({
          received: received,
          roundtrip: roundtripDuration,
          msg: msg
        });
        if (attempts >= initialAttempts) {
          initialize();
          scheduleNextBatch();
        }
        else {
          scheduleNextAttempt();
        }
        return false;
      }

      // Adjust the minimum round trip and threshold if needed
      if (roundtripDuration < roundtripMin) {
        roundtripThreshold = Math.ceil(
          roundtripThreshold * (roundtripDuration / roundtripMin));
        if (roundtripThreshold < minRoundtripThreshold) {
          roundtripThreshold = minRoundtripThreshold;
        }
        roundtripMin = roundtripDuration;
      }


      // Sync message can be directly applied
      skew = ((msg.server.sent + msg.server.received) -
          (msg.client.sent + received)) / 2.0;
      if (Math.abs(skew - self.skew) < 1) {
        skew = self.skew;
      }
      else {
        skew = Math.round(skew);
      }
      logger.info('sync message received, skew={}', skew);

      // Save the new skew
      // (this triggers a "change" event if value changed)
      self.skew = skew;

      // No need to schedule another attempt,
      // let's simply schedule the next sync batch of attempts
      scheduleNextBatch();

      return false;
    };

    // NB: calling "addEventListener" does not work in a Node.js environment
    // because the WebSockets library used only supports basic "onXXX"
    // constructs. The code below works around that limitation but note that
    // only works provided the clock is associated with the socket *after* the
    // timing provider object!
    var previousErrorHandler = this.socket.onerror;
    var previousOpenHandler = this.socket.onopen;
    var previousCloseHandler = this.socket.onclose;
    var previousMessageHandler = this.socket.onmessage;
    if (this.socket.addEventListener) {
      this.socket.addEventListener('error', errorHandler);
      this.socket.addEventListener('open', openHandler);
      this.socket.addEventListener('close', closeHandler);
      this.socket.addEventListener('message', messageHandler);
    }
    else {
      this.socket.onerror = function (evt) {
        var propagate = errorHandler(evt);
        if (propagate && previousErrorHandler) {
          previousErrorHandler(evt);
        }
      };
      this.socket.onopen = function (evt) {
        var propagate = openHandler(evt);
        if (propagate && previousOpenHandler) {
          previousOpenHandler(evt);
        }
      };
      this.socket.onclose = function (evt) {
        var propagate = closeHandler(evt);
        if (propagate && previousCloseHandler) {
          previousCloseHandler(evt);
        }
      };
      this.socket.onmessage = function (evt) {
        var propagate = messageHandler(evt);
        if (propagate && previousMessageHandler) {
          previousMessageHandler(evt);
        }
      };
    }


    /**
     * Helper function to send a "sync" request to the socket server
     */
    var sendSyncRequest = function () {
      logger.log('send a "sync" request');
      attemptId = url + '#' + Date.now();
      self.socket.send(stringify({
        type: 'sync',
        id: attemptId,
        client: {
          sent: Date.now()
        }
      }));
      attemptTimeout = null;

      timeoutTimeout = setTimeout(function () {
        attempts += 1;
        timeoutTimeout = null;
        logger.log('sync request timed out');
        if (attempts >= maxAttempts) {
          if (self.readyState === 'connecting') {
            initialize();
          }
          else {
            roundtripThreshold = Math.ceil(roundtripThreshold * 1.20);
            logger.log('all sync attempts failed, increase threshold to {}',
              roundtripThreshold);
          }
          scheduleNextBatch();
        }
        else {
          scheduleNextAttempt();
        }
      }, roundtripThreshold);
    };


    /**
     * Helper function to schedule the next sync attempt
     *
     * @function
     */
    var scheduleNextAttempt = function () {
      var interval = (self.readyState === 'connecting') ?
        initialInterval :
        attemptInterval;
      if (timeoutTimeout) {
        clearTimeout(timeoutTimeout);
        timeoutTimeout = null;
      }
      if (attemptTimeout) {
        clearTimeout(attemptTimeout);
        attemptTimeout = null;
      }
      attemptTimeout = setTimeout(sendSyncRequest, interval);
    };


    /**
     * Helper function to schedule the next batch of sync attempts
     *
     * @function
     */
    var scheduleNextBatch = function () {
      if (timeoutTimeout) {
        clearTimeout(timeoutTimeout);
        timeoutTimeout = null;
      }
      if (attemptTimeout) {
        clearTimeout(attemptTimeout);
        attemptTimeout = null;
      }
      attempts = 0;
      attemptTimeout = setTimeout(sendSyncRequest, batchInterval);
    };


    /**
     * Helper function that computes the initial skew based on the
     * sync messages received so far and adjust the roundtrip threshold
     * accordingly.
     *
     * The function also sets the clock's ready state to "open".
     *
     * @function
     */
    var initialize = function () {
      var msg = null;
      var skew = null;
      var received = 0;
      var pos = 0;

      logger.log('compute initial settings');

      // Sort messages received according to round trip
      initialSyncMessages.sort(function (a, b) {
        return a.roundtrip - b.roundtrip;
      });

      // Use the first message to compute the initial skew
      if (initialSyncMessages.length > 0) {
        msg = initialSyncMessages[0].msg;
        received = initialSyncMessages[0].received;
        roundtripMin = initialSyncMessages[0].roundtrip;

        if (isNumber(msg.delta)) {
          self.delta = msg.delta;
        }

        skew = ((msg.server.sent + msg.server.received) -
            (msg.client.sent + received)) / 2.0;
        if (Math.abs(skew - self.skew) < 1) {
          skew = self.skew;
        }
        else {
          skew = Math.round(skew);
        }
        self.skew = skew;
      }

      // Adjust the threshold to preserve at least half of the sync messages
      // that should have been received.
      pos = Math.ceil(initialAttempts / 2) - 1;
      if (pos >= initialSyncMessages.length) {
        pos = initialSyncMessages.length - 1;
      }
      if (pos >= 0) {
        roundtripThreshold = initialSyncMessages[pos].roundtrip;
      }

      // Ensure the threshold is not too low compared to the
      // known minimum roundtrip duration
      if (roundtripThreshold < roundtripMin * 1.30) {
        roundtripThreshold = Math.ceil(roundtripMin * 1.30);
      }
      if (roundtripThreshold < minRoundtripThreshold) {
        roundtripThreshold = minRoundtripThreshold;
      }

      // Clock is ready
      logger.info('clock is ready: ' +
        'skew={}, delta={}, roundtrip min={}, threshold={}',
        self.skew, self.delta, roundtripMin, roundtripThreshold);
      self.readyState = 'open';
      initialSyncMessages = [];
    };


    /**
     * Method that stops the background synchronization
     */
    this.stopSync = function () {
      if (attemptTimeout) {
        clearTimeout(attemptTimeout);
        attemptTimeout = null;
      }
      if (timeoutTimeout) {
        clearTimeout(timeoutTimeout);
        timeoutTimeout = null;
      }
    };

    logger.info('created');
  };
  SocketSyncClock.prototype = new AbstractSyncClock();


  /**
   * Stops synchronizing the clock with the reference clock
   *
   * Note that a closed synchronized clock object cannot be re-used.
   *
   * @function
   */
  SocketSyncClock.prototype.close = function () {
    if ((this.readyState === 'closing') ||
        (this.readyState === 'closed')) {
      return;
    }
    this.readyState = 'closing';
    this.stopSync();
    this.socket = null;
    this.readyState = 'closed';
  };


  // Expose the class to the outer world
  return SocketSyncClock;
});
define(function (require) {
  var woodman = require('woodman');
  var logger = woodman.getLogger('SocketTimingProvider');

  var AbstractTimingProvider = require('./AbstractTimingProvider');
  var StateVector = require('./StateVector');
  var SocketSyncClock = require('./SocketSyncClock');
  var isNull = require('./utils').isNull;
  var stringify = require('./utils').stringify;
  
  var W3CWebSocket = null;
  try {
    W3CWebSocket = require('websocket').w3cwebsocket;
  }
  catch (err) {
    W3CWebSocket = window.WebSocket;
  }


  // Web Sockets ready state constants
  var CONNECTING = 0;
  var OPEN = 1;
  var CLOSING = 2;
  var CLOSED = 3;


  /**
   * Creates a timing provider
   *
   * @class
   * @param {String} url The Web socket URL of the remote timing object
   * @param {WebSocket} socket An opened Web socket to use as communication
   *   channel. The parameter is optional, the object will create the
   *   communication channel if not given.
   * @param {AbstractSyncClock} clock A clock to use for synchronization with
   *   the online server clock. If not given, a clock that uses the underlying
   *   WebSocket will be created and used.
   */
  var SocketTimingProvider = function (url, socket, clock) {
    var self = this;

    /**
     * The URL of the online object, it is used as
     * identifier in exchanges with the backend server
     */
    this.url = url;

    /**
     * The current vector as returned by the server.
     *
     * Updating the property through the setter automatically updates
     * the exposed vector as well, converting the server timestamp into
     * a local timestamp based on the underlying synchronized clock's readings
     */
    var serverVector = null;
    Object.defineProperty(this, 'serverVector', {
      get: function () {
        return serverVector;
      },
      set: function (vector) {
        var now = Date.now();
        serverVector = vector;
        self.vector = new StateVector({
          position: vector.position,
          velocity: vector.velocity,
          acceleration: vector.acceleration,
          timestamp: vector.timestamp + (now - self.clock.getTime(now)) / 1000.0
        });
      }
    });

    /**
     * List of "change" events already received from the server but
     * whose estimated timestamps lie in the future
     */
    var pendingChanges = [];

    /**
     * The ID of the timeout used to trigger the first of the remaining
     * pending change events to process
     */
    var pendingTimeoutId = null;

    /**
     * Helper function that schedules the propagation of the next pending
     * change. Note that the function calls itself as long as there are
     * pending changes to schedule.
     *
     * The function should be called whenever the synchronized clock reports
     * changes on its skew evaluation, since that affects the time at which
     * pending changes need to be executed.
     */
    var scheduleNextPendingChange = function () {
      stopSchedulingPendingChanges();
      if (pendingChanges.length === 0) {
        return;
      }

      var now = Date.now();
      var vector = pendingChanges[0];
      var localTimestamp = (vector.timestamp * 1000.0) +
        now - self.clock.getTime(now);
      logger.log('schedule next pending change',
        'delay=' + (localTimestamp - now));

      var applyNextPendingChange = function () {
        // Since we cannot control when this function runs precisely,
        // note we may have to skip over the first few changes. We'll
        // only trigger the change that is closest to now
        logger.log('apply next pending change');
        var now = Date.now();
        var vector = pendingChanges.shift();
        var nextVector = null;
        var localTimestamp = 0.0;
        while (pendingChanges.length > 0) {
          nextVector = pendingChanges[0];
          localTimestamp = nextVector.timestamp * 1000.0 +
            now - self.clock.getTime(now);
          if (localTimestamp > now) {
            break;
          }
          vector = pendingChanges.shift();
        }

        self.serverVector = vector;
        scheduleNextPendingChange();
      };

      if (localTimestamp > now) {
        pendingTimeoutId = setTimeout(
          applyNextPendingChange,
          localTimestamp - now);
      }
      else {
        applyNextPendingChange();
      }
    };


    /**
     * Helper function that stops the pending changes scheduler
     *
     * @function
     */
    var stopSchedulingPendingChanges = function () {
      logger.log('stop scheduling pending changes');
      if (pendingTimeoutId) {
        clearTimeout(pendingTimeoutId);
        pendingTimeoutId = null;
      }
    };


    /**
     * Helper function that processes the "info" message from the
     * socket server when the clock is ready.
     *
     * @function
     */
    var processInfoWhenPossible = function (msg) {
      // This should really just happen during initialization
      if (self.readyState !== 'connecting') {
        logger.warn(
          'timing info to process but state is "{}"',
          self.readyState);
        return;
      }

      // If clock is not yet ready, schedule processing for when it is
      // (note that this function should only really be called once but
      // not a big deal if we receive more than one info message from the
      // server)
      if (self.clock.readyState !== 'open') {
        self.clock.addEventListener('readystatechange', function () {
          if (self.clock.readyState === 'open') {
            processInfoWhenPossible(msg);
          }
        });
        return;
      }

      if (self.clock.delta) {
        // The info will be applied right away, but if the server imposes
        // some delta to all clients (to improve synchronization), it
        // should be applied to the timestamp received.
        msg.vector.timestamp -= (self.clock.delta / 1000.0);
      }
      self.serverVector = new StateVector(msg.vector);

      // TODO: set the range as well when feature is implemented

      // The timing provider object should now be fully operational
      self.readyState = 'open';
    };


    // Initialize the base class with default data
    AbstractTimingProvider.call(this);

    // Connect to the Web socket
    if (socket) {
      this.socket = socket;
      this.socketProvided = true;
    }
    else {
      this.socket = new W3CWebSocket(url, 'echo-protocol');
      this.socketProvided = false;
    }

    this.socket.onerror = function (err) {
      logger.warn('WebSocket error', err);
      // TODO: implement a connection recovery mechanism
    };

    this.socket.onopen = function () {
      logger.info('WebSocket client connected');
      self.socket.send(stringify({
        type: 'info',
        id: url
      }));
    };

    this.socket.onclose = function() {
      logger.info('WebSocket closed');
      self.close();
    };

    this.socket.onmessage = function (evt) {
      var msg = null;
      var vector = null;
      var now = Date.now();
      var localTimestamp = 0;

      if (typeof evt.data === 'string') {
        try {
          msg = JSON.parse(evt.data) || {};
        }
        catch (err) {
          logger.warn('message received from server could not be parsed as JSON');
          return;
        }

        if (msg.id !== url) {
          logger.log('message is for another timing object, ignored');
          return;
        }

        switch (msg.type) {
        case 'info':
          // Info received from the socket server but note that the clock may
          // not yet be synchronized with that of the server, let's wait for
          // that.
          logger.log('timing object info received', msg.vector);
          processInfoWhenPossible(msg);
          break;

        case 'change':
          if (self.readyState !== 'open') {
            logger.log('change message received, but not yet open, ignored');
            return;
          }

          // TODO: not sure what to do when the server sends an update with
          // a timestamp that lies in the past of the current vector we have,
          // ignoring for now
          if (msg.vector.timestamp < self.serverVector.timestamp) {
            logger.warn('change message received, but more ancient than current vector, ignored');
            return;
          }

          // Create a new Media state vector from the one received
          vector = new StateVector(msg.vector);

          // Determine whether the change event is to be applied now or to be
          // queued up for later
          localTimestamp = vector.timestamp * 1000.0 +
              now - self.clock.getTime(now);
          if (localTimestamp < now) {
            logger.log('change message received, execute now');
            self.serverVector = vector;
          }
          else {
            logger.log('change message received, queue for later');
            pendingChanges.push(vector);
            pendingChanges.sort(function (a, b) {
              return a.timestamp - b.timestamp;
            });
            scheduleNextPendingChange();
          }
          break;
        }
      }
    };

    // Create the clock
    if (clock) {
      this.clock = clock;
    }
    else {
      this.clock = new SocketSyncClock(url, this.socket);
      this.clock.addEventListener('change', function () {
        if (self.readyState !== 'open') {
          return;
        }
        logger.log('apply new skew to pending changes');
        scheduleNextPendingChange();
      });
    }




    // Check the initial state of the socket connection
    if (this.socket.readyState === OPEN) {
      logger.info('WebSocket client connected');
      this.socket.send(stringify({
        type: 'info',
        id: url
      }));
    }
    else if (this.socket.readyState === CLOSED) {
      logger.log('WebSocket closed');
      self.close();
    }

    logger.info('created');
  };
  SocketTimingProvider.prototype = new AbstractTimingProvider();


  /**
   * Sends an update command to the online timing service.
   *
   * @function
   * @param {Object} vector The new motion vector
   * @param {Number} vector.position The new motion position.
   *   If null, the position at the current time is used.
   * @param {Number} vector.velocity The new velocity.
   *   If null, the velocity at the current time is used.
   * @param {Number} vector.acceleration The new acceleration.
   *   If null, the acceleration at the current time is used.
   * @returns {Promise} The promise to get an updated StateVector that
   *   represents the updated motion on the server once the update command
   *   has been processed by the server.
   *   The promise is rejected if the connection with the online timing service
   *   is not possible for some reason (no connection, timing object on the
   *   server was deleted, timeout, permission issue).
   */
  SocketTimingProvider.prototype.update = function (vector) {
    vector = vector || {};
    logger.log('update',
      '(position=' + vector.position +
      ', velocity=' + vector.velocity +
      ', acceleration=' + vector.acceleration + ')');

    if (this.readyState !== 'open') {
      return new Promise(function (resolve, reject) {
        logger.warn('update', 'socket was closed, cannot process update');
        reject(new Error('Underlying socket was closed'));
      });
    }
    this.socket.send(stringify({
      type: 'update',
      id: this.url,
      vector: vector
    }));

    return new Promise(function (resolve, reject) {
      // TODO: To be able to resolve the promise, we would need to know
      // when the server has received and processed the request. This
      // requires an ack that does not yet exist. Also, should the promise
      // only be resolved when the update is actually done (which may take
      // place after some time and may actually not take place at all?)
      resolve();
    });
  };


  /**
   * Closes the timing provider object, releasing any resource that the
   * object might use.
   *
   * Note that a closed timing provider object cannot be re-used.
   *
   * @function
   */
  SocketTimingProvider.prototype.close = function () {
    if ((this.readyState === 'closing') ||
        (this.readyState === 'closed')) {
      return;
    }
    this.readyState = 'closing';
    this.clock.close();
    if (!this.socketProvided && (this.socket.readyState !== CLOSED)) {
      this.socket.close();
    }
    this.socket = null;
    this.readyState = 'closed';
  };


  // Expose the class to the outer world
  return SocketTimingProvider;
});
define(function (require) {
  var woodman = require('woodman');
  var logger = woodman.getLogger('TimingMediaController');

  var EventTarget = require('event-target');
  var TimingObject = require('./TimingObject');
  var StateVector = require('./StateVector');


  /**
   * Constructor of a timing media controller
   *
   * @class
   * @param {TimingObject} timing The timing object attached to the controller
   * @param {Object} options controller settings
   */
  var TimingMediaController = function (timing, options) {
    var self = this;
    options = options || {};    

    if (!timing || (!timing instanceof TimingObject)) {
      throw new Error('No timing object provided');
    }

    /**
     * The timing media controller's internal settings
     */
    var settings = {
      // Media elements are considered in sync with the timing object if the
      // difference between the position they report and the position of the
      // timing object is below that threshold (in seconds).
      minDiff: options.minDiff || 0.010,

      // Maximum delay for catching up (in seconds).
      // If the code cannot meet the maxDelay constraint,
      // it will have the media element directly seek to the right position.
      maxDelay: options.maxDelay || 1.0,

      // Amortization period (in seconds).
      // The amortization period is used when adjustments are made to
      // the playback rate of the video.
      amortPeriod: options.amortPeriod || 2.0
    };


    /**
     * The list of Media elements controlled by this timing media controller.
     *
     * For each media element, the controller maintains a state vector
     * representation of the element's position and velocity, a drift rate
     * to adjust the playback rate, whether we asked the media element to
     * seek or not, and whether there is an amortization period running for
     * the element
     *
     * {
     *   vector: {},
     *   driftRate: 0.0,
     *   seeked: false,
     *   amortization: false,
     *   element: {}
     * }
     */
    var controlledElements = [];


    /**
     * The timing object's state vector last time we checked it.
     * This variable is used in particular at the end of the amortization
     * period to compute the media element's drift rate
     */
    var timingVector = null;


    /**
     * Pointer to the amortization period timeout.
     * The controller uses only one amortization period for all media elements
     * under control.
     */
    var amortTimeout = null;


    Object.defineProperties(this, {
      /**
       * Report the state of the underlying timing object
       *
       * TODO: should that also take into account the state of the controlled
       * elements? Hard to find a proper definition though
       */
      readyState: {
        get: function () {
          return timingProvider.readyState;
        }
      },


      /**
       * The currentTime attribute returns the position that all controlled
       * media elements should be at, in other words the position of the
       * timing media controller when this method is called.
       *
       * On setting, the timing object's state vector is updated with the
       * provided value, which will (asynchronously) affect all controlled
       * media elements.
       *
       * Note that getting "currentTime" right after setting it may not return
       * the value that was just set.
       */
      currentTime: {
        get: function () {
          return timing.currentPosition;
        },
        set: function (value) {
          timing.update(value, null);
        }
      },


      /**
       * The current playback rate of the controller (controlled media elements
       * may have a slightly different playback rate since the role of the
       * controller is precisely to adjust their playback rate to ensure they
       * keep up with the controller's position.
       *
       * On setting, the timing object's state vector is updated with the
       * provided value, which will (asynchronously) affect all controlled
       * media elements.
       *
       * Note that getting "playbackRate" right after setting it may not return
       * the value that was just set.
       */
      playbackRate: {
        get: function () {
          return timing.currentVelocity;
        },
        set: function (value) {
          timing.update(null, value);
        }
      }
    });


    /**
     * Start playing the controlled elements
     *
     * @function
     */
    this.play = function () {
      timing.update(null, 1.0);
    };


    /**
     * Pause playback
     *
     * @function
     */
    this.pause = function () {
      timing.update(null, 0.0);
    };


    /**
     * Add a media element to the list of elements controlled by this
     * controller
     *
     * @function
     * @param {MediaElement} element The media element to associate with the
     *  controller.
     */
    this.addMediaElement = function (element) {
      var found = false;
      controlledElements.forEach(function (wrappedEl) {
        if (wrappedEl.element === element) {
          found = true;
        }
      });
      if (found) {
        return;
      }
      controlledElements.push({
        element: element,
        vector: null,
        driftRate: 0.0,
        seeked: false,
        amortization: false
      });
    };


    /**
     * Helper function that cancels a running amortization period
     */
    var cancelAmortizationPeriod = function () {
      if (!amortTimeout) {
        return;
      }
      clearTimeout(amortTimeout);
      amortTimeout = null;
      controlledElements.forEach(function (wrappedEl) {
        wrappedEl.amortization = false;
        wrappedEl.seeked = false;
      });
    };


    /**
     * Helper function to stop the playback adjustment once the amortization
     * period is over.
     */
    var stopAmortizationPeriod = function () {
      var now = Date.now() / 1000.0;
      amortTimeout = null;

      controlledElements.forEach(function (wrappedEl) {
        // Nothing to do if element was not part of amortization period
        if (!wrappedEl.amortization) {
          return;
        }
        wrappedEl.amortization = false;

        // Don't adjust playback rate and drift rate if video was seeked
        // or if element was not part of that amortization period.
        if (wrappedEl.seeked) {
          logger.log('end of amortization period for seek');
          wrappedEl.seeked = false;
          return;
        }

        // Compute the difference between the position the video should be and
        // the position it is reported to be at.
        var diff = wrappedEl.vector.computePosition(now) -
          wrappedEl.element.currentTime;

        // Compute the new video drift rate
        wrappedEl.driftRate = diff / (now - wrappedEl.vector.timestamp);

        // Switch back to the current vector's velocity,
        // adjusted with the newly computed drift rate
        wrappedEl.vector.velocity = timingVector.velocity + wrappedEl.driftRate;
        wrappedEl.element.playbackRate = wrappedEl.vector.velocity;

        logger.log('end of amortization period',
          'new drift=' + wrappedEl.driftRate,
          'playback rate=' + wrappedEl.vector.velocity);
      });
    };



    /**
     * React to timing object's changes, harnessing the controlled
     * elements to align them with the timing object's position and velocity
     */
    var onTimingChange = function () {
      cancelAmortizationPeriod();
      controlElements();
    };


    /**
     * Ensure media elements are aligned with the current timing object's
     * state vector
     */
    var controlElements = function () {
      // Do not adjust anything during an amortization period
      if (amortTimeout) {
        return;
      }

      // Get new readings from Timing object
      timingVector = timing.query();

      controlledElements.forEach(controlElement);

      var amortNeeded = false;
      controlledElements.forEach(function (wrappedEl) {
        if (wrappedEl.amortization) {
          amortNeeded = true;
        }
      });

      if (amortNeeded) {
        logger.info('start amortization period');
        amortTimeout = setTimeout(stopAmortizationPeriod, settings.amortPeriod * 1000);
      }

      // Queue a task to fire a simple event named "timeupdate"
      setTimeout(function () {
        self.dispatchEvent({
          type: 'timeupdate'
        }, 0);
      });
    };


    /**
     * Ensure the given media element (wrapped in info structure) is aligned
     * with the current timing object's state vector
     */
    var controlElement = function (wrappedEl) {
      var element = wrappedEl.element;
      var diff = 0.0;
      var futurePos = 0.0;

      if ((timingVector.velocity === 0.0) &&
          (timingVector.acceleration === 0.0)) {
        logger.info('stop element and seek to right position');
        element.pause();
        element.currentTime = timingVector.position;
        wrappedEl.vector = new StateVector(timingVector);
      }
      else if (element.paused) {
        logger.info('play video');
        wrappedEl.vector = new StateVector({
          position: timingVector.position,
          velocity: timingVector.velocity + wrappedEl.driftRate,
          acceleration: 0.0,
          timestamp: timingVector.timestamp
        });
        wrappedEl.seeked = true;
        wrappedEl.amortization = true;
        element.currentTime = wrappedEl.vector.position;
        element.playbackRate = wrappedEl.vector.velocity;
        element.play();
      }
      else {
        wrappedEl.vector = new StateVector({
          position: element.currentTime,
          velocity: wrappedEl.vector.velocity,
        });
        diff = timingVector.position - wrappedEl.vector.position;
        if (Math.abs(diff) < settings.minDiff) {
          logger.info('video and vector are in sync!');
        }
        else if (Math.abs(diff) > settings.maxDelay) {
          logger.info('seek video to pos={}', timingVector.position);
          wrappedEl.vector.position = timingVector.position;
          wrappedEl.vector.velocity = timingVector.velocity + wrappedEl.driftRate;
          wrappedEl.seeked = true;
          wrappedEl.amortization = true;
          element.currentTime = wrappedEl.vector.position;
          element.playbackRate = wrappedEl.vector.velocity;
        }
        else {
          futurePos = timingVector.computePosition(
            timingVector.timestamp + settings.amortPeriod);
          wrappedEl.vector.velocity =
            wrappedEl.driftRate +
            (futurePos - wrappedEl.vector.position) / settings.amortPeriod;
          wrappedEl.amortization = true;
          element.playbackRate = wrappedEl.vector.velocity;
          logger.info('new playbackrate={}', wrappedEl.vector.velocity);
        }
      }
    };
    

    /**********************************************************************
    Listen to the timing object
    **********************************************************************/
    logger.info('add listener to "timeupdate" events...');
    timing.addEventListener('timeupdate', controlElements);
    timing.addEventListener('change', onTimingChange);
    logger.info('add listener to "timeupdate" events... done');

    logger.info('add listener to "readystatechange" events...');
    timing.addEventListener('readystatechange', function (evt) {
      self.dispatchEvent(evt);
    });
    logger.info('add listener to "readystatechange" events... done');

    logger.info('created');
  };


  // TimingMediaController implements EventTarget
  TimingMediaController.prototype.addEventListener = EventTarget.addEventListener;
  TimingMediaController.prototype.removeEventListener = EventTarget.removeEventListener;
  TimingMediaController.prototype.dispatchEvent = EventTarget.dispatchEvent;


  // Expose the class to the outer world
  return TimingMediaController;
});
示例#7
0
/**
* @fileOverview Basic Web socket server that can manage a set of timing objects
*
* To run the server from the root repository folder:
*   node src/server.js
*/

var woodman = require('woodman');
var woodmanConfig = require('./woodmanConfig');
var logger = woodman.getLogger('main');

var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var TimingObject = require('../src/TimingObject');
var stringify = require('../src/utils').stringify;



var cfg = {
  ssl: true,
  port: 8080,
  ssl_key: '/home/VICOMTECH/itamayo/projects/tbargia-aldalur/certs/key.pem',
  ssl_cert: '/home/VICOMTECH/itamayo/projects/tbargia-aldalur/certs/cert.pem',
  passphrase:'XXX'

};

var httpServ = ( cfg.ssl ) ? require('https') : require('http');

var WebSocketServer = require('websocket').server;
示例#8
0
define(function (require) {
  var fs = require('fs');
  var mkdirp = require('mkdirp');
  var path = require('path');
  var async = require('async');
  var uuid = require('node-uuid');
  var _ = require('underscore');
  var woodman = require('woodman');
  var Mutex = require('./Mutex');
  var ParamError = require('./errors/ParamError');
  var ProxyError = require('./errors/ProxyError');
  var InternalError = require('./errors/InternalError');

  var logger = woodman.getLogger('filequeue');


  /**
   * Mutex used to serialize task files operations
   */
  var mutex = new Mutex();


  /**
   * Creates a new task queue that processes tasks one after the other.
   *
   * @class
   * @param {function(Object, function)} worker Function to call to run a task.
   *  The function receives the task's parameters as first parameter and a
   *  callback function that it must call when the task is over (with a
   *  potential error as first parameter).
   */
  var TaskQueue = function (worker, options) {
    /**
     * The tasks that are currently running. Tasks are run in parallel
     * up to options.maxItems if defined.
     */
    this.runningTasks = [];

    /**
     * The worker function to run to process each task
     */
    this.worker = worker;

    /**
     * Queue options
     */
    this.options = options || {};
    if (typeof this.options.maxItems === 'undefined') {
      this.options.maxItems = -1;
    }

    /**
     * Base folder to store tasks
     */
    this.baseFolder = path.resolve(__dirname, '..',
      (options.taskFolder || 'tasks'));

    /**
     * Has the file storage been initialized?
     */
    this.initialized = false;


    // Start next pending task if needed
    this.start();
  };


  /**
   * Starts the task queue, scheduling the first pending task
   * if there is one.
   *
   * @function
   * @private
   */
  TaskQueue.prototype.start = function () {
    this.whenReady(_.bind(function (err) {
      if (err) return;
      this.checkNextTask();
    }, this));
  };


  /**
   * Initializes the task queue, creating the appropriate folders if needed
   *
   * @function
   * @private
   * @param {function} callback Callback function called when folders are ready
   */
  TaskQueue.prototype.whenReady = function (callback) {
    callback = callback || function () {};

    if (this.initialized) return callback();
    if (this.error) return callback(this.error);

    var self = this;
    async.each([
      'id',
      'pending',
      'running'
    ], function (folder, next) {
      var fullPath = self.baseFolder + path.sep + folder;
      mkdirp(fullPath, function (err) {
        if (err) {
          return next(new InternalError(
            'Path "' + fullPath + '" could not be created', err));
        }
        return next();
      });
    }, function (err) {
      self.initialized = true;
      self.error = err;

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

      // Ensure that the "running" folder is empty, warn about that otherwise
      fs.readdir(self.baseFolder + path.sep + 'running', function (err, files) {
        if (err) {
          logger.error('when ready', 'could not check "running" folder', err);
          return callback(err);
        }
        var file = _.find(files, function (file) {
          return file.match(/\.json$/);
        });
        if (file) {
          logger.warn('ghost task in "running" folder', 'file=' + file);
        }
        return callback();
      });
    });
  };


  /**
   * Takes the lock on the task queue and runs the callback once locked
   *
   * @function
   * @param {Object} task The task that wants to lock the queue
   * @param {function} callback Function called when lock has been taken
   */
  TaskQueue.prototype.lock = function (task, callback) {
    callback = callback || function () {};

    mutex.lock(task.id, _.bind(function () {
      this.locked = task.id;
      this.whenReady(callback);
    }, this));
  };


  /**
   * Releases the lock on the queue
   *
   * @function
   * @param {Object} task Task that had the lock
   */
  TaskQueue.prototype.unlock = function (task) {
    mutex.unlock(task.id);
  };


  /**
   * Saves the given task to the specified folder
   *
   * @function
   * @param {Object} task The task to save
   * @param {string} folder The folder the task should be saved to
   * @param {function} callback Function called when task was saved.
   */
  TaskQueue.prototype.saveTaskToFolder = function (task, folder, callback) {
    callback = callback || function () {};
    if (!task || !task.id) return callback();

    var filename = this.baseFolder + path.sep + folder +
      path.sep + task.id + '.json';
    fs.writeFile(filename, JSON.stringify(task, null, 2), function (err) {
      if (err) {
        logger.error('save', 'taskId=' + task.id,
          'folder=' + folder, 'error', err.toString());
        return callback(new InternalError(
          'Could not save task to file', err));
      }

      logger.log('save', 'taskId=' + task.id,
        'folder=' + folder, 'done');
      return callback();
    });
  };


  /**
   * Removes the given task from the given folder
   *
   * @function
   * @param {Object} task The task to save
   * @param {string} folder The folder from which the task should be removed
   * @param {function} callback Function called when task was deleted.
   */
  TaskQueue.prototype.removeTaskFromFolder = function (task, folder, callback) {
    callback = callback || function () {};
    if (!task || !task.id) return callback();

    var filename = this.baseFolder + path.sep + folder +
      path.sep + task.id + '.json';
    fs.unlink(filename, function (err) {
      if (err) {
        logger.error('remove', 'taskId=' + task.id,
          'folder=' + folder, 'error', err.toString());
        return callback(new InternalError(
          'Could not save task to file', err));
      }

      logger.log('remove', 'taskId=' + task.id,
        'folder=' + folder, 'done');
      return callback();
    });
  };

  /**
   * Creates a new task with the given parameters.
   *
   * The new task gets processed when possible.
   *
   * @function
   * @param {Object} params Task params
   * @param {function} callback Called with the created task ID
   */
  TaskQueue.prototype.push = function (params, callback) {
    callback = callback || function () {};

    if (!params) {
      logger.warn('push', 'no task received');
      throw new ParamError('Invalid empty build task received');
    }

    var task = {
      id: uuid.v1(),
      params: params,
      status: 'pending',
      dateCreated: (new Date()).toISOString()
    };

    logger.log('push', 'taskId=' + task.id, 'name=' + params.name);

    var self = this;
    async.waterfall([
      function (next) {
        logger.log('push', 'taskId=' + task.id, 'take the lock');
        self.lock(task, next);
      },
      function (next) {
        logger.log('push', 'taskId=' + task.id,
          'save task to "id" folder');
        self.saveTaskToFolder(task, 'id', next);
      },
      function (next) {
        logger.log('push', 'taskId=' + task.id,
          'save task to "pending" folder');
        self.saveTaskToFolder(task, 'pending', next);
      }
    ], function (err) {
      logger.log('push', 'taskId=' + task.id, 'release the lock');
      self.unlock(task);

      if (err) {
        logger.error('push', 'taskId=' + task.id, 'error', err.toString());
        return callback(err);
      }

      logger.log('push', 'taskId=' + task.id,
        'schedule check for next task');
      _.defer(_.bind(self.checkNextTask, self));
      return callback(null, task.id);
    });
  };


  /**
   * Returns the number of tasks that are running
   *
   * @function
   * @return {Number} The number of tasks
   */
  TaskQueue.prototype.getNbRunningTasks = function () {
    if (!this.runningTasks) return 0;
    return this.runningTasks.length;
  };


  /**
   * Checks whether we may run another task. Schedules next pending task for
   * execution if we can.
   *
   * @function
   * @private
   */
  TaskQueue.prototype.checkNextTask = function () {
    // Fake "runner" task to take the lock
    var runnerTask = {
      id: 'runner-' + uuid.v1()
    };

    // Nothing to do if the maximum number of tasks that may be run in
    // parallel has been reached. The task will eventually be picked up
    // in the "pending" folder once a slot becomes available.
    if (this.runningTasks && (this.options.maxItems > 0) &&
        (this.runningTasks.length >= this.options.maxItems)) {
      logger.log('check', 'need to wait, too many tasks running at once');
      return;
    }

    var self = this;
    var runningTask = null;
    async.waterfall([
      function (next) {
        logger.log('check', 'take the lock');
        self.lock(runnerTask, next);
      },
      function (next) {
        logger.log('check', 'read "pending" folder');
        fs.readdir(self.baseFolder + path.sep + 'pending', next);
      },
      function (files, next) {
        logger.log('check', 'find first task to run');
        var file = _.find(files, function (file) {
          return file.match(/\.json$/);
        });
        if (!file) {
          logger.log('check', 'no more task to run');
          return next('all run');
        }
        return next(null, file);
      },
      function (file, next) {
        logger.log('check', 'read task file', 'file=' + file);
        fs.readFile(
          self.baseFolder + path.sep + 'pending' + path.sep + file,
          next);
      },
      function (data, next) {
        logger.log('check', 'parse JSON');
        var task = null;
        try {
          task = JSON.parse(data);
          return next(null, task);
        }
        catch (err) {
          return next(err);
        }
      },
      function (task, next) {
        logger.log('check', 'taskId=' + task.id,
          'set status to "running" and save to "id" folder');
        self.runningTasks.push(task);
        runningTask = task;
        task.status = 'running';
        self.saveTaskToFolder(task, 'id', next);
      },
      function (next) {
        logger.log('check', 'taskId=' + runningTask.id,
          'save task in "running" folder');
        self.saveTaskToFolder(runningTask, 'running', next);
      },
      function (next) {
        logger.log('check', 'taskId=' + runningTask.id,
          'remove task from "pending" folder');
        self.removeTaskFromFolder(runningTask, 'pending', next);
      }
    ], function (err) {
      logger.log('check', 'release the lock');
      self.unlock(runnerTask);

      if (err) {
        // No more task to process? Great!
        if (err === 'all run') {
          return;
        }

        if (runningTask) {
          logger.error('check',
            'taskId=' + runningTask.id,
            'error', err.toString());
        }
        else {
          logger.error('check', 'error', err.toString());
        }
        return;
      }

      logger.info('check',
        'taskId=' + runningTask.id,
        'schedule execution');
      _.defer(function () {
        self.runTask(runningTask);
      });
      return;
    });
  };


  /**
   * Processes the given task
   *
   * @function
   * @private
   * @param {Object} task The task to run
   */
  TaskQueue.prototype.runTask = function (task) {
    if (!task) return;

    var self = this;

    logger.log('run task', 'taskId=' + task.id, 'apply worker');
    this.worker(task.params, function (err, result) {
      if (err) {
        task.status = 'failure';
        task.error = err.toString();
        if (err instanceof ParamError) {
          logger.warn('run task', 'taskId=' + task.id,
            'wrong parameters', task.error);
          task.errorCode = 400;
        }
        else if (err instanceof ProxyError) {
          logger.warn('run task', 'taskId=' + task.id,
            'third party error', task.error);
          task.errorCode = 503;
        }
        else {
          logger.error('run task', 'taskId=' + task.id,
            'error', task.error);
          task.errorCode = 500;
        }
      }
      else {
        logger.log('run task', 'taskId=' + task.id, 'done');
        task.status = 'success';
        if (result) {
          task.result = result;
        }
      }
      task.dateFinished = (new Date()).toISOString();

      self.runningTasks = _.without(self.runningTasks, task);
      self.saveTaskToFolder(task, 'id', function (err) {
        if (err) {
          logger.error('run task', 'taskId=' + task.id,
            'could not save task result', 'status=' + task.status,
            err.toString());
        }

        self.removeTaskFromFolder(task, 'running', function (err) {
          if (err) {
            logger.error('run task', 'taskId=' + task.id,
              'could not remove task from "running" folder',
              err.toString());
          }

          // On to next pending task
          logger.log('run task', 'taskId=' + task.id, 'on to next task');
          _.defer(_.bind(self.checkNextTask, self));
          return;
        });
      });
    });
  };


  /**
   * Retrieves information about the task given as parameter.
   *
   * Note the function returns a copy of the task, not the task itself.
   *
   * @function
   * @param {string} taskId The ID of the task to retrieve
   */
  TaskQueue.prototype.get = function (taskId, callback) {
    callback = callback || function () {};

    var getTask = {
      id: 'get-' + uuid.v1()
    };

    var self = this;
    async.waterfall([
      function (next) {
        // logger.log('get', 'taskId=' + taskId, 'take the lock');
        self.lock(getTask, next);
      },
      function (next) {
        var file = self.baseFolder + path.sep + 'id' +
          path.sep + taskId + '.json';
        fs.exists(file, function (exists) {
          if (exists) {
            fs.readFile(file, next);
          }
          else {
            return next('not found');
          }
        });
      },
      function (data, next) {
        var task = null;
        try {
          task = JSON.parse(data);
          return next(null, task);
        }
        catch (err) {
          return next(err);
        }
      }
    ], function (err, task) {
      // logger.log('get', 'taskId=' + taskId, 'release the lock');
      self.unlock(getTask);

      if (err === 'not found') {
        // logger.log('get', 'taskId=' + taskId, 'not found');
        return callback();
      }

      if (err) {
        // logger.error('get', 'taskId=' + taskId, 'error', err.toString());
        return callback(err);
      }

      // logger.log('get', 'taskId=' + taskId, 'status=' + task.status);
      return callback(null, task);
    });
  };

  return TaskQueue;
});
示例#9
0
define(function (require) {
  var woodman = require('woodman');
  var logger = woodman.getLogger('StateVector');

  /**
   * Default constructor for a state vector
   *
   * @class
   * @param {Object} vector The initial motion vector
   * @param {Number} vector.position The initial position (0.0 if null)
   * @param {Number} vector.velocity The initial velocity (0.0 if null)
   * @param {Number} vector.acceleration The initial acceleration (0.0 if null)
   * @param {Number} vector.timestamp The initial time in seconds (now if null)
   */
  var StateVector = function (vector) {
    vector = vector || {};

    /**
     * The position of the motion along its axis.
     *
     * The position unit may be anything.
     */
    this.position = vector.position || 0.0;

    /**
     * The velocity of the motion in position units per second.
     */
    this.velocity = vector.velocity || 0.0;

    /**
     * The acceleration of the motion in position units per second squared.
     */
    this.acceleration = vector.acceleration || 0.0;

    /**
     * The local time in milliseconds when the position, velocity and
     * acceleration are evaluated.
     */
    this.timestamp = vector.timestamp || (Date.now() / 1000.0);

    logger.info('created', this);
  };


  /**
   * Computes the position along the uni-dimensional axis at the given time
   *
   * @function
   * @param {Number} timestamp The reference time in seconds
   */
  StateVector.prototype.computePosition = function (timestamp) {
    var elapsed = timestamp - this.timestamp;
    var result = this.position +
      this.velocity * elapsed +
      0.5 * this.acceleration * elapsed * elapsed;
    logger.log('compute position returns', result);
    return result;
  };


  /**
   * Computes the velocity along the uni-dimensional axis at the given time
   *
   * @function
   * @param {Number} timestamp The reference time in seconds
   */
  StateVector.prototype.computeVelocity = function (timestamp) {
    var elapsed = timestamp - this.timestamp;
    var result = this.velocity +
      this.acceleration * elapsed;
    logger.log('compute velocity returns', result);
    return result;
  };


  /**
   * Computes the acceleration along the uni-dimensional axis at the given time
   *
   * Note that this function merely exists for symmetry with computePosition and
   * computeAcceleration. In practice, this function merely returns the vector's
   * acceleration which is unaffected by time.
   *
   * @function
   * @param {Number} timestamp The reference time in seconds
   */
  StateVector.prototype.computeAcceleration = function (timestamp) {
    logger.log('compute acceleration returns', this.acceleration);
    return this.acceleration;
  };


  /**
   * Compares this vector with the specified vector for order. Returns a
   * negative integer, zero, or a positive integer as this vector is less than,
   * equal to, or greater than the specified object.
   *
   * Note that the notions of "less than" or "greater than" do not necessarily
   * mean much when comparing motions. In practice, the specified vector is
   * evaluated at the timestamp of this vector. Position is compared first.
   * If equal, velocity is compared next. If equal, acceleration is compared.
   *
   * TODO: the function probably returns differences in cases where it should
   * not because of the limited precision of floating numbers. Fix that.
   *
   * @function
   * @param {StateVector} vector The vector to compare
   * @returns {Integer} The comparison result
   */
  StateVector.prototype.compareTo = function (vector) {
    var timestamp = this.timestamp;
    var value = 0.0;

    value = vector.computePosition(timestamp);
    if (this.position < value) {
      return -1;
    }
    else if (this.position > value) {
      return 1;
    }

    value = vector.computeVelocity(timestamp);
    if (this.velocity < value) {
      return -1;
    }
    else if (this.velocity > value) {
      return 1;
    }

    value = vector.computeAcceleration(timestamp);
    if (this.acceleration < value) {
      return -1;
    }
    else if (this.acceleration > value) {
      return 1;
    }

    return 0;
  };


  /**
   * Overrides toString to return a meaningful string serialization of the
   * object for logging
   *
   * @function
   * @returns {String} A human-readable serialization of the vector
   */
  StateVector.prototype.toString = function () {
    return '(position=' + this.position +
      ', velocity=' + this.velocity +
      ', acceleration=' + this.acceleration +
      ', timestamp=' + this.timestamp + ')';
  };


  // Expose the Media State Vector constructor
  return StateVector;
});
示例#10
0
var woodman = require('woodman');
var _ = require("lodash");

// since this initiates the code with a worker process we need to configure woodman
woodman.load('console %domain - %message');
var logger = woodman.getLogger('events-reader');

// initiate the oplog eventsReader with the Mongodb oplog url and optionally start tailing
module.exports = function (harvestApp) {
    (!harvestApp.options.oplogConnectionString) && (function () {
        throw new Error("Missing config.options.oplogConnectionString")
    }());

    return harvestApp.eventsReader(harvestApp.options.oplogConnectionString).then(function (EventsReader) {
        logger.info('start tailing the oplog');
        var eventsReader = new EventsReader();
        eventsReader.tail();
        return eventsReader;
    }).catch(function (e) {
            logger.error(e);
            throw e;
        });
};
示例#11
0
define(function (require) {
  var spawn = require('child_process').spawn;
  var path = require('path');
  var _ = require('underscore');
  var woodman = require('woodman');
  var ParamError = require('./errors/ParamError');
  var InternalError = require('./errors/InternalError');

  var logger = woodman.getLogger('gitaction');

  return function (action, callback) {
    callback = callback || function () {};
    action.branch = action.branch || 'master';
    logger.info('action received',
      'origin=' + action.origin,
      'branch=' + action.branch,
      'script=' + action.script,
      'check=' + (action.check || 'none'));

    if (!action.origin) {
      logger.warn('Git origin not found');
      return callback(new ParamError(
        'The origin of the Git repository to clone must be specified'));
    }
    if (!action.script) {
      logger.warn('Git origin not found');
      return callback(new ParamError('No action script to run'));
    }

    var env = _.clone(action.env || {});
    env.PATH = process.env.PATH;
    if (action.privatekey) {
      env.GIT_SSH = path.resolve(__dirname, '..', action.dataFolder,
        'deploykeys', 'ssh-' + action.privatekey + '.sh');
    }
    else {
      env.GIT_SSH = path.resolve(__dirname, 'ssh-noprompt.sh');
    }

    var script = spawn('lib/gitaction.sh', [
      action.origin,
      (action.dataFolder || 'data'),
      action.branch,
      action.script,
      (action.check ? ' ' + action.check : '')
    ], {
      cwd: path.resolve(__dirname, '..'),
      env: env
    });

    // Kill the script if it takes too much time
    var timeout = setTimeout(function () {
      if (!script) return;
      script.kill('SIGTERM');
      setTimeout(function () {
        if (!script) return;
        script.kill('SIGKILL');
        return;
      }, 10000); // Give 10 seconds to the process to exit
    }, 1000 * (action.timeout || (60 * 10)));   // 10 minutes by default

    var outFragment = '';
    var errFragment = '';
    var log = function (type) {
      var fragment = (type === 'stdout') ? outFragment : errFragment;
      return function (data) {
        var str = fragment + data;
        var lines = str.split('\n');

        // Save the line if not the end of it
        if (lines.length === 1) {
          if (type === 'stdout') {
            outFragment = lines[0];
          }
          else {
            errFragment = lines[0];
          }
          return;
        }

        var i = 0;
        while (i < lines.length - 1) {
          logger.log(type + ' |', lines[i].replace(/\s+$/g, ''));
          i += 1;
        }
        if (type === 'stdout') {
          outFragment = lines[i];
        }
        else {
          errFragment = lines[i];
        }
      };
    };

    script.stdout.on('data', log('stdout'));
    script.stderr.on('data', log('stderr'));
    script.on('close', function (code) {
      clearTimeout(timeout);
      script = null;
      if (outFragment) {
        logger.log('stdout |', outFragment.replace(/\s+$/g, ''));
        outFragment = null;
      }
      if (errFragment) {
        logger.log('stderr |', outFragment.replace(/\s+$/g, ''));
        errFragment = null;
      }
      if (code === null) {
        logger.error('git action got killed',
          'origin=' + action.origin,
          'branch=' + action.branch,
          'script=' + action.script,
          'check=' + (action.check || 'none'));
        return callback(new InternalError(
          'git action script got killed', code));
      }
      if (code !== 0) {
        logger.error('could not run git action',
          'origin=' + action.origin,
          'branch=' + action.branch,
          'script=' + action.script,
          'check=' + (action.check || 'none'),
          'exit code=' + code);
        return callback(new InternalError(
          'git action script reported an error', code));
      }
      logger.log('run action', 'done',
        'origin=' + action.origin,
        'branch=' + action.branch,
        'script=' + action.script,
        'check=' + (action.check || 'none'));
      return callback();
    });
  };
});