<commit_before>  var Event = {};
  Event.on = function(evt, callback) {
      var cb = this._evt_callbacks = this._evt_callbacks || {};
      var l = cb[evt] || (cb[evt] = []);
      l.push(callback);
      return this;
  };

  Event.trigger = function(evt) {
      var c = this._evt_callbacks && this._evt_callbacks[evt];
      for(var i = 0; c && i < c.length; ++i) {
          c[i].apply(this, Array.prototype.slice.call(arguments, 1));
      }
      return this;
  };

  Event.fire = Event.trigger;

  Event.off = function (evt, callback) {
      var c = this._evt_callbacks && this._evt_callbacks[evt];
      if (c && !callback) {
        delete this._evt_callbacks[evt];
        return this;
     }
     var remove = [];
     for(var i = 0; c && i < c.length; ++i) {
       if(c[i] === callback) remove.push(i);
     }
     while((i = remove.pop()) !== undefined) c.splice(i, 1);
    return this;
  };

  Event.callbacks = function(evt) {
    return (this._evt_callbacks && this._evt_callbacks[evt]) || [];
  };

  function extend() {
      var objs = arguments;
      var a = objs[0];
      for (var i = 1; i < objs.length; ++i) {
          var b = objs[i];
          for (var k in b) {
              a[k] = b[k];
          }
      }
      return a;
  }

  function clone(a) {
    return extend({}, a);
  }

  function isFunction(f) {
    return typeof f == 'function' || false;
  }

  function isArray(value) {
      return value && typeof value == 'object' && Object.prototype.toString.call(value) == '[object Array]';
  }

  // types
  var types = {
    Uint8Array: typeof(global['Uint8Array']) !== 'undefined' ? global.Uint8Array : Array,
    Uint8ClampedArray: typeof(global['Uint8ClampedArray']) !== 'undefined' ? global.Uint8ClampedArray: Array,
    Uint32Array: typeof(global['Uint32Array']) !== 'undefined' ? global.Uint32Array : Array,
    Int16Array: typeof(global['Int16Array']) !== 'undefined' ? global.Int16Array : Array,
    Int32Array: typeof(global['Int32Array']) !== 'undefined' ? global.Int32Array: Array
  };

  function isBrowserSupported() {
    return !!document.createElement('canvas');
  }

  function userAgent() {
      return typeof navigator !== 'undefined' ? navigator.userAgent : '';
  }

  var flags = {
    sprites_to_images: userAgent().indexOf('Safari') === -1
  };

module.exports = {
    Event: Event,
    extend: extend,
    clone: clone,
    isFunction: isFunction,
    isArray: isArray,
    types: types,
    isBrowserSupported: isBrowserSupported,
    flags: flags
};
<commit_msg>doesn't add flag if firefox<commit_after>  var Event = {};
  Event.on = function(evt, callback) {
      var cb = this._evt_callbacks = this._evt_callbacks || {};
      var l = cb[evt] || (cb[evt] = []);
      l.push(callback);
      return this;
  };

  Event.trigger = function(evt) {
      var c = this._evt_callbacks && this._evt_callbacks[evt];
      for(var i = 0; c && i < c.length; ++i) {
          c[i].apply(this, Array.prototype.slice.call(arguments, 1));
      }
      return this;
  };

  Event.fire = Event.trigger;

  Event.off = function (evt, callback) {
      var c = this._evt_callbacks && this._evt_callbacks[evt];
      if (c && !callback) {
        delete this._evt_callbacks[evt];
        return this;
     }
     var remove = [];
     for(var i = 0; c && i < c.length; ++i) {
       if(c[i] === callback) remove.push(i);
     }
     while((i = remove.pop()) !== undefined) c.splice(i, 1);
    return this;
  };

  Event.callbacks = function(evt) {
    return (this._evt_callbacks && this._evt_callbacks[evt]) || [];
  };

  function extend() {
      var objs = arguments;
      var a = objs[0];
      for (var i = 1; i < objs.length; ++i) {
          var b = objs[i];
          for (var k in b) {
              a[k] = b[k];
          }
      }
      return a;
  }

  function clone(a) {
    return extend({}, a);
  }

  function isFunction(f) {
    return typeof f == 'function' || false;
  }

  function isArray(value) {
      return value && typeof value == 'object' && Object.prototype.toString.call(value) == '[object Array]';
  }

  // types
  var types = {
    Uint8Array: typeof(global['Uint8Array']) !== 'undefined' ? global.Uint8Array : Array,
    Uint8ClampedArray: typeof(global['Uint8ClampedArray']) !== 'undefined' ? global.Uint8ClampedArray: Array,
    Uint32Array: typeof(global['Uint32Array']) !== 'undefined' ? global.Uint32Array : Array,
    Int16Array: typeof(global['Int16Array']) !== 'undefined' ? global.Int16Array : Array,
    Int32Array: typeof(global['Int32Array']) !== 'undefined' ? global.Int32Array: Array
  };

  function isBrowserSupported() {
    return !!document.createElement('canvas');
  }

  function userAgent() {
      return typeof navigator !== 'undefined' ? navigator.userAgent : '';
  }

  var flags = {
    sprites_to_images: userAgent().indexOf('Safari') === -1 && userAgent().indexOf('Firefox') === -1
  };

module.exports = {
    Event: Event,
    extend: extend,
    clone: clone,
    isFunction: isFunction,
    isArray: isArray,
    types: types,
    isBrowserSupported: isBrowserSupported,
    flags: flags
};
<|endoftext|>


<commit_before>'use strict';

var Utils = require('./utils');

/**
 * The transaction object is used to identify a running transaction. It is created by calling `Sequelize.transaction()`.
 *
 * To run a query under a transaction, you should pass the transaction in the options object.
 * @class Transaction
 */
var Transaction = module.exports = function(sequelize, options) {
  this.sequelize = sequelize;
  this.savepoints = [];
  this.options = Utils._.extend({
    autocommit: true,
    isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
  }, options || {});
  this.id = this.options.transaction ? this.options.transaction.id : Utils.generateUUID();

  if (this.options.transaction) {
    this.id = this.options.transaction.id;
    this.options.transaction.savepoints.push(this);
    this.name = this.id + '-savepoint-' + this.options.transaction.savepoints.length;
    this.parent = this.options.transaction;
  } else {
    this.id = this.name = Utils.generateUUID();
  }
};

/**
 * The possible isolations levels to use when starting a transaction
 *
 * ```js
 * {
 *   READ_UNCOMMITTED: "READ UNCOMMITTED",
 *   READ_COMMITTED: "READ COMMITTED",
 *   REPEATABLE_READ: "REPEATABLE READ",
 *   SERIALIZABLE: "SERIALIZABLE"
 * }
 * ```
 *
 * @property ISOLATION_LEVELS
 */
Transaction.ISOLATION_LEVELS = {
  READ_UNCOMMITTED: 'READ UNCOMMITTED',
  READ_COMMITTED: 'READ COMMITTED',
  REPEATABLE_READ: 'REPEATABLE READ',
  SERIALIZABLE: 'SERIALIZABLE'
};

/**
 * Possible options for row locking. Used in conjuction with `find` calls:
 *
 * ```js
 * t1 // is a transaction
 * Model.findAll({
 *   where: ...
 * }, {
 *   transaction: t1,
 *   lock: t1.LOCK.UPDATE,
 *   lock: t1.LOCK.SHARE,
 *   lock: t1.LOCK.KEY_SHARE, // Postgres 9.3+ only
 *   lock: t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only
 * });
 * ```
 *
 * Postgres also supports specific locks while eager loading by using OF.
 *
 * ```js
 * UserModel.findAll({
 *   where: ...,
 *   include: [TaskModel, ...]
 * }, {
 *   transaction: t1,
 *   lock: {
 *     level: t1.LOCK...,
 *     of: UserModel
 *   }
 * });
 * ```
 * UserModel will be locked but TaskModel won't!
 *
 * @property LOCK
 */
Transaction.LOCK = Transaction.prototype.LOCK = {
  UPDATE: 'UPDATE',
  SHARE: 'SHARE',
  KEY_SHARE: 'KEY SHARE',
  NO_KEY_UPDATE: 'NO KEY UPDATE'
};

/**
 * Commit the transaction
 *
 * @return {this}
 */
Transaction.prototype.commit = function() {
  var self = this;

  return this
    .sequelize
    .getQueryInterface()
    .commitTransaction(this, this.options)
    .finally(function() {
      self.finished = 'commit';
      if (!self.options.transaction) {
        self.cleanup();
      }
    });
};

/**
 * Rollback (abort) the transaction
 *
 * @return {this}
 */
Transaction.prototype.rollback = function() {
  var self = this;

  return this
    .sequelize
    .getQueryInterface()
    .rollbackTransaction(this, this.options)
    .finally(function() {
      self.finished = 'rollback';
      if (!self.options.transaction) {
        self.cleanup();
      }
    });
};

Transaction.prototype.prepareEnvironment = function() {
  var self = this;

  return Utils.Promise.resolve(
    self.options.transaction ? self.options.transaction.connection : self.sequelize.connectionManager.getConnection({ uuid: self.id })
  ).then(function (connection) {
    self.connection = connection;
    self.connection.uuid = self.id;

    if (self.sequelize.constructor.cls) {
      self.sequelize.constructor.cls.set('transaction', self);
    }
  }).then(function () {
    return self.begin();
  }).then(function () {
    return self.setIsolationLevel();
  }).then(function () {
    return self.setAutocommit();
  });
};

Transaction.prototype.begin = function() {
  return this
    .sequelize
    .getQueryInterface()
    .startTransaction(this, this.options);
};

Transaction.prototype.setAutocommit = function() {
  return this
    .sequelize
    .getQueryInterface()
    .setAutocommit(this, this.options.autocommit, this.options);
};

Transaction.prototype.setIsolationLevel = function() {
  return this
    .sequelize
    .getQueryInterface()
    .setIsolationLevel(this, this.options.isolationLevel, this.options);
};

Transaction.prototype.cleanup = function() {
  var cls = this.sequelize.constructor.cls;

  this.connection.uuid = undefined;

  if (cls) {
    if (cls.get('transaction') === this) {
      cls.set('transaction', null);
    }
  }

  return this.sequelize.connectionManager.releaseConnection(this.connection);
};
<commit_msg>Update transaction lock docs for version 3.0<commit_after>'use strict';

var Utils = require('./utils');

/**
 * The transaction object is used to identify a running transaction. It is created by calling `Sequelize.transaction()`.
 *
 * To run a query under a transaction, you should pass the transaction in the options object.
 * @class Transaction
 */
