<commit_before>'use strict';

/* eslint no-console:0 */

const cq = require('concurrent-queue'),
  Promise = require('bluebird'),
  log = require('intel').getLogger('sitespeedio.queuehandler'),
  messageMaker = require('../support/messageMaker'),
  queueStats = require('./queueStatistics');

const make = messageMaker('queueHandler').make;

function shortenData(key, value) {
  if (key === 'data') {
    return '{...}';
  }
  return value;
}

const messageTypeDepths = {};
const groupsPerSummaryType = {};

/**
 * Check some message format best practices that applies to sitespeed.io.
 * Throws an error if message doesn't follow the rules.
 * @param message the message to check
 */
function validateMessageFormat(message) {
  function validateTypeStructure(message) {
    const typeParts = message.type.split('.'),
      baseType = typeParts[0],
      typeDepth = typeParts.length;

    if (typeDepth > 2)
      throw new Error(
        'Message type has too many dot separated sections: ' + message.type
      );

    const previousDepth = messageTypeDepths[baseType];

    if (previousDepth && previousDepth !== typeDepth) {
      throw new Error(
        `All messages of type ${baseType} must have the same structure. ` +
          `${message.type} has ${typeDepth} part(s), but earlier messages had ${previousDepth} part(s).`
      );
    }

    messageTypeDepths[baseType] = typeDepth;
  }

  function validatePageSummary(message) {
    const type = message.type;
    if (!type.endsWith('.pageSummary')) return;

    if (!message.url)
      throw new Error(`Page summary message (${type}) didn't specify a url`);

    if (!message.group)
      throw new Error(`Page summary message (${type}) didn't specify a group.`);
  }

  function validateSummaryMessage(message) {
    const type = message.type;
    if (!type.endsWith('.summary')) return;

    if (message.url)
      throw new Error(
        `Summary message (${type}) shouldn't be url specific, use .pageSummary instead.`
      );

    if (!message.group)
      throw new Error(`Summary message (${type}) didn't specify a group.`);

    const groups = groupsPerSummaryType[type] || [];
    if (groups.includes(message.group)) {
      throw new Error(
        `Multiple summary messages of type ${type} and group ${message.group}`
      );
    }
    groupsPerSummaryType[type] = groups.concat(message.group);
  }

  validateTypeStructure(message);
  validatePageSummary(message);
  validateSummaryMessage(message);
}

class QueueHandler {
  constructor(plugins, options) {
    this.options = options;
    this.errors = [];

    this.createQueues(plugins);
  }

  createQueues(plugins) {
    this.queues = plugins
      .filter(plugin => plugin.processMessage)
      .map(plugin => {
        const concurrency = plugin.concurrency || Infinity;
        const queue = cq().limit({ concurrency });

        queue.plugin = plugin;

        const messageWaitingStart = {},
          messageProcessingStart = {};

        queue.enqueued(obj => {
          const message = obj.item;
          messageWaitingStart[message.uuid] = process.hrtime();
        });

        queue.processingStarted(obj => {
          const message = obj.item;

          const waitingDuration = process.hrtime(
              messageWaitingStart[message.uuid]
            ),
            waitingNanos = waitingDuration[0] * 1e9 + waitingDuration[1];

          queueStats.registerQueueTime(message, queue.plugin, waitingNanos);

          messageProcessingStart[message.uuid] = process.hrtime();
        });

        // FIXME handle rejections (i.e. failures while processing messages) properly
        queue.processingEnded(obj => {
          const message = obj.item;
          const err = obj.err;
          if (err) {
            let rejectionMessage =
              'Rejected ' +
              JSON.stringify(message, shortenData, 2) +
              ' for plugin: ' +
              plugin.name();

            if (message && message.url)
              rejectionMessage += ', url: ' + message.url;

            if (err.stack) {
              log.error(err.stack);
            }
            this.errors.push(rejectionMessage + '\n' + JSON.stringify(err));
          }

          const processingDuration = process.hrtime(
            messageWaitingStart[message.uuid]
          );
          const processingNanos =
            processingDuration[0] * 1e9 + processingDuration[1];

          queueStats.registerProcessingTime(
            message,
            queue.plugin,
            processingNanos
          );
        });

        return { plugin, queue };
      });
  }
  run(sources) {
    /*
   setup - plugins chance to talk to each other or setup what they need.
   url - urls passed around to analyze
   summarize - all analyze is finished and we can summarize all data
   render - plugin store data to disk
   final - is there anything you wanna do before sitespeed exist? upload files to the S3?
   */
    return this.startProcessingQueues()
      .then(() => this.postMessage(make('sitespeedio.setup')))
      .then(() => this.drainAllQueues())
      .then(() => Promise.map(sources, source => source.findUrls(this)))
      .then(() => this.drainAllQueues())
      .then(() => this.postMessage(make('sitespeedio.summarize')))
      .then(() => this.drainAllQueues())
      .then(() => this.postMessage(make('sitespeedio.render')))
      .then(() => this.drainAllQueues())
      .then(() => {
        if (this.options.queueStats) {
          log.info(JSON.stringify(queueStats.generateStatistics(), null, 2));
        }
      })
      .return(this.errors);
  }

  postMessage(message) {
    validateMessageFormat(message);

    // Don't return promise in loop - we don't want to wait for completion,
    // just post the message.
    for (let item of this.queues) {
      item.queue(message);
    }
  }

  startProcessingQueues() {
    return Promise.each(this.queues, item => {
      const queue = item.queue,
        plugin = item.plugin;
      queue.process(message =>
        Promise.resolve(plugin.processMessage(message, this))
      );
    });
  }

  drainAllQueues() {
    const queues = this.queues;
    return new Promise(resolve => {
      queues.forEach(item =>
        item.queue.drained(() => {
          if (queues.every(item => item.queue.isDrained)) {
            resolve();
          }
        })
      );
    });
  }
}

module.exports = QueueHandler;
<commit_msg>If we get an error on the queue make sure we exit with an error #1267 (#1779)<commit_after>'use strict';

/* eslint no-console:0 */

const cq = require('concurrent-queue'),
  Promise = require('bluebird'),
  log = require('intel').getLogger('sitespeedio.queuehandler'),
  messageMaker = require('../support/messageMaker'),
  queueStats = require('./queueStatistics');

const make = messageMaker('queueHandler').make;

function shortenData(key, value) {
  if (key === 'data') {
    return '{...}';
  }
  return value;
}

const messageTypeDepths = {};
const groupsPerSummaryType = {};

/**
 * Check some message format best practices that applies to sitespeed.io.
 * Throws an error if message doesn't follow the rules.
 * @param message the message to check
 */
function validateMessageFormat(message) {
  function validateTypeStructure(message) {
    const typeParts = message.type.split('.'),
      baseType = typeParts[0],
      typeDepth = typeParts.length;

    if (typeDepth > 2)
      throw new Error(
        'Message type has too many dot separated sections: ' + message.type
      );

    const previousDepth = messageTypeDepths[baseType];

    if (previousDepth && previousDepth !== typeDepth) {
      throw new Error(
        `All messages of type ${baseType} must have the same structure. ` +
          `${message.type} has ${typeDepth} part(s), but earlier messages had ${previousDepth} part(s).`
      );
    }

    messageTypeDepths[baseType] = typeDepth;
  }

  function validatePageSummary(message) {
    const type = message.type;
    if (!type.endsWith('.pageSummary')) return;

    if (!message.url)
      throw new Error(`Page summary message (${type}) didn't specify a url`);

    if (!message.group)
      throw new Error(`Page summary message (${type}) didn't specify a group.`);
  }

  function validateSummaryMessage(message) {
    const type = message.type;
    if (!type.endsWith('.summary')) return;

    if (message.url)
      throw new Error(
        `Summary message (${type}) shouldn't be url specific, use .pageSummary instead.`
      );

    if (!message.group)
      throw new Error(`Summary message (${type}) didn't specify a group.`);

    const groups = groupsPerSummaryType[type] || [];
    if (groups.includes(message.group)) {
      throw new Error(
        `Multiple summary messages of type ${type} and group ${message.group}`
      );
    }
    groupsPerSummaryType[type] = groups.concat(message.group);
  }

  validateTypeStructure(message);
  validatePageSummary(message);
  validateSummaryMessage(message);
}

class QueueHandler {
  constructor(plugins, options) {
    this.options = options;
    this.errors = [];

    this.createQueues(plugins);
  }

  createQueues(plugins) {
    this.queues = plugins
      .filter(plugin => plugin.processMessage)
      .map(plugin => {
        const concurrency = plugin.concurrency || Infinity;
        const queue = cq().limit({ concurrency });

        queue.plugin = plugin;

        const messageWaitingStart = {},
          messageProcessingStart = {};

        queue.enqueued(obj => {
          const message = obj.item;
          messageWaitingStart[message.uuid] = process.hrtime();
        });

        queue.processingStarted(obj => {
          const message = obj.item;

          const waitingDuration = process.hrtime(
              messageWaitingStart[message.uuid]
            ),
            waitingNanos = waitingDuration[0] * 1e9 + waitingDuration[1];

          queueStats.registerQueueTime(message, queue.plugin, waitingNanos);

          messageProcessingStart[message.uuid] = process.hrtime();
        });

        // FIXME handle rejections (i.e. failures while processing messages) properly
        queue.processingEnded(obj => {
          const message = obj.item;
          // if we get a error message on the queue make sure we register that
          if (message.type === 'error' || message.type.endsWith('.error')) {
            this.errors.push(message.data);
          }
          const err = obj.err;
          if (err) {
            let rejectionMessage =
              'Rejected ' +
              JSON.stringify(message, shortenData, 2) +
              ' for plugin: ' +
              plugin.name();

            if (message && message.url)
              rejectionMessage += ', url: ' + message.url;

            if (err.stack) {
              log.error(err.stack);
            }
            this.errors.push(rejectionMessage + '\n' + JSON.stringify(err));
          }

          const processingDuration = process.hrtime(
            messageWaitingStart[message.uuid]
          );
          const processingNanos =
            processingDuration[0] * 1e9 + processingDuration[1];

          queueStats.registerProcessingTime(
            message,
            queue.plugin,
            processingNanos
          );
        });

        return { plugin, queue };
      });
  }
  run(sources) {
    /*
   setup - plugins chance to talk to each other or setup what they need.
   url - urls passed around to analyze
   summarize - all analyze is finished and we can summarize all data
   render - plugin store data to disk
   final - is there anything you wanna do before sitespeed exist? upload files to the S3?
   */
    return this.startProcessingQueues()
      .then(() => this.postMessage(make('sitespeedio.setup')))
      .then(() => this.drainAllQueues())
      .then(() => Promise.map(sources, source => source.findUrls(this)))
      .then(() => this.drainAllQueues())
      .then(() => this.postMessage(make('sitespeedio.summarize')))
      .then(() => this.drainAllQueues())
      .then(() => this.postMessage(make('sitespeedio.render')))
      .then(() => this.drainAllQueues())
      .then(() => {
        if (this.options.queueStats) {
          log.info(JSON.stringify(queueStats.generateStatistics(), null, 2));
        }
      })
      .return(this.errors);
  }

