<commit_before>// Copyright 2012 Rackspace Hosting, 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.

/**
 * Tracers only for Node.
 * Note: Node tracers assume tryfer library is included in a long running
 * process.
 */

if (typeof module === 'undefined' || !module.exports) {
  return;
}

var util = require('util');

var request = require('request');
var async = require('async');

var EndAnnotationTracer = require('./tracers').EndAnnotationTracer;
var formatters = require('./formatters');

/**
 * Buffer traces and defer recording until maxTraces have been received or
 * sendInterval has elapsed since the last trace was recorded.
 *
 * @param {Tracer} tracer Tracer provider to record buffered tracers to.
 * @param {Options} options Options object with the following keys:
 * - maxTraces - Number of traces to buffer before recording occurs (default:
 *   50).
 * - sendInterval - An average number of seconds which can pass after the last
 *   trace has been sent to the backend before sending all the buffered traces
 *   to the backend again (default: 10).
 */
function BufferingTracer(tracer, options) {
  options = options || {};

  var self = this;

  this._tracer = tracer;
  this._maxTraces = options.maxTraces || 50;
  this._sendInterval = options.sendInterval ? (options.sendInterval * 1000) : 10 * 1000;
  this._lastSentTs = Date.now();
  this._buffer = [];
  this._stopped = false;

  this._periodSendTimeoutId = setTimeout(this._periodicSendFunction.bind(this),
                                         this._sendInterval);
}

BufferingTracer.prototype.sendTraces = function(traces) {
  var buffer;

  this._buffer = this._buffer.concat(traces);

  if (this._buffer.length >= this._maxTraces) {
    buffer = this._buffer.slice();
    this._buffer = [];

    // Flush the buffer in the next tick
    process.nextTick(this._sendTraces.bind(this, buffer));
  }
};

/**
 * Clear any outgoing timers and stop the tracer.
 */
BufferingTracer.prototype.stop = function() {
  clearTimeout(this._periodSendTimeoutId);
  this._stopped = true;
};

BufferingTracer.prototype._periodicSendFunction = function() {
  var now = Date.now(), buffer;

  if (((now - this._sendInterval) > this._lastSentTs) &&
      this._buffer.length >= 1) {
    buffer = this._buffer.slice();
    this._buffer = [];
    this._sendTraces(buffer);
  }

  // Re-schedule itself
  this._periodSendTimeoutId = setTimeout(this._periodicSendFunction.bind(this),
                                         this._sendInterval);
};

BufferingTracer.prototype._sendTraces = function(traces) {
  this._lastSentTs = Date.now();
  this._tracer.sendTraces(traces);
};

/**
 * A tracer that records to zipkin through the RESTkin http interface.
 * Requires a keystone client ({node-keystone-client}).
 *
 * This implementation posts all traces immediately and does not implement
 * buffering.
 *
 * @param {String} traceUrl The URL to the RESTkin endpoint including the
 * version. For example: https://example.com/v1.0
 * @param {keystone-client.KeystoneClient object} keystoneClient The
 * keystone client to use to authenticate against keystone to get a token
 * and tenant id.
 */
function RawRESTkinHTTPTracer(traceUrl, keystoneClient) {
  if (traceUrl.charAt(traceUrl.length - 1) === '/') {
    traceUrl = traceUrl.slice(0, -1);
  }

  this._traceUrl = traceUrl;
  this._keystoneClient = keystoneClient;

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RawRESTkinHTTPTracer, EndAnnotationTracer);

RawRESTkinHTTPTracer.prototype.performRequest = function (token, tenantId, body, callback) {
  var url = this._traceUrl + '/' + tenantId + '/trace', headers;

  headers = {
    'X-Auth-Token': token,
    'X-Tenant-Id': tenantId,
    'Content-Type': 'application/json'
  };

  request.post({
    url: url,
    headers: headers,
    body: body
  }, callback);
};

RawRESTkinHTTPTracer.prototype.sendTraces = function(traces) {
  var self = this;

  async.waterfall([
    this._keystoneClient.getTenantIdAndToken.bind(this._keystoneClient, {}),

    function formatTraces(result, callback) {
      formatters.formatForRestkin(traces, function(err, formattedTraces) {
        callback(err, result, formattedTraces);
      });
    },

    function makeRequest(result, formattedTraces, callback) {
      self.performRequest(result.token, result.tenantId, formattedTraces, callback);
    }
  ],

  function(err) {
    if (err) {
      console.log('Failed to send traces to backend: ' + err.toString());
    }
  });
};

/**
 * A tracer that records to zipkin through the RESTkin http interface.
 * Requires a keystone client ({node-keystone-client}).
 *
 * This is equivalent to EndAnnotationTracer(BufferingTracer(RawRESTkinHTTPTracerTests())).
 *
 * @param {String} traceUrl The URL to the RESTkin endpoint including the
 * version. For example: https://example.com/v1.0
 * @param {keystone-client.KeystoneClient object} keystoneClient The
 * keystone client to use to authenticate against keystone to get a token
 * and tenant id.
 * @param {Object} options Options passed to the BufferingTracer constructor.
 */
function RESTkinHTTPTracer(traceUrl, keystoneClient, options) {
  var rawTracer = new module.exports.RawRESTkinHTTPTracer(traceUrl, keystoneClient);
  this._tracer = new module.exports.BufferingTracer(rawTracer, options);

  this.stop = this._tracer.stop.bind(this._tracer);

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RESTkinHTTPTracer, EndAnnotationTracer);

RESTkinHTTPTracer.prototype.sendTraces = function(traces) {
  this._tracer.sendTraces(traces);
};

/**
 * A tracer that records to zipkin through scribe. Requires a scribe
 * client ({node-scribe}).
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function RawZipkinTracer(scribeClient, category) {
  this.scribeClient = scribeClient;
  this.category = (category) ? category : 'zipkin';

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RawZipkinTracer, EndAnnotationTracer);

RawZipkinTracer.prototype._sendTrace = function(tuple) {
  var trace = tuple[0], annotations = tuple[1];

  async.waterfall([
    formatters.formatForZipkin.bind(null, trace, annotations),
    this.scribeClient.send.bind(this.scribeClient, this.category)
  ], function(err) {});
};

RawZipkinTracer.prototype.sendTraces = function(traces) {
  var callback = function() {};

  async.forEachLimit(traces, 10, this._sendTrace.bind(this),
                     callback);
};

/**
 * A tracer that records to zipkin through scribe. Requires a scribe
 * client ({node-scribe}).
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function ZipkinTracer(scribeClient, category, options) {
  var rawTracer = new RawZipkinTracer(scribeClient, category);
  this._tracer = new BufferingTracer(rawTracer, options);

  this.stop = this._tracer.stop.bind(this._tracer);

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(ZipkinTracer, EndAnnotationTracer);

ZipkinTracer.prototype.sendTraces = function(traces) {
  this._tracer.sendTraces(traces);
};

/**
 * A tracer that records to RESTkin through scribe.  Requires a scribe
 * client ({node-scribe}).
 *
 * This implementation logs all annotations immediately and does not implement
 * buffering of any sort.
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function RawRESTkinScribeTracer(scribeClient, category) {
  this.scribeClient = scribeClient;
  this.category = (category) ? category : 'restkin';

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RawRESTkinScribeTracer, EndAnnotationTracer);

RawRESTkinScribeTracer.prototype.sendTraces = function(traces) {
  var self = this;

  async.waterfall([
    formatters.formatForRestkin.bind(null, traces),

    function send(formattedTraces, callback) {
      self.scribeClient.send(self.category, formattedTraces);
      callback();
    }
  ],

  function(err) {
    if (err) {
      console.log('Failed to send traces to backend: ' + err.toString());
    }
  });
};

/**
 * A tracer that records to RESTkin through scribe.  Requires a scribe
 * client ({node-scribe}).
 *
 * This implementation logs all annotations immediately and does not implement
 * buffering of any sort.
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function RESTkinScribeTracer(scribeClient, category) {
  this._tracer = new BufferingTracer(rawTracer, options);
  this.stop = this._tracer.stop.bind(this._tracer);

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RESTkinScribeTracer, EndAnnotationTracer);

RESTkinScribeTracer.prototype.sendTraces = function(traces) {
  this._tracer.sendTraces(traces);
};

module.exports.BufferingTracer = BufferingTracer;
module.exports.RawRESTkinHTTPTracer = RawRESTkinHTTPTracer;
module.exports.RESTkinHTTPTracer = RESTkinHTTPTracer;
module.exports.RawZipkinTracer = RawZipkinTracer;
module.exports.ZipkinTracer = ZipkinTracer;
module.exports.RawRESTkinScribeTracer = RawRESTkinScribeTracer;
module.exports.RESTkinScribeTracer = RESTkinScribeTracer;
<commit_msg>Fix a bug with RESTkinScribeTracer discovered by lint.<commit_after>// Copyright 2012 Rackspace Hosting, 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.

/**
 * Tracers only for Node.
 * Note: Node tracers assume tryfer library is included in a long running
 * process.
 */

if (typeof module === 'undefined' || !module.exports) {
  return;
}

var util = require('util');

var request = require('request');
var async = require('async');

var EndAnnotationTracer = require('./tracers').EndAnnotationTracer;
var formatters = require('./formatters');

/**
 * Buffer traces and defer recording until maxTraces have been received or
 * sendInterval has elapsed since the last trace was recorded.
 *
 * @param {Tracer} tracer Tracer provider to record buffered tracers to.
 * @param {Options} options Options object with the following keys:
 * - maxTraces - Number of traces to buffer before recording occurs (default:
 *   50).
 * - sendInterval - An average number of seconds which can pass after the last
 *   trace has been sent to the backend before sending all the buffered traces
 *   to the backend again (default: 10).
 */
function BufferingTracer(tracer, options) {
  options = options || {};

  var self = this;

  this._tracer = tracer;
  this._maxTraces = options.maxTraces || 50;
  this._sendInterval = options.sendInterval ? (options.sendInterval * 1000) : 10 * 1000;
  this._lastSentTs = Date.now();
  this._buffer = [];
  this._stopped = false;

  this._periodSendTimeoutId = setTimeout(this._periodicSendFunction.bind(this),
                                         this._sendInterval);
}