var Transaction = module.exports = function(sequelize, options) {
  this.sequelize = sequelize;
  this.savepoints = [];
  this.options = Utils._.extend({
    autocommit: true,
    isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ
  }, options || {});
  this.id = this.options.transaction ? this.options.transaction.id : Utils.generateUUID();

  if (this.options.transaction) {
    this.id = this.options.transaction.id;
    this.options.transaction.savepoints.push(this);
    this.name = this.id + '-savepoint-' + this.options.transaction.savepoints.length;
    this.parent = this.options.transaction;
  } else {
    this.id = this.name = Utils.generateUUID();
  }
};

/**
 * The possible isolations levels to use when starting a transaction
 *
 * ```js
 * {
 *   READ_UNCOMMITTED: "READ UNCOMMITTED",
 *   READ_COMMITTED: "READ COMMITTED",
 *   REPEATABLE_READ: "REPEATABLE READ",
 *   SERIALIZABLE: "SERIALIZABLE"
 * }
 * ```
 *
 * @property ISOLATION_LEVELS
 */
Transaction.ISOLATION_LEVELS = {
  READ_UNCOMMITTED: 'READ UNCOMMITTED',
  READ_COMMITTED: 'READ COMMITTED',
  REPEATABLE_READ: 'REPEATABLE READ',
  SERIALIZABLE: 'SERIALIZABLE'
};

/**
 * Possible options for row locking. Used in conjuction with `find` calls:
 *
 * ```js
 * t1 // is a transaction
 * t1.LOCK.UPDATE,
 * t1.LOCK.SHARE,
 * t1.LOCK.KEY_SHARE, // Postgres 9.3+ only
 * t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only
 * ```
 *
 * Usage:
 * ```js
 * t1 // is a transaction
 * Model.findAll({
 *   where: ...,
 *   transaction: t1,
 *   lock: t1.LOCK...
 * });
 * ```
 *
 * Postgres also supports specific locks while eager loading by using OF:
 * ```js
 * UserModel.findAll({
 *   where: ...,
 *   include: [TaskModel, ...],
 *   transaction: t1,
 *   lock: {
 *     level: t1.LOCK...,
 *     of: UserModel
 *   }
 * });
 * ```
 * UserModel will be locked but TaskModel won't!
 *
 * @property LOCK
 */
Transaction.LOCK = Transaction.prototype.LOCK = {
  UPDATE: 'UPDATE',
  SHARE: 'SHARE',
  KEY_SHARE: 'KEY SHARE',
  NO_KEY_UPDATE: 'NO KEY UPDATE'
};

/**
 * Commit the transaction
 *
 * @return {this}
 */
Transaction.prototype.commit = function() {
  var self = this;

  return this
    .sequelize
    .getQueryInterface()
    .commitTransaction(this, this.options)
    .finally(function() {
      self.finished = 'commit';
      if (!self.options.transaction) {
        self.cleanup();
      }
    });
};

/**
 * Rollback (abort) the transaction
 *
 * @return {this}
 */
Transaction.prototype.rollback = function() {
  var self = this;

  return this
    .sequelize
    .getQueryInterface()
    .rollbackTransaction(this, this.options)
    .finally(function() {
      self.finished = 'rollback';
      if (!self.options.transaction) {
        self.cleanup();
      }
    });
};

Transaction.prototype.prepareEnvironment = function() {
  var self = this;

  return Utils.Promise.resolve(
    self.options.transaction ? self.options.transaction.connection : self.sequelize.connectionManager.getConnection({ uuid: self.id })
  ).then(function (connection) {
    self.connection = connection;
    self.connection.uuid = self.id;

    if (self.sequelize.constructor.cls) {
      self.sequelize.constructor.cls.set('transaction', self);
    }
  }).then(function () {
    return self.begin();
  }).then(function () {
    return self.setIsolationLevel();
  }).then(function () {
    return self.setAutocommit();
  });
};

Transaction.prototype.begin = function() {
  return this
    .sequelize
    .getQueryInterface()
    .startTransaction(this, this.options);
};

Transaction.prototype.setAutocommit = function() {
  return this
    .sequelize
    .getQueryInterface()
    .setAutocommit(this, this.options.autocommit, this.options);
};

Transaction.prototype.setIsolationLevel = function() {
  return this
    .sequelize
    .getQueryInterface()
    .setIsolationLevel(this, this.options.isolationLevel, this.options);
};

Transaction.prototype.cleanup = function() {
  var cls = this.sequelize.constructor.cls;

  this.connection.uuid = undefined;

  if (cls) {
    if (cls.get('transaction') === this) {
      cls.set('transaction', null);
    }
  }

  return this.sequelize.connectionManager.releaseConnection(this.connection);
};
<|endoftext|>


<commit_before>'use strict';

var async = require('async');
var isString = require('101/is-string');
var FsDriver = require('./fs-driver');
var Warning = require('./warning');
var exists = require('101/exists');

/**
 * @module fs-transform:Transformer
 * @author Ryan Sandor Richards
 */
module.exports = Transformer;

/**
 * Class that performs a series of filesystem tranformations given a root
 * directory and list of rules.
 * @class
 * @author Ryan Sandor Richards
 * @param {String} root Root directory to run the transformations.
 * @param {String|Array} rules An array of transformations, or a JSON string that
 *   parses into a stream of transformations.
 */
function Transformer(root, rules) {
  if (isString(rules)) {
    rules = JSON.parse(rules);
  }
  if (!Array.isArray(rules)) {
    throw new Error('Rules must be an array.');
  }

  this.driver = new FsDriver(root);
  this.rules = rules;
  this._ruleActions = {};
  this.warnings = [];

  this.setAction('copy', this.copy);
  this.setAction('rename', this.rename);
  this.setAction('replace', this.replace);
}

/**
 * Callback executed when transformations have been completed, or if an error
 * has occurred.
 * @callback fs-transform~Callback
 * @param {Error} err An error if something went wrong when processing the
 *   transformation rules.
 */

/**
 * Performs a series of filesystem transformations.
 * @param {String} root Root directory to run the transformations.
 * @param {String|Array} rules An array of transformations, or a JSON string that
 *   parses into a stream of transformations.
 * @param {fs-transform~Callback} cb Callback to execute once the
 *   transformations have been completed or if an error has occurred.
 */
Transformer.transform = function(root, rules, cb) {
  try {
    new Transformer(root, rules).execute(cb);
  }
  catch (err) {
    cb(err);
  }
};

/**
 * Executes the transformations.
 * @param {fs-transform~Callback} cb Callback to execute once the
 *   transformations have been completed or if an error has occurred.
 */
Transformer.prototype.execute = function (cb) {
  var self = this;
  async.mapSeries(this.rules, function (rule, ruleCallback) {
    self.applyRule(rule, ruleCallback);
  }, function (err) {
    cb(err, self);
  });
};

/**
 * Adds a rule generated warning to the transformer.
 * @param object Object that generated the warning.
 * @param msg Message for the warning.
 */
Transformer.prototype.addWarning = function (object, msg) {
  this.warnings.push(new Warning(object, msg));
};

/**
 * Allows the user to override existing rule actions and define new ones.
 * Note the action function will be applied within the context of this
 * Transformation.
 * @param {string} name Name of the action to define or override.
 * @param {fs-transform~Rule} fn Function to apply when encountering the rule
 *   with the given action name.
 */
Transformer.prototype.setAction = function(name, fn) {
  var self = this;
  this._ruleActions[name] = function() {
    fn.apply(self, arguments);
  };
};

/**
 * Determine the rule action handler for the action name.
 * @param {string} name Name of the action.
 * @return {function} The rule handler for the action.
 */
Transformer.prototype.getAction = function(name) {
  return this._ruleActions[name];
};

/**
 * Callback for the application of rule actions.
 * @callback fs-transform~Rule
 * @param {object} rule Rule to apply.
 * @param {function} cb Callback to execute after the rule has been applied.
 */

/**
 * Applys a given transformation rule.
 * @param {object} rule Rule to apply.
 * @param {function} cb Callback to execute once the rule has been processed.
 */
Transformer.prototype.applyRule = function (rule, cb) {
  if (!rule) {
    return cb(new Error('Null or undfined rule encountered.'));
  }

  if (!isString(rule.action)) {
    return cb(new Error('Invalid rule action: ' + rule.action));
  }

  var actionMethod = this.getAction(rule.action);
  if (!actionMethod) {
    return cb(new Error('Action not found: ' + rule.action));
  }

  actionMethod(rule, cb);
};

/**
 * Performs validations for copy and rename rules.
 * @param {object} rule Rule to validate.
 * @return {boolean} `true` if validations pass, `false` otherwise.
 */
Transformer.prototype._isValidRenameOrCopy = function (rule) {
  if (!isString(rule.source)) {
    this.addWarning(rule, 'Missing source file.');
    return false;
  }
  if (!isString(rule.dest)) {
    this.addWarning(rule, 'Missing destination file.');
    return false;
  }
  if (!this.driver.exists(rule.source)) {
    this.addWarning(rule, 'Source file does not exist.');
    return false;
  }
  if (this.driver.exists(rule.dest)) {
    this.addWarning(rule, 'Overwrites destination file.');
  }
  return true;
}

/**
 * Copy a file.
 * @param {object} rule Defines the source and destination for the file copy.
 * @param {function} cb Callback to execute once the file has been copied.
 */
Transformer.prototype.copy = function (rule, cb) {
  if (!this._isValidRenameOrCopy(rule)) {
    return cb();
  }
  this.driver.copy(rule.source, rule.dest, cb);
};

/**
 * Rename a file.
 * @param {object} rule Defines the source and destination for the file rename.
 * @param {function} cb Callback to execute once the file has been renamed.
 */
Transformer.prototype.rename = function (rule, cb) {
  if (!this._isValidRenameOrCopy(rule)) {
    return cb();
  }
  this.driver.move(rule.source, rule.dest, cb);
};

/**
 * Text search and replace for many files.
 * @param {object} rule Rules that define what to search for, replace, and
 *   exclude from the search.
 * @param {function} cb Callback to execute once the search and replace has been
 *   performed.
 */