  postMessage(message) {
    validateMessageFormat(message);

    // Don't return promise in loop - we don't want to wait for completion,
    // just post the message.
    for (let item of this.queues) {
      item.queue(message);
    }
  }

  startProcessingQueues() {
    return Promise.each(this.queues, item => {
      const queue = item.queue,
        plugin = item.plugin;
      queue.process(message =>
        Promise.resolve(plugin.processMessage(message, this))
      );
    });
  }

  drainAllQueues() {
    const queues = this.queues;
    return new Promise(resolve => {
      queues.forEach(item =>
        item.queue.drained(() => {
          if (queues.every(item => item.queue.isDrained)) {
            resolve();
          }
        })
      );
    });
  }
}

module.exports = QueueHandler;
<|endoftext|>


<commit_before>// Copyright, 2013-2014, by Tomas Korcak. <korczis@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

(function () {
    'use strict';

    var define = require('amdefine')(module);

    /**
     * Array of modules this one depends on.
     * @type {Array}
     */
    var deps = [];

    define(deps, function () {
        var CustomErrors = require('./../error');

        var notImplementedMsg = 'This must be implemented in SerializerBase implementation!';

        /**
         * Base Interface for Serializer Class
         * @class
         */
        function SerializerBase(opts) {
            this.opts = opts || {};

            return this;
        }

        SerializerBase.prototype.init = function() {
            throw new CustomErrors.NotImplemented(notImplementedMsg);
        };

        SerializerBase.prototype.insert = function() {
            throw new CustomErrors.NotImplemented(notImplementedMsg);
        };

        module.exports = SerializerBase;
    });
}());<commit_msg>Removed serializer<commit_after><|endoftext|>


<commit_before>"use strict";

const logger              = require('../../util/logger'),
      promiseWhile        = require('../../util/promise').promiseWhile,
      buildModel          = require('../model').build,
      ServerCodeService   = require('../services/server-code'),
      BackendlessRegistry = require('../services/registry'),
      tasksExecutor       = require('./tasks/executor');

const EVENTS_WAIT_TIMEOUT = 5;

class DebugCodeRunner {
  constructor(opts) {
    this.options = opts;
    this.scs = new ServerCodeService(opts.app, opts.backendless.apiServer);
    this.model = buildModel(opts.app);
  }

  start() {
    Promise.resolve()
      .then(() => this.addShutdownHook())
      .then(() => this.registerRunner())
      .then(() => this.keepDebugSessionAlive())
      .then(() => this.registerModel())
      .then(() => this.listenTasksChannel())
      .catch(err => {
        logger.error(err.message || err);
        this.stop();
      });

    return this;
  }

  addShutdownHook() {
    const hook = this.stop.bind(this);

    process.on('exit', hook);
    process.on('SIGINT', hook);
    process.on('SIGTERM', hook);
  }

  stop() {
    if (!this.stopped) {
      logger.info('Shutting down Code Runner. Please wait..');

      if (this.sessionRenewalTimer) {
        clearInterval(this.sessionRenewalTimer);
        delete this.sessionRenewalTimer;
      }

      if (this.debugSessionId) {
        this.scs.unregisterRunner();
        this.registry && this.registry.del(this.debugSessionId);

        delete this.debugSessionId;
      }

      if (this.registry) {
        logger.debug('Closing Redis connection..');

        this.registry.stop();
      }

      this.stopped = true;
    }
  }

  registerModel() {
    return this.scs.registerModel(this.model.serialize());
  }

  registerRunner() {
    return this.scs.registerRunner()
      .then(debugId => (this.debugSessionId = debugId));
  }

  getRegistry() {
    if (!this.registry) {
      const registryOpts = this.options.backendless.registry;

      this.registry = new BackendlessRegistry(registryOpts.host, registryOpts.port);
    }

    return this.registry;
  }

  keepDebugSessionAlive() {
    this.sessionRenewalTimer && clearInterval(this.sessionRenewalTimer);

    this.sessionRenewalTimer = setTimeout(() => {
      this.getRegistry().expire(this.debugSessionId, 60)
        .then(result => {
          let keyExists = result === 1;

          if (keyExists) {
            this.keepDebugSessionAlive();
          } else {
            throw new Error("Debug Session ID doesn't exist on server anymore");
          }
        })
        .catch(err => {
          logger.error(err.message || err);
          delete this.sessionRenewalTimer;

          if (!this.stopped) {
            logger.error('Oops.. Looks like we were disconnected. Trying to reconnect..');

            this.start();
          }
        });
    }, 45000);
  }

  listenTasksChannel() {
    logger.info('Waiting for Server Code tasks..');

    const running = () => !this.stopped;

    promiseWhile(running, () => this.getTask().then(task => task && this.processTask(task)));
  }

  getTask() {
    return this.getRegistry().blpop(this.options.app.id, EVENTS_WAIT_TIMEOUT)
      .then(task => task && task[1] && JSON.parse(task[1]))//TODO: discuss with the backend guys why it's array
      .catch(e => {
        logger.error('Error while waiting for events: ' + e);
        throw e;
      });
  }

  processTask(task) {
    logger.info(`New task arrived! [id:${task.id}]`);

    const registry = this.getRegistry();

    tasksExecutor.execute(task, this.model)
      .then(result => {
        logger.info(`[${task.id}] Processing finished`);

        if (result) {
          logger.debug(`[${task.id}] Sending results to Redis`);

          return registry.rpush(task.id, result)
            .then(()=> registry.expire(task.id, 10))
            .then(()=> {
              logger.debug(`[${task.id}] Task results sent`);
            });
        }
      })
      .catch(err => {
        logger.debug(`Error during task execution. Task: ${task.id}. Error: ${err.message || err}`);
      });
  }
}

exports.start = function(opts) {
  logger.info('Starting Debug Code Runner...');

  return new DebugCodeRunner(opts).start();
};<commit_msg>fix (debug runner) too long process exiting when stopped<commit_after>"use strict";

const logger              = require('../../util/logger'),
      promiseWhile        = require('../../util/promise').promiseWhile,
      buildModel          = require('../model').build,
      ServerCodeService   = require('../services/server-code'),
      BackendlessRegistry = require('../services/registry'),
      tasksExecutor       = require('./tasks/executor');

const EVENTS_WAIT_TIMEOUT = 5;

class DebugCodeRunner {
  constructor(opts) {
    this.options = opts;
    this.scs = new ServerCodeService(opts.app, opts.backendless.apiServer);
    this.model = buildModel(opts.app);
  }

  start() {
    Promise.resolve()
      .then(() => this.addShutdownHook())
      .then(() => this.registerRunner())
      .then(() => this.keepDebugSessionAlive())
      .then(() => this.registerModel())
      .then(() => this.listenTasksChannel())
      .catch(err => {
        logger.error(err.message || err);
        this.stop();
      });

    return this;
  }

  addShutdownHook() {
    const hook = this.stop.bind(this);

    process.on('exit', hook);
    process.on('SIGINT', hook);
    process.on('SIGTERM', hook);
  }

  stop() {
    if (!this.stopped) {
      logger.info('Shutting down Code Runner. Please wait..');

      if (this.sessionRenewalTimer) {
        clearTimeout(this.sessionRenewalTimer);
        delete this.sessionRenewalTimer;
      }

      if (this.debugSessionId) {
        this.scs.unregisterRunner();
        this.registry && this.registry.del(this.debugSessionId);

        delete this.debugSessionId;
      }

      if (this.registry) {
        logger.debug('Closing Redis connection..');

        this.registry.stop();
      }

      this.stopped = true;
    }
  }

  registerModel() {
    return this.scs.registerModel(this.model.serialize());
  }

  registerRunner() {
    return this.scs.registerRunner()
      .then(debugId => (this.debugSessionId = debugId));
  }

  getRegistry() {
    if (!this.registry) {
      const registryOpts = this.options.backendless.registry;

      this.registry = new BackendlessRegistry(registryOpts.host, registryOpts.port);
    }

    return this.registry;
  }

  keepDebugSessionAlive() {
    this.sessionRenewalTimer && clearInterval(this.sessionRenewalTimer);

    this.sessionRenewalTimer = setTimeout(() => {
      this.getRegistry().expire(this.debugSessionId, 60)
        .then(result => {
          let keyExists = result === 1;

          if (keyExists) {
            this.keepDebugSessionAlive();
          } else {
            throw new Error("Debug Session ID doesn't exist on server anymore");
          }
        })
        .catch(err => {
          logger.error(err.message || err);
          delete this.sessionRenewalTimer;

          if (!this.stopped) {
            logger.error('Oops.. Looks like we were disconnected. Trying to reconnect..');

            this.start();
          }
        });
    }, 45000);
  }

  listenTasksChannel() {
    logger.info('Waiting for Server Code tasks..');

    const running = () => !this.stopped;

    promiseWhile(running, () => this.getTask().then(task => task && this.processTask(task)));
  }

  getTask() {
    return this.getRegistry().blpop(this.options.app.id, EVENTS_WAIT_TIMEOUT)
      .then(task => task && task[1] && JSON.parse(task[1]))//TODO: discuss with the backend guys why it's array
      .catch(e => {
        logger.error('Error while waiting for events: ' + e);
        throw e;
      });
  }

  processTask(task) {
    logger.info(`New task arrived! [id:${task.id}]`);

    const registry = this.getRegistry();

    tasksExecutor.execute(task, this.model)
      .then(result => {
        logger.info(`[${task.id}] Processing finished`);

        if (result) {
          logger.debug(`[${task.id}] Sending results to Redis`);

          return registry.rpush(task.id, result)
            .then(()=> registry.expire(task.id, 10))
            .then(()=> {
              logger.debug(`[${task.id}] Task results sent`);
            });
        }
      })
      .catch(err => {
        logger.debug(`Error during task execution. Task: ${task.id}. Error: ${err.message || err}`);
      });
  }
}

exports.start = function(opts) {
  logger.info('Starting Debug Code Runner...');

  return new DebugCodeRunner(opts).start();
};<|endoftext|>


<commit_before>var amqp = require('amqp')
  , util = require('util');

var NONPERSISTENT_MODE = 1;
var PERSISTENT_MODE = 2;

/**
 * `Broker` constructor.
 *
 * @api public
 */
function Broker() {
  this._exchange = null;
  this._queues = {};
}