BufferingTracer.prototype.sendTraces = function(traces) {
  var buffer;

  this._buffer = this._buffer.concat(traces);

  if (this._buffer.length >= this._maxTraces) {
    buffer = this._buffer.slice();
    this._buffer = [];

    // Flush the buffer in the next tick
    process.nextTick(this._sendTraces.bind(this, buffer));
  }
};

/**
 * Clear any outgoing timers and stop the tracer.
 */
BufferingTracer.prototype.stop = function() {
  clearTimeout(this._periodSendTimeoutId);
  this._stopped = true;
};

BufferingTracer.prototype._periodicSendFunction = function() {
  var now = Date.now(), buffer;

  if (((now - this._sendInterval) > this._lastSentTs) &&
      this._buffer.length >= 1) {
    buffer = this._buffer.slice();
    this._buffer = [];
    this._sendTraces(buffer);
  }

  // Re-schedule itself
  this._periodSendTimeoutId = setTimeout(this._periodicSendFunction.bind(this),
                                         this._sendInterval);
};

BufferingTracer.prototype._sendTraces = function(traces) {
  this._lastSentTs = Date.now();
  this._tracer.sendTraces(traces);
};

/**
 * A tracer that records to zipkin through the RESTkin http interface.
 * Requires a keystone client ({node-keystone-client}).
 *
 * This implementation posts all traces immediately and does not implement
 * buffering.
 *
 * @param {String} traceUrl The URL to the RESTkin endpoint including the
 * version. For example: https://example.com/v1.0
 * @param {keystone-client.KeystoneClient object} keystoneClient The
 * keystone client to use to authenticate against keystone to get a token
 * and tenant id.
 */
function RawRESTkinHTTPTracer(traceUrl, keystoneClient) {
  if (traceUrl.charAt(traceUrl.length - 1) === '/') {
    traceUrl = traceUrl.slice(0, -1);
  }

  this._traceUrl = traceUrl;
  this._keystoneClient = keystoneClient;

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RawRESTkinHTTPTracer, EndAnnotationTracer);

RawRESTkinHTTPTracer.prototype.performRequest = function (token, tenantId, body, callback) {
  var url = this._traceUrl + '/' + tenantId + '/trace', headers;

  headers = {
    'X-Auth-Token': token,
    'X-Tenant-Id': tenantId,
    'Content-Type': 'application/json'
  };

  request.post({
    url: url,
    headers: headers,
    body: body
  }, callback);
};

RawRESTkinHTTPTracer.prototype.sendTraces = function(traces) {
  var self = this;

  async.waterfall([
    this._keystoneClient.getTenantIdAndToken.bind(this._keystoneClient, {}),

    function formatTraces(result, callback) {
      formatters.formatForRestkin(traces, function(err, formattedTraces) {
        callback(err, result, formattedTraces);
      });
    },

    function makeRequest(result, formattedTraces, callback) {
      self.performRequest(result.token, result.tenantId, formattedTraces, callback);
    }
  ],

  function(err) {
    if (err) {
      console.log('Failed to send traces to backend: ' + err.toString());
    }
  });
};

/**
 * A tracer that records to zipkin through the RESTkin http interface.
 * Requires a keystone client ({node-keystone-client}).
 *
 * This is equivalent to EndAnnotationTracer(BufferingTracer(RawRESTkinHTTPTracerTests())).
 *
 * @param {String} traceUrl The URL to the RESTkin endpoint including the
 * version. For example: https://example.com/v1.0
 * @param {keystone-client.KeystoneClient object} keystoneClient The
 * keystone client to use to authenticate against keystone to get a token
 * and tenant id.
 * @param {Object} options Options passed to the BufferingTracer constructor.
 */
function RESTkinHTTPTracer(traceUrl, keystoneClient, options) {
  var rawTracer = new module.exports.RawRESTkinHTTPTracer(traceUrl, keystoneClient);
  this._tracer = new module.exports.BufferingTracer(rawTracer, options);

  this.stop = this._tracer.stop.bind(this._tracer);

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RESTkinHTTPTracer, EndAnnotationTracer);

RESTkinHTTPTracer.prototype.sendTraces = function(traces) {
  this._tracer.sendTraces(traces);
};

/**
 * A tracer that records to zipkin through scribe. Requires a scribe
 * client ({node-scribe}).
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function RawZipkinTracer(scribeClient, category) {
  this.scribeClient = scribeClient;
  this.category = (category) ? category : 'zipkin';

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RawZipkinTracer, EndAnnotationTracer);

RawZipkinTracer.prototype._sendTrace = function(tuple) {
  var trace = tuple[0], annotations = tuple[1];

  async.waterfall([
    formatters.formatForZipkin.bind(null, trace, annotations),
    this.scribeClient.send.bind(this.scribeClient, this.category)
  ], function(err) {});
};

RawZipkinTracer.prototype.sendTraces = function(traces) {
  var callback = function() {};

  async.forEachLimit(traces, 10, this._sendTrace.bind(this),
                     callback);
};

/**
 * A tracer that records to zipkin through scribe. Requires a scribe
 * client ({node-scribe}).
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function ZipkinTracer(scribeClient, category, options) {
  var rawTracer = new RawZipkinTracer(scribeClient, category);
  this._tracer = new BufferingTracer(rawTracer, options);

  this.stop = this._tracer.stop.bind(this._tracer);

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(ZipkinTracer, EndAnnotationTracer);

ZipkinTracer.prototype.sendTraces = function(traces) {
  this._tracer.sendTraces(traces);
};

/**
 * A tracer that records to RESTkin through scribe.  Requires a scribe
 * client ({node-scribe}).
 *
 * This implementation logs all annotations immediately and does not implement
 * buffering of any sort.
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 */
function RawRESTkinScribeTracer(scribeClient, category) {
  this.scribeClient = scribeClient;
  this.category = (category) ? category : 'restkin';

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RawRESTkinScribeTracer, EndAnnotationTracer);

RawRESTkinScribeTracer.prototype.sendTraces = function(traces) {
  var self = this;

  async.waterfall([
    formatters.formatForRestkin.bind(null, traces),

    function send(formattedTraces, callback) {
      self.scribeClient.send(self.category, formattedTraces);
      callback();
    }
  ],

  function(err) {
    if (err) {
      console.log('Failed to send traces to backend: ' + err.toString());
    }
  });
};

/**
 * A tracer that records to RESTkin through scribe.  Requires a scribe
 * client ({node-scribe}).
 *
 * This implementation logs all annotations immediately and does not implement
 * buffering of any sort.
 *
 * @param {scribe.Scribe object} scribeClient The client to use to write to
 *    scribe
 * @param {String} category The category to use when writing to scribe -
 *    defaults to "zipkin" if not passed
 * @param {Object} options Options passed to the BufferingTracer constructor.
 */
function RESTkinScribeTracer(scribeClient, category, options) {
  var rawTracer = new RawRESTkinScribeTracer(scribeClient, category);

  this._tracer = new module.exports.BufferingTracer(rawTracer, options);
  this.stop = this._tracer.stop.bind(this._tracer);

  EndAnnotationTracer.call(this, this.sendTraces);
}

util.inherits(RESTkinScribeTracer, EndAnnotationTracer);

RESTkinScribeTracer.prototype.sendTraces = function(traces) {
  this._tracer.sendTraces(traces);
};

module.exports.BufferingTracer = BufferingTracer;
module.exports.RawRESTkinHTTPTracer = RawRESTkinHTTPTracer;
module.exports.RESTkinHTTPTracer = RESTkinHTTPTracer;
module.exports.RawZipkinTracer = RawZipkinTracer;
module.exports.ZipkinTracer = ZipkinTracer;
module.exports.RawRESTkinScribeTracer = RawRESTkinScribeTracer;
module.exports.RESTkinScribeTracer = RESTkinScribeTracer;
<|endoftext|>


<commit_before>var Q = require('q'),
    INHERIT = require('inherit'),
    U = require('../util'),
    PATH = require('path'),

    BlockNode = require('./block').BlockNode,
    FileNode = require('./file').FileNode,
    BemCreateNode = require('./create').BemCreateNode,
    BemBuildNode = require('./build').BemBuildNode,
    registry = require('../make').NodesRegistry,
    LOGGER = require('../logger');