Transformer.prototype.replace = function (rule, cb) {
  var self = this;

  var search = rule.search;
  if (!isString(search)) {
    this.addWarning(rule, 'Search pattern not specified.');
    return cb();
  }

  var replace = rule.replace;
  if (!isString(replace)) {
    this.addWarning(rule, 'Replacement not specified.');
    return cb();
  }

  var exclude = rule.exclude || [];
  if (!Array.isArray(exclude)) {
    this.addWarning(rule, 'Excludes not supplied as an array, omitting.');
    exclude = [];
  }

  this.driver.grep(rule.search, function (err, grepResult) {
    var files = [];

    // 1. Determine files & lines from grep
    grepResult.split('\n').forEach(function (line) {
      var match = line.match(/(.*):(\d+):.*/);
      if (match) {
        files.push({ name: match[1], line: match[2] });
      }
    });

    // 1.1 If no files were returned by grep, issue a warning and get out of here
    if (files.length === 0) {
      self.addWarning(rule, 'Search did not return any results.');
      return cb();
    }

    // 2. Remove excluded files/lines from result set

    // 2.1 Keep track of when excludes are used to filter results
    var ruleApplied = [];
    exclude.forEach(function(excludeRule, index) {
      ruleApplied[index] = false;
    });

    // 2.2 Determine which files to keep
    files = files.filter(function (file) {
      var keepFile = true;

      exclude.forEach(function(excludeRule, index) {
        var name = excludeRule.name;
        var line = excludeRule.line;

        if (exists(name) && exists(line)) {
          if (name === file.name && parseInt(line) === parseInt(file.line)) {
            keepFile = false;
            ruleApplied[index] = true;
            return;
          }
        }
        else if (exists(name)) {
          if (name === file.name) {
            ruleApplied[index] = true;
            keepFile = false;
            return;
          }
        }
      });
      return keepFile;
    });

    // 2.3 Issue warnings for unused excludes
    exclude.forEach(function (excludeRule, index) {
      if (!ruleApplied[index]) {
        self.addWarning(excludeRule, 'Unused exclude.');
      }
    });

    // 2.4 Issue a warning and return if the excludes removed all files
    if (files.length === 0) {
      self.addWarning(rule, 'All results were excluded.')
      return cb();
    }

    // 3. Apply in-place `sed` to result set
    async.each(files, function (file, cb) {
      self.driver.sed(search, replace, file.name, file.line, cb);
    }, cb);
  });
};
<commit_msg>Added jsdoc for exceptions.<commit_after>'use strict';

var async = require('async');
var isString = require('101/is-string');
var FsDriver = require('./fs-driver');
var Warning = require('./warning');
var exists = require('101/exists');

/**
 * @module fs-transform:Transformer
 * @author Ryan Sandor Richards
 */
module.exports = Transformer;

/**
 * Class that performs a series of filesystem tranformations given a root
 * directory and list of rules.
 * @class
 * @author Ryan Sandor Richards
 * @param {String} root Root directory to run the transformations.
 * @param {String|Array} rules An array of transformations, or a JSON string that
 *   parses into a stream of transformations.
 * @throws SyntaxError If rules were provided as a string, but were not valid
 *   JSON.
 * @throws Error If the given rules were not an array.
 */
function Transformer(root, rules) {
  if (isString(rules)) {
    rules = JSON.parse(rules);
  }
  if (!Array.isArray(rules)) {
    throw new Error('Rules must be an array.');
  }

  this.driver = new FsDriver(root);
  this.rules = rules;
  this._ruleActions = {};
  this.warnings = [];

  this.setAction('copy', this.copy);
  this.setAction('rename', this.rename);
  this.setAction('replace', this.replace);
}

/**
 * Callback executed when transformations have been completed, or if an error
 * has occurred.
 * @callback fs-transform~Callback
 * @param {Error} err An error if something went wrong when processing the
 *   transformation rules.
 */

/**
 * Performs a series of filesystem transformations.
 * @param {String} root Root directory to run the transformations.
 * @param {String|Array} rules An array of transformations, or a JSON string that
 *   parses into a stream of transformations.
 * @param {fs-transform~Callback} cb Callback to execute once the
 *   transformations have been completed or if an error has occurred.
 */
Transformer.transform = function(root, rules, cb) {
  try {
    new Transformer(root, rules).execute(cb);
  }
  catch (err) {
    cb(err);
  }
};

/**
 * Executes the transformations.
 * @param {fs-transform~Callback} cb Callback to execute once the
 *   transformations have been completed or if an error has occurred.
 */
Transformer.prototype.execute = function (cb) {
  var self = this;
  async.mapSeries(this.rules, function (rule, ruleCallback) {
    self.applyRule(rule, ruleCallback);
  }, function (err) {
    cb(err, self);
  });
};

/**
 * Adds a rule generated warning to the transformer.
 * @param object Object that generated the warning.
 * @param msg Message for the warning.
 */
Transformer.prototype.addWarning = function (object, msg) {
  this.warnings.push(new Warning(object, msg));
};

/**
 * Allows the user to override existing rule actions and define new ones.
 * Note the action function will be applied within the context of this
 * Transformation.
 * @param {string} name Name of the action to define or override.
 * @param {fs-transform~Rule} fn Function to apply when encountering the rule
 *   with the given action name.
 */
Transformer.prototype.setAction = function(name, fn) {
  var self = this;
  this._ruleActions[name] = function() {
    fn.apply(self, arguments);
  };
};

/**
 * Determine the rule action handler for the action name.
 * @param {string} name Name of the action.
 * @return {function} The rule handler for the action.
 */
Transformer.prototype.getAction = function(name) {
  return this._ruleActions[name];
};

/**
 * Callback for the application of rule actions.
 * @callback fs-transform~Rule
 * @param {object} rule Rule to apply.
 * @param {function} cb Callback to execute after the rule has been applied.
 */

/**
 * Applys a given transformation rule.
 * @param {object} rule Rule to apply.
 * @param {function} cb Callback to execute once the rule has been processed.
 */
Transformer.prototype.applyRule = function (rule, cb) {
  if (!rule) {
    return cb(new Error('Null or undfined rule encountered.'));
  }

  if (!isString(rule.action)) {
    return cb(new Error('Invalid rule action: ' + rule.action));
  }

  var actionMethod = this.getAction(rule.action);
  if (!actionMethod) {
    return cb(new Error('Action not found: ' + rule.action));
  }

  actionMethod(rule, cb);
};

/**
 * Performs validations for copy and rename rules.
 * @param {object} rule Rule to validate.
 * @return {boolean} `true` if validations pass, `false` otherwise.
 */
Transformer.prototype._isValidRenameOrCopy = function (rule) {
  if (!isString(rule.source)) {
    this.addWarning(rule, 'Missing source file.');
    return false;
  }
  if (!isString(rule.dest)) {
    this.addWarning(rule, 'Missing destination file.');
    return false;
  }
  if (!this.driver.exists(rule.source)) {
    this.addWarning(rule, 'Source file does not exist.');
    return false;
  }
  if (this.driver.exists(rule.dest)) {
    this.addWarning(rule, 'Overwrites destination file.');
  }
  return true;
}

/**
 * Copy a file.
 * @param {object} rule Defines the source and destination for the file copy.
 * @param {function} cb Callback to execute once the file has been copied.
 */
Transformer.prototype.copy = function (rule, cb) {
  if (!this._isValidRenameOrCopy(rule)) {
    return cb();
  }
  this.driver.copy(rule.source, rule.dest, cb);
};

/**
 * Rename a file.
 * @param {object} rule Defines the source and destination for the file rename.
 * @param {function} cb Callback to execute once the file has been renamed.
 */
Transformer.prototype.rename = function (rule, cb) {
  if (!this._isValidRenameOrCopy(rule)) {
    return cb();
  }
  this.driver.move(rule.source, rule.dest, cb);
};

/**
 * Text search and replace for many files.
 * @param {object} rule Rules that define what to search for, replace, and
 *   exclude from the search.
 * @param {function} cb Callback to execute once the search and replace has been
 *   performed.
 */
Transformer.prototype.replace = function (rule, cb) {
  var self = this;

  var search = rule.search;
  if (!isString(search)) {
    this.addWarning(rule, 'Search pattern not specified.');
    return cb();
  }

  var replace = rule.replace;
  if (!isString(replace)) {
    this.addWarning(rule, 'Replacement not specified.');
    return cb();
  }

  var exclude = rule.exclude || [];
  if (!Array.isArray(exclude)) {
    this.addWarning(rule, 'Excludes not supplied as an array, omitting.');
    exclude = [];
  }

  this.driver.grep(rule.search, function (err, grepResult) {
    var files = [];

    // 1. Determine files & lines from grep
    grepResult.split('\n').forEach(function (line) {
      var match = line.match(/(.*):(\d+):.*/);
      if (match) {
        files.push({ name: match[1], line: match[2] });
      }
    });

    // 1.1 If no files were returned by grep, issue a warning and get out of here
    if (files.length === 0) {
      self.addWarning(rule, 'Search did not return any results.');
      return cb();
    }

    // 2. Remove excluded files/lines from result set

    // 2.1 Keep track of when excludes are used to filter results
    var ruleApplied = [];
    exclude.forEach(function(excludeRule, index) {
      ruleApplied[index] = false;
    });

    // 2.2 Determine which files to keep
    files = files.filter(function (file) {
      var keepFile = true;

      exclude.forEach(function(excludeRule, index) {
        var name = excludeRule.name;
        var line = excludeRule.line;

        if (exists(name) && exists(line)) {
          if (name === file.name && parseInt(line) === parseInt(file.line)) {
            keepFile = false;
            ruleApplied[index] = true;
            return;
          }
        }
        else if (exists(name)) {
          if (name === file.name) {
            ruleApplied[index] = true;
            keepFile = false;
            return;
          }
        }
      });
      return keepFile;
    });

    // 2.3 Issue warnings for unused excludes
    exclude.forEach(function (excludeRule, index) {
      if (!ruleApplied[index]) {
        self.addWarning(excludeRule, 'Unused exclude.');
      }
    });

    // 2.4 Issue a warning and return if the excludes removed all files
    if (files.length === 0) {
      self.addWarning(rule, 'All results were excluded.')
      return cb();
    }

    // 3. Apply in-place `sed` to result set
    async.each(files, function (file, cb) {
      self.driver.sed(search, replace, file.name, file.line, cb);
    }, cb);
  });
};
<|endoftext|>


<commit_before>'use strict';

var request = require('request'),
    utils = require('./utils'),
    pkg = require('../package.json');

/**
 * Default transporter constructor.
 * Wraps request and callback functions.
 */
function Transporter(options) {
    this.options = options;
}

/**
 * Default user agent.
 */
Transporter.prototype.USER_AGENT = 'languagelayer-javascript-client/' + pkg.version;

/**
 * Configures request options before making a request.
 * @param {object} opts Options to configure.
 * @return {object} Configured options.
 */
Transporter.prototype.configure = function (args) {

    // set transporter user agent
    var headers = args && args.headers ? args.headers : {};
    headers['User-Agent'] = headers['User-Agent'] ? headers['User-Agent'] + ' ' + this.USER_AGENT : this.USER_AGENT;

    args = utils.extend(
        this.options,
        args,
        {headers: headers}
    );

    return args;
};

/**
 * Makes a request with given options and invokes callback.
 * @param {object} opts Options.
 * @param {Function=} opt_callback Optional callback.
 * @return {Request} Request object
 */
Transporter.prototype.request = function (args, callback) {
    args = this.configure(args);
    return request(args, callback);
};

/**
 * Exports Transporter.
 */
module.exports = Transporter;<commit_msg>UserAgent made generic<commit_after>'use strict';