Broker.prototype.connect = function(options, cb) {
  var self = this;
  this._connection = amqp.createConnection(options);
  this._connection.once('ready', function() {
    var exchange = options.exchange;
    if (!exchange) {
      // Get the default exchange, which is a direct exchange, and thus suitable
      // for use as a task queue.
      self._exchange = self._connection.exchange();
      return cb && cb();
    } else {
      var name = exchange
        , opts = {};
      if (typeof exchange == 'object') {
        name = exchange.name;
        opts = exchange.options;
      }

      opts.type = (opts.type === undefined) ? 'direct' : opts.type;
      opts.durable = (opts.durable === undefined) ? true : opts.durable;
      opts.autoDelete = (opts.autoDelete === undefined) ? false : opts.autoDelete;
      opts.confirm = (opts.confirm === undefined) ? true : opts.confirm;

      self._exchange = self._connection.exchange(name, opts, function(err) {
        return cb && cb();
      });
    }
  });
}

Broker.prototype.declare = function(name, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // Task queues are declared as durable, by default.  This ensures that tasks
  // are not lost in the event that that server is stopped or crashes.
  options.durable = (options.durable === undefined) ? true : options.durable;
  options.autoDelete = (options.autoDelete === undefined) ? false : options.autoDelete;

  var q = this._connection.queue(name, options, function(q) {
    q.removeListener('error', onQueueDeclareError);

    var exchange = options.bind;
    if (exchange) {
      q.bind(exchange, name, function(q) {
        q.removeListener('error', onQueueBindError);
        return cb && cb();
      });

      // Listen for events that indicate that the queue failed to be bound.
      var onQueueBindError = function(err) {
        return cb && cb(err);
      };
      q.once('error', onQueueBindError);
    } else {
      return cb && cb();
    }
  });
  this._queues[name] = q;

  // Listen for events that indicate that the queue failed to be declared.
  var onQueueDeclareError = function(err) {
    return cb && cb(err);
  };
  q.once('error', onQueueDeclareError);
}

Broker.prototype.enqueue = function(queue, msg, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  options.deliveryMode = (options.deliveryMode === undefined) ? PERSISTENT_MODE : options.deliveryMode;
  // TODO: This option appears to have no effect. Why?
  //options.mandatory = (options.mandatory === undefined) ? true : options.mandatory;

  if (this._exchange.options && this._exchange.options.confirm) {
    this._exchange.publish(queue, msg, options, function(hadError) {
      var err;
      if (hadError) {
        err = new Error('Failed to publish message');
      }
      return cb(err);
    });
  } else {
    this._exchange.publish(queue, msg, options);
    return cb();
  }
}

Broker.prototype.subscribe = function(queue, options, handler, cb) {
  if (typeof options == 'function') {
    handler = options;
    options = undefined;
  }
  options = options || {};

  options.ack = (options.ack === undefined) ? true : options.ack;

  var q = this._queues[queue]
  q.subscribe(options, function(message) {
    function shift() { q.shift(); };
    handler(message, shift);
  });

  // Listen for events that indicate the subscription has been processed.
  // `basicConsumeOk` is emitted when the subscription is successful.
  // `error` is emitted when the subscription failed.  In practice, this
  // has only been observed when attempting to subscribe to queue that has a
  // previously existing exclusive subscription.

  var onBasicConsumeOk = function(args) {
    q.removeListener('error', onBasicConsumeError);
    return cb && cb();
  };

  var onBasicConsumeError = function(err) {
    q.removeListener('basicConsumeOk', onBasicConsumeOk);
    return cb && cb(err);
  };

  q.once('basicConsumeOk', onBasicConsumeOk);
  q.once('error', onBasicConsumeError);
}

/**
 * Expose `Broker`.
 */
module.exports = Broker;
<commit_msg>Inherit from EventEmitter.<commit_after>var EventEmitter = require('events').EventEmitter
  , amqp = require('amqp')
  , util = require('util');

var NONPERSISTENT_MODE = 1;
var PERSISTENT_MODE = 2;

/**
 * `Broker` constructor.
 *
 * @api public
 */
function Broker() {
  EventEmitter.call(this);
  this._exchange = null;
  this._queues = {};
}

/**
 * Inherit from `EventEmitter`.
 */
util.inherits(Broker, EventEmitter);

Broker.prototype.connect = function(options, readyListener) {
  var self = this;
  this._connection = amqp.createConnection(options);
  this._connection.once('ready', function() {
    var exchange = options.exchange;
    if (!exchange) {
      // Get the default exchange, which is a direct exchange, and thus suitable
      // for use as a task queue.
      self._exchange = self._connection.exchange();
      return readyListener && readyListener();
    } else {
      var name = exchange
        , opts = {};
      if (typeof exchange == 'object') {
        name = exchange.name;
        opts = exchange.options;
      }

      opts.type = (opts.type === undefined) ? 'direct' : opts.type;
      opts.durable = (opts.durable === undefined) ? true : opts.durable;
      opts.autoDelete = (opts.autoDelete === undefined) ? false : opts.autoDelete;
      opts.confirm = (opts.confirm === undefined) ? true : opts.confirm;

      self._exchange = self._connection.exchange(name, opts, function(err) {
        return readyListener && readyListener();
      });
    }
  });

  this._connection.on('error', this.emit.bind(this, 'error'));
}

Broker.prototype.declare = function(name, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  // Task queues are declared as durable, by default.  This ensures that tasks
  // are not lost in the event that that server is stopped or crashes.
  options.durable = (options.durable === undefined) ? true : options.durable;
  options.autoDelete = (options.autoDelete === undefined) ? false : options.autoDelete;

  var q = this._connection.queue(name, options, function(q) {
    q.removeListener('error', onQueueDeclareError);

    var exchange = options.bind;
    if (exchange) {
      q.bind(exchange, name, function(q) {
        q.removeListener('error', onQueueBindError);
        return cb && cb();
      });

      // Listen for events that indicate that the queue failed to be bound.
      var onQueueBindError = function(err) {
        return cb && cb(err);
      };
      q.once('error', onQueueBindError);
    } else {
      return cb && cb();
    }
  });
  this._queues[name] = q;

  // Listen for events that indicate that the queue failed to be declared.
  var onQueueDeclareError = function(err) {
    return cb && cb(err);
  };
  q.once('error', onQueueDeclareError);
}

Broker.prototype.enqueue = function(queue, msg, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  options.deliveryMode = (options.deliveryMode === undefined) ? PERSISTENT_MODE : options.deliveryMode;
  // TODO: This option appears to have no effect. Why?
  //options.mandatory = (options.mandatory === undefined) ? true : options.mandatory;

  if (this._exchange.options && this._exchange.options.confirm) {
    this._exchange.publish(queue, msg, options, function(hadError) {
      var err;
      if (hadError) {
        err = new Error('Failed to publish message');
      }
      return cb(err);
    });
  } else {
    this._exchange.publish(queue, msg, options);
    return cb();
  }
}

Broker.prototype.subscribe = function(queue, options, handler, cb) {
  if (typeof options == 'function') {
    handler = options;
    options = undefined;
  }
  options = options || {};

  options.ack = (options.ack === undefined) ? true : options.ack;

  var q = this._queues[queue]
  q.subscribe(options, function(message) {
    function shift() { q.shift(); };
    handler(message, shift);
  });

  // Listen for events that indicate the subscription has been processed.
  // `basicConsumeOk` is emitted when the subscription is successful.
  // `error` is emitted when the subscription failed.  In practice, this
  // has only been observed when attempting to subscribe to queue that has a
  // previously existing exclusive subscription.

  var onBasicConsumeOk = function(args) {
    q.removeListener('error', onBasicConsumeError);
    return cb && cb();
  };

  var onBasicConsumeError = function(err) {
    q.removeListener('basicConsumeOk', onBasicConsumeOk);
    return cb && cb(err);
  };

  q.once('basicConsumeOk', onBasicConsumeOk);
  q.once('error', onBasicConsumeError);
}

/**
 * Expose `Broker`.
 */
module.exports = Broker;
<|endoftext|>


<commit_before>import angular from 'angular';
import * as olEvents from 'ol/events.js';
import RenderEvent from 'ol/render/Event.js';
/**
 * @type {angular.IModule}
 * @hidden
 */
const module = angular.module('ngeoMapswipe', []);

module.run(
  /**
   * @ngInject
   * @param {angular.ITemplateCacheService} $templateCache
   */
  $templateCache => {
    // @ts-ignore: webpack
    $templateCache.put('ngeo/src/map/swipe', require('./swipe.html'));
  }
);

module.value(
  'ngeoMapswipeTemplateUrl',
  /**
   * @param {angular.IAttributes} $attrs Attributes.
   * @return {string} The template url.
   */
  $attrs => {
    const templateUrl = $attrs.ngeoMapswipeTemplateUrl;
    return templateUrl !== undefined ? templateUrl : 'ngeo/src/map/swipe';
  }
);

/**
 * @param {angular.IAttributes} $attrs Attributes.
 * @param {function(angular.IAttributes): string} ngeoMapswipeTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 * @private
 * @hidden
 */
function ngeoMapswipeTemplateUrl($attrs, ngeoMapswipeTemplateUrl) {
  return ngeoMapswipeTemplateUrl($attrs);
}

/**
 * The controller for the Mapswipe component.
 */
class SwipeController {
  /**
   * @param {angular.IScope} $scope Angular scope.
   * @param {JQuery} $element Element.
   * @param {angular.auto.IInjectorService} $injector Main injector.
   * @constructor
   * @private
   * @hidden
   * @ngInject
   * @ngdoc controller
   * @ngname ngeoMapswipeController
   */
  constructor($scope, $element, $injector) {
    /**
     * @type {import('ol/Map.js').default}
     */
    this.map;

    /**
     * @type {import('ol/layer/Tile.js').default}
     */
    this.layer;

    /**
     * @type {angular.IScope}
     * @private
     */
    this.scope_ = $scope;

    /**
     * @type {number}
     */
    this.swipeValue = 50;

    /**
     * @type {JQuery}
     * @private
     */
    this.swipeInput_ = $element.find('.swipe-input');

    /**
     * @type {olEvents.EventsKey[]}
     * @private
     */
    this.layerKeys_ = [];
  }

  /**
   * Init the controller
   */
  $onInit() {
    this.layerKeys_.push(olEvents.listen(this.layer, 'prerender', this.handleLayerPrerender_, this));
    this.layerKeys_.push(olEvents.listen(this.layer, 'postrender', this.handleLayerPostrender_, this));

    this.swipeInput_.on('input change', event => {
      this.swipeValue = Number($(event.target).val());
      this.map.render();
    });
  }