var BundleNode = exports.BundleNode = INHERIT(BlockNode, {

    make: function() {

        return this.ctx.arch.withLock(this.alterArch(), this);

    },

    alterArch: function() {

        var ctx = this.ctx;

        return function() {

            // create real node for page
            var arch = ctx.arch,
                bundleNode;

            if (arch.hasNode(this.path)) {
                bundleNode = arch.getNode(this.path);
            } else {
                bundleNode = new FileNode({
                    root: this.root,
                    path: this.path
                });
                arch.setNode(bundleNode, arch.getParents(this));
            }

            // generate targets for page files
            this.getTechs().map(function(tech) {
                var techNode = this.createTechNode(tech, bundleNode, this);
                if (techNode) {
                    this.createOptimizerNode(tech, techNode, bundleNode);
                }
            }, this);

        };

    },

    lastModified: function() {
        return Q.resolve(0);
    },

    createTechNode: function(tech, bundleNode, magicNode) {

        var f = 'create-' + tech + '-node';
        f = typeof this[f] === 'function'? f : 'createDefaultTechNode';

        LOGGER.fdebug('Using %s() to create node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    createOptimizerNode: function(tech, sourceNode, bundleNode) {

        var f = 'create-' + tech + '-optimizer-node';
        f = typeof this[f] === 'function'? f : 'createDefaultOptimizerNode';

        LOGGER.fdebug('Using %s() to create optimizer node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    getBundlePath: function(tech) {
        return this.level.getPath(this.getNodePrefix(), tech);
    },

    getTechs: function() {
        return [
            'bemjson.js',
            'bemdecl.js',
            'deps.js',
            'bemhtml.js',
            'css',
            'ie.css',
            'js',
            'html'
        ];
    },

    cleanup: function() {
        var arch = this.ctx.arch;
        if (!arch.hasNode(this.path)) return;
        arch.removeTree(this.path);
    },

    getLevels: function(prefix) {
        return [].concat(
            this.level.getConfig().bundleBuildLevels,
            [ PATH.join(PATH.dirname(prefix), 'blocks') ]);
    },

    useFileOrBuild: function(node) {

        var deps = node.getDependencies();

        for(var i = 0, l = deps.length; i < l; i++) {
            var d = deps[i];

            if (!this.ctx.arch.hasNode(d)) {
                LOGGER.fverbose('Dependency %s is required to build %s but does not exist, checking if target already built', d, node.getId());

                if (!PATH.existsSync(node.getPath())) {
                    LOGGER.fwarn('%s will not be built because dependency file %s does not exist', node.path, d);
                    return null;
                }

                return new FileNode({
                    root: this.root,
                    path: node.getId()
                });
            }
        }

        return node;

    },

    /**
     * Create a bem build node, add it to the arch, add
     * dependencies to it. Then create a meta node and link
     * it to the build node.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} declPath
     * @param {String} bundleNode
     * @param {String} magicNode
     * @param {Boolean} [forked]
     */
    setBemBuildNode: function(techName, techPath, declPath, bundleNode, magicNode, forked) {

        var arch = this.ctx.arch,
            buildNode = new BemBuildNode({
                root: this.root,
                bundlesLevel: this.level,
                levels: this.getLevels(PATH.resolve(this.root, this.getNodePrefix())),
                declPath: declPath,
                techPath: techPath,
                techName: techName,
                output: this.getNodePrefix(),
                forked: forked
            }),
            metaNode = buildNode.getMetaNode();

        // Set bem build node to arch
        arch.setNode(buildNode)
            .addChildren(buildNode, buildNode.getDependencies());

        bundleNode && arch.addParents(buildNode, bundleNode);
        magicNode && arch.addChildren(buildNode, magicNode);

        // Set bem build meta node to arch
        arch.setNode(metaNode, buildNode, metaNode.getDependencies());

        return buildNode;

    },

    createDefaultTechNode: function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('deps.js'),
            bundleNode,
            magicNode);

    },

    'create-bemjson.js-node': function(tech, bundleNode, magicNode) {

        var arch = this.ctx.arch,
            filePath = this.getBundlePath(tech);

        if (!PATH.existsSync(PATH.resolve(this.root, filePath))) return;

        var node = new FileNode({
            root: this.root,
            path: filePath
        });

        arch.setNode(node);

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    'create-bemdecl.js-node': function(tech, bundleNode, magicNode) {

        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemCreateNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: this.level.resolveTech(tech),
                techName: tech
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    'create-deps.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('bemdecl.js'),
            bundleNode,
            magicNode);

    },

    'create-html-node': function(tech, bundleNode, magicNode) {

        // TODO: move node to bem-bl/blocks-common/i-bem/bem
        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemCreateNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: this.level.resolveTech(tech),
                techName: tech,
                force: true
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    createDefaultOptimizerNode: function(tech, sourceNode, bundleNode) {},

    'create-js-optimizer-node': function(tech, sourceNode, bundleNode) {

        LOGGER.fdebug('Creating borschik node for %s', sourceNode.getId());

        var node = new (registry.getNodeClass('BorschikNode'))({
            root: this.root,
            sourceNode: sourceNode,
            tech: 'js',
            forked: true
        });

        this.ctx.arch
            .setNode(node)
            .addParents(node, bundleNode)
            .addChildren(node, sourceNode);

        return node;

    },

    'create-priv.js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-js-optimizer-node'].apply(this, arguments);
    },

    'create-css-optimizer-node': function(tech, sourceNode, bundleNode) {

        LOGGER.fdebug('Creating borschik node for %s', sourceNode.getId());

        var node = new (registry.getNodeClass('BorschikNode'))({
            root: this.root,
            sourceNode: sourceNode,
            tech: 'css-fast',
            forked: true
        });

        this.ctx.arch
            .setNode(node)
            .addParents(node, bundleNode)
            .addChildren(node, sourceNode);

        return node;

    },

    'create-ie.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var node = this['create-css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.addChildren(node, this.getBundlePath('css'));
        return node;

    },

    'create-ie6.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var node = this['create-ie.css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.addChildren(node, this.getBundlePath('ie.css'));
        return node;

    },

    'create-ie7.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie8.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie9.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie.css-optimizer-node'].apply(this, arguments);
    }

});
<commit_msg>Refactor BundleNode<commit_after>var Q = require('q'),
    INHERIT = require('inherit'),
    U = require('../util'),
    PATH = require('path'),

    BlockNode = require('./block').BlockNode,
    FileNode = require('./file').FileNode,
    BemCreateNode = require('./create').BemCreateNode,
    BemBuildNode = require('./build').BemBuildNode,
    registry = require('../make').NodesRegistry,
    LOGGER = require('../logger');

var BundleNode = exports.BundleNode = INHERIT(BlockNode, {

    make: function() {

        return this.ctx.arch.withLock(this.alterArch(), this);

    },

    alterArch: function() {

        var ctx = this.ctx;

        return function() {

            // create real node for page
            var arch = ctx.arch,
                bundleNode;

            if (arch.hasNode(this.path)) {
                bundleNode = arch.getNode(this.path);
            } else {
                bundleNode = new FileNode({
                    root: this.root,
                    path: this.path
                });
                arch.setNode(bundleNode, arch.getParents(this));
            }

            // generate targets for page files
            this.getTechs().map(function(tech) {
                var techNode = this.createTechNode(tech, bundleNode, this);
                if (techNode) {
                    this.createOptimizerNode(tech, techNode, bundleNode);
                }
            }, this);

        };

    },

    lastModified: function() {
        return Q.resolve(0);
    },

    createTechNode: function(tech, bundleNode, magicNode) {

        var f = 'create-' + tech + '-node';
        f = typeof this[f] === 'function'? f : 'createDefaultTechNode';

        LOGGER.fdebug('Using %s() to create node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    createOptimizerNode: function(tech, sourceNode, bundleNode) {

        var f = 'create-' + tech + '-optimizer-node';
        f = typeof this[f] === 'function'? f : 'createDefaultOptimizerNode';

        LOGGER.fdebug('Using %s() to create optimizer node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    getBundlePath: function(tech) {
        return this.level.getPath(this.getNodePrefix(), tech);
    },

    getTechs: function() {
        return [
            'bemjson.js',
            'bemdecl.js',
            'deps.js',
            'bemhtml.js',
            'css',
            'ie.css',
            'js',
            'html'
        ];
    },

    cleanup: function() {
        var arch = this.ctx.arch;
        if (!arch.hasNode(this.path)) return;
        arch.removeTree(this.path);
    },

    getLevels: function(prefix) {
        return [].concat(
            this.level.getConfig().bundleBuildLevels,
            [ PATH.join(PATH.dirname(prefix), 'blocks') ]);
    },

    useFileOrBuild: function(node) {

        var deps = node.getDependencies();

        for(var i = 0, l = deps.length; i < l; i++) {
            var d = deps[i];

            if (!this.ctx.arch.hasNode(d)) {
                LOGGER.fverbose('Dependency %s is required to build %s but does not exist, checking if target already built', d, node.getId());

                if (!PATH.existsSync(node.getPath())) {
                    LOGGER.fwarn('%s will not be built because dependency file %s does not exist', node.path, d);
                    return null;
                }

                return new FileNode({
                    root: this.root,
                    path: node.getId()
                });
            }
        }

        return node;

    },

    /**
     * Create a bem build node, add it to the arch, add
     * dependencies to it. Then create a meta node and link
     * it to the build node.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} declPath
     * @param {String} bundleNode
     * @param {String} magicNode
     * @param {Boolean} [forked]
     */
    setBemBuildNode: function(techName, techPath, declPath, bundleNode, magicNode, forked) {

        var arch = this.ctx.arch,
            buildNode = new BemBuildNode({
                root: this.root,
                bundlesLevel: this.level,
                levels: this.getLevels(PATH.resolve(this.root, this.getNodePrefix())),
                declPath: declPath,
                techPath: techPath,
                techName: techName,
                output: this.getNodePrefix(),
                forked: forked
            }),
            metaNode = buildNode.getMetaNode();

        // Set bem build node to arch
        arch.setNode(buildNode)
            .addChildren(buildNode, buildNode.getDependencies());

        bundleNode && arch.addParents(buildNode, bundleNode);
        magicNode && arch.addChildren(buildNode, magicNode);

        // Set bem build meta node to arch
        arch.setNode(metaNode, buildNode, metaNode.getDependencies());

        return buildNode;

    },

    /**
     * Create a bem create node, add it to the arch,
     * add dependencies to it.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} bundleNode
     * @param {String} magicNode
     */
    setBemCreateNode: function(techName, techPath, bundleNode, magicNode) {

        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemCreateNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: techPath,
                techName: techName
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    /**
     * Create file node, add it to the arch, add dependencies to it.
     *
     * @param {String} tech
     * @param {String} bundleNode
     * @param {String} magicNode
     */
    setFileNode: function(tech, bundleNode, magicNode) {

        var arch = this.ctx.arch,
            filePath = this.getBundlePath(tech);

        if (!PATH.existsSync(PATH.resolve(this.root, filePath))) return;

        var node = new FileNode({
            root: this.root,
            path: filePath
        });

        arch.setNode(node);

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    createDefaultTechNode: function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('deps.js'),
            bundleNode,
            magicNode);

    },

    createDefaultOptimizerNode: function(tech, sourceNode, bundleNode) {},

    'create-bemjson.js-node': function(tech, bundleNode, magicNode) {
        return this.setFileNode.apply(this, arguments);
    },

    'create-bemdecl.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemCreateNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode);

    },

    'create-deps.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('bemdecl.js'),
            bundleNode,
            magicNode);

    },

    'create-html-node': function(tech, bundleNode, magicNode) {

        return this.setBemCreateNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode);

    },

    'create-js-optimizer-node': function(tech, sourceNode, bundleNode) {

        LOGGER.fdebug('Creating borschik node for %s', sourceNode.getId());

        var node = new (registry.getNodeClass('BorschikNode'))({
            root: this.root,
            sourceNode: sourceNode,
            tech: 'js',
            forked: true
        });

        this.ctx.arch
            .setNode(node)
            .addParents(node, bundleNode)
            .addChildren(node, sourceNode);

        return node;

    },

    'create-priv.js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-js-optimizer-node'].apply(this, arguments);
    },

    'create-css-optimizer-node': function(tech, sourceNode, bundleNode) {

        LOGGER.fdebug('Creating borschik node for %s', sourceNode.getId());

        var node = new (registry.getNodeClass('BorschikNode'))({
            root: this.root,
            sourceNode: sourceNode,
            tech: 'css-fast',
            forked: true
        });

        this.ctx.arch
            .setNode(node)
            .addParents(node, bundleNode)
            .addChildren(node, sourceNode);

        return node;

    },

    'create-ie.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var node = this['create-css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.addChildren(node, this.getBundlePath('css'));
        return node;

    },

    'create-ie6.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var node = this['create-ie.css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.addChildren(node, this.getBundlePath('ie.css'));
        return node;

    },

    'create-ie7.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie8.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie9.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie.css-optimizer-node'].apply(this, arguments);
    }

});
<|endoftext|>