var request = require('request'),
    utils = require('./utils'),
    pkg = require('../package.json');

/**
 * Default transporter constructor.
 * Wraps request and callback functions.
 */
function Transporter(options) {
    this.options = options;
}

/**
 * Default user agent.
 */
Transporter.prototype.USER_AGENT = pkg.name + '.' + pkg.version;

/**
 * Configures request options before making a request.
 * @param {object} opts Options to configure.
 * @return {object} Configured options.
 */
Transporter.prototype.configure = function (args) {

    // set transporter user agent
    var headers = args && args.headers ? args.headers : {};
    headers['User-Agent'] = headers['User-Agent'] ? headers['User-Agent'] + ' ' + this.USER_AGENT : this.USER_AGENT;

    args = utils.extend(
        this.options,
        args,
        {headers: headers}
    );

    return args;
};

/**
 * Makes a request with given options and invokes callback.
 * @param {object} opts Options.
 * @param {Function=} opt_callback Optional callback.
 * @return {Request} Request object
 */
Transporter.prototype.request = function (args, callback) {
    args = this.configure(args);
    return request(args, callback);
};

/**
 * Exports Transporter.
 */
module.exports = Transporter;<|endoftext|>


<commit_before>'use strict';
var RateLimiter = require('limiter').RateLimiter;
var RSVP = require('rsvp');
var _ = require('../util.js');

function TwitAPI(_twit, _logger, _metrics, _user) {
  this.T = _twit;
  this.logger = _logger;
  this.metrics = _metrics;
  if ( _.isEmpty(_user)) {
    this.limiters = setAppRateLimiters();
  } else {
    this.limiters = setUserRateLimiters();
  }
}

TwitAPI.prototype.apiHarness = function(method, api, params, limiter ) {
  return new RSVP.Promise(function(resolve, reject) {
    //T.setAuth(tokens)
    limiter.removeTokens(1, function(err, remainingRequests) {
      T[method](api, params, function (err, data)
      {
        if ( !_.isEmpty(err)){
          reject(err);
        } else {
          metrics.ApiFinished.increment();
          resolve(data);
        }
      });
    });
  });
};

TwitAPI.prototype.pagedAPIQuery = function(param, query, saveFn) {
    logger.trace("start %j", param);
    return new RSVP.Promise(function(resolve, reject) {
      var cursor = cursor || "-1";

      function successHandler(results){
        var queryResults = results.query;
        var jobs = {};
        logger.trace(results);
        jobs.save = saveFn( param, queryResults);
        if (queryResults.next_cursor_str !== "0"){
          jobs.query = query(param, queryResults.next_cursor_str );
          RSVP.hash(jobs)
          .then(successHandler, errorHandler);
        } else {
          jobs.save.then(function() {
            resolve(param);
          }, reject);
        }
      }
      function errorHandler(results) {
        logger.debug("%s\t%j\n%j", query.name, param, results);
        reject(results);
      }
      RSVP.hash( { query: query(param, cursor) } )
      .then(successHandler, errorHandler);
    });
};

TwitAPI.prototype.friendsIDs = function(user, cursor) {
  return this.apiHarness('get', 'friends/ids', { user_id: user.id_str, cursor: cursor, count: 5000, stringify_ids: true }, this.limiters.friendsIDsLimiter);
};

TwitAPI.prototype.users = function(id_str_list) {
  return this.apiHarness('post', 'users/lookup', { user_id: id_str_list }, this.limiters.userLimiter);
};

TwitAPI.prototype.listMembers = function(list, cursor) {
  return this.apiHarness('post', 'lists/members', { list_id: list.id_str, cursor: cursor, count: 5000 }, this.limiters.listMembersLimiter);
};

TwitAPI.prototype.userListOwnership = function(user, cursor) {
  return this.apiHarness('get', 'lists/ownerships', { user_id: user.id_str, cursor: cursor, count: 1000 }, this.limiters.userListOwnershipLimiter);
};

TwitAPI.prototype.userListSubscriptions = function(user, cursor) {
  return this.apiHarness('get', 'lists/subscriptions', { user_id: user.id_str, cursor: cursor, count: 1000 }, this.limiters.userListSubscriptions);
};

function per15MinRateLimiter(num){
  return new RateLimiter(1, (1 / (num - 1) ) * 15 * 60 * 1000);
}
function setAppRateLimiters(){
  return {
    friendsIDsLimiter: per15MinRateLimiter(15),
    userLimiter: per15MinRateLimiter(60),
    listMembersLimiter: per15MinRateLimiter(15),
    userListOwnershipLimiter: per15MinRateLimiter(15),
    userListSubscriptions: per15MinRateLimiter(15)
  };
}
function setUserRateLimiters(){
  return {
    friendsIDsLimiter: per15MinRateLimiter(15),
    userLimiter: per15MinRateLimiter(180),
    listMembersLimiter: per15MinRateLimiter(180),
    userListOwnershipLimiter: per15MinRateLimiter(15),
    userListSubscriptions: per15MinRateLimiter(15)
  };
}

module.exports = TwitAPI;
<commit_msg>use object's t<commit_after>'use strict';
var RateLimiter = require('limiter').RateLimiter;
var RSVP = require('rsvp');
var _ = require('../util.js');

function TwitAPI(_twit, _logger, _metrics, _user) {
  this.T = _twit;
  this.logger = _logger;
  this.metrics = _metrics;
  if ( _.isEmpty(_user)) {
    this.limiters = setAppRateLimiters();
  } else {
    this.limiters = setUserRateLimiters();
  }
}

TwitAPI.prototype.apiHarness = function(method, api, params, limiter ) {
  return new RSVP.Promise(function(resolve, reject) {
    //T.setAuth(tokens)
    limiter.removeTokens(1, function(err, remainingRequests) {
      this.T[method](api, params, function (err, data)
      {
        if ( !_.isEmpty(err)){
          reject(err);
        } else {
          metrics.ApiFinished.increment();
          resolve(data);
        }
      });
    });
  });
};

TwitAPI.prototype.pagedAPIQuery = function(param, query, saveFn) {
    logger.trace("start %j", param);
    return new RSVP.Promise(function(resolve, reject) {
      var cursor = cursor || "-1";

      function successHandler(results){
        var queryResults = results.query;
        var jobs = {};
        logger.trace(results);
        jobs.save = saveFn( param, queryResults);
        if (queryResults.next_cursor_str !== "0"){
          jobs.query = query(param, queryResults.next_cursor_str );
          RSVP.hash(jobs)
          .then(successHandler, errorHandler);
        } else {
          jobs.save.then(function() {
            resolve(param);
          }, reject);
        }
      }
      function errorHandler(results) {
        logger.debug("%s\t%j\n%j", query.name, param, results);
        reject(results);
      }
      RSVP.hash( { query: query(param, cursor) } )
      .then(successHandler, errorHandler);
    });
};

TwitAPI.prototype.friendsIDs = function(user, cursor) {
  return this.apiHarness('get', 'friends/ids', { user_id: user.id_str, cursor: cursor, count: 5000, stringify_ids: true }, this.limiters.friendsIDsLimiter);
};

TwitAPI.prototype.users = function(id_str_list) {
  return this.apiHarness('post', 'users/lookup', { user_id: id_str_list }, this.limiters.userLimiter);
};

TwitAPI.prototype.listMembers = function(list, cursor) {
  return this.apiHarness('post', 'lists/members', { list_id: list.id_str, cursor: cursor, count: 5000 }, this.limiters.listMembersLimiter);
};

TwitAPI.prototype.userListOwnership = function(user, cursor) {
  return this.apiHarness('get', 'lists/ownerships', { user_id: user.id_str, cursor: cursor, count: 1000 }, this.limiters.userListOwnershipLimiter);
};

TwitAPI.prototype.userListSubscriptions = function(user, cursor) {
  return this.apiHarness('get', 'lists/subscriptions', { user_id: user.id_str, cursor: cursor, count: 1000 }, this.limiters.userListSubscriptions);
};

function per15MinRateLimiter(num){
  return new RateLimiter(1, (1 / (num - 1) ) * 15 * 60 * 1000);
}
function setAppRateLimiters(){
  return {
    friendsIDsLimiter: per15MinRateLimiter(15),
    userLimiter: per15MinRateLimiter(60),
    listMembersLimiter: per15MinRateLimiter(15),
    userListOwnershipLimiter: per15MinRateLimiter(15),
    userListSubscriptions: per15MinRateLimiter(15)
  };
}
function setUserRateLimiters(){
  return {
    friendsIDsLimiter: per15MinRateLimiter(15),
    userLimiter: per15MinRateLimiter(180),
    listMembersLimiter: per15MinRateLimiter(180),
    userListOwnershipLimiter: per15MinRateLimiter(15),
    userListSubscriptions: per15MinRateLimiter(15)
  };
}

module.exports = TwitAPI;
<|endoftext|>


<commit_before>/**
  Delve API Type Classes
  @description provides structures around each type used by the Delve RPC Server
  @author tylerFowler
**/
'use strict';

// Types:
// - DebuggerState
// - DebuggerCommand
// - Breakpoint
// - BreakpointInfo
// - Thread
// - Location
// - Stackframe
// - Function
// - Variable
// - Goroutine
// - EvalScope

var types;
module.exports = exports = types = {};
types.Breakpoint = {};

/**
  @type Breakpoint
  @desc Converts a Delve breakpoint to a more friendly format
  @param { Object } bp a breakpoint in the format given by the Delve API
  @param { Number } bp.id
  @param { String } bp.name
  @param { Number } bp.addr memory address of the breakpoint
  @param { String } bp.file
  @param { Number } bp.line
  @param { String|Null } bp.functionName
  @param { Bool } bp.continue tracepoint flag
  @param { Bool } bp.goroutine
  @param { Number } bp.stacktrace # of stack frames to retrieve
  @param { String[] } bp.variables
  @param { Object } bp.hitCount { goroutineId: hitCount, ... }
  @param { Number } bp.totalHitCount
**/
types.Breakpoint.decodeBreakpoint = function Breakpoint(bp) {
  try {
    let newBreakpoint = {
      id: bp.id,
      name: bp.name,
      location: {
        addr: bp.addr,
        file: bp.file,
        line: bp.line,
        functionName: bp.functionName || null,
        goroutine: bp.goroutine
      },
      continue: bp.continue,
      stacktrace: bp.stacktrace,
      variables: bp.variables || [],
      hitCount: bp.hitCount || [],
      totalHitCount: bp.totalHitCount || 0
    };

    Object.assign(this, newBreakpoint);
  } catch (err) {
    throw new DelveError.MalformedBreakpointError(err);
  }
};

types.Scope = {
  makeScope: function makeScope(GoroutineID, Frame) {
    return { GoroutineID, Frame };
  }
};
<commit_msg>Return newly created breakpoint<commit_after>/**
  Delve API Type Classes
  @description provides structures around each type used by the Delve RPC Server
  @author tylerFowler
**/
'use strict';