  /**
   * @param {?Event|import("ol/events/Event.js").default} evt OpenLayers object event.
   * @private
   */
  handleLayerPrerender_(evt) {
    if (!(evt instanceof RenderEvent)) {
      return;
    }
    const ctx = evt.context;
      if (!ctx) {
        return;
      }
      const width = ctx.canvas.width * (this.swipeValue / 100);
      ctx.save();
      ctx.beginPath();
      ctx.rect(0, 0, width, ctx.canvas.height);
      ctx.clip();
  }

  /**
   * @param {?Event|import("ol/events/Event.js").default} evt OpenLayers object event.
   * @private
   */
  handleLayerPostrender_(evt) {
    if (evt instanceof RenderEvent) {
      const ctx = evt.context;
      if (!ctx) {
        return;
      }
      ctx.restore();
    }
  }

  $onDestroy() {
    this.layerKeys_.forEach(olEvents.unlistenByKey);
    this.layerKeys_.length = 0;
    this.swipeInput_.off();
  }
}

module.component('ngeoMapswipe', {
  controller: SwipeController,
  bindings: {
    map: '<',
    layer: '<'
  },
  templateUrl: ngeoMapswipeTemplateUrl
});

export default module;
<commit_msg>Fix indentation<commit_after>import angular from 'angular';
import * as olEvents from 'ol/events.js';
import RenderEvent from 'ol/render/Event.js';
/**
 * @type {angular.IModule}
 * @hidden
 */
const module = angular.module('ngeoMapswipe', []);

module.run(
  /**
   * @ngInject
   * @param {angular.ITemplateCacheService} $templateCache
   */
  $templateCache => {
    // @ts-ignore: webpack
    $templateCache.put('ngeo/src/map/swipe', require('./swipe.html'));
  }
);

module.value(
  'ngeoMapswipeTemplateUrl',
  /**
   * @param {angular.IAttributes} $attrs Attributes.
   * @return {string} The template url.
   */
  $attrs => {
    const templateUrl = $attrs.ngeoMapswipeTemplateUrl;
    return templateUrl !== undefined ? templateUrl : 'ngeo/src/map/swipe';
  }
);

/**
 * @param {angular.IAttributes} $attrs Attributes.
 * @param {function(angular.IAttributes): string} ngeoMapswipeTemplateUrl Template function.
 * @return {string} Template URL.
 * @ngInject
 * @private
 * @hidden
 */
function ngeoMapswipeTemplateUrl($attrs, ngeoMapswipeTemplateUrl) {
  return ngeoMapswipeTemplateUrl($attrs);
}

/**
 * The controller for the Mapswipe component.
 */
class SwipeController {
  /**
   * @param {angular.IScope} $scope Angular scope.
   * @param {JQuery} $element Element.
   * @param {angular.auto.IInjectorService} $injector Main injector.
   * @constructor
   * @private
   * @hidden
   * @ngInject
   * @ngdoc controller
   * @ngname ngeoMapswipeController
   */
  constructor($scope, $element, $injector) {
    /**
     * @type {import('ol/Map.js').default}
     */
    this.map;

    /**
     * @type {import('ol/layer/Tile.js').default}
     */
    this.layer;

    /**
     * @type {angular.IScope}
     * @private
     */
    this.scope_ = $scope;

    /**
     * @type {number}
     */
    this.swipeValue = 50;

    /**
     * @type {JQuery}
     * @private
     */
    this.swipeInput_ = $element.find('.swipe-input');

    /**
     * @type {olEvents.EventsKey[]}
     * @private
     */
    this.layerKeys_ = [];
  }

  /**
   * Init the controller
   */
  $onInit() {
    this.layerKeys_.push(olEvents.listen(this.layer, 'prerender', this.handleLayerPrerender_, this));
    this.layerKeys_.push(olEvents.listen(this.layer, 'postrender', this.handleLayerPostrender_, this));

    this.swipeInput_.on('input change', event => {
      this.swipeValue = Number($(event.target).val());
      this.map.render();
    });
  }

  /**
   * @param {?Event|import("ol/events/Event.js").default} evt OpenLayers object event.
   * @private
   */
  handleLayerPrerender_(evt) {
    if (!(evt instanceof RenderEvent)) {
      return;
    }
    const ctx = evt.context;
    if (!ctx) {
      return;
    }
    const width = ctx.canvas.width * (this.swipeValue / 100);
    ctx.save();
    ctx.beginPath();
    ctx.rect(0, 0, width, ctx.canvas.height);
    ctx.clip();
  }

  /**
   * @param {?Event|import("ol/events/Event.js").default} evt OpenLayers object event.
   * @private
   */
  handleLayerPostrender_(evt) {
    if (evt instanceof RenderEvent) {
      const ctx = evt.context;
      if (!ctx) {
        return;
      }
      ctx.restore();
    }
  }

  $onDestroy() {
    this.layerKeys_.forEach(olEvents.unlistenByKey);
    this.layerKeys_.length = 0;
    this.swipeInput_.off();
  }
}

module.component('ngeoMapswipe', {
  controller: SwipeController,
  bindings: {
    map: '<',
    layer: '<'
  },
  templateUrl: ngeoMapswipeTemplateUrl
});

export default module;
<|endoftext|>


<commit_before>'use strict';

function DashWatch(settings) {
  this.settings = settings;
  this.sheetkey = settings.sheetkey;
  this.googauth = settings.googauth;
  this.dashmac = settings.dashmac;

  this.run = function() {
    var MongoClient = require('mongodb').MongoClient;
    var assert = require('assert');
    var ObjectId = require('mongodb').ObjectID;
    var url = 'mongodb://localhost:27017/fat_cats';
    var dateFormat = require('dateformat');
    var dash_button = require('node-dash-button');
    var dash = dash_button(this.dashmac);

    dash.on("detected", function (){
      var now = new Date();
      var nowFormat = dateFormat(now, "dddd, mmmm dS yyyy @ HH:MM:ss");
      var insertDocument = function(db, callback) {
        db.collection('feedings').insertOne({
          "datetime": now
        }, function(err, result) {
          assert.equal(err, null);
          console.log("The Authorities fed us " + nowFormat + ".");
          callback();
        });
      };
      MongoClient.connect(url, function(err, db) {
        assert.equal(null, err);
        insertDocument(db, function() {
          db.close();
        })
      })
    });
  };

  this.tokencheck = function() {
    console.log('sheet:'+this.sheetkey);
    console.log('googauth:'+this.googauth);
    console.log('dashmac:'+this.dashmac);
  }

  return this;
}

module.exports = DashWatch;
<commit_msg>Moved CRUD actions to lib/crud.js<commit_after>'use strict';

function DashWatch(settings) {
  this.settings = settings;
  this.sheetkey = settings.sheetkey;
  this.googauth = settings.googauth;
  this.dashmac = settings.dashmac;

  this.run = function() {
    var dateFormat = require('dateformat');
    var dash_button = require('node-dash-button');
    var dash = dash_button(this.dashmac);

    dash.on("detected", function (){
      var now = new Date();
      var nowTimezone = new Date(now.getTime() - (now.getTimezoneOffset() * 60000));
      var CrudAction = require("./crud.js");
      var collection = "feedings"
      var url = 'mongodb://localhost:27017/fat_cats';
      var doc = {};

      var fat_catsDb = new CrudAction({
        collection: collection,
        url: url
      });

      doc.datetime = nowTimezone;
      fat_catsDb.insertDocument(doc);

    });
  };

  this.tokencheck = function() {
    console.log('sheet:'+this.sheetkey);
    console.log('googauth:'+this.googauth);
    console.log('dashmac:'+this.dashmac);
  }

  return this;
}

module.exports = DashWatch;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.dash.SegmentList');

goog.require('goog.asserts');
goog.require('shaka.dash.MpdUtils');
goog.require('shaka.dash.SegmentBase');
goog.require('shaka.log');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.media.SegmentReference');
goog.require('shaka.util.Error');
goog.require('shaka.util.Functional');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.XmlUtils');

/**
 * @namespace shaka.dash.SegmentList
 * @summary A set of functions for parsing SegmentList elements.
 */

/**
 * Creates a new Stream object or updates the Stream in the manifest.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
 * @return {shaka.dash.DashParser.StreamInfo}
 */
shaka.dash.SegmentList.createStream = function(context, segmentIndexMap) {
  goog.asserts.assert(context.representation.segmentList,
                      'Should only be called with SegmentList');
  var SegmentList = shaka.dash.SegmentList;

  var init = shaka.dash.SegmentBase.createInitSegment(
      context, SegmentList.fromInheritance_);
  var info = SegmentList.parseSegmentListInfo_(context);

  SegmentList.checkSegmentListInfo_(context, info);

  /** @type {shaka.media.SegmentIndex} */
  var segmentIndex = null;
  var id = null;
  if (context.period.id && context.representation.id) {
    // Only check/store the index if period and representation IDs are set.
    id = context.period.id + ',' + context.representation.id;
    segmentIndex = segmentIndexMap[id];
  }

  var references = SegmentList.createSegmentReferences_(
      context.periodInfo.duration, info.startNumber,
      context.representation.baseUris, info);

  if (segmentIndex) {
    segmentIndex.merge(references);
    var start = context.presentationTimeline.getSegmentAvailabilityStart();
    segmentIndex.evict(start - context.periodInfo.start);
  } else {
    context.presentationTimeline.notifySegments(
        context.periodInfo.start, references);
    segmentIndex = new shaka.media.SegmentIndex(references);
    if (id && context.dynamic)
      segmentIndexMap[id] = segmentIndex;
  }

  segmentIndex.fit(!context.dynamic || !context.periodInfo.isLastPeriod,
                   context.periodInfo.duration);

  return {
    createSegmentIndex: Promise.resolve.bind(Promise),
    findSegmentPosition: segmentIndex.find.bind(segmentIndex),
    getSegmentReference: segmentIndex.get.bind(segmentIndex),
    initSegmentReference: init,
    presentationTimeOffset: info.presentationTimeOffset
  };
};

/**
 * @typedef {{
 *   mediaUri: string,
 *   start: number,
 *   end: ?number
 * }}
 *
 * @property {string} mediaUri
 *   The URI of the segment.
 * @property {number} start
 *   The start byte of the segment.
 * @property {?number} end
 *   The end byte of the segment, or null.
 */
shaka.dash.SegmentList.MediaSegment;

/**
 * @typedef {{
 *   segmentDuration: ?number,
 *   startTime: number,
 *   startNumber: number,
 *   presentationTimeOffset: number,
 *   timeline: Array.<shaka.dash.MpdUtils.TimeRange>,
 *   mediaSegments: !Array.<shaka.dash.SegmentList.MediaSegment>
 * }}
 * @private
 *
 * @description
 * Contains information about a SegmentList.
 *
 * @property {?number} segmentDuration
 *   The duration of the segments, if given.
 * @property {number} startTime
 *   The start time of the first segment, in seconds.
 * @property {number} startNumber
 *   The start number of the segments; 1 or greater.
 * @property {number} presentationTimeOffset
 *   The presentationTimeOffset of the representation, in seconds.
 * @property {Array.<shaka.dash.MpdUtils.TimeRange>} timeline
 *   The timeline of the representation, if given.  Times in seconds.
 * @property {!Array.<shaka.dash.SegmentList.MediaSegment>} mediaSegments
 *   The URI and byte-ranges of the media segments.
 */