<commit_before>'use strict';

const webdriver = require('selenium-webdriver');
const log = require('intel').getLogger('browsertime');
const SeleniumRunner = require('../seleniumRunner');
const preURL = require('../../support/preURL');
const setResourceTimingBufferSize = require('../../support/setResourceTimingBufferSize');
const ScreenshotManager = require('../../screenshot/');
const ExtensionServer = require('../../extensionserver/');
const engineUtils = require('../../support/engineUtils');
const Video = require('../../video/video');
const stop = require('../../support/stop');
const AddText = require('./command/addText.js');
const Click = require('./command/click.js');
const Wait = require('./command/wait.js');
const Measure = require('./command/measure.js');
const JavaScript = require('./command/javaScript.js');
const Switch = require('./command/switch.js');
const Screenshot = require('./command/screenshot.js');
const Set = require('./command/set.js');
const Cache = require('./command/cache.js');
const Meta = require('./command/meta.js');
const Android = require('./command/android.js');
const ChromeDevToolsProtocol = require('./command/chromeDevToolsProtocol.js');
const { addConnectivity, removeConnectivity } = require('../../connectivity');
const util = require('../../support/util.js');

const { isAndroidConfigured } = require('../../android');

const delay = ms => new Promise(res => setTimeout(res, ms));

// Make this configurable
const ANDROID_DELAY_TIME = 2000;
/**
 * Create a new Iteration instance. This is the iteration flow, what
 * Browsertime will do through one iteration of testing a URL.
 * @class
 */
class Iteration {
  constructor(
    storageManager,
    engineDelegate,
    scriptsByCategory,
    asyncScriptsByCategory,
    options
  ) {
    try {
      this.preScripts = engineUtils.loadPrePostScripts(options.preScript);
      this.postScripts = engineUtils.loadPrePostScripts(options.postScript);
      this.postURLScripts = engineUtils.loadPrePostScripts(
        options.postURLScript
      );
      this.pageCompleteCheck = engineUtils.loadScript(
        options.pageCompleteCheck
      );
    } catch (e) {
      log.error(e.message);
      throw e;
    }
    this.options = options;
    this.storageManager = storageManager;
    this.engineDelegate = engineDelegate;
    this.scriptsByCategory = scriptsByCategory;
    this.asyncScriptsByCategory = asyncScriptsByCategory;
    this.screenshotManager = new ScreenshotManager(storageManager, options);
  }

  /**
   *  Run one iteration for one url. Here are the whole flow of what we
   * do for one URL per iteration.
   * @param {*} url - The URL that will be tested
   * @param {*} index - Which iteration it is
   */
  async run(navigationScript, index) {
    const options = this.options;
    const browser = new SeleniumRunner(
      this.storageManager.directory,
      this.options
    );
    this.video = new Video(this.storageManager, this.options, browser);
    const recordVideo = options.visualMetrics || options.video;
    const videos = [];
    const result = [];

    const extensionServer = new ExtensionServer(options);

    try {
      if (options.connectivity.variance) {
        await addConnectivity(options);
      }
      await browser.start();
      await this.engineDelegate.afterBrowserStart();

      // The data we push to all selenium scripts
      const context = {
        // TODO we want to have the URL here so we can use scripts to login
        // and then access the supplied URL
        options,
        result,
        log,
        storageManager: this.storageManager,
        taskData: {},
        index,
        selenium: {
          webdriver, // The Selenium WebDriver public API object https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index.html
          driver: browser.getDriver() // The instantiated version of the WebDriver https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
        }
      };

      const measure = new Measure(
        browser,
        index,
        this.pageCompleteCheck,
        result,
        this.engineDelegate,
        extensionServer,
        this.storageManager,
        videos,
        this.scriptsByCategory,
        this.asyncScriptsByCategory,
        this.postURLScripts,
        context,
        this.screenshotManager,
        options
      );

      const cdp = new ChromeDevToolsProtocol(
        this.engineDelegate,
        options.browser
      );
      const commands = {
        click: new Click(browser, this.pageCompleteCheck),
        addText: new AddText(browser),
        wait: new Wait(browser, this.pageCompleteCheck),
        measure: measure,
        navigate: measure._navigate.bind(measure),
        error: measure._error.bind(measure),
        js: new JavaScript(browser, this.pageCompleteCheck),
        switch: new Switch(browser),
        set: new Set(browser),
        cache: new Cache(browser, options.browser, this.extensionServer, cdp),
        meta: new Meta(),
        screenshot: new Screenshot(this.screenshotManager, browser, index),
        cdp,
        android: new Android(options)
      };

      const startURL =
        options.browser === 'safari'
          ? 'data:text/html;'
          : 'data:text/html;charset=utf-8,';
      if (options.spa) {
        await setResourceTimingBufferSize(startURL, browser.getDriver(), 1000);
      } else if (!options.processStartTime) {
        // Before we needed Chrome to open once to get correct values for Chrome HAR
        // but now Firefox first visual change is slower if we remove this ...
        await browser.getDriver().get(startURL);
      }

      // On slowish Android phones it takes some time for the
      // browser to get ready
      if (isAndroidConfigured(options)) {
        await delay(ANDROID_DELAY_TIME);
      }

      await delay(options.timeToSettle || 100);

      if (recordVideo && options.videoParams.debug) {
        await this.video.record(0, index);
      }

      for (const preScript of this.preScripts) {
        await preScript(context, commands);
      }

      if (options.preURL) {
        await preURL(browser, options);
      }

      try {
        await navigationScript(context, commands, this.postURLScripts);
      } catch (e) {
        commands.error(e.message);
        throw e;
      }
      if (commands.measure.areWeMeasuring === true) {
        // someone forgot to call stop();
        log.info(
          'It looks like you missed to call measure.stop() after calling measure.start(..) (without using a URL). Checkout https://www.sitespeed.io/documentation/sitespeed.io/scripting/#measurestartalias'
        );
        await commands.measure.stop();
      }
      await this.engineDelegate.onStopIteration(browser, index, result);

      for (const postScript of this.postScripts) {
        await postScript(context, commands);
      }

      if (recordVideo && options.videoParams.debug) {
        // Just use the first URL
        const url =
          result.length > 0 ? result[0].url : 'https://debug.sitespeed.io';
        await this.video.stop(url);
      }
      await browser.stop();

      if (options.visualMetrics && !options.videoParams.debug) {
        let i = 0;
        for (let myVideo of videos) {
          try {
            const videoMetrics = await myVideo.postProcessing(
              result[i].browserScripts.timings.pageTimings,
              result[i].browserScripts.pageinfo.visualElements
            );

            for (let progress of [
              'VisualProgress',
              'ContentfulSpeedIndexProgress',
              'PerceptualSpeedIndexProgress'
            ]) {
              if (videoMetrics.visualMetrics[progress]) {
                videoMetrics.visualMetrics[
                  progress
                ] = util.jsonifyVisualProgress(
                  videoMetrics.visualMetrics[progress]
                );
              }
            }
            result[i].videoRecordingStart = videoMetrics.videoRecordingStart;
            result[i].visualMetrics = videoMetrics.visualMetrics;
            i++;
          } catch (e) {
            // If one of the Visual Metrics runs fail, just swallow and try the next one
            log.error('Visual Metrics failed to analyse the video', e);
          }
        }
      }

      await this.engineDelegate.afterCollect(index, result);

      if (commands.meta.description) {
        result.description = commands.meta.description;
      }
      if (commands.meta.title) {
        result.title = commands.meta.title;
      }
      return result;
    } catch (e) {
      log.error(e);
      if (recordVideo && options.videoParams.debug) {
        // Just use the first URL
        const url =
          result.length > 0 ? result[0].url : 'https://debug.sitespeed.io';
        await this.video.stop(url);
      }

      // In Docker on Desktop we can use a hardcore way to cleanup
      if (options.docker && recordVideo && !isAndroidConfigured(options)) {
        await stop('ffmpeg');
      }
      result.error = [e.name];
      return result;
    } finally {
      // Here we should also make sure FFMPEG is really killed/stopped
      // if something fails, we had bug reports where we miss it
      try {
        if (options.connectivity.variance) {
          await removeConnectivity(options);
        }
        await browser.stop();
      } catch (e) {
        // Most cases the browser been stopped already
      }
    }
  }
}