// Types:
// - DebuggerState
// - DebuggerCommand
// - Breakpoint
// - BreakpointInfo
// - Thread
// - Location
// - Stackframe
// - Function
// - Variable
// - Goroutine
// - EvalScope

var types;
module.exports = exports = types = {};
types.Breakpoint = {};

/**
  @type Breakpoint
  @desc Converts a Delve breakpoint to a more friendly format
  @param { Object } bp a breakpoint in the format given by the Delve API
  @param { Number } bp.id
  @param { String } bp.name
  @param { Number } bp.addr memory address of the breakpoint
  @param { String } bp.file
  @param { Number } bp.line
  @param { String|Null } bp.functionName
  @param { Bool } bp.continue tracepoint flag
  @param { Bool } bp.goroutine
  @param { Number } bp.stacktrace # of stack frames to retrieve
  @param { String[] } bp.variables
  @param { Object } bp.hitCount { goroutineId: hitCount, ... }
  @param { Number } bp.totalHitCount
**/
types.Breakpoint.decodeBreakpoint = function Breakpoint(bp) {
  try {
    let newBreakpoint = {
      id: bp.id,
      name: bp.name,
      location: {
        addr: bp.addr,
        file: bp.file,
        line: bp.line,
        functionName: bp.functionName || null,
        goroutine: bp.goroutine
      },
      continue: bp.continue,
      stacktrace: bp.stacktrace,
      variables: bp.variables || [],
      hitCount: bp.hitCount || [],
      totalHitCount: bp.totalHitCount || 0
    };

    return newBreakpoint;
  } catch (err) {
    throw new DelveError.MalformedBreakpointError(err);
  }
};

types.Scope = {
  makeScope: function makeScope(GoroutineID, Frame) {
    return { GoroutineID, Frame };
  }
};
<|endoftext|>


<commit_before>const User = require('./models/user.model');
const Job = require('./models/job.model');
const Asset = require('./models/asset.model');

module.exports = function update() {
User.find()
    .then(users => {
        users.forEach(function(user) {
            return User.findById(user._id)
                .then(user => {
                    user.age += .084;
                    user.job.months_worked += 1;
                    user.save();
                    console.log('firstThen',user);
                    return user;
                })
                .then(user => {
                    let userJob = user.job;
                    return Job.findById(userJob.job_name)
                        .then(job => {
                            user.bank_account += job.monthlySalary;
                            user.networth += job.monthlySalary;
                            if (user.networth >= 1000000) {
                                user.retired = true;
                                user.save();
                                return 'RETIRED';
                            }
                            user.save();
                            if (job.promotionInterval === 0 || job.promotionInterval > user.job.months_worked) {
                                user.save();
                                return user.job;
                            } else {
                                let newJob = {};
                                return Job.find( { jobType: job.jobType } )
                                    .then(jobs => {
                                        if(job.jobLevel === 'Entry') {
                                            let foundJob = jobs.filter(j => {
                                                return j.jobLevel === 'Mid-level';
                                            });
                                            newJob.start_date = new Date();
                                            newJob.months_worked = 0;
                                            newJob.job_name = foundJob[0]._id;
                                            user.job = newJob;
                                            user.save();
                                            return user;
                                        } else if(job.jobLevel === 'Mid-level') {
                                            let foundJob = jobs.filter(j => {
                                                return j.jobLevel === 'Senior';
                                            });
                                            user.job.months_worked = 0;
                                            newJob.start_date = new Date();
                                            newJob.months_worked = 0;
                                            newJob.job_name = foundJob[0]._id;
                                            user.save();
                                            return user.job = newJob;
                                        }
                                    })

                            }
                        })
                        user.save()
                        return user;
                })
              return user;

        })
    })
    .catch()
};
<commit_msg>fixed linting errors<commit_after>const User = require('./models/user.model');
const Job = require('./models/job.model');

module.exports = function update() {
User.find()
    .then(users => {
        users.forEach(function(user) {
            return User.findById(user._id)
                .then(user => {
                    user.age += .084;
                    user.job.months_worked += 1;
                    user.save();
                    return user;
                })
                .then(user => {
                    let userJob = user.job;
                    return Job.findById(userJob.job_name)
                        .then(job => {
                            user.bank_account += job.monthlySalary;
                            user.networth += job.monthlySalary;
                            if (user.networth >= 1000000) {
                                user.retired = true;
                                user.save();
                                return 'RETIRED';
                            }
                            user.save();
                            if (job.promotionInterval === 0 || job.promotionInterval > user.job.months_worked) {
                                user.save();
                                return user.job;
                            } else {
                                let newJob = {};
                                return Job.find( { jobType: job.jobType } )
                                    .then(jobs => {
                                        if(job.jobLevel === 'Entry') {
                                            let foundJob = jobs.filter(j => {
                                                return j.jobLevel === 'Mid-level';
                                            });
                                            newJob.start_date = new Date();
                                            newJob.months_worked = 0;
                                            newJob.job_name = foundJob[0]._id;
                                            user.job = newJob;
                                            user.save();
                                            return user;
                                        } else if(job.jobLevel === 'Mid-level') {
                                            let foundJob = jobs.filter(j => {
                                                return j.jobLevel === 'Senior';
                                            });
                                            user.job.months_worked = 0;
                                            newJob.start_date = new Date();
                                            newJob.months_worked = 0;
                                            newJob.job_name = foundJob[0]._id;
                                            user.save();
                                            return user.job = newJob;
                                        }
                                    });
                            }
                        });
                });
        });
    })
    .catch();
};
<|endoftext|>


<commit_before>const cli = require('commander')
const moment = require('moment')
const chalk = require('chalk')
const currencyFormatter = require('currency-formatter');
const apiCall = require('./apiCall')
const config = require('./config')

const periods = []

cli
  .option('-f, --from <date>', 'select from <date>.', validateDate)
  .option('-t, --to <date>', 'select to <date>.', validateDate)
  .parse(process.argv)

function printReports () {
  definePeriods()
  periods.forEach(period => {
    const requestBody = {
      start_date: period[0].format('YYYY-MM-DDTHH:MM:ssZ'),
      end_date: period[1].format('YYYY-MM-DDTHH:MM:ssZ')
    }
    apiCall({
      token: config.token,
      task: 'completed',
      body: requestBody
    }).then(res => {
      let report = new Report(res.body, period)
      report.create()
      report.print()
    })
  })
}

function definePeriods () {
  // Add one hour to the default end time to take inconsistencies into account.
  const defaultEndTime = moment.utc().add(1, 'h')
  // Make default periods if the user didn't input any.
  if (!cli.args.length && !cli.to && !cli.from) {
    let month = formatMonth((moment().month() + 1).toString())
    periods.push([moment.utc('2014-01-01'), defaultEndTime])
    periods.push([moment.utc(month), defaultEndTime])
  }
  // Make options periods.
  if (cli.from || cli.to) {
    let start = cli.from ? cli.from : moment.utc('2014-01-01')
    let end = cli.to ? cli.to : defaultEndTime
    periods.push([start, end])
  }
  // Regex expressions to match user input.
  // Matches numbers from 1-12.
  const matchMonth = /^[1-9]|1[012]$/
  // Matches the format YYYY-MM.
  const matchYearMonth = /^(19|20)\d\d[- /.](0[1-9]|1[012])$/

  cli.args.forEach(arg => {
    // Testing what arguments we got from the user.
    if (matchYearMonth.test(arg)) {
      let start = moment(arg).utc()
      // Check if month asked is the current month
      if (arg === moment.utc().format('YYYY-MM')) {
         // Make the last day to query as being today
         let end = moment.utc().endOf('day')
         periods.push([start, end])
      } else {
         // Otherwise, pick the midnight of the first day of next month.
         let end = moment(arg).utc().add(1, 'M')
         periods.push([start, end])
      }
    } else if (matchMonth.test(arg)) {
      let year = moment().year()
      const month = moment().month() + 1
      if (arg > month) {
        year -= 1
      }
      const yearAndMonth = arg < 9 ? `${year}-0${arg}` : `${year}-${arg}`
      let start = moment(yearAndMonth).utc()
      // Check if month asked is the current month
      if (arg === month.toString()) {
         // Make the last day to query as being today
         let end = moment.utc().endOf('day')
         periods.push([start, end])
      } else {
         // Otherwise, pick the midnight of the first day of next month.
         let end = moment(yearAndMonth).utc().add(1, 'M')
         periods.push([start, end])
      }
    }  else if (arg === 'today') {
      let start = moment.utc().startOf('day')
      let end = moment.utc().endOf('day')
      periods.push([start, end])
    } else if (arg === 'yesterday') {
      let start = moment.utc().subtract(1, 'd').startOf('day')
      let end = moment.utc().subtract(1, 'd').endOf('day')
      periods.push([start, end])
    }
  })
}

function formatMonth (month) {
  return month.length === 1 ? `${moment().year()}-0${month}`
                            : `${moment().year()}-${month}`
}

function validateDate (date) {
  date = moment.utc(date)
  if (!date.isValid()) {
    throw new Error(`Invalid date: ${date}`)
  }
  return date
}

class Report {
  constructor (reviews, period) {
    this.period = period
    this.reviews = reviews
    this.projects = {}
    this.totalEarned = 0
    this.totalAssigned = 0
  }

  create () {
    this.reviews.forEach(review => {
      this.countReview(review)
    })
  }

  countReview (review) {
    let id = review.project_id
    let price = parseInt(review.price)
    let assigned_at = moment.utc(review.assigned_at)
    let completed_at = moment.utc(review.completed_at)
    let turnaround_time = completed_at.diff(assigned_at)

    // If the report does not yet contain an entry for the project type, create
    // the entry and try counting the review again.
    if (!this.projects.hasOwnProperty(id)) {
      this.projects[id] = {
        name: review.project.name,
        id: id,
        passed: 0,
        failed: 0,
        ungradeable: 0,
        earned: 0,
        turnaround_time: 0
      }
      this.countReview(review)
    } else {
      this.projects[id][review.result] += 1
      this.projects[id].earned += price
      this.projects[id].turnaround_time += turnaround_time
      this.totalEarned += price
      this.totalAssigned += 1
    }
  }