shaka.dash.SegmentList.SegmentListInfo;

/**
 * @param {?shaka.dash.DashParser.InheritanceFrame} frame
 * @return {Element}
 * @private
 */
shaka.dash.SegmentList.fromInheritance_ = function(frame) {
  return frame.segmentList;
};

/**
 * Parses the SegmentList items to create an info object.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @return {shaka.dash.SegmentList.SegmentListInfo}
 * @private
 */
shaka.dash.SegmentList.parseSegmentListInfo_ = function(context) {
  var SegmentList = shaka.dash.SegmentList;
  var MpdUtils = shaka.dash.MpdUtils;

  var mediaSegments = SegmentList.parseMediaSegments_(context);
  var segmentInfo =
      MpdUtils.parseSegmentInfo(context, SegmentList.fromInheritance_);

  var startNumber = segmentInfo.startNumber;
  if (startNumber == 0) {
    shaka.log.warning('SegmentList@startNumber must be > 0');
    startNumber = 1;
  }

  var startTime = 0;
  if (segmentInfo.segmentDuration) {
    // Consider the presentationTimeOffset
    startTime = segmentInfo.segmentDuration * (startNumber - 1) -
        segmentInfo.presentationTimeOffset;
  } else if (segmentInfo.timeline && segmentInfo.timeline.length > 0) {
    // The presentationTimeOffset was considered in timeline creation
    startTime = segmentInfo.timeline[0].start;
  }

  return {
    segmentDuration: segmentInfo.segmentDuration,
    startTime: startTime,
    startNumber: startNumber,
    presentationTimeOffset: segmentInfo.presentationTimeOffset,
    timeline: segmentInfo.timeline,
    mediaSegments: mediaSegments
  };
};

/**
 * Checks whether a SegmentListInfo object is valid.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @param {shaka.dash.SegmentList.SegmentListInfo} info
 * @throws shaka.util.Error When there is a parsing error.
 * @private
 */