module.exports = Iteration;
<commit_msg>If Visual Metrics fails, add an error to the result so we can signal the error. (#1147)<commit_after>'use strict';

const webdriver = require('selenium-webdriver');
const log = require('intel').getLogger('browsertime');
const SeleniumRunner = require('../seleniumRunner');
const preURL = require('../../support/preURL');
const setResourceTimingBufferSize = require('../../support/setResourceTimingBufferSize');
const ScreenshotManager = require('../../screenshot/');
const ExtensionServer = require('../../extensionserver/');
const engineUtils = require('../../support/engineUtils');
const Video = require('../../video/video');
const stop = require('../../support/stop');
const AddText = require('./command/addText.js');
const Click = require('./command/click.js');
const Wait = require('./command/wait.js');
const Measure = require('./command/measure.js');
const JavaScript = require('./command/javaScript.js');
const Switch = require('./command/switch.js');
const Screenshot = require('./command/screenshot.js');
const Set = require('./command/set.js');
const Cache = require('./command/cache.js');
const Meta = require('./command/meta.js');
const Android = require('./command/android.js');
const ChromeDevToolsProtocol = require('./command/chromeDevToolsProtocol.js');
const { addConnectivity, removeConnectivity } = require('../../connectivity');
const util = require('../../support/util.js');

const { isAndroidConfigured } = require('../../android');

const delay = ms => new Promise(res => setTimeout(res, ms));

// Make this configurable
const ANDROID_DELAY_TIME = 2000;
/**
 * Create a new Iteration instance. This is the iteration flow, what
 * Browsertime will do through one iteration of testing a URL.
 * @class
 */
class Iteration {
  constructor(
    storageManager,
    engineDelegate,
    scriptsByCategory,
    asyncScriptsByCategory,
    options
  ) {
    try {
      this.preScripts = engineUtils.loadPrePostScripts(options.preScript);
      this.postScripts = engineUtils.loadPrePostScripts(options.postScript);
      this.postURLScripts = engineUtils.loadPrePostScripts(
        options.postURLScript
      );
      this.pageCompleteCheck = engineUtils.loadScript(
        options.pageCompleteCheck
      );
    } catch (e) {
      log.error(e.message);
      throw e;
    }
    this.options = options;
    this.storageManager = storageManager;
    this.engineDelegate = engineDelegate;
    this.scriptsByCategory = scriptsByCategory;
    this.asyncScriptsByCategory = asyncScriptsByCategory;
    this.screenshotManager = new ScreenshotManager(storageManager, options);
  }

  /**
   *  Run one iteration for one url. Here are the whole flow of what we
   * do for one URL per iteration.
   * @param {*} url - The URL that will be tested
   * @param {*} index - Which iteration it is
   */
  async run(navigationScript, index) {
    const options = this.options;
    const browser = new SeleniumRunner(
      this.storageManager.directory,
      this.options
    );
    this.video = new Video(this.storageManager, this.options, browser);
    const recordVideo = options.visualMetrics || options.video;
    const videos = [];
    const result = [];

    const extensionServer = new ExtensionServer(options);

    try {
      if (options.connectivity.variance) {
        await addConnectivity(options);
      }
      await browser.start();
      await this.engineDelegate.afterBrowserStart();

      // The data we push to all selenium scripts
      const context = {
        // TODO we want to have the URL here so we can use scripts to login
        // and then access the supplied URL
        options,
        result,
        log,
        storageManager: this.storageManager,
        taskData: {},
        index,
        selenium: {
          webdriver, // The Selenium WebDriver public API object https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index.html
          driver: browser.getDriver() // The instantiated version of the WebDriver https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
        }
      };

      const measure = new Measure(
        browser,
        index,
        this.pageCompleteCheck,
        result,
        this.engineDelegate,
        extensionServer,
        this.storageManager,
        videos,
        this.scriptsByCategory,
        this.asyncScriptsByCategory,
        this.postURLScripts,
        context,
        this.screenshotManager,
        options
      );

      const cdp = new ChromeDevToolsProtocol(
        this.engineDelegate,
        options.browser
      );
      const commands = {
        click: new Click(browser, this.pageCompleteCheck),
        addText: new AddText(browser),
        wait: new Wait(browser, this.pageCompleteCheck),
        measure: measure,
        navigate: measure._navigate.bind(measure),
        error: measure._error.bind(measure),
        js: new JavaScript(browser, this.pageCompleteCheck),
        switch: new Switch(browser),
        set: new Set(browser),
        cache: new Cache(browser, options.browser, this.extensionServer, cdp),
        meta: new Meta(),
        screenshot: new Screenshot(this.screenshotManager, browser, index),
        cdp,
        android: new Android(options)
      };

      const startURL =
        options.browser === 'safari'
          ? 'data:text/html;'
          : 'data:text/html;charset=utf-8,';
      if (options.spa) {
        await setResourceTimingBufferSize(startURL, browser.getDriver(), 1000);
      } else if (!options.processStartTime) {
        // Before we needed Chrome to open once to get correct values for Chrome HAR
        // but now Firefox first visual change is slower if we remove this ...
        await browser.getDriver().get(startURL);
      }

      // On slowish Android phones it takes some time for the
      // browser to get ready
      if (isAndroidConfigured(options)) {
        await delay(ANDROID_DELAY_TIME);
      }

      await delay(options.timeToSettle || 100);

      if (recordVideo && options.videoParams.debug) {
        await this.video.record(0, index);
      }

      for (const preScript of this.preScripts) {
        await preScript(context, commands);
      }

      if (options.preURL) {
        await preURL(browser, options);
      }

      try {
        await navigationScript(context, commands, this.postURLScripts);
      } catch (e) {
        commands.error(e.message);
        throw e;
      }
      if (commands.measure.areWeMeasuring === true) {
        // someone forgot to call stop();
        log.info(
          'It looks like you missed to call measure.stop() after calling measure.start(..) (without using a URL). Checkout https://www.sitespeed.io/documentation/sitespeed.io/scripting/#measurestartalias'
        );
        await commands.measure.stop();
      }
      await this.engineDelegate.onStopIteration(browser, index, result);

      for (const postScript of this.postScripts) {
        await postScript(context, commands);
      }

      if (recordVideo && options.videoParams.debug) {
        // Just use the first URL
        const url =
          result.length > 0 ? result[0].url : 'https://debug.sitespeed.io';
        await this.video.stop(url);
      }
      await browser.stop();

      if (options.visualMetrics && !options.videoParams.debug) {
        let i = 0;
        for (let myVideo of videos) {
          try {
            const videoMetrics = await myVideo.postProcessing(
              result[i].browserScripts.timings.pageTimings,
              result[i].browserScripts.pageinfo.visualElements
            );

            for (let progress of [
              'VisualProgress',
              'ContentfulSpeedIndexProgress',
              'PerceptualSpeedIndexProgress'
            ]) {
              if (videoMetrics.visualMetrics[progress]) {
                videoMetrics.visualMetrics[
                  progress
                ] = util.jsonifyVisualProgress(
                  videoMetrics.visualMetrics[progress]
                );
              }
            }
            result[i].videoRecordingStart = videoMetrics.videoRecordingStart;
            result[i].visualMetrics = videoMetrics.visualMetrics;
            i++;
          } catch (e) {
            // If one of the Visual Metrics runs fail, try the next one
            log.error('Visual Metrics failed to analyse the video', e);
            result[i].error.push(e.message);
          }
        }
      }

      await this.engineDelegate.afterCollect(index, result);

      if (commands.meta.description) {
        result.description = commands.meta.description;
      }
      if (commands.meta.title) {
        result.title = commands.meta.title;
      }
      return result;
    } catch (e) {
      log.error(e);
      if (recordVideo && options.videoParams.debug) {
        // Just use the first URL
        const url =
          result.length > 0 ? result[0].url : 'https://debug.sitespeed.io';
        await this.video.stop(url);
      }

      // In Docker on Desktop we can use a hardcore way to cleanup
      if (options.docker && recordVideo && !isAndroidConfigured(options)) {
        await stop('ffmpeg');
      }
      result.error = [e.name];
      return result;
    } finally {
      // Here we should also make sure FFMPEG is really killed/stopped
      // if something fails, we had bug reports where we miss it
      try {
        if (options.connectivity.variance) {
          await removeConnectivity(options);
        }
        await browser.stop();
      } catch (e) {
        // Most cases the browser been stopped already
      }
    }
  }
}

module.exports = Iteration;
<|endoftext|>


<commit_before>var RippleRestClient = require('ripple-rest-client');
var uuid = require('node-uuid');
var async = require('async');
var gateway = require(__dirname+'/../../');
var hotWallet = gateway.config.get('HOT_WALLET');

function OutgoingPayment(outgoingPaymentRecord) {
  this.record = outgoingPaymentRecord;
  this.rippleRestClient = new RippleRestClient({
    account: hotWallet.address,
    secret: hotWallet.secret
  });
}

OutgoingPayment.prototype = {

  processOutgoingPayment: function(callback) {
    var self = this;
    async.waterfall([
      function(next) {
        self._getRippleAddressRecord(next);
      },
      function(address, next) {
        self._buildPayment(address, next);
      },
      function(payment, next) {
        self._sendPayment(payment, next);
      },
      function(response, next) {
        self._markRecordAsSent(response, next);
      },
      function(response, next) {
        self._confirmSuccessfulPayment(response, next);
      }
    ], function(error) {
      if (error) {
        self._handleRippleRestFailure(error, function() {
          logger.error('payments:outgoing:failed', error, self.record.toJSON());
          callback(error, self.record);
        });
      } else {
        logger.info('payment:outgoing:succeeded', self.record.toJSON());
        callback(error, self.record);
      }
    });
  },

  _recordAcceptanceOrRejectionStatus: function(payment, callback) {
    var self = this;
    self.record.transaction_state = payment.result;
    self.record.transaction_hash = payment.hash;
    switch(self.record.transaction_state) {
    case 'tesSUCCESS':
      self.record.state = 'succeeded';
      break;
    default:
      self.record.state = 'failed';
    }
    self.record.save().complete(function(error, record){
      //depositCallbackJob.perform([self.record.id], console.log);
      callback(null, record);
    });
  },

  _confirmSuccessfulPayment: function(response, callback) {
    var statusUrl = response.status_url;
    var self = this;
    self.rippleRestClient.pollPaymentStatus(statusUrl, function(error, paymentStatus) {
      if (error) {
        return callback(error, null);
      }
      self._recordAcceptanceOrRejectionStatus(paymentStatus, function(error, record) {
        callback(null, record);
      });
    });
  },

  _markRecordAsSent: function(response, callback) {
    var self = this;
    self.record.state = 'sent';
    self.record.uid = response.client_resource_id;
    self.record.save().complete(function(){
      callback(null, response);
    });
  },

  _handleRippleRestFailure: function(error, callback) {
    var self = this;
    if (typeof error === 'string' && error.match('No paths found')){
      error = 'noPathFound';
    }
    if (error.match('No connection to rippled') ||
        error.match('try again') ||
        error.match('Cannot connect to rippled'))
    {
      error = 'retry';
    }

    switch(error) {
      case 'retry':
        self.record.state = 'outgoing';
        break;
      case 'noPathFound':
        self.record.transaction_state = 'tecPATH_DRY';
        self.record.state = 'failed';
        break;
      default:
        self.record.state = 'failed';
    }
    self.record.save().complete(function(){
      //depositCallbackJob.perform([self.record.id], console.log);
      callback();
    });
  },

  _getRippleAddressRecord: function(callback) {
    var self = this;
    gateway.data.rippleAddresses.read(self.record.to_address_id, callback);
  },

  _buildPayment: function(address, callback) {
    var self = this;
    self.rippleRestClient.buildPayment({
      amount: self.record.to_amount,
      currency: self.record.to_currency,
      issuer: gateway.config.get('COLD_WALLET'),
      account: hotWallet.address,
      recipient: address.address
    }, function(error, response) {
      if (error) {
        return callback(error.message, null);
      }
      if (response.success) {
        callback(null, response.payments[0]);
      } else {
        callback(response.message, null);
      }
    });
  },

  _sendPayment: function(payment, callback) {
    var self = this;
    payment.source_tag = self.record.from_address_id.toString();
    self.rippleRestClient.sendPayment({
      payment: payment,
      client_resource_id: uuid.v4(),
      secret: hotWallet.secret
    }, function(error, response){
      if (error) {
        callback(error, null);
      } else if (response.success){
        callback(null, response);
      } else {
        callback(response.message, null);
      }
    });
  }

};

module.exports = OutgoingPayment;

<commit_msg>[TASK] Add RippledConnectionError regex match in outgoing.js.<commit_after>var RippleRestClient = require('ripple-rest-client');
var uuid = require('node-uuid');
var async = require('async');
var gateway = require(__dirname+'/../../');
var hotWallet = gateway.config.get('HOT_WALLET');

function OutgoingPayment(outgoingPaymentRecord) {
  this.record = outgoingPaymentRecord;
  this.rippleRestClient = new RippleRestClient({
    account: hotWallet.address,
    secret: hotWallet.secret
  });
}

OutgoingPayment.prototype = {

  processOutgoingPayment: function(callback) {
    var self = this;
    async.waterfall([
      function(next) {
        self._getRippleAddressRecord(next);
      },
      function(address, next) {
        self._buildPayment(address, next);
      },
      function(payment, next) {
        self._sendPayment(payment, next);
      },
      function(response, next) {
        self._markRecordAsSent(response, next);
      },
      function(response, next) {
        self._confirmSuccessfulPayment(response, next);
      }
    ], function(error) {
      if (error) {
        self._handleRippleRestFailure(error, function() {
          logger.error('payments:outgoing:failed', error, self.record.toJSON());
          callback(error, self.record);
        });
      } else {
        logger.info('payment:outgoing:succeeded', self.record.toJSON());
        callback(error, self.record);
      }
    });
  },

  _recordAcceptanceOrRejectionStatus: function(payment, callback) {
    var self = this;
    self.record.transaction_state = payment.result;
    self.record.transaction_hash = payment.hash;
    switch(self.record.transaction_state) {
    case 'tesSUCCESS':
      self.record.state = 'succeeded';
      break;
    default:
      self.record.state = 'failed';
    }
    self.record.save().complete(function(error, record){
      //depositCallbackJob.perform([self.record.id], console.log);
      callback(null, record);
    });
  },

  _confirmSuccessfulPayment: function(response, callback) {
    var statusUrl = response.status_url;
    var self = this;
    self.rippleRestClient.pollPaymentStatus(statusUrl, function(error, paymentStatus) {
      if (error) {
        return callback(error, null);
      }
      self._recordAcceptanceOrRejectionStatus(paymentStatus, function(error, record) {
        callback(null, record);
      });
    });
  },

  _markRecordAsSent: function(response, callback) {
    var self = this;
    self.record.state = 'sent';
    self.record.uid = response.client_resource_id;
    self.record.save().complete(function(){
      callback(null, response);
    });
  },

  _handleRippleRestFailure: function(error, callback) {
    var self = this;
    if (typeof error === 'string' && error.match('No paths found')){
      error = 'noPathFound';
    }
    if (error.match('No connection to rippled') ||
        error.match('try again') ||
        error.match('Cannot connect to rippled') ||
        error.match('RippledConnectionError'))
    {
      error = 'retry';
    }
    switch(error) {
      case 'retry':
        self.record.state = 'outgoing';
        break;
      case 'noPathFound':
        self.record.transaction_state = 'tecPATH_DRY';
        self.record.state = 'failed';
        break;
      default:
        self.record.state = 'failed';
    }
    self.record.save().complete(function(){
      //depositCallbackJob.perform([self.record.id], console.log);
      callback();
    });
  },

  _getRippleAddressRecord: function(callback) {
    var self = this;
    gateway.data.rippleAddresses.read(self.record.to_address_id, callback);
  },

  _buildPayment: function(address, callback) {
    var self = this;
    self.rippleRestClient.buildPayment({
      amount: self.record.to_amount,
      currency: self.record.to_currency,
      issuer: gateway.config.get('COLD_WALLET'),
      account: hotWallet.address,
      recipient: address.address
    }, function(error, response) {
      if (error) {
        return callback(error.message, null);
      }
      if (response.success) {
        callback(null, response.payments[0]);
      } else {
        callback(response.message, null);
      }
    });
  },

  _sendPayment: function(payment, callback) {
    var self = this;
    payment.source_tag = self.record.from_address_id.toString();
    self.rippleRestClient.sendPayment({
      payment: payment,
      client_resource_id: uuid.v4(),
      secret: hotWallet.secret
    }, function(error, response){
      if (error) {
        callback(error, null);
      } else if (response.success){
        callback(null, response);
      } else {
        callback(response.message, null);
      }
    });
  }

};

module.exports = OutgoingPayment;

<|endoftext|>


<commit_before>// This plugin adds simple queries to nStore
module.exports = function QueryPlugin() {
  return {

    // Get all the records as either a single object or a document/key stream
    all: function all(callback) {
      return this.find(undefined, callback);
    },

    // Find records using a query
    find: function find(query, callback) {
      var filter = compileQuery(query);
      var stream;
      var results = {};
      if (!callback) {
        stream = new EventEmitter();
      }
      var keys = Object.keys(this.index);
      var counter = keys.length;
	  if(keys.length === 0) {
		  if (stream) stream.emit('end');
		  if (callback) callback(null, results);
	  } else {
		  keys.forEach(function (key) {
			  this.get(key, function (err, doc, key) {
				  if (err) {
					  if (stream) stream.emit('error', err);
					  if (callback) callback(err);
					  return;
				  }
				  if (filter(doc, key)) {
					  if (stream) stream.emit('document', doc, key);
					  if (callback) results[key] = doc;
				  };
				  counter--;
				  if (counter === 0) {
					  if (stream) stream.emit('end');
					  if (callback) callback(null, results);
				  }
			  });
		  }, this);
	  }
    }

  };
}

function compileQuery(query) {
  if (typeof query === 'function') {
    return query;
  }
  var exp;
  if (Array.isArray(query)) {
     exp = query.map(compileSection).join(" || ") || "true";
  } else if (typeof query === 'object') {
    exp = compileSection(query);
  } else {
    exp = "true";
  }
  var filter = new Function("doc", "key", "return " + exp + ";");
  return filter;
}

function compileSection(obj) {
  var parts = Object.keys(obj).map(function (key) {
    var p = key.indexOf(" ");
    var name = key;
    var operator = "=";
    if (p > 0) {
      name = key.substr(0, p);
      operator = key.substr(p + 1);
    }
    if (operator === '=') operator = '===';
    if (operator === '<>') operator = '!==';
    if (name !== "key") {
      name = "doc." + name;
    }
    return "(" + name + " " + operator + " " + JSON.stringify(obj[key]) + ")";
  })
  return "(" + parts.join(" && ") + ")";
}
<commit_msg>wrapping empty keys handling in process.nextTick<commit_after>// This plugin adds simple queries to nStore
module.exports = function QueryPlugin() {
  return {

    // Get all the records as either a single object or a document/key stream
    all: function all(callback) {
      return this.find(undefined, callback);
    },

    // Find records using a query
    find: function find(query, callback) {
      var filter = compileQuery(query);
      var stream;
      var results = {};
      if (!callback) {
        stream = new EventEmitter();
      }
      var keys = Object.keys(this.index);
      var counter = keys.length;
	  if(keys.length === 0) {
		  process.nextTick(function() {
			  if (stream) stream.emit('end');
			  if (callback) callback(null, results);
		  });
	  } else {
		  keys.forEach(function (key) {
			  this.get(key, function (err, doc, key) {
				  if (err) {
					  if (stream) stream.emit('error', err);
					  if (callback) callback(err);
					  return;
				  }
				  if (filter(doc, key)) {
					  if (stream) stream.emit('document', doc, key);
					  if (callback) results[key] = doc;
				  };
				  counter--;
				  if (counter === 0) {
					  if (stream) stream.emit('end');
					  if (callback) callback(null, results);
				  }
			  });
		  }, this);
	  }
    }

  };
}

function compileQuery(query) {
  if (typeof query === 'function') {
    return query;
  }
  var exp;
  if (Array.isArray(query)) {
     exp = query.map(compileSection).join(" || ") || "true";
  } else if (typeof query === 'object') {
    exp = compileSection(query);
  } else {
    exp = "true";
  }
  var filter = new Function("doc", "key", "return " + exp + ";");
  return filter;
}

function compileSection(obj) {
  var parts = Object.keys(obj).map(function (key) {
    var p = key.indexOf(" ");
    var name = key;
    var operator = "=";
    if (p > 0) {
      name = key.substr(0, p);
      operator = key.substr(p + 1);
    }
    if (operator === '=') operator = '===';
    if (operator === '<>') operator = '!==';
    if (name !== "key") {
      name = "doc." + name;
    }
    return "(" + name + " " + operator + " " + JSON.stringify(obj[key]) + ")";
  })
  return "(" + parts.join(" && ") + ")";
}
<|endoftext|>


<commit_before>/*
 * node-ostatus - An implementation of OStatus for node.js
 *
 * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
 *
 * 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.
*/

var Sys  = require('sys'),
	Url  = require('url'),
	Flow = require('flow'),
	Util = require('util'),
	Http = require('./http.js'),
    Xml  = require('o3-xml');
	Path = require('path'),
	Mu 	 = require('mu');

function render(updates, profile, callback) {
	Mu.compile('updates.xml.mu', function (err, parsed) {
	    if (err) return callback(err);
	    var host = global.config.host;
	    var buffer = '';
	    var context = profile;
	    context.updates = updates;
	    context.host = host;
	    if (updates && updates.length > 0) context.updated = updates[0].updated;
	    Mu.render(parsed,context)
	      .on('data', function (c) { buffer += c.toString(); })
	      .on('end', function () {callback(null, buffer);})
	      .on('error', function(err) {callback(err, buffer);});
	  });
}

function getFeed(url, callback) {
	console.log("Requesting atom feed at " + url);
	Flow.exec(
			function() {
				Http.get(url,this);
			},
			function(err,response,body) {
		    	if (err) return callback(err);
		    	if (response.statusCode != 200) return callback(new Error("Http request returned status " + response.statusCode));
		    	parseFeed(body, this);
			},
			function(err, activities) {
				if (err) return callback(err);
				activities.url = url;
				callback(null, activities);
			}
	);
}

function parseFeed(body, callback) {
	try {
		var doc = Xml.parseFromString(body);
		var childNodes = doc.documentElement.childNodes;
		var feed = {};
		feed.items = [];
		feed.links = [];
		var items = [];
		for ( var i = 0; i < childNodes.length; i++) {
			var name = childNodes[i].nodeName;
			if (name == "entry") {
				var entry = _readEntry(childNodes[i]);
				if (entry)
					feed.items.push(entry);
			} else if (name == "link") {
				var link = _readLink(childNodes[i]);
				if (link)
					feed.links.push(link);
			} else if (name == "title") {
				feed.title = childNodes[i].nodeValue;
			} else if (name == "subtitle") {
				feed.subtitle = childNodes[i].nodeValue;
			} else if (name == "updated") {
				feed.updated = childNodes[i].nodeValue;
			}
		}
		callback(null, feed);
	} catch (exception) {
		callback(exception);
	}
}

var _elements = {
		"id": _parseId,
		"title": _parseTitle,
		"object-type": _parseType,
		"verb" : _parseVerb,
		"content": _parseContent,
		"updated": _parseUpdated
};

function _readLink(node) {
	var link = _readAttributes(node, ["href", "rel", "type"]);
	return link;
}

function _readEntry(node) {
	var childNodes = node.childNodes;
	var entry = {};
	for (var i=0; i<childNodes.length; i++) {
		var name = childNodes[i].nodeName;
		if (name in _elements) {
			_elements[name](childNodes[i], entry);
		}
	}
	return entry;
}

function _readAttributes(node, attributes) {
	var result = {};
	for(i= 0; i<attributes.length; i++) {
		var name = attributes[i];
		if (attribute = node.attributes.getNamedItem(name)) result[name] = attribute.value;
	}
	return result;
}

function _parseId(node, entry) {
	if (node != null) {
		entry["id"] = node.nodeValue;
	}
	return entry;
}

function _parseTitle(node, entry) {
	if (node != null) {
		entry["title"] = node.nodeValue;
	}
	return entry;
}

function _parseVerb(node, entry) {
	if (node != null) {
		var verb = node.nodeValue;
		entry["verb"] = verb.substr(verb.lastIndexOf("/") + 1);
	}
	return entry;
}

function _parseType(node, entry) {
	if (node != null) {
		var verb = node.nodeValue;
		entry["type"] = verb.substr(verb.lastIndexOf("/") + 1);
	}
	return entry;
}

function _parseContent(node, entry) {
	if (node != null) {
		var body = node.nodeValue;
		entry["body"] = body;
	}
	return entry;
}

function _parseUpdated(node, entry) {
	if (node != null) {
		var updated = node.nodeValue;
		// TODO Convert to W3CDTF
		entry["postedTime"] = updated;
	}
	return entry;
}

exports.getFeed = getFeed;
exports.parseFeed = parseFeed;
exports.render = render;<commit_msg>Removed a dependency on a global config variable.<commit_after>/*
 * node-ostatus - An implementation of OStatus for node.js
 *
 * Copyright (C) 2010 Laurent Eschenauer <laurent@eschenauer.be>
 *
 * 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.
*/

var Sys  = require('sys'),
	Url  = require('url'),
	Flow = require('flow'),
	Util = require('util'),
	Http = require('./http.js'),
    Xml  = require('o3-xml');
	Path = require('path'),
	Mu 	 = require('mu');

function render(host, updates, profile, callback) {
	Mu.compile('updates.xml.mu', function (err, parsed) {
	    if (err) return callback(err);
	    var buffer = '';
	    var context = profile;
	    context.updates = updates;
	    context.host = host;
	    if (updates && updates.length > 0) context.updated = updates[0].updated;
	    Mu.render(parsed,context)
	      .on('data', function (c) { buffer += c.toString(); })
	      .on('end', function () {callback(null, buffer);})
	      .on('error', function(err) {callback(err, buffer);});
	  });
}

function getFeed(url, callback) {
	console.log("Requesting atom feed at " + url);
	Flow.exec(
			function() {
				Http.get(url,this);
			},
			function(err,response,body) {
		    	if (err) return callback(err);
		    	if (response.statusCode != 200) return callback(new Error("Http request returned status " + response.statusCode));
		    	parseFeed(body, this);
			},
			function(err, activities) {
				if (err) return callback(err);
				activities.url = url;
				callback(null, activities);
			}
	);
}

function parseFeed(body, callback) {
	try {
		var doc = Xml.parseFromString(body);
		var childNodes = doc.documentElement.childNodes;
		var feed = {};
		feed.items = [];
		feed.links = [];
		var items = [];
		for ( var i = 0; i < childNodes.length; i++) {
			var name = childNodes[i].nodeName;
			if (name == "entry") {
				var entry = _readEntry(childNodes[i]);
				if (entry)
					feed.items.push(entry);
			} else if (name == "link") {
				var link = _readLink(childNodes[i]);
				if (link)
					feed.links.push(link);
			} else if (name == "title") {
				feed.title = childNodes[i].nodeValue;
			} else if (name == "subtitle") {
				feed.subtitle = childNodes[i].nodeValue;
			} else if (name == "updated") {
				feed.updated = childNodes[i].nodeValue;
			}
		}
		callback(null, feed);
	} catch (exception) {
		callback(exception);
	}
}

var _elements = {
		"id": _parseId,
		"title": _parseTitle,
		"object-type": _parseType,
		"verb" : _parseVerb,
		"content": _parseContent,
		"updated": _parseUpdated
};

function _readLink(node) {
	var link = _readAttributes(node, ["href", "rel", "type"]);
	return link;
}

function _readEntry(node) {
	var childNodes = node.childNodes;
	var entry = {};
	for (var i=0; i<childNodes.length; i++) {
		var name = childNodes[i].nodeName;
		if (name in _elements) {
			_elements[name](childNodes[i], entry);
		}
	}
	return entry;
}

function _readAttributes(node, attributes) {
	var result = {};
	for(i= 0; i<attributes.length; i++) {
		var name = attributes[i];
		if (attribute = node.attributes.getNamedItem(name)) result[name] = attribute.value;
	}
	return result;
}

function _parseId(node, entry) {
	if (node != null) {
		entry["id"] = node.nodeValue;
	}
	return entry;
}

function _parseTitle(node, entry) {
	if (node != null) {
		entry["title"] = node.nodeValue;
	}
	return entry;
}

function _parseVerb(node, entry) {
	if (node != null) {
		var verb = node.nodeValue;
		entry["verb"] = verb.substr(verb.lastIndexOf("/") + 1);
	}
	return entry;
}

function _parseType(node, entry) {
	if (node != null) {
		var verb = node.nodeValue;
		entry["type"] = verb.substr(verb.lastIndexOf("/") + 1);
	}
	return entry;
}

function _parseContent(node, entry) {
	if (node != null) {
		var body = node.nodeValue;
		entry["body"] = body;
	}
	return entry;
}

function _parseUpdated(node, entry) {
	if (node != null) {
		var updated = node.nodeValue;
		// TODO Convert to W3CDTF
		entry["postedTime"] = updated;
	}
	return entry;
}

exports.getFeed = getFeed;
exports.parseFeed = parseFeed;
exports.render = render;<|endoftext|>


<commit_before>var dictionary = {
	"address": "port o' call",
	"admin": "helm",
	"am": "be",
	"an": "a",
	"and": "n'",
	"are": "be",
	"award": "prize",
	"bathroom" : "head",
	"beer": "grog",
	"before": "afore",
	"belief": "creed",
	"between": "betwixt",
	"big": "vast",
	"bill": "coin",
	"bills": "coins",
	"boss": "admiral",
	"bourbon": "rum",
	"box": "barrel",
	"boy": "lad",
	"buddy": "mate",
	"calling": "callin'",
	"cash": "coin",
	"cat": "parrot",
	"cheat": "hornswaggle",
	"comes": "hails",
	"comments": "yer words",
	"cool" : "shipshape",
	"country": "land",
	"dashboard": "shanty",
	"dead": "in Davy Jones's Locker",
	"disconnect": "keelhaul",
	"do": "d'",
	"dog": "parrot",
	"dollar" : "doubloon",
	"dollar": "doubloon",
	"dollars" : "doubloons",
	"dude": "pirate",
	"employee": "crew",
	"everyone" : "all hands",
	"eye": "eye-patch",
	"family": "kin",
	"fee": "debt",
	"female": "wench",
	"females": "wenches",
	"for": "fer",
	"food": "grub",
	"friend": "mate",
	"friend": "shipmate",
	"friends": "crew",
	"fuck" : "shiver me timbers",
	"gin": "rum",
	"girl": "lass",
	"girls": "lassies",
	"go": "sail",
	"good": "jolly good",
	"grave": "Davy Jones's Locker",
	"group": "maties",
	"gun" : "bluderbuss",
	"hand": "hook",
	"haha": "yo ho",
	"hahaha": "yo ho ho",
	"hahahaha": "yo ho ho ho",
	"happy": "grog-filled",
	"hello" : "ahoy",
	"hello": "ahoy",
	"hey" : "ahoy",
	"hey": "ahoy",
	"hi" : "ahoy",
	"hotel": "fleebag inn",
	"i": "me",
	"i'm": "i be",
	"internet": "series o' tubes",
	"invalid": "sunk",
	"is": "be",
	"island": "isle",
	"isn't": "be not",
	"it's": "'tis",
	"jail": "brig",
	"kill": "keelhaul",
	"king": "king",
	"ladies": "lasses",
	"lady": "lass",
	"lawyer": "scurvy land lubber",
	"left" : "port",
	"leg": "peg",
	"logout": "walk the plank",
	"lol" : "blimey",
	"male": "pirate",
	"man": "pirate",
	"manager": "admiral",
	"money": "doubloons",
	"month": "moon",
	"my" : "me",
	"my": "me",
	"never": "nary",
	"no" : "nay",
	"not": "nay",
	"of": "o'",
	"old": "barnacle-covered",
	"omg" : "shiver me timbers",
	"over": "o'er",
	"page": "parchment",
	"people": "scallywags",
	"person": "scurvy dog",
	"posted": "tacked to the yardarm",
	"president": "king",
	"prison": "brig",
	"quickly": "smartly",
	"really": "verily",
	"relative": "kin",
	"relatives": "kin",
	"religion": "creed",
	"restaurant": "galley",
	"right" : "starboard",
	"rotf" : "rollin' on the decks",
	"say": "cry",
	"seconds": "ticks o' tha clock",
	"shipping": "cargo",
	"shit" : "shiver me timbers",
	"snack": "grub",
	"small": "puny",
	"soldier": "sailor",
	"sorry": "yarr",
	"spouse": "ball 'n' chain",
	"state": "land",
	"supervisor": "Cap'n",
	"that's": "that be",
	"the": "thar",
	"theif": "swoggler",
	"them": "'em",
	"to": "t'",
	"together": "t'gether",
	"treasure": "booty",
	"vodka": "rum",
	"was": "be",
	"water": "grog",
	"we": "our jolly crew",
	"we're": "we's",
	"whiskey": "rum",
	"whisky": "rum",
	"wine": "grog",
	"with": "wit'",
	"woman": "comely wench",
	"women": "beauties",
	"work": "duty",
	"yah": "aye",
	"yeah": "aye",
	"yes" : "aye",
	"you" : "ye",
	"you've": "ye",
	"your": "yer",
	"yup" : "aye"
};

function translateWord(word) {
	var pirateWord = dictionary[word.toLowerCase()];
	if (pirateWord === undefined) return word;
	else return applyCase(word, pirateWord);
}

// Take the case from wordA and apply it to wordB
function applyCase(wordA, wordB) {
	// Exception to avoid words like "I" being converted to "ME"
	if (wordA.length === 1 && wordB.length !== 1) return wordB;
	// Uppercase
	if (wordA === wordA.toUpperCase()) return wordB.toUpperCase();
	// Lowercase
	if (wordA === wordA.toLowerCase()) return wordB.toLowerCase();
	// Capitialized
	var firstChar = wordA.slice(0, 1);
	var otherChars = wordA.slice(1);
	if (firstChar === firstChar.toUpperCase() && otherChars === otherChars.toLowerCase()) {
		return wordB.slice(0, 1).toUpperCase() + wordB.slice(1).toLowerCase();
	}
	// Other cases
	return wordB;
};

function isLetter(character) {
	if (character.search(/[a-zA-Z'-]/) === -1) return false;
	return true;
}

module.exports.dictionary = dictionary;

module.exports.translate = function (text) {
	var translatedText = "";

	// Loop through the text, one character at a time.
	var word = "";
	for (var i = 0; i < text.length; i += 1) {
		var character = text[i];
		// If the char is a letter, then we are in the middle of a word, so we
		// should accumulate the letter into the word variable
		if (isLetter(character)) {
			word += character;
		}
		// If the char is not a letter, then we hit the end of a word, so we
		// should translate the current word and add it to the translation
		else {
			if (word != "") {
				// If we've just finished a word, translate it
				var pirateWord = translateWord(word);
				translatedText += pirateWord;
				word = "";
			}
			translatedText += character; // Add the non-letter character
		}
	}

	// If we ended the loop before translating a word, then translate the final
	// word and add it to the translation.
	if (word !== "") translatedText += translateWord(word);

	return translatedText;
};<commit_msg>Update dictionary<commit_after>var dictionary = {
	"address": "port o' call",
	"admin": "helm",
	"am": "be",
	"an": "a",
	"and": "n'",
	"are": "be",
	"award": "prize",
	"bathroom": "head",
	"beer": "grog",
	"before": "afore",
	"belief": "creed",
	"between": "betwixt",
	"big": "vast",
	"bill": "coin",
	"bills": "coins",
	"boss": "admiral",
	"bourbon": "rum",
	"box": "barrel",
	"boy": "lad",
	"buddy": "mate",
	"business": "company",
	"businesses": "companies",
	"calling": "callin'",
	"canada": "Great North",
	"cash": "coin",
	"cat": "parrot",
	"cheat": "hornswaggle",
	"comes": "hails",
	"comments": "yer words",
	"cool": "shipshape",
	"country": "land",
	"dashboard": "shanty",
	"dead": "in Davy Jones's Locker",
	"disconnect": "keelhaul",
	"do": "d'",
	"dog": "parrot",
	"dollar": "doubloon",
	"dollars": "doubloons",
	"dude": "pirate",
	"employee": "crew",
	"everyone": "all hands",
	"eye": "eye-patch",
	"family": "kin",
	"fee": "debt",
	"female": "wench",
	"females": "wenches",
	"food": "grub",
	"for": "fer",
	"friend": "mate",
	"friend": "shipmate",
	"friends": "crew",
	"fuck": "shiver me timbers",
	"gin": "rum",
	"girl": "lass",
	"girls": "lassies",
	"go": "sail",
	"good": "jolly good",
	"grave": "Davy Jones's Locker",
	"group": "maties",
	"gun": "bluderbuss",
	"haha": "yo ho",
	"hahaha": "yo ho ho",
	"hahahaha": "yo ho ho ho",
	"hand": "hook",
	"happy": "grog-filled",
	"hello": "ahoy",
	"hey": "ahoy",
	"hi": "ahoy",
	"hotel": "fleebag inn",
	"i": "me",
	"i'm": "i be",
	"internet": "series o' tubes",
	"invalid": "sunk",
	"is": "be",
	"island": "isle",
	"isn't": "be not",
	"it's": "'tis",
	"jail": "brig",
	"kill": "keelhaul",
	"king": "king",
	"ladies": "lasses",
	"lady": "lass",
	"lawyer": "scurvy land lubber",
	"left": "port",
	"leg": "peg",
	"logout": "walk the plank",
	"lol": "blimey",
	"male": "pirate",
	"man": "pirate",
	"manager": "admiral",
	"money": "doubloons",
	"month": "moon",
	"my": "me",
	"never": "nary",
	"no": "nay",
	"not": "nay",
	"of": "o'",
	"old": "barnacle-covered",
	"omg": "shiver me timbers",
	"over": "o'er",
	"page": "parchment",
	"people": "scallywags",
	"person": "scurvy dog",
	"posted": "tacked to the yardarm",
	"president": "king",
	"prison": "brig",
	"quickly": "smartly",
	"really": "verily",
	"relative": "kin",
	"relatives": "kin",
	"religion": "creed",
	"restaurant": "galley",
	"right": "starboard",
	"ring": "ring",
	"role": "job",
	"rotf": "rollin' on the decks",
	"say": "cry",
	"seconds": "ticks o' tha clock",
	"shipping": "cargo",
	"shit": "shiver me timbers",
	"small": "puny",
	"snack": "grub",
	"soldier": "sailor",
	"sorry": "yarr",
	"spouse": "ball 'n' chain",
	"state": "land",
	"supervisor": "Cap'n",
	"that's": "that be",
	"the": "thar",
	"theif": "swoggler",
	"them": "'em",
	"this": "dis",
	"to": "t'",
	"together": "t'gether",
	"treasure": "booty",
	"vodka": "rum",
	"was": "be",
	"water": "grog",
	"we": "our jolly crew",
	"we're": "we's",
	"whiskey": "rum",
	"whisky": "rum",
	"wine": "grog",
	"with": "wit'",
	"woman": "comely wench",
	"woman": "wench",
	"work": "duty",
	"yah": "aye",
	"yeah": "aye",
	"yes": "aye",
	"you": "ye",
	"you're": "you be",
	"you've": "ye",
	"your": "yer",
	"yup": "aye"
};

function translateWord(word) {
	var pirateWord = dictionary[word.toLowerCase()];
	if (pirateWord === undefined) return word;
	else return applyCase(word, pirateWord);
}

// Take the case from wordA and apply it to wordB
function applyCase(wordA, wordB) {
	// Exception to avoid words like "I" being converted to "ME"
	if (wordA.length === 1 && wordB.length !== 1) return wordB;
	// Uppercase
	if (wordA === wordA.toUpperCase()) return wordB.toUpperCase();
	// Lowercase
	if (wordA === wordA.toLowerCase()) return wordB.toLowerCase();
	// Capitialized
	var firstChar = wordA.slice(0, 1);
	var otherChars = wordA.slice(1);
	if (firstChar === firstChar.toUpperCase() && otherChars === otherChars.toLowerCase()) {
		return wordB.slice(0, 1).toUpperCase() + wordB.slice(1).toLowerCase();
	}
	// Other cases
	return wordB;
};

function isLetter(character) {
	if (character.search(/[a-zA-Z'-]/) === -1) return false;
	return true;
}

module.exports.dictionary = dictionary;

module.exports.translate = function (text) {
	var translatedText = "";

	// Loop through the text, one character at a time.
	var word = "";
	for (var i = 0; i < text.length; i += 1) {
		var character = text[i];
		// If the char is a letter, then we are in the middle of a word, so we
		// should accumulate the letter into the word variable
		if (isLetter(character)) {
			word += character;
		}
		// If the char is not a letter, then we hit the end of a word, so we
		// should translate the current word and add it to the translation
		else {
			if (word != "") {
				// If we've just finished a word, translate it
				var pirateWord = translateWord(word);
				translatedText += pirateWord;
				word = "";
			}
			translatedText += character; // Add the non-letter character
		}
	}

	// If we ended the loop before translating a word, then translate the final
	// word and add it to the translation.
	if (word !== "") translatedText += translateWord(word);

	return translatedText;
};
<|endoftext|>