  print () {
    let startDate = moment(this.period[0]).format('YYYY-MM-DD')
    let endDate = moment(this.period[1]).format('YYYY-MM-DD')
    let numberOfDays = moment(endDate).diff(startDate, 'days')

    console.log(chalk.blue(`\nEarnings Report for ${startDate} to ${endDate}:`))
    console.log('=============================================')
    console.log(chalk.bgBlack.white(`Total Projects Assigned: ${this.totalAssigned}`))

    for (let p in this.projects) {
      let {name, id, ungradeable, passed, failed, earned, turnaround_time} = this.projects[p]
      let totalAssigned = passed + failed + ungradeable
      let avg_turnaround_time = moment.utc(turnaround_time/(totalAssigned))
      let projectMessage = `Project: ${name} (${id}):`

      console.log(`
      ${chalk.white(projectMessage)}
      ${chalk.white(new Array(projectMessage.length + 1).join('-'))}
          ${chalk.white(`Total Assigned: ${totalAssigned}`)}
              ${chalk.white(`Reviewed: ${passed + failed}`)}
              ${chalk.white(`Ungradeable: ${ungradeable}`)}
          ${chalk.white(`Earned: ${currencyFormatter.format(earned, {code: 'USD'})}`)}
          ${chalk.white(`Average Turnaround Time: ${avg_turnaround_time.format('HH:mm')}`)}
      `)
    }

    console.log(chalk.white(`Total Earned: ${currencyFormatter.format(this.totalEarned, {code: 'USD'})}`))

    // Only print Daily Average if we have more than one day to average
    if (numberOfDays > 1) {
        console.log(chalk.white(`Daily Average: ${currencyFormatter.format(this.totalEarned/numberOfDays, {code: 'USD'})}`))
    }
    console.log('=============================================')
  }
}

printReports()
<commit_msg>Obtain the first day on the job<commit_after>const cli = require('commander')
const moment = require('moment')
const chalk = require('chalk')
const currencyFormatter = require('currency-formatter');
const apiCall = require('./apiCall')
const config = require('./config')

const periods = []

cli
  .option('-f, --from <date>', 'select from <date>.', validateDate)
  .option('-t, --to <date>', 'select to <date>.', validateDate)
  .parse(process.argv)

function printReports () {
  definePeriods()
  periods.forEach(period => {
    const requestBody = {
      start_date: period[0].format('YYYY-MM-DDTHH:MM:ssZ'),
      end_date: period[1].format('YYYY-MM-DDTHH:MM:ssZ')
    }
    apiCall({
      token: config.token,
      task: 'completed',
      body: requestBody
    }).then(res => {
      let report = new Report(res.body, period)
      report.create()
      report.print()
    })
  })
}

function definePeriods () {
  // Add one hour to the default end time to take inconsistencies into account.
  const defaultEndTime = moment.utc().add(1, 'h')
  // Make default periods if the user didn't input any.
  if (!cli.args.length && !cli.to && !cli.from) {
    let month = formatMonth((moment().month() + 1).toString())
    periods.push([moment.utc('2014-01-01'), defaultEndTime])
    periods.push([moment.utc(month), defaultEndTime])
  }
  // Make options periods.
  if (cli.from || cli.to) {
    let start = cli.from ? cli.from : moment.utc('2014-01-01')
    let end = cli.to ? cli.to : defaultEndTime
    periods.push([start, end])
  }
  // Regex expressions to match user input.
  // Matches numbers from 1-12.
  const matchMonth = /^[1-9]|1[012]$/
  // Matches the format YYYY-MM.
  const matchYearMonth = /^(19|20)\d\d[- /.](0[1-9]|1[012])$/

  cli.args.forEach(arg => {
    // Testing what arguments we got from the user.
    if (matchYearMonth.test(arg)) {
      let start = moment(arg).utc()
      // Check if month asked is the current month
      if (arg === moment.utc().format('YYYY-MM')) {
         // Make the last day to query as being today
         let end = moment.utc().endOf('day')
         periods.push([start, end])
      } else {
         // Otherwise, pick the midnight of the first day of next month.
         let end = moment(arg).utc().add(1, 'M')
         periods.push([start, end])
      }
    } else if (matchMonth.test(arg)) {
      let year = moment().year()
      const month = moment().month() + 1
      if (arg > month) {
        year -= 1
      }
      const yearAndMonth = arg < 9 ? `${year}-0${arg}` : `${year}-${arg}`
      let start = moment(yearAndMonth).utc()
      // Check if month asked is the current month
      if (arg === month.toString()) {
         // Make the last day to query as being today
         let end = moment.utc().endOf('day')
         periods.push([start, end])
      } else {
         // Otherwise, pick the midnight of the first day of next month.
         let end = moment(yearAndMonth).utc().add(1, 'M')
         periods.push([start, end])
      }
    }  else if (arg === 'today') {
      let start = moment.utc().startOf('day')
      let end = moment.utc().endOf('day')
      periods.push([start, end])
    } else if (arg === 'yesterday') {
      let start = moment.utc().subtract(1, 'd').startOf('day')
      let end = moment.utc().subtract(1, 'd').endOf('day')
      periods.push([start, end])
    }
  })
}

function formatMonth (month) {
  return month.length === 1 ? `${moment().year()}-0${month}`
                            : `${moment().year()}-${month}`
}

function validateDate (date) {
  date = moment.utc(date)
  if (!date.isValid()) {
    throw new Error(`Invalid date: ${date}`)
  }
  return date
}

class Report {
  constructor (reviews, period) {
    this.startDate = period[0].format('YYYY-MM-DD')
    this.endDate = period[1].format('YYYY-MM-DD')
    // We need to check if we're using the default startDate, so that we can
    // find the earliest date a submission was assigned. This date is needed to
    // get the correct number of days of the period, so we can calculate the
    // average daily earnings for the period.
    if (this.startDate === '2014-01-01') {
      const firstDate = reviews
        .map(review => moment(review.assigned_at)) // returns date of review
        .map(date => date.valueOf()) // returns date in Unix Time (milliseconds from 1970)
        .reduce((acc, val) => acc < val ? acc : val) // returns the smallest number
      this.startDate = moment(firstDate).format('YYYY-MM-DD')
    }
    this.reviews = reviews
    this.projects = {}
    this.totalEarned = 0
    this.totalAssigned = 0
  }

  create () {
    this.reviews.forEach(review => {
      this.countReview(review)
    })
  }

  countReview (review) {
    let id = review.project_id
    let price = parseInt(review.price)
    let assigned_at = moment.utc(review.assigned_at)
    let completed_at = moment.utc(review.completed_at)
    let turnaround_time = completed_at.diff(assigned_at)

    // If the report does not yet contain an entry for the project type, create
    // the entry and try counting the review again.
    if (!this.projects.hasOwnProperty(id)) {
      this.projects[id] = {
        name: review.project.name,
        id: id,
        passed: 0,
        failed: 0,
        ungradeable: 0,
        earned: 0,
        turnaround_time: 0
      }
      this.countReview(review)
    } else {
      this.projects[id][review.result] += 1
      this.projects[id].earned += price
      this.projects[id].turnaround_time += turnaround_time
      this.totalEarned += price
      this.totalAssigned += 1
    }
  }

  print () {
    console.log(chalk.blue(`\nEarnings Report for ${this.startDate} to ${this.endDate}:`))
    console.log('=============================================')
    console.log(chalk.bgBlack.white(`Total Projects Assigned: ${this.totalAssigned}`))

    for (let p in this.projects) {
      let {name, id, ungradeable, passed, failed, earned, turnaround_time} = this.projects[p]
      let totalAssigned = passed + failed + ungradeable
      let avg_turnaround_time = moment.utc(turnaround_time/(totalAssigned))
      let projectMessage = `Project: ${name} (${id}):`

      console.log(`
      ${chalk.white(projectMessage)}
      ${chalk.white(new Array(projectMessage.length + 1).join('-'))}
          ${chalk.white(`Total Assigned: ${totalAssigned}`)}
              ${chalk.white(`Reviewed: ${passed + failed}`)}
              ${chalk.white(`Ungradeable: ${ungradeable}`)}
          ${chalk.white(`Earned: ${currencyFormatter.format(earned, {code: 'USD'})}`)}
          ${chalk.white(`Average Turnaround Time: ${avg_turnaround_time.format('HH:mm')}`)}
      `)
    }

    console.log(chalk.white(`Total Earned: ${currencyFormatter.format(this.totalEarned, {code: 'USD'})}`))

    // Only print Daily Average if we have more than one day to average
    let numberOfDays = moment(this.endDate).diff(this.startDate, 'days')
    if (numberOfDays > 1) {
        console.log(chalk.white(`Daily Average: ${currencyFormatter.format(this.totalEarned/numberOfDays, {code: 'USD'})}`))
    }
    console.log('=============================================')
  }
}

printReports()
<|endoftext|>


<commit_before>/**
 * Interface to the User model.
 */
var dslogger = require('./dslogger')
    ;

// Initialize logger.
dslogger.init('mb-logging-api-server', false);

/**
 * Constructor to the Log object.
 *
 * @param model
 *   The model of the log document.
 */
function UserImport(model) {
  this.docModel = model;
}

/**
 * Converts a timestamp in seconds to a Date object.
 *
 * @param timestamp
 *   Timestamp in seconds.
 */
var convertToDate = function(timestamp) {
  return new Date(timestamp * 1000);
};

/**
 * Create a log document from the supplied values
 *
 * @param req
 *  The request object in a POST callback.
 * @param res
 *  The response object in a POST callback.
 */
UserImport.prototype.post = function(req, res) {
  this.request = req;
  this.response = res;
  var addArgs = {};

  // Include parameter values in post
  addArgs.source = this.request.query.source;

  if (req.query.origin !== undefined) {
    var timestamp = parseInt(this.request.query.staarted_timestamp);
    addArgs.origin = {
      "name" : this.request.query.origin ,
      "started" : convertToDate(timestamp)
    }
  }

  // POST submission values
  if (this.request.body.logging_timestamp !== undefined) {
    // Convert timestamp string to Date object
    var timestamp = parseInt(this.request.body.logging_timestamp);
    addArgs.logged_date = convertToDate(timestamp);
  }
  if (this.request.body.phone !== undefined) {
    addArgs.phone = {
      "number" : this.request.body.phone,
      "status" : this.request.body.phone_status
    }
  }
  if (this.request.body.email !== undefined) {
    addArgs.email = {
      "address" : this.request.body.email,
      "status" : this.request.body.email_status
    }
    if (this.request.body.email_acquired_timestamp !== undefined) {
      var timestamp = parseInt(this.request.body.email_acquired_timestamp);
      addArgs.email.acquired = convertToDate(timestamp);
    }
  }
  if (this.request.body.drupal_uid !== undefined) {
    addArgs.drupal = {
      "email" : this.request.body.email,
      "uid" : this.request.body.drupal_uid
    }
  }

  var logEntry = new this.docModel(addArgs);
  logEntry.save(function(err) {
    if (err) {
      res.send(500, err);
      dslogger.error(err);
    }

    // Added log entry to db
    res.send(201, addArgs);
    dslogger.log('Save executed on: ' + addArgs + '. Raw response from mongo:');
    console.log("Created log entry." + addArgs);
  });
};