shaka.dash.SegmentList.checkSegmentListInfo_ = function(context, info) {
  if (!info.segmentDuration && !info.timeline &&
      info.mediaSegments.length > 1) {
    shaka.log.warning(
        'SegmentList does not contain sufficient segment information:',
        'the SegmentList specifies multiple segments,',
        'but does not specify a segment duration or timeline.',
        context.representation);
    throw new shaka.util.Error(
        shaka.util.Error.Category.MANIFEST,
        shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  }

  if (!info.segmentDuration && !context.periodInfo.duration && !info.timeline &&
      info.mediaSegments.length == 1) {
    shaka.log.warning(
        'SegmentList does not contain sufficient segment information:',
        'the SegmentList specifies one segment,',
        'but does not specify a segment duration, period duration,',
        'or timeline.',
        context.representation);
    throw new shaka.util.Error(
        shaka.util.Error.Category.MANIFEST,
        shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  }

  if (info.timeline && info.timeline.length == 0) {
    shaka.log.warning(
        'SegmentList does not contain sufficient segment information:',
        'the SegmentList has an empty timeline.',
        context.representation);
    throw new shaka.util.Error(
        shaka.util.Error.Category.MANIFEST,
        shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  }
};

/**
 * Creates an array of segment references for the given data.
 *
 * @param {?number} periodDuration in seconds.
 * @param {number} startNumber
 * @param {!Array.<string>} baseUris
 * @param {shaka.dash.SegmentList.SegmentListInfo} info
 * @return {!Array.<!shaka.media.SegmentReference>}
 * @private
 */
shaka.dash.SegmentList.createSegmentReferences_ = function(
    periodDuration, startNumber, baseUris, info) {
  var ManifestParserUtils = shaka.util.ManifestParserUtils;

  var max = info.mediaSegments.length;
  if (info.timeline && info.timeline.length != info.mediaSegments.length) {
    max = Math.min(info.timeline.length, info.mediaSegments.length);
    shaka.log.warning(
        'The number of items in the segment timeline and the number of segment',
        'URLs do not match, truncating', info.mediaSegments.length, 'to', max);
  }

  /** @type {!Array.<!shaka.media.SegmentReference>} */
  var references = [];
  var prevEndTime = info.startTime;
  for (var i = 0; i < max; i++) {
    var segment = info.mediaSegments[i];
    var mediaUri = ManifestParserUtils.resolveUris(
        baseUris, [segment.mediaUri]);

    var startTime = prevEndTime;
    var endTime;

    if (info.segmentDuration != null) {
      endTime = startTime + info.segmentDuration;
    } else if (info.timeline) {
      // Ignore the timepoint start since they are continuous.
      endTime = info.timeline[i].end;
    } else {
      // If segmentDuration and timeline are null then there must
      // only be one segment.
      goog.asserts.assert(
          info.mediaSegments.length == 1 && periodDuration,
          'There should only be one segment with a Period duration.');
      endTime = startTime + periodDuration;
    }

    var getUris = (function(uris) { return uris; }.bind(null, mediaUri));
    references.push(
        new shaka.media.SegmentReference(
            i + startNumber, startTime, endTime, getUris, segment.start,
            segment.end));
    prevEndTime = endTime;
  }

  return references;
};

/**
 * Parses the media URIs from the context.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @return {!Array.<shaka.dash.SegmentList.MediaSegment>}
 * @private
 */
shaka.dash.SegmentList.parseMediaSegments_ = function(context) {
  var Functional = shaka.util.Functional;
  /** @type {!Array.<!Element>} */
  var segmentLists = [
    context.representation.segmentList,
    context.adaptationSet.segmentList,
    context.period.segmentList
  ].filter(Functional.isNotNull);

  var XmlUtils = shaka.util.XmlUtils;
  // Search each SegmentList for one with at least one SegmentURL element,
  // select the first one, and convert each SegmentURL element to a tuple.
  return segmentLists
      .map(function(node) { return XmlUtils.findChildren(node, 'SegmentURL'); })
      .reduce(function(all, part) { return all.length > 0 ? all : part; })
      .map(function(urlNode) {
        if (urlNode.getAttribute('indexRange') &&
            !context.indexRangeWarningGiven) {
          context.indexRangeWarningGiven = true;
          shaka.log.warning(
              'We do not support the SegmentURL@indexRange attribute on ' +
              'SegmentList.  We only use the SegmentList@duration attribute ' +
              'or SegmentTimeline, which must be accurate.');
        }

        var uri = urlNode.getAttribute('media');
        var range = XmlUtils.parseAttr(
            urlNode, 'mediaRange', XmlUtils.parseRange, {start: 0, end: null});
        return {mediaUri: uri, start: range.start, end: range.end};
      });
};

<commit_msg>Don't use PTO in SegmentList with duration.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.dash.SegmentList');

goog.require('goog.asserts');
goog.require('shaka.dash.MpdUtils');
goog.require('shaka.dash.SegmentBase');
goog.require('shaka.log');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.media.SegmentReference');
goog.require('shaka.util.Error');
goog.require('shaka.util.Functional');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.XmlUtils');

/**
 * @namespace shaka.dash.SegmentList
 * @summary A set of functions for parsing SegmentList elements.
 */

/**
 * Creates a new Stream object or updates the Stream in the manifest.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
 * @return {shaka.dash.DashParser.StreamInfo}
 */
shaka.dash.SegmentList.createStream = function(context, segmentIndexMap) {
  goog.asserts.assert(context.representation.segmentList,
                      'Should only be called with SegmentList');
  var SegmentList = shaka.dash.SegmentList;

  var init = shaka.dash.SegmentBase.createInitSegment(
      context, SegmentList.fromInheritance_);
  var info = SegmentList.parseSegmentListInfo_(context);

  SegmentList.checkSegmentListInfo_(context, info);

  /** @type {shaka.media.SegmentIndex} */
  var segmentIndex = null;
  var id = null;
  if (context.period.id && context.representation.id) {
    // Only check/store the index if period and representation IDs are set.
    id = context.period.id + ',' + context.representation.id;
    segmentIndex = segmentIndexMap[id];
  }

  var references = SegmentList.createSegmentReferences_(
      context.periodInfo.duration, info.startNumber,
      context.representation.baseUris, info);

  if (segmentIndex) {
    segmentIndex.merge(references);
    var start = context.presentationTimeline.getSegmentAvailabilityStart();
    segmentIndex.evict(start - context.periodInfo.start);
  } else {
    context.presentationTimeline.notifySegments(
        context.periodInfo.start, references);
    segmentIndex = new shaka.media.SegmentIndex(references);
    if (id && context.dynamic)
      segmentIndexMap[id] = segmentIndex;
  }

  segmentIndex.fit(!context.dynamic || !context.periodInfo.isLastPeriod,
                   context.periodInfo.duration);

  return {
    createSegmentIndex: Promise.resolve.bind(Promise),
    findSegmentPosition: segmentIndex.find.bind(segmentIndex),
    getSegmentReference: segmentIndex.get.bind(segmentIndex),
    initSegmentReference: init,
    presentationTimeOffset: info.presentationTimeOffset
  };
};

/**
 * @typedef {{
 *   mediaUri: string,
 *   start: number,
 *   end: ?number
 * }}
 *
 * @property {string} mediaUri
 *   The URI of the segment.
 * @property {number} start
 *   The start byte of the segment.
 * @property {?number} end
 *   The end byte of the segment, or null.
 */
shaka.dash.SegmentList.MediaSegment;

/**
 * @typedef {{
 *   segmentDuration: ?number,
 *   startTime: number,
 *   startNumber: number,
 *   presentationTimeOffset: number,
 *   timeline: Array.<shaka.dash.MpdUtils.TimeRange>,
 *   mediaSegments: !Array.<shaka.dash.SegmentList.MediaSegment>
 * }}
 * @private
 *
 * @description
 * Contains information about a SegmentList.
 *
 * @property {?number} segmentDuration
 *   The duration of the segments, if given.
 * @property {number} startTime
 *   The start time of the first segment, in seconds.
 * @property {number} startNumber
 *   The start number of the segments; 1 or greater.
 * @property {number} presentationTimeOffset
 *   The presentationTimeOffset of the representation, in seconds.
 * @property {Array.<shaka.dash.MpdUtils.TimeRange>} timeline
 *   The timeline of the representation, if given.  Times in seconds.
 * @property {!Array.<shaka.dash.SegmentList.MediaSegment>} mediaSegments
 *   The URI and byte-ranges of the media segments.
 */
shaka.dash.SegmentList.SegmentListInfo;

/**
 * @param {?shaka.dash.DashParser.InheritanceFrame} frame
 * @return {Element}
 * @private
 */
shaka.dash.SegmentList.fromInheritance_ = function(frame) {
  return frame.segmentList;
};

/**
 * Parses the SegmentList items to create an info object.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @return {shaka.dash.SegmentList.SegmentListInfo}
 * @private
 */
shaka.dash.SegmentList.parseSegmentListInfo_ = function(context) {
  var SegmentList = shaka.dash.SegmentList;
  var MpdUtils = shaka.dash.MpdUtils;

  var mediaSegments = SegmentList.parseMediaSegments_(context);
  var segmentInfo =
      MpdUtils.parseSegmentInfo(context, SegmentList.fromInheritance_);

  var startNumber = segmentInfo.startNumber;
  if (startNumber == 0) {
    shaka.log.warning('SegmentList@startNumber must be > 0');
    startNumber = 1;
  }

  var startTime = 0;
  if (segmentInfo.segmentDuration) {
    // See DASH sec. 5.3.9.5.3
    // Don't use presentationTimeOffset for @duration.
    startTime = segmentInfo.segmentDuration * (startNumber - 1);
  } else if (segmentInfo.timeline && segmentInfo.timeline.length > 0) {
    // The presentationTimeOffset was considered in timeline creation
    startTime = segmentInfo.timeline[0].start;
  }

  return {
    segmentDuration: segmentInfo.segmentDuration,
    startTime: startTime,
    startNumber: startNumber,
    presentationTimeOffset: segmentInfo.presentationTimeOffset,
    timeline: segmentInfo.timeline,
    mediaSegments: mediaSegments
  };
};

/**
 * Checks whether a SegmentListInfo object is valid.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @param {shaka.dash.SegmentList.SegmentListInfo} info
 * @throws shaka.util.Error When there is a parsing error.
 * @private
 */
shaka.dash.SegmentList.checkSegmentListInfo_ = function(context, info) {
  if (!info.segmentDuration && !info.timeline &&
      info.mediaSegments.length > 1) {
    shaka.log.warning(
        'SegmentList does not contain sufficient segment information:',
        'the SegmentList specifies multiple segments,',
        'but does not specify a segment duration or timeline.',
        context.representation);
    throw new shaka.util.Error(
        shaka.util.Error.Category.MANIFEST,
        shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  }

  if (!info.segmentDuration && !context.periodInfo.duration && !info.timeline &&
      info.mediaSegments.length == 1) {
    shaka.log.warning(
        'SegmentList does not contain sufficient segment information:',
        'the SegmentList specifies one segment,',
        'but does not specify a segment duration, period duration,',
        'or timeline.',
        context.representation);
    throw new shaka.util.Error(
        shaka.util.Error.Category.MANIFEST,
        shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  }

  if (info.timeline && info.timeline.length == 0) {
    shaka.log.warning(
        'SegmentList does not contain sufficient segment information:',
        'the SegmentList has an empty timeline.',
        context.representation);
    throw new shaka.util.Error(
        shaka.util.Error.Category.MANIFEST,
        shaka.util.Error.Code.DASH_NO_SEGMENT_INFO);
  }
};

/**
 * Creates an array of segment references for the given data.
 *
 * @param {?number} periodDuration in seconds.
 * @param {number} startNumber
 * @param {!Array.<string>} baseUris
 * @param {shaka.dash.SegmentList.SegmentListInfo} info
 * @return {!Array.<!shaka.media.SegmentReference>}
 * @private
 */
shaka.dash.SegmentList.createSegmentReferences_ = function(
    periodDuration, startNumber, baseUris, info) {
  var ManifestParserUtils = shaka.util.ManifestParserUtils;

  var max = info.mediaSegments.length;
  if (info.timeline && info.timeline.length != info.mediaSegments.length) {
    max = Math.min(info.timeline.length, info.mediaSegments.length);
    shaka.log.warning(
        'The number of items in the segment timeline and the number of segment',
        'URLs do not match, truncating', info.mediaSegments.length, 'to', max);
  }

  /** @type {!Array.<!shaka.media.SegmentReference>} */
  var references = [];
  var prevEndTime = info.startTime;
  for (var i = 0; i < max; i++) {
    var segment = info.mediaSegments[i];
    var mediaUri = ManifestParserUtils.resolveUris(
        baseUris, [segment.mediaUri]);

    var startTime = prevEndTime;
    var endTime;

    if (info.segmentDuration != null) {
      endTime = startTime + info.segmentDuration;
    } else if (info.timeline) {
      // Ignore the timepoint start since they are continuous.
      endTime = info.timeline[i].end;
    } else {
      // If segmentDuration and timeline are null then there must
      // only be one segment.
      goog.asserts.assert(
          info.mediaSegments.length == 1 && periodDuration,
          'There should only be one segment with a Period duration.');
      endTime = startTime + periodDuration;
    }

    var getUris = (function(uris) { return uris; }.bind(null, mediaUri));
    references.push(
        new shaka.media.SegmentReference(
            i + startNumber, startTime, endTime, getUris, segment.start,
            segment.end));
    prevEndTime = endTime;
  }

  return references;
};

/**
 * Parses the media URIs from the context.
 *
 * @param {shaka.dash.DashParser.Context} context
 * @return {!Array.<shaka.dash.SegmentList.MediaSegment>}
 * @private
 */
shaka.dash.SegmentList.parseMediaSegments_ = function(context) {
  var Functional = shaka.util.Functional;
  /** @type {!Array.<!Element>} */
  var segmentLists = [
    context.representation.segmentList,
    context.adaptationSet.segmentList,
    context.period.segmentList
  ].filter(Functional.isNotNull);

  var XmlUtils = shaka.util.XmlUtils;
  // Search each SegmentList for one with at least one SegmentURL element,
  // select the first one, and convert each SegmentURL element to a tuple.
  return segmentLists
      .map(function(node) { return XmlUtils.findChildren(node, 'SegmentURL'); })
      .reduce(function(all, part) { return all.length > 0 ? all : part; })
      .map(function(urlNode) {
        if (urlNode.getAttribute('indexRange') &&
            !context.indexRangeWarningGiven) {
          context.indexRangeWarningGiven = true;
          shaka.log.warning(
              'We do not support the SegmentURL@indexRange attribute on ' +
              'SegmentList.  We only use the SegmentList@duration attribute ' +
              'or SegmentTimeline, which must be accurate.');
        }

        var uri = urlNode.getAttribute('media');
        var range = XmlUtils.parseAttr(
            urlNode, 'mediaRange', XmlUtils.parseRange, {start: 0, end: null});
        return {mediaUri: uri, start: range.start, end: range.end};
      });
};

<|endoftext|>


<commit_before>(function($) {

suite = Echo.Tests.Unit.StreamServerAPI = function() {};

suite.prototype.info = {
	"className": "Echo.StreamServer.API.Request",
	"suiteName": "StreamServer.API",
	"functions": ["_AS2KVL", "_changeLiveUpdatesTimeout", "abort", "_search", "_submit", "_count", "send"]
};

suite.prototype.params = {
	"appkey": "test.aboutecho.com",
	"q": "childrenof:http://example.com/sdk/test-target/" + Math.random() + " state:Untouched,ModeratorApproved children:1 state:Untouched,ModeratorApproved"
};

suite.prototype.cases = {};

suite.prototype.cases.simpleSearchRequest = function(callback) {
	Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": $.extend({}, this.params),
		"onOpen": function() {
			QUnit.ok(true, "Checking if the \"onOpen\" callback was executed before data sending");
		},
		"onData": function(data) {
			QUnit.ok(data && data.entries,
				"Checking if the \"onData\" callback was executed after the regular request.");
			callback();
		}
	}).send();
};

suite.prototype.cases.searchRequestWithError = function(callback) {
	Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": $.extend({}, this.params, {"q": "unknown:predicate"}),
		"onError": function(data) {
			QUnit.ok(data && data.result === "error",
				"Checking if the \"onError\" callback was executed when the search query was defined incorrectly.");
			callback();
		}
	}).send();
};

suite.prototype.cases.requestWithAbort = function(callback) {
	var req = Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": $.extend({}, this.params),
		"onError": function(data) {
			QUnit.ok(data && data.result === "error" && data.errorCode === "connection_failure",
				"Checking if the \"onError\" callback was executed when the request aborted");
			callback();
		},
		"onClose": function() {
			QUnit.ok(true,
				"Checking if the \"onClose\" callback was executed when the request aborted");
		}
	});
	req.send();
	setTimeout(function() {
		req.abort();
	}, 50);
};

suite.prototype.cases.checkLiveUpdate = function(callback) {
	var self = this;
	var item = $.extend(true, {}, this.items.post);
	var params = $.extend({}, this.params);
	var target = this.params.q.replace(/childrenof:(http:\/\/\S+).*/, function($0, $1) {
		return $1;
	});
	item.targets[0].id = target;
	item.targets[0].conversationID = target;
	item.object.id = target;
	var cuReq = Echo.StreamServer.API.request({
		"endpoint": "count",
		"recurring": "true",
		"onData": function(response) {
			if (response && response.count) {
				QUnit.equal(1, response.count, "Checking if live updates mecahnism by count works correctly after posting");
				cuReq.abort();
				callback();
			}
		},
		"data": $.extend({}, params)
	});
	var luReq = Echo.StreamServer.API.request({
		"endpoint": "search",
		"recurring": true,
		"onData": function(response) {
			if (response && response.entries && response.entries.length) {
				QUnit.equal(response.entries[0].object.content, self.items.post.object.content,
					"checking if the live update mechanism by search works correctly after posting");
				luReq.abort();
				cuReq.send({force: true});
			}
		},
		"data": $.extend({}, params)
	});
	var sReq = Echo.StreamServer.API.request({
		"endpoint": "submit",
		"onData": function(response) {
			luReq.send({force: true});
		},
		"data": $.extend({}, params, {
			content: item,
			targetURL: target
		})
	});
	sReq.send();
};

suite.prototype.cases.simpleLiveUpdatesRequest = function(callback) {
	var maxCounts = 4, currentCount = 0;
	var req = Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": this.params,
		"liveUpdatesTimeout": 1,
		"recurring": true,
		"onData": function(data) {
			if (maxCounts === ++currentCount) {
				QUnit.ok(data && data.entries,
					"Checking if the \"onData\" callback was executed after the live update request.");
				req.abort();
				callback();
			}
		}
	});
	req.send();
};

suite.prototype.tests = {};

suite.prototype.tests.PublicInterfaceTests = {
	 "config": {
		 "async": true,
		 "testTimeout": 20000 // 20 secs
	},
	"check": function() {
		this.sequentialAsyncTests([
			"simpleSearchRequest",
			"searchRequestWithError",
			"requestWithAbort",
			"checkLiveUpdate",
			"simpleLiveUpdatesRequest"
		], "cases");
	}
};

suite.prototype.tests.PrivateFunctionsTests = {
	"check": function() {
		var req = Echo.StreamServer.API.request({
			"endpoint": "search",
			"data": $.extend({}, this.params)
		});
		QUnit.ok(req._isWaitingForData({"result": "error", "errorCode": "view_limit", "extra": {}}), "Checking if error responsed JSON contains waiting error code");
		QUnit.ok(req._isErrorWithTimer({"result": "error", "errorCode": "view_update_capacity_exceeded", "extra": {}}), "Checking if error responsed JSON contains error timer code");
		QUnit.deepEqual(req._AS2KVL(this.items.post), {
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square",
			"content": "For the record, I think your neck looked just fine.\n\nPeace out, Nora.",
			"markers": undefined,
			"name": "TheNext_MrsBass",
			"source": {
				"name": "aboutecho.com",
				"uri": "http://aboutecho.com/"
			},
			"tags": undefined,
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"verb": "post",
			"type": "comment",
			"itemURIPattern": undefined
		}, "Check decompiler from AS to KVL");
		QUnit.deepEqual(req._AS2KVL(this.items.postWithMetadata), {
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square",
			"content": "For the record, I think your neck looked just fine.\n\nPeace out, Nora.",
			"markers": "marker1,marker2,marker3",
			"name": "TheNext_MrsBass",
			"source": {
				"name": "aboutecho.com",
				"uri": "http://aboutecho.com/"
			},
			"tags": "tag1,tag2,tag3",
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"verb": "post",
			"type": "comment",
			"itemURIPattern": undefined
		}, "Check decompiler from AS to KVL with post and metadata");
		QUnit.deepEqual(req._AS2KVL(this.items.postWithMetadata.slice(1)), [
		{
			"tags": "tag1,tag2,tag3",
			"verb": "tag",
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html"
		},
		{
			"tags": "marker1,marker2,marker3",
			"verb": "tag",
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html"
		}
		], "Check decompiler from AS to KVL with metadata only");
		req._changeLiveUpdatesTimeout({
			"liveUpdatesTimeout": 4
		});
		QUnit.equal(5, req.config.get("liveUpdatesTimeout"), "Checking liveUpdatesTimeout when server responsed less value than default");
		req._changeLiveUpdatesTimeout({
			"liveUpdatesTimeout": 6
		});
		QUnit.equal(6, req.config.get("liveUpdatesTimeout"), "Checking liveUpdatesTimeout when server responsed more value than default");
	}
};

// test data for checking AS -> KVL transformation

suite.prototype.items = {};

suite.prototype.items.post = {
	"id": "http://js-kit.com/activities/post/d71cb5f6239dfd7ebf66ac794125acee",
	"actor": {
		"objectTypes": [
			"http://activitystrea.ms/schema/1.0/person"
		],
		"id": "http://my.nymag.com/thenext_mrsbass",
		"title": "TheNext_MrsBass",
		"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square"
	},
	"object": {
		"objectTypes": [
			"http://activitystrea.ms/schema/1.0/comment"
		],
		"context": [{
			"uri": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"title": "Nora Ephron, 1941-2012"
		}],
		"content": "For the record, I think your neck looked just fine.\n\nPeace out, Nora.",
	},
	"source": {
		"name": "aboutecho.com",
		"uri": "http://aboutecho.com/"
	},
	"provider": {
		"name": "echo",
		"uri": "http://aboutecho.com/",
		"icon": "http://cdn.js-kit.com/images/echo.png"
	},
	"verbs": [
		"http://activitystrea.ms/schema/1.0/post"
	],
	"targets": [{
		"id": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
		"conversationID": "http://nymag.com/ECHO/item/1340804828-788-121"
	}]
};

suite.prototype.items.postWithMetadata = [
	suite.prototype.items.post,
	{
		"id": "http://js-kit.com/activities/post/adshg5f6239dfd7ebf66ac794125acee",
		"actor": {
			"id": "http://my.nymag.com/thenext_mrsbass",
			"title": "TheNext_MrsBass",
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square"
		},
		"object": {
			"id": "http://nymag.com/ECHO/item/121231340804828-788-121",
			"objectTypes": [
				"http://activitystrea.ms/schema/1.0/tag"
			],
			"content": "tag1,tag2,tag3"
		},
		"verbs": [
			"http://activitystrea.ms/schema/1.0/tag"
		],
		"targets": [{
			"id": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"conversationID": "http://nymag.com/ECHO/item/1340804828-788-121"
		}]
	}, {
		"id": "http://js-kit.com/activities/post/adshg5f6239dfd7ebf66ac794125acee",
		"actor": {
			"id": "http://my.nymag.com/thenext_mrsbass",
			"title": "TheNext_MrsBass",
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square"
		},
		"object": {
			"id": "http://nymag.com/ECHO/item/121231340804828-788-121",
			"objectTypes": [
				"http://activitystrea.ms/schema/1.0/marker"
			],
			"content": "marker1,marker2,marker3"
		},
		"verbs": [
			"http://activitystrea.ms/schema/1.0/tag"
		],
		"targets": [{
			"id": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"conversationID": "http://nymag.com/ECHO/item/1340804828-788-121"
		}]
	}
];

})(jQuery);
<commit_msg>Added missing var instruction<commit_after>(function($) {

var suite = Echo.Tests.Unit.StreamServerAPI = function() {};

suite.prototype.info = {
	"className": "Echo.StreamServer.API.Request",
	"suiteName": "StreamServer.API",
	"functions": ["_AS2KVL", "_changeLiveUpdatesTimeout", "abort", "_search", "_submit", "_count", "send"]
};

suite.prototype.params = {
	"appkey": "test.aboutecho.com",
	"q": "childrenof:http://example.com/sdk/test-target/" + Math.random() + " state:Untouched,ModeratorApproved children:1 state:Untouched,ModeratorApproved"
};

suite.prototype.cases = {};

suite.prototype.cases.simpleSearchRequest = function(callback) {
	Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": $.extend({}, this.params),
		"onOpen": function() {
			QUnit.ok(true, "Checking if the \"onOpen\" callback was executed before data sending");
		},
		"onData": function(data) {
			QUnit.ok(data && data.entries,
				"Checking if the \"onData\" callback was executed after the regular request.");
			callback();
		}
	}).send();
};

suite.prototype.cases.searchRequestWithError = function(callback) {
	Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": $.extend({}, this.params, {"q": "unknown:predicate"}),
		"onError": function(data) {
			QUnit.ok(data && data.result === "error",
				"Checking if the \"onError\" callback was executed when the search query was defined incorrectly.");
			callback();
		}
	}).send();
};

suite.prototype.cases.requestWithAbort = function(callback) {
	var req = Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": $.extend({}, this.params),
		"onError": function(data) {
			QUnit.ok(data && data.result === "error" && data.errorCode === "connection_failure",
				"Checking if the \"onError\" callback was executed when the request aborted");
			callback();
		},
		"onClose": function() {
			QUnit.ok(true,
				"Checking if the \"onClose\" callback was executed when the request aborted");
		}
	});
	req.send();
	setTimeout(function() {
		req.abort();
	}, 50);
};

suite.prototype.cases.checkLiveUpdate = function(callback) {
	var self = this;
	var item = $.extend(true, {}, this.items.post);
	var params = $.extend({}, this.params);
	var target = this.params.q.replace(/childrenof:(http:\/\/\S+).*/, function($0, $1) {
		return $1;
	});
	item.targets[0].id = target;
	item.targets[0].conversationID = target;
	item.object.id = target;
	var cuReq = Echo.StreamServer.API.request({
		"endpoint": "count",
		"recurring": "true",
		"onData": function(response) {
			if (response && response.count) {
				QUnit.equal(1, response.count, "Checking if live updates mecahnism by count works correctly after posting");
				cuReq.abort();
				callback();
			}
		},
		"data": $.extend({}, params)
	});
	var luReq = Echo.StreamServer.API.request({
		"endpoint": "search",
		"recurring": true,
		"onData": function(response) {
			if (response && response.entries && response.entries.length) {
				QUnit.equal(response.entries[0].object.content, self.items.post.object.content,
					"checking if the live update mechanism by search works correctly after posting");
				luReq.abort();
				cuReq.send({force: true});
			}
		},
		"data": $.extend({}, params)
	});
	var sReq = Echo.StreamServer.API.request({
		"endpoint": "submit",
		"onData": function(response) {
			luReq.send({force: true});
		},
		"data": $.extend({}, params, {
			content: item,
			targetURL: target
		})
	});
	sReq.send();
};

suite.prototype.cases.simpleLiveUpdatesRequest = function(callback) {
	var maxCounts = 4, currentCount = 0;
	var req = Echo.StreamServer.API.request({
		"endpoint": "search",
		"data": this.params,
		"liveUpdatesTimeout": 1,
		"recurring": true,
		"onData": function(data) {
			if (maxCounts === ++currentCount) {
				QUnit.ok(data && data.entries,
					"Checking if the \"onData\" callback was executed after the live update request.");
				req.abort();
				callback();
			}
		}
	});
	req.send();
};

suite.prototype.tests = {};

suite.prototype.tests.PublicInterfaceTests = {
	 "config": {
		 "async": true,
		 "testTimeout": 20000 // 20 secs
	},
	"check": function() {
		this.sequentialAsyncTests([
			"simpleSearchRequest",
			"searchRequestWithError",
			"requestWithAbort",
			"checkLiveUpdate",
			"simpleLiveUpdatesRequest"
		], "cases");
	}
};