/**
 * Retrieve existing user log documents. Example request:
 * /api/v1/imports/2014-09-01/2014-10-01?type=user&exists=1&source=niche.com
 *
 * @param req
 *   The request object in the GET callback.
 * @param res
 *   The response object in the GET callback.
 */
UserImport.prototype.get = function(req, res) {

  if (req.param("start_date") == 0) {
    var targetStartDate = new Date('2014-08-01');
  }
  else {
    var targetStartDate = new Date(req.param("start_date"));
  }
  if (req.param("end_date") == 0) {
    var targetEndDate = new Date();
  }
  else {
    var targetEndDate = new Date(req.param("end_date"));
  }

  var data = {
    request: req,
    response: res
  };
  this.docModel.find( {
    $and : [
      { 'logged_date' : {$gte : targetStartDate, $lte : targetEndDate} },
      { 'source' : req.query.source }
    ]},
    function (err, docs) {
      if (err) {
        data.response.send(500, err);
        dslogger.error(err);
        return;
      }

      // Send results
      console.log('Summary query returned.');
      data.response.send(201, docs);
  })
};

module.exports = UserImport;
<commit_msg>Update UserImport.prototype.post<commit_after>/**
 * Interface to the User model.
 */
var dslogger = require('./dslogger')
    ;

// Initialize logger.
dslogger.init('mb-logging-api-server', false);

/**
 * Constructor to the Log object.
 *
 * @param model
 *   The model of the log document.
 */
function UserImport(model) {
  this.docModel = model;
}

/**
 * Converts a timestamp in seconds to a Date object.
 *
 * @param timestamp
 *   Timestamp in seconds.
 */
var convertToDate = function(timestamp) {
  return new Date(timestamp * 1000);
};

/**
 * Create a log document from the supplied values
 *
 * @param req
 *  The request object in a POST callback.
 * @param res
 *  The response object in a POST callback.
 */
UserImport.prototype.post = function(req, res) {
  this.request = req;
  this.response = res;
  var addArgs = {};

  // Include parameter values in post
  addArgs.source = this.request.query.source;

  if (req.query.origin !== undefined) {
    var timestamp = parseInt(this.request.query.started_timestamp);
    addArgs.origin = {
      "name" : this.request.query.origin ,
      "started" : convertToDate(timestamp)
    }
  }

  // POST submission values
  if (this.request.body.logging_timestamp !== undefined) {
    // Convert timestamp string to Date object
    var timestamp = parseInt(this.request.body.logging_timestamp);
    addArgs.logged_date = convertToDate(timestamp);
  }
  if (this.request.body.phone !== undefined) {
    addArgs.phone = {
      "number" : this.request.body.phone,
      "status" : this.request.body.phone_status
    }
  }
  if (this.request.body.email !== undefined) {
    addArgs.email = {
      "address" : this.request.body.email,
      "status" : this.request.body.email_status
    }
    if (this.request.body.email_acquired_timestamp !== undefined) {
      var timestamp = parseInt(this.request.body.email_acquired_timestamp);
      addArgs.email.acquired = convertToDate(timestamp);
    }
  }
  if (this.request.body.drupal_uid !== undefined) {
    addArgs.drupal = {
      "email" : this.request.body.email,
      "uid" : this.request.body.drupal_uid
    }
  }

  var logEntry = new this.docModel(addArgs);
  logEntry.save(function(err) {
    if (err) {
      res.send(500, err);
      dslogger.error(err);
    }

    // Added log entry to db
    res.send(201, addArgs);
    dslogger.log('Save executed on: ' + addArgs + '. Raw response from mongo:');
    console.log("Created log entry." + addArgs);
  });
};

/**
 * Retrieve existing user log documents. Example request:
 * /api/v1/imports/2014-09-01/2014-10-01?type=user&exists=1&source=niche.com
 *
 * @param req
 *   The request object in the GET callback.
 * @param res
 *   The response object in the GET callback.
 */
UserImport.prototype.get = function(req, res) {

  if (req.param("start_date") == 0) {
    var targetStartDate = new Date('2014-08-01');
  }
  else {
    var targetStartDate = new Date(req.param("start_date"));
  }
  if (req.param("end_date") == 0) {
    var targetEndDate = new Date();
  }
  else {
    var targetEndDate = new Date(req.param("end_date"));
  }

  var data = {
    request: req,
    response: res
  };
  this.docModel.find( {
    $and : [
      { 'logged_date' : {$gte : targetStartDate, $lte : targetEndDate} },
      { 'source' : req.query.source }
    ]},
    function (err, docs) {
      if (err) {
        data.response.send(500, err);
        dslogger.error(err);
        return;
      }

      // Send results
      console.log('Summary query returned.');
      data.response.send(201, docs);
  })
};

module.exports = UserImport;
<|endoftext|>


<commit_before>/* CouchDB view heater
 *
 * TODO:
 *   * Detect when the signature changes and run /db/_view_cleanup
 *   * Some simple heuristic for when enough updates have happened and run /db/_compact/_design/ddoc
 */

var sys = require('sys')
  , lib = require('lib')
  , querystring = require('querystring')
  , couch_changes = require('lib/couch_changes')
  ;

var LOG = lib.logging.getLogger('view_heater');

exports.heat = function(opts) {
  var db = opts.db;
  var clean_db = db.replace(/^(https?:\/\/)[^:]+:[^@]+@(.*)$/, '$1$2'); // Scrub username and password
  //var LOG = lib.logging.getLogger('view_heater.' + clean_db);
  var max_age = opts.max_age || (3 * 60 * 1000); // ms
  var max_seq = opts.max_seq || 100;
  LOG.info("Heating views on " + clean_db + " at most " + max_age + "ms or " + max_seq + " changes");

  var update_db = function(latest_known, cb) {
    ping_db(db, latest_known.seq, function(er, new_latest) {
      if(er)
        return cb && cb(er);

      LOG.debug('Latest DB update for ' + clean_db + ': ' + JSON.stringify(new_latest));
      return cb && cb(null, new_latest);
    })
  }

  LOG.debug("First update: " + clean_db);
  update_db({seq:0, time:new Date('1981')}, function(er, latest) {
    if(er)
      lib.thr0w(er);

    // Great. Now start watching changes and also watch the clock, and whichever happens first, ping the db again.
    LOG.debug("Initial DB update complete, starting sequence and timer watchers: " + clean_db);

    var heater_state = latest;

    var timed_out = function() {
      LOG.debug('Time threshold met (' + max_age + 'ms); updating ' + clean_db);
      update_db(heater_state, function(er, new_latest) {
        if(er)
          lib.thr0w(er);

        LOG.debug('Time-based update complete on "' + clean_db + '": ' + JSON.stringify(new_latest));
        heater_state.seq = new_latest.seq;
        heater_state.time = new_latest.time;

        heater_state.timer = null;
        new_timeout();
      })
    }

    var new_timeout = function() {
      if(heater_state.timer) {
        clearTimeout(heater_state.timer);
        heater_state.timer = null;
        LOG.debug('Cleared old heater timer: ' + clean_db);
      }

      var time_since_query = (new Date) - heater_state.time;
      var ms_until_next_timeout = max_age - time_since_query;
      heater_state.timer = setTimeout(timed_out, ms_until_next_timeout);
      LOG.debug('Set new heater timer: ' + clean_db);
    }

    // And kick off the first timer, which will re-set itself as needed.
    new_timeout();

    // Also begin following changes for seq-based updates.
    var updating_for_seq = false;
    couch_changes.follow({db:db, heartbeat:55000, include_docs:false, since:latest.seq}, function(change) {
      //LOG.debug("Got a change: " + JSON.stringify(change));
      if(updating_for_seq) {
        //LOG.debug("Ignoring seq update since one is in progress: " + change.seq);
        return;
      }

      if(heater_state.seq + max_seq <= change.seq) {
        LOG.debug("Sequence threshold met (" + max_seq + "); updating " + clean_db);

        updating_for_seq = true;
        update_db(heater_state, function(er, new_latest) {
          try {
            if(er) {
              lib.thr0w(er);
            }

            LOG.debug('Sequence-based update complete on ' + clean_db + ': ' + JSON.stringify(new_latest));
            heater_state.seq = new_latest.seq;
            heater_state.time = new_latest.time;
            new_timeout();
          } finally {
            updating_for_seq = false;
          }
        })
      }
    })

    LOG.debug("Began changes follower since " + latest.seq + ": " + clean_db);
  })
}