suite.prototype.tests.PrivateFunctionsTests = {
	"check": function() {
		var req = Echo.StreamServer.API.request({
			"endpoint": "search",
			"data": $.extend({}, this.params)
		});
		QUnit.ok(req._isWaitingForData({"result": "error", "errorCode": "view_limit", "extra": {}}), "Checking if error responsed JSON contains waiting error code");
		QUnit.ok(req._isErrorWithTimer({"result": "error", "errorCode": "view_update_capacity_exceeded", "extra": {}}), "Checking if error responsed JSON contains error timer code");
		QUnit.deepEqual(req._AS2KVL(this.items.post), {
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square",
			"content": "For the record, I think your neck looked just fine.\n\nPeace out, Nora.",
			"markers": undefined,
			"name": "TheNext_MrsBass",
			"source": {
				"name": "aboutecho.com",
				"uri": "http://aboutecho.com/"
			},
			"tags": undefined,
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"verb": "post",
			"type": "comment",
			"itemURIPattern": undefined
		}, "Check decompiler from AS to KVL");
		QUnit.deepEqual(req._AS2KVL(this.items.postWithMetadata), {
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square",
			"content": "For the record, I think your neck looked just fine.\n\nPeace out, Nora.",
			"markers": "marker1,marker2,marker3",
			"name": "TheNext_MrsBass",
			"source": {
				"name": "aboutecho.com",
				"uri": "http://aboutecho.com/"
			},
			"tags": "tag1,tag2,tag3",
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"verb": "post",
			"type": "comment",
			"itemURIPattern": undefined
		}, "Check decompiler from AS to KVL with post and metadata");
		QUnit.deepEqual(req._AS2KVL(this.items.postWithMetadata.slice(1)), [
		{
			"tags": "tag1,tag2,tag3",
			"verb": "tag",
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html"
		},
		{
			"tags": "marker1,marker2,marker3",
			"verb": "tag",
			"target": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html"
		}
		], "Check decompiler from AS to KVL with metadata only");
		req._changeLiveUpdatesTimeout({
			"liveUpdatesTimeout": 4
		});
		QUnit.equal(5, req.config.get("liveUpdatesTimeout"), "Checking liveUpdatesTimeout when server responsed less value than default");
		req._changeLiveUpdatesTimeout({
			"liveUpdatesTimeout": 6
		});
		QUnit.equal(6, req.config.get("liveUpdatesTimeout"), "Checking liveUpdatesTimeout when server responsed more value than default");
	}
};

// test data for checking AS -> KVL transformation

suite.prototype.items = {};

suite.prototype.items.post = {
	"id": "http://js-kit.com/activities/post/d71cb5f6239dfd7ebf66ac794125acee",
	"actor": {
		"objectTypes": [
			"http://activitystrea.ms/schema/1.0/person"
		],
		"id": "http://my.nymag.com/thenext_mrsbass",
		"title": "TheNext_MrsBass",
		"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square"
	},
	"object": {
		"objectTypes": [
			"http://activitystrea.ms/schema/1.0/comment"
		],
		"context": [{
			"uri": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"title": "Nora Ephron, 1941-2012"
		}],
		"content": "For the record, I think your neck looked just fine.\n\nPeace out, Nora.",
	},
	"source": {
		"name": "aboutecho.com",
		"uri": "http://aboutecho.com/"
	},
	"provider": {
		"name": "echo",
		"uri": "http://aboutecho.com/",
		"icon": "http://cdn.js-kit.com/images/echo.png"
	},
	"verbs": [
		"http://activitystrea.ms/schema/1.0/post"
	],
	"targets": [{
		"id": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
		"conversationID": "http://nymag.com/ECHO/item/1340804828-788-121"
	}]
};

suite.prototype.items.postWithMetadata = [
	suite.prototype.items.post,
	{
		"id": "http://js-kit.com/activities/post/adshg5f6239dfd7ebf66ac794125acee",
		"actor": {
			"id": "http://my.nymag.com/thenext_mrsbass",
			"title": "TheNext_MrsBass",
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square"
		},
		"object": {
			"id": "http://nymag.com/ECHO/item/121231340804828-788-121",
			"objectTypes": [
				"http://activitystrea.ms/schema/1.0/tag"
			],
			"content": "tag1,tag2,tag3"
		},
		"verbs": [
			"http://activitystrea.ms/schema/1.0/tag"
		],
		"targets": [{
			"id": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"conversationID": "http://nymag.com/ECHO/item/1340804828-788-121"
		}]
	}, {
		"id": "http://js-kit.com/activities/post/adshg5f6239dfd7ebf66ac794125acee",
		"actor": {
			"id": "http://my.nymag.com/thenext_mrsbass",
			"title": "TheNext_MrsBass",
			"avatar": "http://my.nymag.com/thenext_mrsbass/picture?type=square"
		},
		"object": {
			"id": "http://nymag.com/ECHO/item/121231340804828-788-121",
			"objectTypes": [
				"http://activitystrea.ms/schema/1.0/marker"
			],
			"content": "marker1,marker2,marker3"
		},
		"verbs": [
			"http://activitystrea.ms/schema/1.0/tag"
		],
		"targets": [{
			"id": "http://nymag.com/daily/intel/2012/06/nora-ephron-1941-2012.html",
			"conversationID": "http://nymag.com/ECHO/item/1340804828-788-121"
		}]
	}
];

})(jQuery);
<|endoftext|>


<commit_before>
<commit_msg>Hello, name<commit_after>var gebo = require('gebo-server')();

gebo.start();

<|endoftext|>


<commit_before>'use strict';

var Crawler = require('../lib/crawler');
var expect = require('chai').expect;
var sinon = require('sinon');
var httpbinHost = 'localhost:8000';
var c;
var result = [];
var preRequestResult = [];

describe('Direct feature tests', function() {
    describe('Method direct', function() {
        afterEach(function() {
            c = {};
            result = [];
            preRequestResult = [];
        });

        it('should not trigger preRequest', function(finishTest) {
            c = new Crawler({
                jQuery: false,
                preRequest: function(options, done) {
                    preRequestResult.push(options.uri);
                    done();
                }
            });
            c.direct({
                uri: 'http://' + httpbinHost + '/status/200',
                callback: function(error, res) {
                    expect(error).to.be.null;
                    expect(res.statusCode).to.equal(200);
                    expect(preRequestResult.length).to.equal(0);
                    finishTest();
                }
            });
        });

        it('should be sent directly regardless of current queue of crawler', function(finishTest) {
            c = new Crawler({
                jQuery: false,
                rateLimit: 500,
                callback: function(error, res, done) {
                    expect(error).to.be.null;
                    expect(res.statusCode).to.equal(200);
                    result.push(res.options.uri);
                    done();
                }
            });
            c.queue({
                uri: 'http://' + httpbinHost + '/status/200',
                callback: function(error, res, done) {
                    expect(error).to.be.null;
                    expect(res.statusCode).to.equal(200);
                    result.push(res.options.uri);
                    c.direct({
                        uri: 'http://' + httpbinHost + '/status/200',
                        callback: function(error, res) {
                            expect(error).to.be.null;
                            expect(res.statusCode).to.equal(200);
                            expect(result.length).to.equal(1);
                            preRequestResult.push(res.options.uri);
                        }
                    });
                    done();
                }
            });
            c.queue('http://' + httpbinHost + '/status/200');
            c.queue('http://' + httpbinHost + '/status/200');
            c.queue({
                uri: 'http://' + httpbinHost + '/status/200',
                callback: function(error, res, done) {
                    expect(error).to.be.null;
                    expect(res.statusCode).to.equal(200);
                    expect(result.length).to.equal(3);
                    expect(preRequestResult.length).to.equal(1);
                    expect(preRequestResult[0]).to.equal('http://' + httpbinHost + '/status/200');
                    done();
                    finishTest();
                }
            });
        });

        it('should not trigger Event:request by default', function(finishTest) {
            var events = [];
            c = new Crawler({
                jQuery: false
            });
            c.on('request', function(options) {
                events.push('request');
            });
            c.direct({
                uri: 'http://' + httpbinHost + '/status/200',
                callback: function(error, res) {
                    expect(error).to.be.null;
                    expect(res.statusCode).to.equal(200);
                    expect(events.length).to.equal(0);
                    finishTest();
                }
            });
        });

        it('should trigger Event:request if specified in options', function(finishTest) {
            var events = [];
            c = new Crawler({
                jQuery: false
            });
            c.on('request', function(options) {
                events.push('request');
            });
            c.direct({
                uri: 'http://' + httpbinHost + '/status/200',
                skipEventRequest: false,
                callback: function(error, res) {
                    expect(error).to.be.null;
                    expect(res.statusCode).to.equal(200);
                    expect(events.length).to.equal(1);
                    finishTest();
                }
            });
        });

    });
});<commit_msg>update direct.test.js<commit_after>/*jshint expr:true */
'use strict';

const Crawler = require('../lib/crawler');
const expect = require('chai').expect;
const sinon = require('sinon');

// settings for nock to mock http server
const nock = require('nock');
nock('http://test.crawler.com').get('/').reply(200, 'ok').persist();

// init variables
let cb;
let crawler;

describe('Direct feature tests', function() {

    beforeEach(function() {
        cb = sinon.spy();
        crawler = new Crawler({
            jQuery: false,
            rateLimit: 100,
            preRequest: function(options, done) {
                cb('preRequest');
                done();
            },
            callback: function(err, res, done) {
                if (err) {
                    cb('error');
                } else {
                    cb('callback');
                }
                done();
            }
        });
        crawler.on('request', () => {
            cb('Event:request');
        });
    });

    it('should not trigger preRequest or callback of crawler instance', function(finishTest) {
        crawler.direct({
            uri: 'http://test.crawler.com/',
            callback: function(error, res) {
                expect(error).to.be.null;
                expect(res.statusCode).to.equal(200);
                expect(res.body).to.equal('ok');
                expect(cb.called).to.be.false;
                finishTest();
            }
        });
    });

    it('should be sent directly regardless of current queue of crawler', function(finishTest) {
        crawler.queue({
            uri: 'http://test.crawler.com/',
            callback: function(error, res, done) {
                expect(error).to.be.null;
                crawler.direct({
                    uri: 'http://test.crawler.com/',
                    callback: function() {
                        expect(cb.getCalls().length).to.equal(2);
                        cb('direct');
                    }
                });
                done();
            }
        });
        crawler.queue('http://test.crawler.com/');
        crawler.queue('http://test.crawler.com/');
        crawler.queue({
            uri: 'http://test.crawler.com/',
            callback: function(error, res, done) {
                expect(error).to.be.null;
                let seq = ['preRequest','Event:request','direct','preRequest','Event:request','callback','preRequest','Event:request','callback','preRequest','Event:request'];
                expect(cb.getCalls().map(c => c.args[0]).join()).to.equal(seq.join());
                expect(cb.getCalls().length).to.equal(11);
                done();
                finishTest();
            }
        });
    });

    it('should not trigger Event:request by default', function(finishTest) {
        crawler.direct({
            uri: 'http://test.crawler.com/',
            callback: function(error, res) {
                expect(error).to.be.null;
                expect(res.statusCode).to.equal(200);
                expect(res.body).to.equal('ok');
                expect(cb.called).to.be.false;
                finishTest();
            }
        });
    });

    it('should trigger Event:request if specified in options', function(finishTest) {
        crawler.direct({
            uri: 'http://test.crawler.com/',
            skipEventRequest: false,
            callback: function(error, res) {
                expect(error).to.be.null;
                expect(res.statusCode).to.equal(200);
                expect(res.body).to.equal('ok');
                expect(cb.calledOnce).to.be.true;
                expect(cb.firstCall.args[0]).to.equal('Event:request');
                finishTest();
            }
        });
    });
});<|endoftext|>