function ping_db(db, last_processed_seq, cb) {
  var query_timestamp = new Date();

  lib.req_json({uri:db}, function(er, res, body) {
    if(er || body.error)
      return cb && cb(er || body);

    var db_name = '/' + body.db_name;
    if(!db_name)
      return cb && cb(new Error("Bad return on DB query: " + db));

    var db_update_seq = body.update_seq;
    if(db_update_seq < last_processed_seq)
      return cb && cb(new Error("Woa! Update_seq reduced from " + last_processed_seq + " to " + db_update_seq));

    if(db_update_seq == last_processed_seq) {
      LOG.debug("No updates to " + db_name + ", still on seq " + db_update_seq);
      return cb && cb(null, {seq:db_update_seq, time:query_timestamp});
    }

    if(body.compact_running) {
      LOG.debug("Going easy on " + db_name + " because compaction is running");
      return cb && cb(null, {seq:db_update_seq, time:query_timestamp});
    }

    var ddocs_done = 0;
    var completed_ddoc = function(ddoc, all_ddocs_count) {
      ddocs_done += 1;
      if(ddocs_done === all_ddocs_count) {
        LOG.debug("Completed all ddocs for " + db_name);

        // XXX: This could lie to the caller because Couch may have been given a break due to compaction, updates, etc.
        return cb && cb(null, {seq:db_update_seq, time:query_timestamp});
      }
    }

    var aborted = false;

    LOG.debug("Updating all ddocs in " + db_name);
    forEach_ddoc(db, function(er, result, a, all_ddocs) {
      if(aborted) {
        LOG.debug("This loop has been aborted");
        return;
      }

      if(er && er.error == 'no_ddocs') {
        LOG.debug('Database has no ddocs; nothing to do: ' + db_name);
        return completed_ddoc({}, 1);
      }

      if(er) {
        LOG.error("Error during ddoc update: " + db_name);
        aborted = true;
        return(cb && cb(e));
      }

      var ddoc = result.ddoc
        , info = result.info
        , ddoc_name = [db_name, ddoc._id].join('/');

      LOG.debug('Processing ddoc ' + (a+1) + '/' + all_ddocs.length + ': ' + ddoc_name + "\n" + sys.inspect(info));

      if(info.update_seq > last_processed_seq)
        return(cb && cb(new Error("Woa! DDoc " + ddoc_name + " update_seq=" + info.update_seq + " but " + db_name + " update_seq=" + db_update_seq)));

      if(info.update_seq == last_processed_seq) {
        LOG.debug("No need to query " + ddoc_name + " because it is up-to-date with db seq: " + db_update_seq);
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      if(info.compact_running) {
        LOG.debug("Going easy on " + ddoc_name + " because view compaction is running");
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      if(info.updater_running) {
        LOG.debug("Going easy on " + ddoc_name + " because it is already updating");
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      if(info.waiting_clients) {
        LOG.debug("Going easy on " + ddoc_name + " because other clients are waiting");
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      // Pick a random view just on account of because.
      var view_names = Object.keys(ddoc.views || {}).sort(function() { return Math.round(Math.random()) - 0.5 });
      if(view_names.length == 0) {
        LOG.debug("No views for ddoc: " + ddoc_name);
      } else {
        var view_name = view_names[0];
        var view = [db, ddoc._id, '_view', view_name, '?limit=0'].join('/');
        lib.req_json({uri:view}, function(er, res, body) {
          if(er)
            lib.thr0w(er);

          // At this point it is guaranteed that the view is at least up to db_update_seq (perhaps furter).
          LOG.debug('Views for ' + ddoc_name + ' are at least up to seq ' + db_update_seq);
          return completed_ddoc(ddoc, all_ddocs.length);
        })
      }
    })
  })
}

function forEach_ddoc(db, cb) {
  if(!cb) lib.thr0w("foreach_ddoc must supply a callback");

  var startkey = JSON.stringify("_design/");
  var endkey   = JSON.stringify("_design0");
  var query = querystring.stringify({include_docs:'true', startkey:startkey, endkey:endkey});

  lib.req_json({uri:db + '/_all_docs?' + query}, function(er, res, body) {
    if(er || body.error)
      return(cb && cb(er || body));

    if(body.rows.length == 0)
      return cb && cb({error:"no_ddocs"});

    //LOG.debug("each_ddoc got: " + sys.inspect(body));
    body.rows.forEach(function(row, i, rows) {
      var ddoc = row.doc;
      lib.req_json({uri:db + '/' + ddoc._id + '/_info'}, function(er, res, body) {
        if(er || body.error)
          return(cb && cb(er || body));

        cb && cb(null, {ddoc:ddoc, info:body}, i, rows);
      })
    })
  })
}
<commit_msg>Stop the timer when a seq update happens<commit_after>/* CouchDB view heater
 *
 * TODO:
 *   * Detect when the signature changes and run /db/_view_cleanup
 *   * Some simple heuristic for when enough updates have happened and run /db/_compact/_design/ddoc
 */

var sys = require('sys')
  , lib = require('lib')
  , querystring = require('querystring')
  , couch_changes = require('lib/couch_changes')
  ;

var LOG = lib.logging.getLogger('view_heater');

exports.heat = function(opts) {
  var db = opts.db;
  var clean_db = db.replace(/^(https?:\/\/)[^:]+:[^@]+@(.*)$/, '$1$2'); // Scrub username and password
  //var LOG = lib.logging.getLogger('view_heater.' + clean_db);
  var max_age = opts.max_age || (3 * 60 * 1000); // ms
  var max_seq = opts.max_seq || 100;
  LOG.info("Heating views on " + clean_db + " at most " + max_age + "ms or " + max_seq + " changes");

  var update_db = function(latest_known, cb) {
    ping_db(db, latest_known.seq, function(er, new_latest) {
      if(er)
        return cb && cb(er);

      LOG.debug('Latest DB update for ' + clean_db + ': ' + JSON.stringify(new_latest));
      return cb && cb(null, new_latest);
    })
  }

  LOG.debug("First update: " + clean_db);
  update_db({seq:0, time:new Date('1981')}, function(er, latest) {
    if(er)
      lib.thr0w(er);

    // Great. Now start watching changes and also watch the clock, and whichever happens first, ping the db again.
    LOG.debug("Initial DB update complete, starting sequence and timer watchers: " + clean_db);

    var heater_state = latest;

    var timed_out = function() {
      LOG.debug('Time threshold met (' + max_age + 'ms); updating ' + clean_db);
      update_db(heater_state, function(er, new_latest) {
        if(er)
          lib.thr0w(er);

        LOG.debug('Time-based update complete on "' + clean_db + '": ' + JSON.stringify(new_latest));
        heater_state.seq = new_latest.seq;
        heater_state.time = new_latest.time;

        heater_state.timer = null;
        new_timeout();
      })
    }

    var new_timeout = function() {
      if(heater_state.timer) {
        clearTimeout(heater_state.timer);
        heater_state.timer = null;
        LOG.debug('Cleared old heater timer: ' + clean_db);
      }

      var time_since_query = (new Date) - heater_state.time;
      var ms_until_next_timeout = max_age - time_since_query;
      heater_state.timer = setTimeout(timed_out, ms_until_next_timeout);
      LOG.debug('Set new heater timer: ' + clean_db);
    }

    // And kick off the first timer, which will re-set itself as needed.
    new_timeout();

    // Also begin following changes for seq-based updates.
    var updating_for_seq = false;
    couch_changes.follow({db:db, heartbeat:55000, include_docs:false, since:latest.seq}, function(change) {
      //LOG.debug("Got a change: " + JSON.stringify(change));
      if(updating_for_seq) {
        //LOG.debug("Ignoring seq update since one is in progress: " + change.seq);
        return;
      }

      if(heater_state.seq + max_seq <= change.seq) {
        LOG.debug("Sequence threshold met (" + max_seq + "); updating " + clean_db);

        if(heater_state.timer) {
          clearTimeout(heater_state.timer);
          heater_state.timer = null;
        }

        updating_for_seq = true;
        update_db(heater_state, function(er, new_latest) {
          try {
            if(er) {
              lib.thr0w(er);
            }

            LOG.debug('Sequence-based update complete on ' + clean_db + ': ' + JSON.stringify(new_latest));
            heater_state.seq = new_latest.seq;
            heater_state.time = new_latest.time;
            new_timeout();
          } finally {
            updating_for_seq = false;
          }
        })
      }
    })

    LOG.debug("Began changes follower since " + latest.seq + ": " + clean_db);
  })
}

function ping_db(db, last_processed_seq, cb) {
  var query_timestamp = new Date();

  lib.req_json({uri:db}, function(er, res, body) {
    if(er || body.error)
      return cb && cb(er || body);

    var db_name = '/' + body.db_name;
    if(!db_name)
      return cb && cb(new Error("Bad return on DB query: " + db));

    var db_update_seq = body.update_seq;
    if(db_update_seq < last_processed_seq)
      return cb && cb(new Error("Woa! Update_seq reduced from " + last_processed_seq + " to " + db_update_seq));

    if(db_update_seq == last_processed_seq) {
      LOG.debug("No updates to " + db_name + ", still on seq " + db_update_seq);
      return cb && cb(null, {seq:db_update_seq, time:query_timestamp});
    }

    if(body.compact_running) {
      LOG.debug("Going easy on " + db_name + " because compaction is running");
      return cb && cb(null, {seq:db_update_seq, time:query_timestamp});
    }

    var ddocs_done = 0;
    var completed_ddoc = function(ddoc, all_ddocs_count) {
      ddocs_done += 1;
      if(ddocs_done === all_ddocs_count) {
        LOG.debug("Completed all ddocs for " + db_name);

        // XXX: This could lie to the caller because Couch may have been given a break due to compaction, updates, etc.
        return cb && cb(null, {seq:db_update_seq, time:query_timestamp});
      }
    }

    var aborted = false;

    LOG.debug("Updating all ddocs in " + db_name);
    forEach_ddoc(db, function(er, result, a, all_ddocs) {
      if(aborted) {
        LOG.debug("This loop has been aborted");
        return;
      }

      if(er && er.error == 'no_ddocs') {
        LOG.debug('Database has no ddocs; nothing to do: ' + db_name);
        return completed_ddoc({}, 1);
      }

      if(er) {
        LOG.error("Error during ddoc update: " + db_name);
        aborted = true;
        return(cb && cb(e));
      }

      var ddoc = result.ddoc
        , info = result.info
        , ddoc_name = [db_name, ddoc._id].join('/');

      LOG.debug('Processing ddoc ' + (a+1) + '/' + all_ddocs.length + ': ' + ddoc_name + "\n" + sys.inspect(info));

      if(info.update_seq > last_processed_seq)
        return(cb && cb(new Error("Woa! DDoc " + ddoc_name + " update_seq=" + info.update_seq + " but " + db_name + " update_seq=" + db_update_seq)));

      if(info.update_seq == last_processed_seq) {
        LOG.debug("No need to query " + ddoc_name + " because it is up-to-date with db seq: " + db_update_seq);
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      if(info.compact_running) {
        LOG.debug("Going easy on " + ddoc_name + " because view compaction is running");
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      if(info.updater_running) {
        LOG.debug("Going easy on " + ddoc_name + " because it is already updating");
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      if(info.waiting_clients) {
        LOG.debug("Going easy on " + ddoc_name + " because other clients are waiting");
        return completed_ddoc(ddoc, all_ddocs.length);
      }

      // Pick a random view just on account of because.
      var view_names = Object.keys(ddoc.views || {}).sort(function() { return Math.round(Math.random()) - 0.5 });
      if(view_names.length == 0) {
        LOG.debug("No views for ddoc: " + ddoc_name);
      } else {
        var view_name = view_names[0];
        var view = [db, ddoc._id, '_view', view_name, '?limit=0'].join('/');
        lib.req_json({uri:view}, function(er, res, body) {
          if(er)
            lib.thr0w(er);

          // At this point it is guaranteed that the view is at least up to db_update_seq (perhaps furter).
          LOG.debug('Views for ' + ddoc_name + ' are at least up to seq ' + db_update_seq);
          return completed_ddoc(ddoc, all_ddocs.length);
        })
      }
    })
  })
}

function forEach_ddoc(db, cb) {
  if(!cb) lib.thr0w("foreach_ddoc must supply a callback");

  var startkey = JSON.stringify("_design/");
  var endkey   = JSON.stringify("_design0");
  var query = querystring.stringify({include_docs:'true', startkey:startkey, endkey:endkey});

  lib.req_json({uri:db + '/_all_docs?' + query}, function(er, res, body) {
    if(er || body.error)
      return(cb && cb(er || body));

    if(body.rows.length == 0)
      return cb && cb({error:"no_ddocs"});

    //LOG.debug("each_ddoc got: " + sys.inspect(body));
    body.rows.forEach(function(row, i, rows) {
      var ddoc = row.doc;
      lib.req_json({uri:db + '/' + ddoc._id + '/_info'}, function(er, res, body) {
        if(er || body.error)
          return(cb && cb(er || body));

        cb && cb(null, {ddoc:ddoc, info:body}, i, rows);
      })
    })
  })
}
<|endoftext|>